added theme support
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<section class="portal-header"><div><span class="eyebrow">CMS</span><h1>{{ $page->exists ? 'Edit page' : 'New page' }}</h1></div><a class="btn btn-secondary" href="{{ route('admin.cms.index') }}">Back to pages</a></section>
|
||||
<div class="panel"><form method="post" action="{{ $page->exists ? route('admin.cms.update',$page) : route('admin.cms.store') }}">@csrf @if($page->exists) @method('PUT') @endif
|
||||
<label>Title<input name="title" value="{{ old('title',$page->title) }}" required maxlength="160"></label>
|
||||
<label>Slug<input name="slug" value="{{ old('slug',$page->slug) }}" placeholder="generated-from-title" maxlength="180"></label>
|
||||
<label>Excerpt<textarea name="excerpt" rows="3" maxlength="1000">{{ old('excerpt',$page->excerpt) }}</textarea></label>
|
||||
<label>Body<textarea name="body" rows="18" required>{{ old('body',$page->body) }}</textarea><small>Plain text is rendered safely with paragraphs and line breaks.</small></label>
|
||||
<section class="seo-section" style="padding:24px 0 4px;border-top:1px solid var(--line);margin-top:24px">
|
||||
<div class="seo-section-head"><div><span class="eyebrow">Search appearance</span><h2>Page SEO</h2><p>Override global defaults only where this page needs specific search or social metadata.</p></div><a class="btn btn-secondary" href="{{ route('admin.seo.edit') }}">Global SEO settings</a></div>
|
||||
<div class="form-grid two"><label>SEO title<input name="meta_title" value="{{ old('meta_title',$page->meta_title) }}" maxlength="160" placeholder="Defaults to the page title"></label><label>Canonical URL<input type="url" name="canonical_url" value="{{ old('canonical_url',$page->canonical_url) }}" maxlength="2048" placeholder="Leave blank for this page URL"></label></div>
|
||||
<label>SEO description<textarea name="meta_description" rows="3" maxlength="320" placeholder="Defaults to the excerpt">{{ old('meta_description',$page->meta_description) }}</textarea></label>
|
||||
<label>Keywords<input name="meta_keywords" value="{{ old('meta_keywords',$page->meta_keywords) }}" maxlength="500" placeholder="Optional comma-separated search topics"></label>
|
||||
<div class="form-grid two"><label>Open Graph title<input name="og_title" value="{{ old('og_title',$page->og_title) }}" maxlength="160" placeholder="Defaults to SEO title"></label><label>Open Graph image URL<input type="url" name="og_image" value="{{ old('og_image',$page->og_image) }}" maxlength="2048" placeholder="Defaults to global social image"></label></div>
|
||||
<label>Open Graph description<textarea name="og_description" rows="3" maxlength="320" placeholder="Defaults to SEO description">{{ old('og_description',$page->og_description) }}</textarea></label>
|
||||
<div class="form-grid two"><label>Sitemap priority<input type="number" name="sitemap_priority" value="{{ old('sitemap_priority',$page->sitemap_priority ?? '0.5') }}" min="0" max="1" step="0.1" required></label><label>Change frequency<select name="sitemap_change_frequency" required>@foreach(['always','hourly','daily','weekly','monthly','yearly','never'] as $frequency)<option value="{{ $frequency }}" @selected(old('sitemap_change_frequency',$page->sitemap_change_frequency ?? 'monthly') === $frequency)>{{ ucfirst($frequency) }}</option>@endforeach</select></label></div>
|
||||
<div class="form-grid two"><label class="consent-check"><input type="checkbox" name="robots_index" value="1" @checked(old('robots_index',$page->exists ? $page->robots_index : true))><span>Allow search engines to index this page</span></label><label class="consent-check"><input type="checkbox" name="robots_follow" value="1" @checked(old('robots_follow',$page->exists ? $page->robots_follow : true))><span>Allow crawlers to follow links</span></label></div>
|
||||
<label class="consent-check"><input type="checkbox" name="include_in_sitemap" value="1" @checked(old('include_in_sitemap',$page->exists ? $page->include_in_sitemap : true))><span>Include this page in sitemap.xml</span></label>
|
||||
</section>
|
||||
<label class="consent-check"><input type="checkbox" name="is_published" value="1" @checked(old('is_published',$page->is_published))><span>Published</span></label>
|
||||
<div class="support-actions"><button type="submit">Save page</button>@if($page->exists && $page->is_published)<a class="btn btn-secondary" target="_blank" href="{{ route('cms.show',$page->slug) }}">View page</a>@endif</div>
|
||||
</form></div>
|
||||
@if($page->exists)<form method="post" action="{{ route('admin.cms.destroy',$page) }}" style="margin-top:16px" onsubmit="return confirm('Delete this page?')">@csrf @method('DELETE')<button class="danger-outline">Delete page</button></form>@endif
|
||||
@endsection
|
||||
@@ -1,9 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<section class="portal-header"><div><span class="eyebrow">CMS</span><h1>Content pages</h1><p class="lead compact">Manage SonarDesk website content.</p></div><a class="btn" href="{{ route('admin.cms.create') }}">New page</a></section>
|
||||
<div class="panel"><div class="admin-ticket-list">
|
||||
@forelse($pages as $page)
|
||||
<a class="admin-ticket-row" href="{{ route('admin.cms.edit', $page) }}"><div><strong>{{ $page->title }}</strong><small>/pages/{{ $page->slug }}</small></div><span class="ticket-status {{ $page->is_published ? 'status-open' : '' }}">{{ $page->is_published ? 'Published' : 'Draft' }}</span><time>{{ $page->updated_at->diffForHumans() }}</time><span>›</span></a>
|
||||
@empty <p class="muted">No CMS pages yet.</p> @endforelse
|
||||
</div>{{ $pages->links() }}</div>
|
||||
@endsection
|
||||
@@ -1,15 +0,0 @@
|
||||
@extends('layouts.app', ['title' => 'Download products — Admin'])
|
||||
@section('content')
|
||||
<div class="distribution-admin">
|
||||
<section class="portal-header distribution-header"><div><span class="eyebrow">Distribution / Product management</span><h1>Download products</h1><p class="lead compact">Manage installers, releases and publication status in one place.</p></div><div class="distribution-header-actions"><a class="btn btn-secondary" href="{{ route('release') }}" target="_blank" rel="noopener">View public downloads ↗</a><a class="btn btn-secondary" href="{{ route('admin.releases.index') }}">Releases</a></div></section>
|
||||
@if(session('status'))<div class="distribution-notice" role="status">{{ session('status') }}</div>@endif
|
||||
@if($errors->any())<div class="distribution-errors" role="alert"><strong>Please correct the following:</strong><ul>@foreach($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>@endif
|
||||
<div class="distribution-summary"><div><span>Total products</span><strong>{{ $products->count() }}</strong></div><div><span>Published</span><strong>{{ $products->where('is_published', true)->count() }}</strong></div><div><span>Drafts</span><strong>{{ $products->where('is_published', false)->count() }}</strong></div><div><span>Total downloads</span><strong>{{ number_format($products->sum('download_count')) }}</strong></div></div>
|
||||
<section class="admin-table-card distribution-table"><div class="admin-table-title"><div><h2>Product catalogue</h2><p>Publish or unpublish products instantly, or edit their details.</p></div><button class="btn btn-primary" type="button" onclick="const panel=document.getElementById('new-product');panel.open=true;panel.scrollIntoView({behavior:'smooth',block:'start'});">+ Add product</button></div><div class="table-scroll"><table class="pro-table"><thead><tr><th>Product</th><th>Version / Platform</th><th>Downloads</th><th>Status</th><th style="text-align:right">Actions</th></tr></thead><tbody>
|
||||
@forelse($products as $product)<tr><td><strong>{{ $product->name }}</strong><small>{{ \Illuminate\Support\Str::limit($product->description ?: $product->original_filename, 70) }}</small></td><td><strong>{{ $product->version ?: '—' }}</strong><small>{{ $product->platform ?: 'Unspecified' }} · {{ $product->formatted_size }}</small></td><td>{{ number_format($product->download_count) }}</td><td><span class="status-chip {{ $product->is_published ? 'success' : 'muted' }}">{{ $product->is_published ? 'Published' : 'Draft' }}</span></td><td><div class="distribution-row-actions"><button type="button" class="dist-action" onclick="const panel=document.getElementById('edit-product-{{ $product->id }}');panel.open=true;panel.scrollIntoView({behavior:'smooth',block:'start'});">Edit</button><form method="post" action="{{ route('admin.download-products.publication', $product) }}">@csrf @method('PATCH')<input type="hidden" name="is_published" value="{{ $product->is_published ? '0' : '1' }}"><button type="submit" class="dist-action {{ $product->is_published ? 'dist-unpublish' : 'dist-publish' }}">{{ $product->is_published ? 'Unpublish' : 'Publish' }}</button></form></div></td></tr>
|
||||
@empty<tr><td colspan="5" class="empty-cell">No additional products yet. Add your first installer below.</td></tr>@endforelse
|
||||
</tbody></table></div></section>
|
||||
<details class="distribution-panel" id="new-product" @if($errors->any() && !old('_method')) open @endif><summary><span><strong>Add a new product</strong><small>Upload an installer and choose whether to publish it.</small></span><span class="dist-chevron">+</span></summary><form class="distribution-form" method="post" action="{{ route('admin.download-products.store') }}" enctype="multipart/form-data">@csrf<div class="form-grid"><label>Product name<input name="name" value="{{ old('name') }}" required maxlength="150" placeholder="e.g. SonarDesk CLI"></label><label>Version<input name="version" value="{{ old('version') }}" maxlength="80" placeholder="e.g. 2.1.0"></label><label>Platform<input name="platform" value="{{ old('platform') }}" maxlength="120" placeholder="macOS, Windows, Linux…"></label></div><label>Description (Markdown supported)<textarea name="description" rows="12" maxlength="12000" placeholder="## Overview Describe the product. ## Features - Feature one - Feature two">{{ old('description') }}</textarea></label><label class="distribution-file">Installer / package <small>Maximum 1 GB</small><input type="file" name="package" required></label><label class="distribution-check"><input type="checkbox" name="is_published" value="1" @checked(old('is_published'))><span><strong>Publish immediately</strong><small>Make this download available on the public page.</small></span></label><button class="btn btn-primary" type="submit">Add product</button></form></details>
|
||||
@foreach($products as $product)<details class="distribution-panel" id="edit-product-{{ $product->id }}"><summary><span><strong>Edit {{ $product->name }}</strong><small>{{ $product->original_filename }} · {{ $product->formatted_size }}</small></span><span class="dist-chevron">+</span></summary><form class="distribution-form" method="post" action="{{ route('admin.download-products.update',$product) }}" enctype="multipart/form-data">@csrf @method('PUT')<div class="form-grid"><label>Product name<input name="name" value="{{ $product->name }}" required maxlength="150"></label><label>Version<input name="version" value="{{ $product->version }}" maxlength="80"></label><label>Platform<input name="platform" value="{{ $product->platform }}" maxlength="120"></label></div><label>Description (Markdown supported)<textarea name="description" rows="12" maxlength="12000">{{ $product->description }}</textarea></label><label class="distribution-file">Replace package (optional)<small>Current: {{ $product->original_filename }} · Maximum 1 GB</small><input type="file" name="package"></label><label class="distribution-check"><input type="checkbox" name="is_published" value="1" @checked($product->is_published)><span><strong>Published</strong><small>Untick to save this product as a draft.</small></span></label><button class="btn btn-primary" type="submit">Save changes</button></form><form class="distribution-delete" method="post" action="{{ route('admin.download-products.destroy',$product) }}" onsubmit="return confirm('Permanently delete this product and its installer?')">@csrf @method('DELETE')<button type="submit" class="danger-outline">Delete product</button></form></details>@endforeach
|
||||
</div>
|
||||
@endsection
|
||||
@@ -1,6 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<section class="portal-header"><div><span class="eyebrow">Admin</span><h1>Licensing administration</h1><p class="lead compact">Manage licences, activations and customer support.</p></div></section>
|
||||
<div class="grid"><div class="card"><strong>{{ $licenseCount ?? '—' }}</strong><p>Licences</p></div><div class="card"><strong>{{ $activeCount ?? '—' }}</strong><p>Not revoked</p></div><div class="card"><strong>{{ $activationCount ?? '—' }}</strong><p>Active installations</p></div><div class="card"><strong>{{ $userCount ?? '—' }}</strong><p>Portal users</p></div></div>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:18px"><a class="btn btn-primary" href="{{ route('admin.releases.index') }}">Manage releases</a><a class="btn" href="{{ route('admin.licenses') }}">Manage licences</a><a class="btn" href="{{ route('admin.users.index') }}">Manage users</a><a class="btn btn-secondary" href="{{ route('admin.cms.index') }}">Manage content</a><a class="btn btn-secondary" href="{{ route('stylist.index') }}">Manage themes</a><a class="btn btn-secondary" href="{{ route('admin.seo.edit') }}">SEO & search</a><a class="btn btn-secondary" href="{{ route('admin.support.index') }}">Support inbox</a></div>
|
||||
@endsection
|
||||
@@ -1 +0,0 @@
|
||||
@extends('layouts.app') @section('content')<div class="card"><h1>Licence •••• {{ $license->license_key_last4 }}</h1><p>{{ $license->customer_name }} · {{ $license->customer_email }}</p><p>{{ $license->edition }} · {{ $license->activation_limit }} seats</p>@if($license->revoked_at)<form method="post" action="{{ route('admin.restore',$license) }}">@csrf<button>Restore licence</button></form>@else<form method="post" action="{{ route('admin.revoke',$license) }}">@csrf<label>Reason</label><input name="reason"><button class="danger">Revoke licence</button></form>@endif</div><div class="card"><h2>Activations</h2><table><tr><th>ID</th><th>Platform</th><th>Version</th><th>Status</th></tr>@foreach($license->activations as $a)<tr><td>{{ $a->installation_name ?: $a->installation_id }}</td><td>{{ $a->platform }}</td><td>{{ $a->app_version }}</td><td>{{ $a->deactivated_at ? 'Released':'Active' }}</td></tr>@endforeach</table></div>@endsection
|
||||
@@ -1 +0,0 @@
|
||||
@extends('layouts.app') @section('content')<h1>Licences</h1><form><input name="q" value="{{ request('q') }}" placeholder="Search email, name or last 4"><button>Search</button></form><div class="card"><table><tr><th>Customer</th><th>Edition</th><th>Key</th><th>Status</th></tr>@foreach($licenses as $l)<tr><td><a href="{{ route('admin.license',$l) }}">{{ $l->customer_email ?: 'Manual' }}</a></td><td>{{ $l->edition }}</td><td>•••• {{ $l->license_key_last4 }}</td><td>{{ $l->revoked_at ? 'Revoked':'Active' }}</td></tr>@endforeach</table>{{ $licenses->links() }}</div>@endsection
|
||||
@@ -1,9 +0,0 @@
|
||||
@extends('layouts.app', ['title' => ($release->exists ? 'Edit release' : 'New release').' — SonarDesk Admin'])
|
||||
@section('content')
|
||||
<section class="portal-header"><div><span class="eyebrow">Distribution</span><h1>{{ $release->exists ? 'Edit SonarDesk '.$release->version : 'Create a release' }}</h1><p class="lead compact">Upload the notarized distribution package and control exactly when it becomes public.</p></div><a class="btn btn-secondary" href="{{ route('admin.releases.index') }}">Back to releases</a></section>
|
||||
<form class="release-editor" method="post" enctype="multipart/form-data" action="{{ $release->exists ? route('admin.releases.update',$release) : route('admin.releases.store') }}">@csrf @if($release->exists) @method('PUT') @endif
|
||||
<div class="editor-main"><section class="editor-card"><div class="editor-card-head"><span>01</span><div><h2>Release identity</h2><p>Version and compatibility information shown to customers.</p></div></div><div class="form-grid"><label>Version<input name="version" value="{{ old('version',$release->version) }}" placeholder="1.2.0" required></label><label>Build number<input type="number" min="1" name="build_number" value="{{ old('build_number',$release->build_number ?: 1) }}" required></label><label>Channel<select name="channel"><option value="stable" @selected(old('channel',$release->channel)==='stable')>Stable</option><option value="beta" @selected(old('channel',$release->channel)==='beta')>Beta</option></select></label><label>Minimum macOS<input name="minimum_macos" value="{{ old('minimum_macos',$release->minimum_macos) }}" placeholder="14.0"></label></div></section>
|
||||
<section class="editor-card"><div class="editor-card-head"><span>02</span><div><h2>Distribution package</h2><p>DMG or ZIP. SHA-256 is calculated automatically after upload.</p></div></div>@if($release->file_path)<div class="package-current"><strong>{{ $release->original_filename }}</strong><span>{{ $release->formatted_size }} · SHA-256 {{ $release->sha256 }}</span></div>@endif<label class="upload-zone"><strong>{{ $release->file_path ? 'Replace package' : 'Choose distribution package' }}</strong><span>Signed and notarized .dmg or .zip · up to 1 GB</span><input type="file" name="package" accept=".dmg,.zip" {{ $release->exists ? '' : 'required' }}></label><label>Sparkle EdDSA signature<textarea name="sparkle_signature" rows="3" placeholder="Paste the signature generated by Sparkle's sign_update tool">{{ old('sparkle_signature',$release->sparkle_signature) }}</textarea></label></section>
|
||||
<section class="editor-card"><div class="editor-card-head"><span>03</span><div><h2>Release notes</h2><p>Professional customer-facing notes. Plain text is rendered safely with line breaks.</p></div></div><label>What's new<textarea name="release_notes" rows="12" placeholder="New in this release…">{{ old('release_notes',$release->release_notes) }}</textarea></label></section></div>
|
||||
<aside class="editor-side"><div class="editor-card sticky-card"><h2>Publishing</h2><p>A draft is never exposed publicly or in the Sparkle feed.</p><label class="toggle-row"><input type="checkbox" name="is_published" value="1" @checked(old('is_published',$release->is_published))><span><strong>Published</strong><small>Make this build downloadable</small></span></label><label class="toggle-row"><input type="checkbox" name="is_featured" value="1" @checked(old('is_featured',$release->is_featured))><span><strong>Featured release</strong><small>Mark as the highlighted build</small></span></label><button class="btn btn-primary full-width" type="submit">{{ $release->exists ? 'Save release' : 'Create release' }}</button>@if($release->is_published)<a class="btn btn-secondary full-width" href="{{ route('download.release',$release) }}">Test download</a>@endif</div></aside></form>
|
||||
@endsection
|
||||
@@ -1,7 +0,0 @@
|
||||
@extends('layouts.app', ['title' => 'Releases — SonarDesk Admin'])
|
||||
@section('content')
|
||||
<section class="portal-header admin-release-head"><div><span class="eyebrow">Distribution</span><h1>Release management</h1><p class="lead compact">Publish SonarDesk builds, manage the public download experience and keep Sparkle updates under control.</p></div><div class="support-actions"><a class="btn btn-secondary" href="{{ route('admin.download-products.index') }}">Other products</a><a class="btn btn-secondary" href="{{ route('admin.releases.settings') }}">Download page settings</a><a class="btn btn-primary" href="{{ route('admin.releases.create') }}">New release</a></div></section>
|
||||
<div class="grid admin-metrics"><div class="card"><strong>{{ $releases->total() }}</strong><p>Total releases</p></div><div class="card"><strong>{{ $publishedCount }}</strong><p>Published</p></div><div class="card"><strong>{{ number_format($downloadCount) }}</strong><p>Total downloads</p></div><div class="card"><strong>Live</strong><p>Sparkle feed</p></div></div>
|
||||
<div class="admin-table-card"><div class="admin-table-title"><div><h2>SonarDesk builds</h2><p>Only published builds with an uploaded package are available publicly.</p></div><a href="{{ route('release') }}" class="btn btn-secondary" target="_blank">View public page</a></div><div class="table-scroll"><table class="pro-table"><thead><tr><th>Release</th><th>Channel</th><th>Status</th><th>Package</th><th>Downloads</th><th>Published</th><th></th></tr></thead><tbody>@forelse($releases as $release)<tr><td><strong>SonarDesk {{ $release->version }}</strong><small>Build {{ $release->build_number }} · macOS {{ $release->minimum_macos ?: 'Any supported' }}</small></td><td><span class="status-chip">{{ ucfirst($release->channel) }}</span></td><td>@if($release->is_published)<span class="status-chip success">Published</span>@else<span class="status-chip muted">Draft</span>@endif</td><td>{{ $release->file_path ? $release->formatted_size : 'No package' }}</td><td>{{ number_format($release->download_count) }}</td><td>{{ $release->published_at?->format('j M Y H:i') ?: '—' }}</td><td class="table-actions"><a class="btn btn-secondary" href="{{ route('admin.releases.edit',$release) }}">Edit</a><form method="post" action="{{ route('admin.releases.destroy',$release) }}" onsubmit="return confirm('Delete this release and its package?')">@csrf @method('DELETE')<button class="danger-outline" type="submit">Delete</button></form></td></tr>@empty<tr><td colspan="7" class="empty-cell">No releases yet. Create your first signed SonarDesk build.</td></tr>@endforelse</tbody></table></div></div>
|
||||
{{ $releases->links() }}
|
||||
@endsection
|
||||
@@ -1,5 +0,0 @@
|
||||
@extends('layouts.app', ['title' => 'Download page — SonarDesk Admin'])
|
||||
@section('content')
|
||||
<section class="portal-header"><div><span class="eyebrow">Website</span><h1>Download page content</h1><p class="lead compact">Control the customer-facing download area without editing a template or deploying code.</p></div><a class="btn btn-secondary" href="{{ route('admin.releases.index') }}">Back to releases</a></section>
|
||||
<form class="editor-card settings-editor" method="post" action="{{ route('admin.releases.settings.update') }}">@csrf @method('PUT')<label>Eyebrow<input name="eyebrow" value="{{ old('eyebrow',$settings['downloads.eyebrow'] ?: 'SonarDesk for macOS') }}" required></label><label>Page title<input name="title" value="{{ old('title',$settings['downloads.title'] ?: 'Download SonarDesk') }}" required></label><label>Introduction<textarea name="intro" rows="4" required>{{ old('intro',$settings['downloads.intro'] ?: 'A focused native SonarQube workspace, designed for macOS and built for daily engineering work.') }}</textarea></label><label>System requirements<textarea name="requirements" rows="4" required>{{ old('requirements',$settings['downloads.requirements'] ?: 'Apple silicon and Intel Macs supported. A SonarQube server and valid SonarDesk licence are required.') }}</textarea></label><label>Support message<textarea name="support_text" rows="4" required>{{ old('support_text',$settings['downloads.support_text'] ?: 'Need help installing or activating SonarDesk? Our support team can help.') }}</textarea></label><div><button class="btn btn-primary" type="submit">Save website content</button> <a class="btn btn-secondary" target="_blank" href="{{ route('release') }}">Preview page</a></div></form>
|
||||
@endsection
|
||||
@@ -1,39 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<section class="portal-header admin-release-head">
|
||||
<div><span class="eyebrow">Search & discovery</span><h1>SEO settings</h1><p class="lead compact">Control how SonarDesk appears in search engines and when pages are shared.</p></div>
|
||||
<div class="support-actions"><a class="btn btn-secondary" target="_blank" href="{{ route('sitemap') }}">View sitemap</a><a class="btn btn-secondary" target="_blank" href="{{ route('robots') }}">View robots.txt</a></div>
|
||||
</section>
|
||||
<form method="post" action="{{ route('admin.seo.update') }}" class="seo-admin-form">@csrf @method('PUT')
|
||||
<section class="panel seo-section">
|
||||
<div class="seo-section-head"><div><span class="eyebrow">Search appearance</span><h2>Site defaults</h2><p>Fallback metadata used whenever a page does not provide its own SEO values.</p></div><span class="seo-badge">Global</span></div>
|
||||
<div class="form-grid two">
|
||||
<label>Site name<input name="seo_site_name" value="{{ old('seo_site_name',$settings['seo_site_name'] ?: 'SonarDesk') }}" required maxlength="80"></label>
|
||||
<label>Title suffix<input name="seo_title_suffix" value="{{ old('seo_title_suffix',$settings['seo_title_suffix'] ?: 'SonarDesk') }}" maxlength="80"><small>Appended to page titles where appropriate.</small></label>
|
||||
</div>
|
||||
<label>Default SEO title<input name="seo_default_title" value="{{ old('seo_default_title',$settings['seo_default_title'] ?: 'SonarDesk — Native SonarQube Monitoring for macOS') }}" required maxlength="160"></label>
|
||||
<label>Default meta description<textarea name="seo_default_description" rows="3" maxlength="320" required>{{ old('seo_default_description',$settings['seo_default_description'] ?: 'Native SonarQube monitoring for macOS. Track projects, quality gates, bugs, vulnerabilities and code smells from one professional desktop app.') }}</textarea></label>
|
||||
<label>Default keywords<input name="seo_default_keywords" value="{{ old('seo_default_keywords',$settings['seo_default_keywords']) }}" maxlength="500" placeholder="SonarQube, macOS, code quality, developer tools"></label>
|
||||
<label>Canonical base URL<input type="url" name="seo_canonical_base_url" value="{{ old('seo_canonical_base_url',$settings['seo_canonical_base_url']) }}" maxlength="2048" placeholder="https://sonardesk.app"><small>Leave blank to use the application URL. Use the public production origin only.</small></label>
|
||||
</section>
|
||||
|
||||
<section class="panel seo-section">
|
||||
<div class="seo-section-head"><div><span class="eyebrow">Social sharing</span><h2>Open Graph & social</h2><p>Professional defaults for link previews across social networks and messaging applications.</p></div></div>
|
||||
<label>Default social image URL<input type="url" name="seo_default_og_image" value="{{ old('seo_default_og_image',$settings['seo_default_og_image']) }}" maxlength="2048" placeholder="https://…/og-sonardesk.png"><small>Recommended: 1200 × 630 px.</small></label>
|
||||
<div class="form-grid two"><label>Organization name<input name="seo_organization_name" value="{{ old('seo_organization_name',$settings['seo_organization_name'] ?: 'SonarDesk') }}" maxlength="120"></label><label>Organization logo URL<input type="url" name="seo_organization_logo" value="{{ old('seo_organization_logo',$settings['seo_organization_logo']) }}" maxlength="2048"></label></div>
|
||||
<label>X / Twitter account<input name="seo_twitter_site" value="{{ old('seo_twitter_site',$settings['seo_twitter_site']) }}" maxlength="40" placeholder="@SonarDesk"></label>
|
||||
</section>
|
||||
|
||||
<section class="panel seo-section">
|
||||
<div class="seo-section-head"><div><span class="eyebrow">Google Search Console</span><h2>Domain verification</h2><p>Use a Domain property in Google Search Console. Google will provide a TXT value that you add at your DNS provider.</p></div><span class="seo-badge success">DNS TXT</span></div>
|
||||
<div class="seo-dns-callout"><strong>Recommended verification method</strong><span>Add Google’s <code>google-site-verification=…</code> TXT record to the root domain. DNS verification covers all protocols and subdomains.</span></div>
|
||||
<label>Optional Google verification token<input name="seo_google_site_verification" value="{{ old('seo_google_site_verification',$settings['seo_google_site_verification']) }}" maxlength="255" placeholder="Only needed if you also choose HTML-tag verification"><small>DNS TXT verification does not require this field. It is available as a secondary verification method.</small></label>
|
||||
</section>
|
||||
|
||||
<section class="panel seo-section">
|
||||
<div class="seo-section-head"><div><span class="eyebrow">Crawler control</span><h2>Indexing</h2><p>Production crawler controls. Non-production environments remain blocked automatically.</p></div></div>
|
||||
<label class="consent-check"><input type="checkbox" name="seo_robots_enabled" value="1" @checked(old('seo_robots_enabled',$robotsEnabled))><span><strong>Allow production search-engine indexing</strong><small>When disabled, robots.txt blocks crawling and pages emit noindex.</small></span></label>
|
||||
</section>
|
||||
<div class="seo-savebar"><div><strong>Search configuration</strong><span>Changes apply to public metadata immediately.</span></div><button type="submit" class="btn btn-primary">Save SEO settings</button></div>
|
||||
</form>
|
||||
@endsection
|
||||
@@ -1,24 +0,0 @@
|
||||
@extends('layouts.app', ['title' => 'Support Inbox — SonarDesk'])
|
||||
@section('content')
|
||||
<section class="portal-header"><div><span class="eyebrow">Admin</span><h1>Support inbox</h1><p class="lead compact">Customer, licence, billing and privacy requests.</p></div></section>
|
||||
<form method="get" class="filter-bar">
|
||||
<label for="status">Status</label>
|
||||
<select id="status" name="status" onchange="this.form.submit()">
|
||||
<option value="">All</option>
|
||||
@foreach(['new','open','waiting','resolved','closed'] as $status)<option value="{{ $status }}" @selected(request('status') === $status)>{{ ucfirst($status) }}</option>@endforeach
|
||||
</select>
|
||||
</form>
|
||||
<div class="admin-ticket-list">
|
||||
@forelse($tickets as $ticket)
|
||||
<a class="admin-ticket-row" href="{{ route('admin.support.show', $ticket) }}">
|
||||
<div><span class="ticket-ref">{{ $ticket->reference }}</span><strong>{{ $ticket->subject }}</strong><small>{{ $ticket->name }} · {{ $ticket->email }}</small></div>
|
||||
<span class="ticket-category">{{ ucfirst($ticket->category) }}</span>
|
||||
<span class="ticket-status status-{{ $ticket->status }}">{{ ucfirst($ticket->status) }}</span>
|
||||
<time>{{ $ticket->created_at->diffForHumans() }}</time>
|
||||
</a>
|
||||
@empty
|
||||
<div class="empty-state"><h2>No support requests</h2><p>The support inbox is empty.</p></div>
|
||||
@endforelse
|
||||
</div>
|
||||
{{ $tickets->links() }}
|
||||
@endsection
|
||||
@@ -1,14 +0,0 @@
|
||||
@extends('layouts.app', ['title' => $ticket->reference.' — Support'])
|
||||
@section('content')
|
||||
<a class="back-link" href="{{ route('admin.support.index') }}">← Support inbox</a>
|
||||
<section class="ticket-detail-head"><div><span class="ticket-ref">{{ $ticket->reference }}</span><h1>{{ $ticket->subject }}</h1><p>{{ $ticket->name }} · <a href="mailto:{{ $ticket->email }}?subject=Re:%20{{ rawurlencode($ticket->reference.' '.$ticket->subject) }}">{{ $ticket->email }}</a></p></div><span class="ticket-status status-{{ $ticket->status }}">{{ ucfirst($ticket->status) }}</span></section>
|
||||
<div class="detail-grid">
|
||||
<article class="panel ticket-message"><h2>Request</h2><div class="ticket-meta"><span>{{ ucfirst($ticket->category) }}</span><span>{{ $ticket->created_at->format('j M Y H:i') }}</span></div><div class="message-body">{!! nl2br(e($ticket->message)) !!}</div><a class="btn btn-primary" href="mailto:{{ $ticket->email }}?subject=Re:%20{{ rawurlencode($ticket->reference.' '.$ticket->subject) }}">Reply by email</a></article>
|
||||
<aside class="panel"><h2>Manage</h2><form method="post" action="{{ route('admin.support.update', $ticket) }}">@csrf @method('PATCH')
|
||||
<label>Status<select name="status">@foreach(['new','open','waiting','resolved','closed'] as $status)<option value="{{ $status }}" @selected($ticket->status === $status)>{{ ucfirst($status) }}</option>@endforeach</select></label>
|
||||
<label>Priority<select name="priority">@foreach(['low','normal','high','urgent'] as $priority)<option value="{{ $priority }}" @selected($ticket->priority === $priority)>{{ ucfirst($priority) }}</option>@endforeach</select></label>
|
||||
<label>Assigned to<select name="assigned_to"><option value="">Unassigned</option>@foreach($admins as $admin)<option value="{{ $admin->id }}" @selected($ticket->assigned_to === $admin->id)>{{ $admin->name }} — {{ $admin->email }}</option>@endforeach</select></label>
|
||||
<button class="full-width">Save changes</button>
|
||||
</form></aside>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -1,31 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<section class="portal-header">
|
||||
<div><span class="eyebrow">Admin</span><h1>Users</h1><p class="lead compact">Manage portal accounts, access and customer licences.</p></div>
|
||||
<a class="btn btn-secondary" href="{{ route('admin.index') }}">Admin dashboard</a>
|
||||
</section>
|
||||
|
||||
<form method="get" class="card" style="display:flex;gap:10px;align-items:end;flex-wrap:wrap;margin-bottom:18px">
|
||||
<label style="flex:1;min-width:240px">Search<input name="q" value="{{ request('q') }}" placeholder="Name or email"></label>
|
||||
<label>Status<select name="status"><option value="">All users</option><option value="admin" @selected(request('status')==='admin')>Administrators</option><option value="suspended" @selected(request('status')==='suspended')>Suspended</option><option value="unverified" @selected(request('status')==='unverified')>Unverified</option></select></label>
|
||||
<button>Search</button>
|
||||
@if(request()->hasAny(['q','status']))<a class="btn btn-secondary" href="{{ route('admin.users.index') }}">Clear</a>@endif
|
||||
</form>
|
||||
|
||||
<div class="admin-table-card">
|
||||
<div class="admin-table-title"><div><h2>Portal users</h2><p>{{ $users->total() }} account{{ $users->total() === 1 ? '' : 's' }}</p></div></div>
|
||||
<div class="table-scroll"><table class="pro-table"><thead><tr><th>User</th><th>Access</th><th>Email</th><th>Licences</th><th>Joined</th><th></th></tr></thead><tbody>
|
||||
@forelse($users as $user)
|
||||
<tr>
|
||||
<td><strong>{{ $user->name }}</strong><small>#{{ $user->id }}</small></td>
|
||||
<td>@if($user->disabled_at)<span class="status-chip muted">Suspended</span>@elseif($user->is_admin)<span class="status-chip">Admin</span>@else<span class="status-chip success">Active</span>@endif</td>
|
||||
<td><strong>{{ $user->email }}</strong><small>{{ $user->hasVerifiedEmail() ? 'Verified' : 'Not verified' }}</small></td>
|
||||
<td>{{ $licenseCounts[strtolower($user->email)] ?? 0 }}</td>
|
||||
<td>{{ $user->created_at?->format('d M Y') }}</td>
|
||||
<td><div class="table-actions"><a class="btn btn-secondary" href="{{ route('admin.users.show',$user) }}">Manage</a></div></td>
|
||||
</tr>
|
||||
@empty<tr><td colspan="6" class="empty-cell">No users found.</td></tr>@endforelse
|
||||
</tbody></table></div>
|
||||
</div>
|
||||
<div style="margin-top:18px">{{ $users->links() }}</div>
|
||||
@endsection
|
||||
@@ -1,57 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
@section('content')
|
||||
<section class="portal-header">
|
||||
<div><span class="eyebrow">Admin · User</span><h1>{{ $user->name }}</h1><p class="lead compact">{{ $user->email }}</p></div>
|
||||
<a class="btn btn-secondary" href="{{ route('admin.users.index') }}">Back to users</a>
|
||||
</section>
|
||||
|
||||
@if($errors->any())<div class="card" style="border-color:#fda29b;margin-bottom:18px"><strong>Could not save changes</strong><ul>@foreach($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>@endif
|
||||
|
||||
<div class="release-editor">
|
||||
<div class="editor-main">
|
||||
<div class="editor-card">
|
||||
<div class="editor-card-head"><span>01</span><div><h2>Account details</h2><p>Edit the portal identity and administrator access.</p></div></div>
|
||||
<form method="post" action="{{ route('admin.users.update',$user) }}">@csrf @method('PUT')
|
||||
<div class="form-grid"><label>Name<input name="name" value="{{ old('name',$user->name) }}" required></label><label>Email<input type="email" name="email" value="{{ old('email',$user->email) }}" required></label></div>
|
||||
<label class="toggle-row"><input type="checkbox" name="is_admin" value="1" @checked($user->is_admin) @disabled(auth()->user()->is($user))><span><strong>Administrator</strong><small>Allows access to SonarDesk administration.</small></span></label>
|
||||
@if(auth()->user()->is($user))<input type="hidden" name="is_admin" value="1"><p><small>Your own administrator access is protected.</small></p>@endif
|
||||
<button>Save user</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="editor-card">
|
||||
<div class="editor-card-head"><span>02</span><div><h2>Licences</h2><p>Licences linked to this user's email address.</p></div></div>
|
||||
@forelse($licenses as $license)
|
||||
<div style="padding:18px 0;border-top:1px solid #eef2f6">
|
||||
<div style="display:flex;justify-content:space-between;gap:16px;align-items:flex-start;flex-wrap:wrap">
|
||||
<div><strong>{{ $license->edition }} · •••• {{ $license->license_key_last4 }}</strong><small style="display:block;color:#667085;margin-top:4px">{{ $license->subscription_status ?: 'No subscription status' }} · {{ $license->payment_provider ?: 'manual' }}</small></div>
|
||||
<div><a class="btn btn-secondary" href="{{ route('admin.license',$license) }}">Full licence</a></div>
|
||||
</div>
|
||||
<form method="post" action="{{ route('admin.users.licences.update',[$user,$license]) }}" style="display:flex;gap:10px;align-items:end;margin-top:14px;flex-wrap:wrap">@csrf @method('PATCH')<label>Activation limit<input type="number" min="1" max="1000" name="activation_limit" value="{{ $license->activation_limit }}" style="max-width:120px"></label><button>Update limit</button></form>
|
||||
@if($license->activations->isNotEmpty())
|
||||
<div class="table-scroll" style="margin-top:14px"><table class="pro-table"><thead><tr><th>Device</th><th>Platform</th><th>Version</th><th>Status</th><th></th></tr></thead><tbody>
|
||||
@foreach($license->activations as $activation)<tr><td>{{ $activation->installation_name ?: $activation->installation_id }}</td><td>{{ $activation->platform ?: '—' }}</td><td>{{ $activation->app_version ?: '—' }}</td><td>{{ $activation->deactivated_at ? 'Released' : 'Active' }}</td><td>@if(!$activation->deactivated_at)<form method="post" action="{{ route('admin.users.activations.destroy',[$user,$license,$activation]) }}">@csrf @method('DELETE')<button class="btn btn-secondary">Release</button></form>@endif</td></tr>@endforeach
|
||||
</tbody></table></div>
|
||||
@endif
|
||||
</div>
|
||||
@empty<p>No licences are linked to this email address.</p>@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="editor-main">
|
||||
<div class="editor-card sticky-card">
|
||||
<div class="editor-card-head"><span>03</span><div><h2>Account actions</h2><p>Security and access controls.</p></div></div>
|
||||
<p><strong>Email</strong><br><span class="status-chip {{ $user->hasVerifiedEmail() ? 'success' : 'muted' }}">{{ $user->hasVerifiedEmail() ? 'Verified' : 'Not verified' }}</span></p>
|
||||
@if(!$user->hasVerifiedEmail())
|
||||
<form method="post" action="{{ route('admin.users.verification-email',$user) }}" style="margin-bottom:10px">@csrf<button class="btn btn-secondary" style="width:100%">Send verification email</button></form>
|
||||
<form method="post" action="{{ route('admin.users.verify',$user) }}" style="margin-bottom:10px">@csrf<button class="btn btn-secondary" style="width:100%">Mark verified</button></form>
|
||||
@endif
|
||||
<form method="post" action="{{ route('admin.users.password-reset',$user) }}" style="margin-bottom:10px">@csrf<button class="btn btn-secondary" style="width:100%">Send password reset</button></form>
|
||||
@unless(auth()->user()->is($user))
|
||||
@if($user->disabled_at)<form method="post" action="{{ route('admin.users.restore',$user) }}" style="margin-bottom:10px">@csrf<button style="width:100%">Restore account</button></form>@else<form method="post" action="{{ route('admin.users.suspend',$user) }}" style="margin-bottom:10px">@csrf<button class="btn btn-secondary" style="width:100%">Suspend account</button></form>@endif
|
||||
<form method="post" action="{{ route('admin.users.destroy',$user) }}" onsubmit="return confirm('Delete this portal user? Their licences will be retained.')">@csrf @method('DELETE')<button class="danger" style="width:100%">Delete user</button></form>
|
||||
@endunless
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user