From 42c074d96618cddf86aa694f5d454aa50404abe3 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Tue, 20 Oct 2026 10:19:18 +0100 Subject: [PATCH] added theme support --- bootstrap/app.php | 2 +- modules/Stylist/Config/config.php | 6 +- .../Http/Controllers/StylistController.php | 4 +- .../Http/Middleware/ApplyActiveTheme.php | 31 + modules/Stylist/Routes/web.php | 3 +- modules/Stylist/SONARDESK-INTEGRATION.md | 15 + .../Stylist/Services/TenantThemeResolver.php | 4 +- .../Stylist/Services/ThemePackageManager.php | 3 + modules/Stylist/Theme/Stylist.php | 4 +- resources/themes/sonardesk-admin/theme.json | 14 + .../views/admin/cms/form.blade.php | 24 + .../views/admin/cms/index.blade.php | 9 + .../admin/download-products/index.blade.php | 15 + .../views/admin/index.blade.php | 6 + .../views/admin/license.blade.php | 1 + .../views/admin/licenses.blade.php | 1 + .../views/admin/releases/form.blade.php | 9 + .../views/admin/releases/index.blade.php | 7 + .../views/admin/releases/settings.blade.php | 5 + .../views/admin/seo/edit.blade.php | 39 ++ .../views/admin/support/index.blade.php | 24 + .../views/admin/support/show.blade.php | 14 + .../views/admin/users/index.blade.php | 31 + .../views/admin/users/show.blade.php | 57 ++ .../views/auth/forgot-password.blade.php | 1 + .../views/auth/login.blade.php | 1 + .../views/auth/register.blade.php | 1 + .../views/auth/reset-password.blade.php | 1 + .../views/auth/verify-email.blade.php | 1 + .../views/layouts/app.blade.php | 529 ++++++++++++++++++ resources/themes/sonardesk/theme.json | 14 + .../views/auth/forgot-password.blade.php | 1 + .../sonardesk/views/auth/login.blade.php | 1 + .../sonardesk/views/auth/register.blade.php | 1 + .../views/auth/reset-password.blade.php | 1 + .../views/auth/verify-email.blade.php | 1 + .../sonardesk/views/checkout/buy.blade.php | 301 ++++++++++ .../themes/sonardesk/views/cms/show.blade.php | 14 + .../views/downloads/appcast.blade.php | 2 + .../sonardesk/views/downloads/index.blade.php | 18 + .../views/downloads/product.blade.php | 13 + .../views/downloads/sonardesk.blade.php | 4 + .../themes/sonardesk/views/features.blade.php | 15 + .../sonardesk/views/layouts/app.blade.php | 529 ++++++++++++++++++ .../sonardesk/views/legal/cookies.blade.php | 16 + .../sonardesk/views/legal/privacy.blade.php | 39 ++ .../sonardesk/views/legal/refund.blade.php | 102 ++++ .../sonardesk/views/legal/terms.blade.php | 146 +++++ .../sonardesk/views/portal/index.blade.php | 114 ++++ .../sonardesk/views/portal/show.blade.php | 81 +++ .../sonardesk/views/portal/welcome.blade.php | 50 ++ .../themes/sonardesk/views/release.blade.php | 16 + .../sonardesk/views/seo/sitemap.blade.php | 18 + .../themes/sonardesk/views/support.blade.php | 100 ++++ .../themes/sonardesk/views/welcome.blade.php | 223 ++++++++ scripts/scan.sh | 0 56 files changed, 2671 insertions(+), 11 deletions(-) create mode 100644 modules/Stylist/Http/Middleware/ApplyActiveTheme.php create mode 100644 modules/Stylist/SONARDESK-INTEGRATION.md create mode 100644 resources/themes/sonardesk-admin/theme.json create mode 100644 resources/themes/sonardesk-admin/views/admin/cms/form.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/cms/index.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/download-products/index.blade.php create mode 100755 resources/themes/sonardesk-admin/views/admin/index.blade.php create mode 100755 resources/themes/sonardesk-admin/views/admin/license.blade.php create mode 100755 resources/themes/sonardesk-admin/views/admin/licenses.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/releases/form.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/releases/index.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/releases/settings.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/seo/edit.blade.php create mode 100755 resources/themes/sonardesk-admin/views/admin/support/index.blade.php create mode 100755 resources/themes/sonardesk-admin/views/admin/support/show.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/users/index.blade.php create mode 100644 resources/themes/sonardesk-admin/views/admin/users/show.blade.php create mode 100755 resources/themes/sonardesk-admin/views/auth/forgot-password.blade.php create mode 100755 resources/themes/sonardesk-admin/views/auth/login.blade.php create mode 100755 resources/themes/sonardesk-admin/views/auth/register.blade.php create mode 100755 resources/themes/sonardesk-admin/views/auth/reset-password.blade.php create mode 100755 resources/themes/sonardesk-admin/views/auth/verify-email.blade.php create mode 100644 resources/themes/sonardesk-admin/views/layouts/app.blade.php create mode 100644 resources/themes/sonardesk/theme.json create mode 100755 resources/themes/sonardesk/views/auth/forgot-password.blade.php create mode 100755 resources/themes/sonardesk/views/auth/login.blade.php create mode 100755 resources/themes/sonardesk/views/auth/register.blade.php create mode 100755 resources/themes/sonardesk/views/auth/reset-password.blade.php create mode 100755 resources/themes/sonardesk/views/auth/verify-email.blade.php create mode 100755 resources/themes/sonardesk/views/checkout/buy.blade.php create mode 100644 resources/themes/sonardesk/views/cms/show.blade.php create mode 100644 resources/themes/sonardesk/views/downloads/appcast.blade.php create mode 100644 resources/themes/sonardesk/views/downloads/index.blade.php create mode 100644 resources/themes/sonardesk/views/downloads/product.blade.php create mode 100644 resources/themes/sonardesk/views/downloads/sonardesk.blade.php create mode 100644 resources/themes/sonardesk/views/features.blade.php create mode 100644 resources/themes/sonardesk/views/layouts/app.blade.php create mode 100755 resources/themes/sonardesk/views/legal/cookies.blade.php create mode 100755 resources/themes/sonardesk/views/legal/privacy.blade.php create mode 100644 resources/themes/sonardesk/views/legal/refund.blade.php create mode 100755 resources/themes/sonardesk/views/legal/terms.blade.php create mode 100755 resources/themes/sonardesk/views/portal/index.blade.php create mode 100755 resources/themes/sonardesk/views/portal/show.blade.php create mode 100755 resources/themes/sonardesk/views/portal/welcome.blade.php create mode 100644 resources/themes/sonardesk/views/release.blade.php create mode 100644 resources/themes/sonardesk/views/seo/sitemap.blade.php create mode 100755 resources/themes/sonardesk/views/support.blade.php create mode 100755 resources/themes/sonardesk/views/welcome.blade.php mode change 100755 => 100644 scripts/scan.sh diff --git a/bootstrap/app.php b/bootstrap/app.php index 5304386..862dd23 100755 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -13,7 +13,7 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->web(append: [\Modules\Stylist\Http\Middleware\ApplyActiveTheme::class]); }) ->withExceptions(function (Exceptions $exceptions): void { $exceptions->shouldRenderJsonWhen( diff --git a/modules/Stylist/Config/config.php b/modules/Stylist/Config/config.php index 71b15d3..1fdaee1 100644 --- a/modules/Stylist/Config/config.php +++ b/modules/Stylist/Config/config.php @@ -1,9 +1,9 @@ env('STYLIST_DEFAULT_THEME', 'admin'), - 'default_backend_theme' => env('STYLIST_BACKEND_THEME', 'admin'), - 'default_frontend_theme' => env('STYLIST_FRONTEND_THEME', 'saasland'), + 'default_theme' => env('STYLIST_DEFAULT_THEME', 'sonardesk-admin'), + 'default_backend_theme' => env('STYLIST_BACKEND_THEME', 'sonardesk-admin'), + 'default_frontend_theme' => env('STYLIST_FRONTEND_THEME', 'sonardesk'), /* | Themes are application resources, never module resources. diff --git a/modules/Stylist/Http/Controllers/StylistController.php b/modules/Stylist/Http/Controllers/StylistController.php index 219359e..c98d044 100644 --- a/modules/Stylist/Http/Controllers/StylistController.php +++ b/modules/Stylist/Http/Controllers/StylistController.php @@ -16,8 +16,8 @@ final class StylistController extends Controller return view('stylist::index', [ 'theme' => $stylist->current(), 'themes' => $stylist->all(), 'variables' => $stylist->variables(), 'settingsService' => $settings, 'accessibility' => $accessibility, 'urls' => $urls, - 'activeFrontendTheme' => $siteSettings->get('frontend_theme', config('stylist.default_frontend_theme', 'saasland'), 'site'), - 'activeBackendTheme' => $siteSettings->get('backend_theme', config('stylist.default_backend_theme', 'admin'), 'site'), + 'activeFrontendTheme' => $siteSettings->get('frontend_theme', config('stylist.default_frontend_theme', 'sonardesk'), 'site'), + 'activeBackendTheme' => $siteSettings->get('backend_theme', config('stylist.default_backend_theme', 'sonardesk-admin'), 'site'), ]); } } diff --git a/modules/Stylist/Http/Middleware/ApplyActiveTheme.php b/modules/Stylist/Http/Middleware/ApplyActiveTheme.php new file mode 100644 index 0000000..c1e67a2 --- /dev/null +++ b/modules/Stylist/Http/Middleware/ApplyActiveTheme.php @@ -0,0 +1,31 @@ +is('admin', 'admin/*', 'stylist', 'stylist/*') ? 'backend' : 'frontend'; + try { + $theme = app(Stylist::class)->current($context); + if ($theme->type() === $context) { + $path = $theme->path().'/views'; + if (is_dir($path)) { + // Prepend so ordinary view('admin.index') and @extends('layouts.app') + // resolve theme files first, then original application files. + View::getFinder()->prependLocation($path); + } + } + } catch (Throwable $exception) { + report($exception); // Original application views remain available. + } + return $next($request); + } +} diff --git a/modules/Stylist/Routes/web.php b/modules/Stylist/Routes/web.php index c39ec85..3ceaa87 100644 --- a/modules/Stylist/Routes/web.php +++ b/modules/Stylist/Routes/web.php @@ -1,10 +1,11 @@ group(function (): void { +Route::middleware(['auth', 'verified', AdminOnly::class])->group(function (): void { Route::get('/', StylistController::class)->name('index'); Route::post('/install', [ThemeManagementController::class, 'install'])->name('install'); Route::put('/{theme}/settings', [ThemeManagementController::class, 'settings'])->name('settings'); diff --git a/modules/Stylist/SONARDESK-INTEGRATION.md b/modules/Stylist/SONARDESK-INTEGRATION.md new file mode 100644 index 0000000..c38d067 --- /dev/null +++ b/modules/Stylist/SONARDESK-INTEGRATION.md @@ -0,0 +1,15 @@ +# SonarDesk Stylist integration — 1.0.0 + +- `resources/themes/sonardesk/views` contains the original frontend Blade templates. +- `resources/themes/sonardesk-admin/views` contains the original backend Blade templates. +- Both are fully editable independent view trees, with application view fallback. +- Active theme is selected by existing Stylist SettingsRepository (`frontend_theme` / `backend_theme`). +- The `ApplyActiveTheme` web middleware prepends the active theme view directory for each request. +- Public pages and authenticated customer pages use frontend; `/admin` and `/stylist` use backend. +- Controllers, routes, licensing, payments, APIs and mail templates remain unchanged. +- Theme management now requires AdminOnly permissions. +- Existing CSS/JS assets remain in place. Replacement themes may provide new layout asset links. +- Run `php artisan optimize:clear` and `php artisan migrate` after deployment. +- Check `/stylist` as an administrator, then verify public, account, admin, and checkout pages. +- If database already contains old `frontend_theme`/`backend_theme` values, activate the new bundled themes from Stylist. +- Theme ZIP installation security and the completeness of third-party theme templates require separate review before production use. diff --git a/modules/Stylist/Services/TenantThemeResolver.php b/modules/Stylist/Services/TenantThemeResolver.php index 58d4920..ddd4ce2 100644 --- a/modules/Stylist/Services/TenantThemeResolver.php +++ b/modules/Stylist/Services/TenantThemeResolver.php @@ -29,8 +29,8 @@ final class TenantThemeResolver $settingKey = $context === 'frontend' ? 'frontend_theme' : 'backend_theme'; $default = $context === 'frontend' - ? config('stylist.default_frontend_theme', 'saasland') - : config('stylist.default_backend_theme', config('stylist.default_theme', 'admin')); + ? config('stylist.default_frontend_theme', 'sonardesk') + : config('stylist.default_backend_theme', config('stylist.default_theme', 'sonardesk-admin')); return (string) $this->settings->get($settingKey, $default, 'site'); } diff --git a/modules/Stylist/Services/ThemePackageManager.php b/modules/Stylist/Services/ThemePackageManager.php index 56d1a4a..50192d6 100644 --- a/modules/Stylist/Services/ThemePackageManager.php +++ b/modules/Stylist/Services/ThemePackageManager.php @@ -61,6 +61,9 @@ final class ThemePackageManager public function uninstall(Theme $theme): void { + if (in_array($theme->key(), ['sonardesk', 'sonardesk-admin'], true)) { + throw new RuntimeException('Bundled SonarDesk themes cannot be uninstalled.'); + } $active = $theme->type() === 'frontend' ? $this->settings->get('frontend_theme', config('stylist.default_frontend_theme'), 'site') : $this->settings->get('backend_theme', config('stylist.default_backend_theme'), 'site'); diff --git a/modules/Stylist/Theme/Stylist.php b/modules/Stylist/Theme/Stylist.php index 82793ef..1481a22 100644 --- a/modules/Stylist/Theme/Stylist.php +++ b/modules/Stylist/Theme/Stylist.php @@ -17,8 +17,8 @@ final class Stylist return $this->loader->find($requested); } catch (ThemeNotFoundException) { $fallback = $context === 'frontend' - ? (string) config('stylist.default_frontend_theme', 'saasland') - : (string) config('stylist.default_backend_theme', config('stylist.default_theme', 'admin')); + ? (string) config('stylist.default_frontend_theme', 'sonardesk') + : (string) config('stylist.default_backend_theme', config('stylist.default_theme', 'sonardesk-admin')); if ($requested !== $fallback) { return $this->loader->find($fallback); } diff --git a/resources/themes/sonardesk-admin/theme.json b/resources/themes/sonardesk-admin/theme.json new file mode 100644 index 0000000..441b078 --- /dev/null +++ b/resources/themes/sonardesk-admin/theme.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 1, + "key": "sonardesk-admin", + "name": "SonarDesk Administration", + "version": "1.0.0", + "type": "backend", + "description": "Original SonarDesk design, independently replaceable through Stylist.", + "entry_view": "admin.index", + "variants": { + "light": { + "variables": {} + } + } +} diff --git a/resources/themes/sonardesk-admin/views/admin/cms/form.blade.php b/resources/themes/sonardesk-admin/views/admin/cms/form.blade.php new file mode 100644 index 0000000..9df446c --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/cms/form.blade.php @@ -0,0 +1,24 @@ +@extends('layouts.app') +@section('content') +
CMS

{{ $page->exists ? 'Edit page' : 'New page' }}

Back to pages
+
@csrf @if($page->exists) @method('PUT') @endif + + + + +
+
Search appearance

Page SEO

Override global defaults only where this page needs specific search or social metadata.

Global SEO settings
+
+ + +
+ +
+
+ +
+ +
@if($page->exists && $page->is_published)View page@endif
+
+@if($page->exists)
@csrf @method('DELETE')
@endif +@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/cms/index.blade.php b/resources/themes/sonardesk-admin/views/admin/cms/index.blade.php new file mode 100644 index 0000000..52037c8 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/cms/index.blade.php @@ -0,0 +1,9 @@ +@extends('layouts.app') +@section('content') +
CMS

Content pages

Manage SonarDesk website content.

New page
+
{{ $pages->links() }}
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/download-products/index.blade.php b/resources/themes/sonardesk-admin/views/admin/download-products/index.blade.php new file mode 100644 index 0000000..6740f7f --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/download-products/index.blade.php @@ -0,0 +1,15 @@ +@extends('layouts.app', ['title' => 'Download products — Admin']) +@section('content') +
+
Distribution / Product management

Download products

Manage installers, releases and publication status in one place.

+ @if(session('status'))
{{ session('status') }}
@endif + @if($errors->any())@endif +
Total products{{ $products->count() }}
Published{{ $products->where('is_published', true)->count() }}
Drafts{{ $products->where('is_published', false)->count() }}
Total downloads{{ number_format($products->sum('download_count')) }}
+

Product catalogue

Publish or unpublish products instantly, or edit their details.

+ @forelse($products as $product) + @empty@endforelse +
ProductVersion / PlatformDownloadsStatusActions
{{ $product->name }}{{ \Illuminate\Support\Str::limit($product->description ?: $product->original_filename, 70) }}{{ $product->version ?: '—' }}{{ $product->platform ?: 'Unspecified' }} · {{ $product->formatted_size }}{{ number_format($product->download_count) }}{{ $product->is_published ? 'Published' : 'Draft' }}
@csrf @method('PATCH')
No additional products yet. Add your first installer below.
+
any() && !old('_method')) open @endif>Add a new productUpload an installer and choose whether to publish it.+
@csrf
+ @foreach($products as $product)
Edit {{ $product->name }}{{ $product->original_filename }} · {{ $product->formatted_size }}+
@csrf @method('PUT')
@csrf @method('DELETE')
@endforeach +
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/index.blade.php b/resources/themes/sonardesk-admin/views/admin/index.blade.php new file mode 100755 index 0000000..61766a6 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/index.blade.php @@ -0,0 +1,6 @@ +@extends('layouts.app') +@section('content') +
Admin

Licensing administration

Manage licences, activations and customer support.

+
{{ $licenseCount }}

Licences

{{ $activeCount }}

Not revoked

{{ $activationCount }}

Active installations

{{ $userCount }}

Portal users

+
Manage releasesManage licencesManage usersManage contentSEO & searchSupport inbox
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/license.blade.php b/resources/themes/sonardesk-admin/views/admin/license.blade.php new file mode 100755 index 0000000..9a2ac70 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/license.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Licence •••• {{ $license->license_key_last4 }}

{{ $license->customer_name }} · {{ $license->customer_email }}

{{ $license->edition }} · {{ $license->activation_limit }} seats

@if($license->revoked_at)
@csrf
@else
@csrf
@endif

Activations

@foreach($license->activations as $a)@endforeach
IDPlatformVersionStatus
{{ $a->installation_name ?: $a->installation_id }}{{ $a->platform }}{{ $a->app_version }}{{ $a->deactivated_at ? 'Released':'Active' }}
@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/licenses.blade.php b/resources/themes/sonardesk-admin/views/admin/licenses.blade.php new file mode 100755 index 0000000..8ff6b69 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/licenses.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Licences

@foreach($licenses as $l)@endforeach
CustomerEditionKeyStatus
{{ $l->customer_email ?: 'Manual' }}{{ $l->edition }}•••• {{ $l->license_key_last4 }}{{ $l->revoked_at ? 'Revoked':'Active' }}
{{ $licenses->links() }}
@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/releases/form.blade.php b/resources/themes/sonardesk-admin/views/admin/releases/form.blade.php new file mode 100644 index 0000000..458dc97 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/releases/form.blade.php @@ -0,0 +1,9 @@ +@extends('layouts.app', ['title' => ($release->exists ? 'Edit release' : 'New release').' — SonarDesk Admin']) +@section('content') +
Distribution

{{ $release->exists ? 'Edit SonarDesk '.$release->version : 'Create a release' }}

Upload the notarized distribution package and control exactly when it becomes public.

Back to releases
+
@csrf @if($release->exists) @method('PUT') @endif +
01

Release identity

Version and compatibility information shown to customers.

+
02

Distribution package

DMG or ZIP. SHA-256 is calculated automatically after upload.

@if($release->file_path)
{{ $release->original_filename }}{{ $release->formatted_size }} · SHA-256 {{ $release->sha256 }}
@endif
+
03

Release notes

Professional customer-facing notes. Plain text is rendered safely with line breaks.

+
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/releases/index.blade.php b/resources/themes/sonardesk-admin/views/admin/releases/index.blade.php new file mode 100644 index 0000000..a8caa71 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/releases/index.blade.php @@ -0,0 +1,7 @@ +@extends('layouts.app', ['title' => 'Releases — SonarDesk Admin']) +@section('content') +
Distribution

Release management

Publish SonarDesk builds, manage the public download experience and keep Sparkle updates under control.

+
{{ $releases->total() }}

Total releases

{{ $publishedCount }}

Published

{{ number_format($downloadCount) }}

Total downloads

Live

Sparkle feed

+

SonarDesk builds

Only published builds with an uploaded package are available publicly.

View public page
@forelse($releases as $release)@empty@endforelse
ReleaseChannelStatusPackageDownloadsPublished
SonarDesk {{ $release->version }}Build {{ $release->build_number }} · macOS {{ $release->minimum_macos ?: 'Any supported' }}{{ ucfirst($release->channel) }}@if($release->is_published)Published@elseDraft@endif{{ $release->file_path ? $release->formatted_size : 'No package' }}{{ number_format($release->download_count) }}{{ $release->published_at?->format('j M Y H:i') ?: '—' }}Edit
@csrf @method('DELETE')
No releases yet. Create your first signed SonarDesk build.
+{{ $releases->links() }} +@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/releases/settings.blade.php b/resources/themes/sonardesk-admin/views/admin/releases/settings.blade.php new file mode 100644 index 0000000..bddc7ac --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/releases/settings.blade.php @@ -0,0 +1,5 @@ +@extends('layouts.app', ['title' => 'Download page — SonarDesk Admin']) +@section('content') +
Website

Download page content

Control the customer-facing download area without editing a template or deploying code.

Back to releases
+
@csrf @method('PUT')
Preview page
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/seo/edit.blade.php b/resources/themes/sonardesk-admin/views/admin/seo/edit.blade.php new file mode 100644 index 0000000..67fcb35 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/seo/edit.blade.php @@ -0,0 +1,39 @@ +@extends('layouts.app') +@section('content') +
+
Search & discovery

SEO settings

Control how SonarDesk appears in search engines and when pages are shared.

+ +
+
@csrf @method('PUT') +
+
Search appearance

Site defaults

Fallback metadata used whenever a page does not provide its own SEO values.

Global
+
+ + +
+ + + + +
+ +
+
Social sharing

Open Graph & social

Professional defaults for link previews across social networks and messaging applications.

+ +
+ +
+ +
+
Google Search Console

Domain verification

Use a Domain property in Google Search Console. Google will provide a TXT value that you add at your DNS provider.

DNS TXT
+
Recommended verification methodAdd Google’s google-site-verification=… TXT record to the root domain. DNS verification covers all protocols and subdomains.
+ +
+ +
+
Crawler control

Indexing

Production crawler controls. Non-production environments remain blocked automatically.

+ +
+
Search configurationChanges apply to public metadata immediately.
+
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/support/index.blade.php b/resources/themes/sonardesk-admin/views/admin/support/index.blade.php new file mode 100755 index 0000000..ae56c8a --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/support/index.blade.php @@ -0,0 +1,24 @@ +@extends('layouts.app', ['title' => 'Support Inbox — SonarDesk']) +@section('content') +
Admin

Support inbox

Customer, licence, billing and privacy requests.

+
+ + +
+
+@forelse($tickets as $ticket) + +
{{ $ticket->reference }}{{ $ticket->subject }}{{ $ticket->name }} · {{ $ticket->email }}
+ {{ ucfirst($ticket->category) }} + {{ ucfirst($ticket->status) }} + +
+@empty +

No support requests

The support inbox is empty.

+@endforelse +
+{{ $tickets->links() }} +@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/support/show.blade.php b/resources/themes/sonardesk-admin/views/admin/support/show.blade.php new file mode 100755 index 0000000..4248c64 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/support/show.blade.php @@ -0,0 +1,14 @@ +@extends('layouts.app', ['title' => $ticket->reference.' — Support']) +@section('content') +← Support inbox +
{{ $ticket->reference }}

{{ $ticket->subject }}

{{ $ticket->name }} · {{ $ticket->email }}

{{ ucfirst($ticket->status) }}
+
+

Request

{{ ucfirst($ticket->category) }}{{ $ticket->created_at->format('j M Y H:i') }}
{!! nl2br(e($ticket->message)) !!}
Reply by email
+ +
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/users/index.blade.php b/resources/themes/sonardesk-admin/views/admin/users/index.blade.php new file mode 100644 index 0000000..9b6d9ef --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/users/index.blade.php @@ -0,0 +1,31 @@ +@extends('layouts.app') +@section('content') +
+
Admin

Users

Manage portal accounts, access and customer licences.

+ Admin dashboard +
+ +
+ + + + @if(request()->hasAny(['q','status']))Clear@endif +
+ +
+

Portal users

{{ $users->total() }} account{{ $users->total() === 1 ? '' : 's' }}

+
+ @forelse($users as $user) + + + + + + + + + @empty@endforelse +
UserAccessEmailLicencesJoined
{{ $user->name }}#{{ $user->id }}@if($user->disabled_at)Suspended@elseif($user->is_admin)Admin@elseActive@endif{{ $user->email }}{{ $user->hasVerifiedEmail() ? 'Verified' : 'Not verified' }}{{ $licenseCounts[strtolower($user->email)] ?? 0 }}{{ $user->created_at?->format('d M Y') }}
No users found.
+
+
{{ $users->links() }}
+@endsection diff --git a/resources/themes/sonardesk-admin/views/admin/users/show.blade.php b/resources/themes/sonardesk-admin/views/admin/users/show.blade.php new file mode 100644 index 0000000..825a9e1 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/admin/users/show.blade.php @@ -0,0 +1,57 @@ +@extends('layouts.app') +@section('content') +
+
Admin · User

{{ $user->name }}

{{ $user->email }}

+ Back to users +
+ +@if($errors->any())
Could not save changes
@endif + +
+
+
+
01

Account details

Edit the portal identity and administrator access.

+
@csrf @method('PUT') +
+ + @if(auth()->user()->is($user))

Your own administrator access is protected.

@endif + +
+
+ +
+
02

Licences

Licences linked to this user's email address.

+ @forelse($licenses as $license) +
+
+
{{ $license->edition }} · •••• {{ $license->license_key_last4 }}{{ $license->subscription_status ?: 'No subscription status' }} · {{ $license->payment_provider ?: 'manual' }}
+ +
+
@csrf @method('PATCH')
+ @if($license->activations->isNotEmpty()) +
+ @foreach($license->activations as $activation)@endforeach +
DevicePlatformVersionStatus
{{ $activation->installation_name ?: $activation->installation_id }}{{ $activation->platform ?: '—' }}{{ $activation->app_version ?: '—' }}{{ $activation->deactivated_at ? 'Released' : 'Active' }}@if(!$activation->deactivated_at)
@csrf @method('DELETE')
@endif
+ @endif +
+ @empty

No licences are linked to this email address.

@endforelse +
+
+ + +
+@endsection diff --git a/resources/themes/sonardesk-admin/views/auth/forgot-password.blade.php b/resources/themes/sonardesk-admin/views/auth/forgot-password.blade.php new file mode 100755 index 0000000..21c04c9 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/auth/forgot-password.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Reset password

@csrf
@endsection diff --git a/resources/themes/sonardesk-admin/views/auth/login.blade.php b/resources/themes/sonardesk-admin/views/auth/login.blade.php new file mode 100755 index 0000000..0701f4d --- /dev/null +++ b/resources/themes/sonardesk-admin/views/auth/login.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Sign in

@csrf
Forgot password?
@endsection diff --git a/resources/themes/sonardesk-admin/views/auth/register.blade.php b/resources/themes/sonardesk-admin/views/auth/register.blade.php new file mode 100755 index 0000000..64b5aa0 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/auth/register.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Create account

Use the same email address used to purchase SonarDesk.

@csrf
@endsection diff --git a/resources/themes/sonardesk-admin/views/auth/reset-password.blade.php b/resources/themes/sonardesk-admin/views/auth/reset-password.blade.php new file mode 100755 index 0000000..d11d1a7 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/auth/reset-password.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Choose a new password

@csrf
@endsection diff --git a/resources/themes/sonardesk-admin/views/auth/verify-email.blade.php b/resources/themes/sonardesk-admin/views/auth/verify-email.blade.php new file mode 100755 index 0000000..bde6474 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/auth/verify-email.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Verify your email

For security, licence ownership is only shown after the account email has been verified.

@csrf
@endsection diff --git a/resources/themes/sonardesk-admin/views/layouts/app.blade.php b/resources/themes/sonardesk-admin/views/layouts/app.blade.php new file mode 100644 index 0000000..8defb68 --- /dev/null +++ b/resources/themes/sonardesk-admin/views/layouts/app.blade.php @@ -0,0 +1,529 @@ + + + + + + + + @php + $seoSiteName = \App\Models\SiteSetting::valueFor('seo_site_name', 'SonarDesk'); + $seoDefaultTitle = \App\Models\SiteSetting::valueFor('seo_default_title', 'SonarDesk — Native SonarQube Monitoring for macOS'); + $seoDefaultDescription = \App\Models\SiteSetting::valueFor('seo_default_description', 'Native SonarQube monitoring for macOS. Track projects, quality gates, bugs, vulnerabilities and code smells from one professional desktop app.'); + $pageTitle = $title ?? $seoDefaultTitle; + $pageDescription = $description ?? $seoDefaultDescription; + $pageKeywords = $keywords ?? \App\Models\SiteSetting::valueFor('seo_default_keywords'); + $canonicalBase = rtrim(\App\Models\SiteSetting::valueFor('seo_canonical_base_url', config('app.url')), '/'); + $canonicalUrl = $canonical ?? ($canonicalBase . request()->getPathInfo()); + $socialImage = $ogImage ?? \App\Models\SiteSetting::valueFor('seo_default_og_image', url('/icons/og-sonardesk.png')); + $ogTitle = $ogTitle ?? $pageTitle; + $ogDescription = $ogDescription ?? $pageDescription; + $robotsIndex = $robotsIndex ?? true; + $robotsFollow = $robotsFollow ?? true; + $productionIndexing = app()->environment('production') && \App\Models\SiteSetting::booleanFor('seo_robots_enabled', true); + $robots = $productionIndexing ? (($robotsIndex ? 'index' : 'noindex').','.($robotsFollow ? 'follow' : 'nofollow').',max-image-preview:large,max-snippet:-1,max-video-preview:-1') : 'noindex,nofollow'; + $twitterSite = \App\Models\SiteSetting::valueFor('seo_twitter_site'); + $googleVerification = \App\Models\SiteSetting::valueFor('seo_google_site_verification'); + $organizationName = \App\Models\SiteSetting::valueFor('seo_organization_name', $seoSiteName); + $organizationLogo = \App\Models\SiteSetting::valueFor('seo_organization_logo', url('/icons/sonardesk-icon-512.png')); + $schema = [ + '@context' => 'https://schema.org', '@type' => 'SoftwareApplication', 'name' => $seoSiteName, + 'applicationCategory' => 'DeveloperApplication', 'operatingSystem' => 'macOS', 'url' => route('home'), + 'description' => $seoDefaultDescription, 'publisher' => ['@type' => 'Organization', 'name' => $organizationName, 'logo' => ['@type' => 'ImageObject', 'url' => $organizationLogo]], + ]; + @endphp + + {{ $pageTitle }} + + @if($pageKeywords)@endif + + + @if($googleVerification)@endif + + + + + + + + + + + + + + + + + + + + + + + + + @if($twitterSite)@endif + + + + + + + @stack('head') + + + + +@hasSection('fullbleed') + @yield('fullbleed') +@else +
+ @if(session('status'))
{{ session('status') }}
@endif + @if($errors->any())
{{ $errors->first() }}
@endif + @yield('content') +
+@endif + + + + + + + + + + + + diff --git a/resources/themes/sonardesk/theme.json b/resources/themes/sonardesk/theme.json new file mode 100644 index 0000000..aca60bc --- /dev/null +++ b/resources/themes/sonardesk/theme.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 1, + "key": "sonardesk", + "name": "SonarDesk Classic", + "version": "1.0.0", + "type": "frontend", + "description": "Original SonarDesk design, independently replaceable through Stylist.", + "entry_view": "portal.welcome", + "variants": { + "light": { + "variables": {} + } + } +} diff --git a/resources/themes/sonardesk/views/auth/forgot-password.blade.php b/resources/themes/sonardesk/views/auth/forgot-password.blade.php new file mode 100755 index 0000000..21c04c9 --- /dev/null +++ b/resources/themes/sonardesk/views/auth/forgot-password.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Reset password

@csrf
@endsection diff --git a/resources/themes/sonardesk/views/auth/login.blade.php b/resources/themes/sonardesk/views/auth/login.blade.php new file mode 100755 index 0000000..0701f4d --- /dev/null +++ b/resources/themes/sonardesk/views/auth/login.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Sign in

@csrf
Forgot password?
@endsection diff --git a/resources/themes/sonardesk/views/auth/register.blade.php b/resources/themes/sonardesk/views/auth/register.blade.php new file mode 100755 index 0000000..64b5aa0 --- /dev/null +++ b/resources/themes/sonardesk/views/auth/register.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Create account

Use the same email address used to purchase SonarDesk.

@csrf
@endsection diff --git a/resources/themes/sonardesk/views/auth/reset-password.blade.php b/resources/themes/sonardesk/views/auth/reset-password.blade.php new file mode 100755 index 0000000..d11d1a7 --- /dev/null +++ b/resources/themes/sonardesk/views/auth/reset-password.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Choose a new password

@csrf
@endsection diff --git a/resources/themes/sonardesk/views/auth/verify-email.blade.php b/resources/themes/sonardesk/views/auth/verify-email.blade.php new file mode 100755 index 0000000..bde6474 --- /dev/null +++ b/resources/themes/sonardesk/views/auth/verify-email.blade.php @@ -0,0 +1 @@ +@extends('layouts.app') @section('content')

Verify your email

For security, licence ownership is only shown after the account email has been verified.

@csrf
@endsection diff --git a/resources/themes/sonardesk/views/checkout/buy.blade.php b/resources/themes/sonardesk/views/checkout/buy.blade.php new file mode 100755 index 0000000..525fc4c --- /dev/null +++ b/resources/themes/sonardesk/views/checkout/buy.blade.php @@ -0,0 +1,301 @@ +@extends('layouts.app') + +@section('content') +
+ SonarDesk +

Choose your licence

+

+ Every subscription starts with a 7-day free trial. + Cancel before the trial ends and you won't be charged. +

+ + @if($paddleEnvironment === 'sandbox') +
+ Sandbox checkout — no real payment is taken +
+ @endif +
+ +
+ @foreach($plans as $code => $plan) +
+
+
+
+

{{ $plan['name'] }}

+
{{ $plan['billing'] }} billing
+
+ + @if($plan['billing'] === 'Annual') + Best value + @endif +
+ +
+ {{ $plan['display_price'] }} +
+ +
    +
  • 7-day free trial
  • +
  • + {{ $plan['activation_limit'] }} + active Mac{{ $plan['activation_limit'] === 1 ? '' : 's' }} +
  • +
  • Secure online activation
  • +
  • Licence management portal
  • +
+
+ + +
+ @endforeach +
+ + + + + + + + +@endsection diff --git a/resources/themes/sonardesk/views/cms/show.blade.php b/resources/themes/sonardesk/views/cms/show.blade.php new file mode 100644 index 0000000..35da777 --- /dev/null +++ b/resources/themes/sonardesk/views/cms/show.blade.php @@ -0,0 +1,14 @@ +@extends('layouts.app', [ + 'title' => $page->meta_title ?: $page->title.' — '.\App\Models\SiteSetting::valueFor('seo_site_name', 'SonarDesk'), + 'description' => $page->meta_description ?: $page->excerpt, + 'keywords' => $page->meta_keywords, + 'canonical' => $page->canonical_url ?: null, + 'ogTitle' => $page->og_title ?: null, + 'ogDescription' => $page->og_description ?: null, + 'ogImage' => $page->og_image ?: null, + 'robotsIndex' => $page->robots_index, + 'robotsFollow' => $page->robots_follow, +]) +@section('content') + +@endsection diff --git a/resources/themes/sonardesk/views/downloads/appcast.blade.php b/resources/themes/sonardesk/views/downloads/appcast.blade.php new file mode 100644 index 0000000..a7060d9 --- /dev/null +++ b/resources/themes/sonardesk/views/downloads/appcast.blade.php @@ -0,0 +1,2 @@ +@php echo ''; @endphp +SonarDesk Updates{{ route('updates.appcast') }}SonarDesk stable release feed@foreach($releases as $release)SonarDesk {{ $release->version }}{{ $release->published_at?->toRfc2822String() }}{{ $release->build_number }}{{ $release->version }}@if($release->minimum_macos){{ $release->minimum_macos }}@endifrelease_notes ?: 'SonarDesk update')) !!}]]>sparkle_signature) sparkle:edSignature="{{ $release->sparkle_signature }}"@endif />@endforeach diff --git a/resources/themes/sonardesk/views/downloads/index.blade.php b/resources/themes/sonardesk/views/downloads/index.blade.php new file mode 100644 index 0000000..ca4aa38 --- /dev/null +++ b/resources/themes/sonardesk/views/downloads/index.blade.php @@ -0,0 +1,18 @@ +@extends('layouts.app', ['title' => 'Downloads — SonarDesk and developer tools', 'description' => 'Browse SonarDesk and other macOS developer utilities. View release details or download installers.']) +@section('fullbleed') +
+
Software downloads

Downloads

Find the latest releases of SonarDesk and our developer tools. View details, check requirements or download directly.

+
Available software

Product catalogue

All downloads in one place.

+
+@if($latest) +
macOSv{{ $latest->version }}

SonarDesk

{{ \Illuminate\Support\Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags(\Illuminate\Support\Str::markdown($latest->release_notes ?: $settings['intro'], ['html_input'=>'strip','allow_unsafe_links'=>false])))), 175) }}

+@endif +@foreach($products as $product) +
{{ $product->platform ?: 'Software' }}{{ $product->version ? 'v'.$product->version : 'Latest' }}

{{ $product->name }}

{{ \Illuminate\Support\Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags(\Illuminate\Support\Str::markdown($product->description ?: 'Download the latest available package.', ['html_input'=>'strip','allow_unsafe_links'=>false])))), 175) }}

+@endforeach +
+@if(!$latest && $products->isEmpty())

No published downloads are available yet.

@endif +
+@if($releases->isNotEmpty())
SonarDesk archive

Previous SonarDesk versions

Older published installers, grouped separately from current products.

@foreach($releases as $release)
SonarDesk {{ $release->version }}Build {{ $release->build_number }} · {{ ucfirst($release->channel) }} · {{ $release->published_at?->format('j M Y') }}
{{ $release->formatted_size }}Download
@endforeach
{{ $releases->links() }}
@endif +
+@endsection diff --git a/resources/themes/sonardesk/views/downloads/product.blade.php b/resources/themes/sonardesk/views/downloads/product.blade.php new file mode 100644 index 0000000..11903d4 --- /dev/null +++ b/resources/themes/sonardesk/views/downloads/product.blade.php @@ -0,0 +1,13 @@ +@extends('layouts.app', ['title' => $product->name.' — Downloads', 'description' => 'Download '.$product->name.' and read product information and release notes.']) +@section('fullbleed') +
+
+← All downloads +
{{ $product->platform ?: 'Software' }}{{ $product->version ? 'v'.$product->version : 'Latest' }}
+

{{ $product->name }}

+

{{ $product->formatted_size }} · {{ $product->platform ?: 'Software' }}

+Download {{ $product->name }} ↓ +
{!! \Illuminate\Support\Str::markdown($product->description ?: 'Download the latest available package.', ['html_input' => 'strip', 'allow_unsafe_links' => false]) !!}
+Download ↓ +
+@endsection diff --git a/resources/themes/sonardesk/views/downloads/sonardesk.blade.php b/resources/themes/sonardesk/views/downloads/sonardesk.blade.php new file mode 100644 index 0000000..a454983 --- /dev/null +++ b/resources/themes/sonardesk/views/downloads/sonardesk.blade.php @@ -0,0 +1,4 @@ +@extends('layouts.app', ['title' => 'SonarDesk — Details and download', 'description' => 'SonarDesk release notes, system requirements and macOS installer.']) +@section('fullbleed') +
← All downloads
macOSv{{ $latest->version }}

SonarDesk

{{ $settings['intro'] }}

Version {{ $latest->version }} · Build {{ $latest->build_number }} · {{ $latest->formatted_size }} · Released {{ $latest->published_at?->format('j F Y') }}

Download SonarDesk ↓

Release notes

{!! \Illuminate\Support\Str::markdown($latest->release_notes ?: 'Latest maintenance and improvements.', ['html_input'=>'strip','allow_unsafe_links'=>false]) !!}

System requirements

{{ $settings['requirements'] }}

@if($latest->minimum_macos)

Minimum macOS version: {{ $latest->minimum_macos }}

@endif

Installation

  1. Download the signed installer.
  2. Open the DMG and move SonarDesk to Applications.
  3. Launch SonarDesk and activate with your licence.
@if($latest->sha256)
SHA-256 checksum{{ $latest->sha256 }}
@endif
+@endsection diff --git a/resources/themes/sonardesk/views/features.blade.php b/resources/themes/sonardesk/views/features.blade.php new file mode 100644 index 0000000..5602e8a --- /dev/null +++ b/resources/themes/sonardesk/views/features.blade.php @@ -0,0 +1,15 @@ +@extends('layouts.app', ['title' => 'Features — SonarDesk', 'description' => 'Explore SonarDesk features for monitoring SonarQube projects, quality gates, issues and measures from macOS.']) +@section('content') +
Features

A focused desktop companion for SonarQube.

Built around the checks developers make every day: project status, quality gates, issues, security findings and measures.

+
+
Projects

Project dashboard

Keep configured SonarQube projects together and scan project health without opening a collection of browser tabs.

+
Quality

Quality gates and measures

Surface the status and metrics that tell you whether a project needs attention, in a layout designed for quick review.

+
Issues

Bugs, vulnerabilities and code smells

Browse the major SonarQube issue categories, filter the list and open an issue to inspect its details and context.

+
Scale

Server-side pagination

Issue browsing is designed to remain practical on projects with larger result sets by paging data from the server.

+
Connections

Multiple SonarQube servers

Configure the environments you work with and keep them available from the same SonarDesk workspace.

+
Security

macOS Keychain

SonarQube tokens are stored using macOS Keychain rather than being treated as ordinary application preferences.

+
Licensing

Email delivery and self-service portal

After checkout, the licence key is emailed to the customer. The SonarDesk portal can resend the key and manage licence information and Mac activations.

+
Distribution

Built for direct Mac distribution

SonarDesk is distributed directly for macOS, giving the product a focused release and support channel outside the Mac App Store.

+
+
7-day free trial

Try the complete app.

Choose Personal for one active Mac or Professional for up to three.

View pricing
+@endsection diff --git a/resources/themes/sonardesk/views/layouts/app.blade.php b/resources/themes/sonardesk/views/layouts/app.blade.php new file mode 100644 index 0000000..8defb68 --- /dev/null +++ b/resources/themes/sonardesk/views/layouts/app.blade.php @@ -0,0 +1,529 @@ + + + + + + + + @php + $seoSiteName = \App\Models\SiteSetting::valueFor('seo_site_name', 'SonarDesk'); + $seoDefaultTitle = \App\Models\SiteSetting::valueFor('seo_default_title', 'SonarDesk — Native SonarQube Monitoring for macOS'); + $seoDefaultDescription = \App\Models\SiteSetting::valueFor('seo_default_description', 'Native SonarQube monitoring for macOS. Track projects, quality gates, bugs, vulnerabilities and code smells from one professional desktop app.'); + $pageTitle = $title ?? $seoDefaultTitle; + $pageDescription = $description ?? $seoDefaultDescription; + $pageKeywords = $keywords ?? \App\Models\SiteSetting::valueFor('seo_default_keywords'); + $canonicalBase = rtrim(\App\Models\SiteSetting::valueFor('seo_canonical_base_url', config('app.url')), '/'); + $canonicalUrl = $canonical ?? ($canonicalBase . request()->getPathInfo()); + $socialImage = $ogImage ?? \App\Models\SiteSetting::valueFor('seo_default_og_image', url('/icons/og-sonardesk.png')); + $ogTitle = $ogTitle ?? $pageTitle; + $ogDescription = $ogDescription ?? $pageDescription; + $robotsIndex = $robotsIndex ?? true; + $robotsFollow = $robotsFollow ?? true; + $productionIndexing = app()->environment('production') && \App\Models\SiteSetting::booleanFor('seo_robots_enabled', true); + $robots = $productionIndexing ? (($robotsIndex ? 'index' : 'noindex').','.($robotsFollow ? 'follow' : 'nofollow').',max-image-preview:large,max-snippet:-1,max-video-preview:-1') : 'noindex,nofollow'; + $twitterSite = \App\Models\SiteSetting::valueFor('seo_twitter_site'); + $googleVerification = \App\Models\SiteSetting::valueFor('seo_google_site_verification'); + $organizationName = \App\Models\SiteSetting::valueFor('seo_organization_name', $seoSiteName); + $organizationLogo = \App\Models\SiteSetting::valueFor('seo_organization_logo', url('/icons/sonardesk-icon-512.png')); + $schema = [ + '@context' => 'https://schema.org', '@type' => 'SoftwareApplication', 'name' => $seoSiteName, + 'applicationCategory' => 'DeveloperApplication', 'operatingSystem' => 'macOS', 'url' => route('home'), + 'description' => $seoDefaultDescription, 'publisher' => ['@type' => 'Organization', 'name' => $organizationName, 'logo' => ['@type' => 'ImageObject', 'url' => $organizationLogo]], + ]; + @endphp + + {{ $pageTitle }} + + @if($pageKeywords)@endif + + + @if($googleVerification)@endif + + + + + + + + + + + + + + + + + + + + + + + + + @if($twitterSite)@endif + + + + + + + @stack('head') + + + + +@hasSection('fullbleed') + @yield('fullbleed') +@else +
+ @if(session('status'))
{{ session('status') }}
@endif + @if($errors->any())
{{ $errors->first() }}
@endif + @yield('content') +
+@endif + + + + + + + + + + + + diff --git a/resources/themes/sonardesk/views/legal/cookies.blade.php b/resources/themes/sonardesk/views/legal/cookies.blade.php new file mode 100755 index 0000000..06981ab --- /dev/null +++ b/resources/themes/sonardesk/views/legal/cookies.blade.php @@ -0,0 +1,16 @@ +@extends('layouts.app', ['title' => 'Cookie Policy — SonarDesk']) + +@section('content') + +@endsection diff --git a/resources/themes/sonardesk/views/legal/privacy.blade.php b/resources/themes/sonardesk/views/legal/privacy.blade.php new file mode 100755 index 0000000..a19a562 --- /dev/null +++ b/resources/themes/sonardesk/views/legal/privacy.blade.php @@ -0,0 +1,39 @@ +@extends('layouts.app', ['title' => 'Privacy Policy — SonarDesk']) + +@section('content') +@php + $legal = config('sonardesk.legal'); + $retention = config('sonardesk.privacy.support_retention_days', 730); +@endphp + +@endsection diff --git a/resources/themes/sonardesk/views/legal/refund.blade.php b/resources/themes/sonardesk/views/legal/refund.blade.php new file mode 100644 index 0000000..4410517 --- /dev/null +++ b/resources/themes/sonardesk/views/legal/refund.blade.php @@ -0,0 +1,102 @@ +@extends('layouts.app', ['title' => 'Refund Policy — SonarDesk']) + +@section('content') +@php($legal = config('sonardesk.legal')) + +@endsection diff --git a/resources/themes/sonardesk/views/legal/terms.blade.php b/resources/themes/sonardesk/views/legal/terms.blade.php new file mode 100755 index 0000000..45c57de --- /dev/null +++ b/resources/themes/sonardesk/views/legal/terms.blade.php @@ -0,0 +1,146 @@ +@extends('layouts.app', ['title' => 'Terms of Service — SonarDesk']) + +@section('content') +@php($legal = config('sonardesk.legal')) + +@endsection diff --git a/resources/themes/sonardesk/views/portal/index.blade.php b/resources/themes/sonardesk/views/portal/index.blade.php new file mode 100755 index 0000000..5424076 --- /dev/null +++ b/resources/themes/sonardesk/views/portal/index.blade.php @@ -0,0 +1,114 @@ +@extends('layouts.app') + +@section('content') +
Latest SonarDeskNeed the Mac app?Download the latest published, signed release from the SonarDesk distribution centre.
Download SonarDesk
+
+
+ Account +

My licences

+

Manage subscriptions, licence keys and activated Macs.

+
+ Buy another licence +
+ +@php + $activeLicences = $licenses->filter(fn ($license) => $license->is_usable); + $historyLicences = $licenses->reject(fn ($license) => $license->is_usable); +@endphp + +@if($activeLicences->isEmpty()) +
+

No active licences

+

No active SonarDesk licence is associated with {{ auth()->user()->email }}.

+ Choose a plan +
+@else +
+ @foreach($activeLicences as $license) + @php + $activeCount = $license->activations->whereNull('deactivated_at')->count(); + $isDuplicatePersonal = $hasProfessional && $license->edition === 'Personal'; + $billing = str_contains(strtolower((string) optional($license->product)->code), 'annual') ? 'Annual' : 'Monthly'; + @endphp +
+
+
+
+
+ Active + {{ $license->edition }} + {{ $billing }} +
+

SonarDesk {{ $license->edition }}

+

Licence ending {{ $license->license_key_last4 }}

+
+ Manage licence +
+ +
+
+ Macs activated + {{ $activeCount }} of {{ $license->activation_limit }} +
+
+ Subscription + {{ ucfirst($license->subscription_status ?: 'active') }} +
+
+ {{ $license->subscription_status === 'trialing' ? 'Trial ends' : 'Renews' }} + {{ optional($license->current_period_ends_at)->format('j M Y') ?: '—' }} +
+
+ +
+ +
+ + @if($isDuplicatePersonal) +
+
+ You already have an active Professional subscription. +

This Personal plan is a separate Paddle subscription and could be billed separately. Cancel it if it was only created while upgrading.

+
+
+ @csrf + @method('DELETE') + +
+
+ @elseif($license->edition === 'Personal') +
+
+ Need more Macs? + Upgrade this subscription to Professional for up to 3 Macs. +
+
+ @csrf + +
+
+ @endif +
+
+ @endforeach +
+@endif + +@if($historyLicences->isNotEmpty()) +
+ Licence history {{ $historyLicences->count() }} +
+ @foreach($historyLicences as $license) +
+
+ SonarDesk {{ $license->edition }} + Licence ending {{ $license->license_key_last4 }} +
+
{{ ucfirst($license->subscription_status ?: ($license->revoked_at ? 'revoked' : 'unavailable')) }}
+ Details +
+ @endforeach +
+
+@endif +@endsection diff --git a/resources/themes/sonardesk/views/portal/show.blade.php b/resources/themes/sonardesk/views/portal/show.blade.php new file mode 100755 index 0000000..cc75ebb --- /dev/null +++ b/resources/themes/sonardesk/views/portal/show.blade.php @@ -0,0 +1,81 @@ +@extends('layouts.app') + +@section('content') +
Latest SonarDeskNeed the Mac app?Download the latest published, signed release from the SonarDesk distribution centre.
Download SonarDesk
+@php + $activeActivations = $license->activations->whereNull('deactivated_at'); + $billing = str_contains(strtolower((string) optional($license->product)->code), 'annual') ? 'Annual' : 'Monthly'; +@endphp + +← My licences + +
+
+
+ {{ $license->is_usable ? 'Active' : 'Unavailable' }} + {{ $license->edition }} + {{ $billing }} +
+

SonarDesk {{ $license->edition }}

+

Licence ending {{ $license->license_key_last4 }}

+
+
+ @csrf + +
+
+ +
+
+
+
+

Activated Macs

+

{{ $activeActivations->count() }} of {{ $license->activation_limit }} activation slots in use.

+
+ {{ $activeActivations->count() }}/{{ $license->activation_limit }} +
+ + @forelse($activeActivations as $activation) +
+
⌘
+
+ {{ $activation->installation_name ?: 'Mac' }} + {{ $activation->platform ?: 'macOS' }}@if($activation->app_version) · SonarDesk {{ $activation->app_version }}@endif + Last seen {{ optional($activation->last_seen_at)->diffForHumans() ?: 'not yet recorded' }} +
+
+ @csrf + @method('DELETE') + +
+
+ @empty +
No Macs are currently activated with this licence.
+ @endforelse +
+ + +
+@endsection diff --git a/resources/themes/sonardesk/views/portal/welcome.blade.php b/resources/themes/sonardesk/views/portal/welcome.blade.php new file mode 100755 index 0000000..c56a643 --- /dev/null +++ b/resources/themes/sonardesk/views/portal/welcome.blade.php @@ -0,0 +1,50 @@ +@extends('layouts.app', [ + 'title' => 'SonarDesk — Native SonarQube monitoring for macOS', + 'description' => 'SonarDesk is a native macOS companion for SonarQube: monitor quality gates, issues, measures and project health from one focused desktop workspace.' +]) +@section('content') +
+ SonarQube, without the browser tab sprawl +

Your code quality.
At home on the Mac.

+

SonarDesk is a focused native macOS companion for developers and teams using SonarQube. See project health, quality gates, bugs, vulnerabilities, code smells and measures in one fast desktop workspace.

+ +

Direct download for macOS · Secure licence activation · Cancel anytime

+
+ +
+ Native macOS appMultiple SonarQube serversKeychain token storageIssue detail workflow +
+ +
+
Built for daily engineering work

See the signal. Open the issue. Get back to code.

SonarDesk keeps the information you check most often close at hand, without replacing SonarQube or changing your existing workflow.

+
+
01

Project health at a glance

Review configured projects, quality gate state and important measures from a clean Mac dashboard.

+
02

Issue details that are useful

Open bugs, vulnerabilities and code smells to see the context you need before switching back to the codebase.

+
03

Focused filtering

Work through issue lists with practical filters and server-side pagination rather than fighting a giant browser view.

+
04

Multiple servers

Keep the SonarQube environments you work with available from one desktop application.

+
05

macOS security

SonarQube access tokens are stored in the macOS Keychain. SonarDesk respects the permissions of the token you provide.

+
06

Native workflow

A purpose-built macOS interface with familiar navigation, desktop behaviour and a focused experience for engineering teams.

+
+
+ +
+
Designed to complement SonarQube

Keep SonarQube as the source of truth.

SonarDesk is a desktop companion, not a replacement. It connects to your SonarQube server using your credentials and presents the project and issue information you need for everyday monitoring.

+
Quality gates and measures
Bugs, vulnerabilities and code smells
Project and issue navigation
Secure token storage in Keychain
Customer portal for licences and activations
+
+ +
+
Straightforward pricing

Start small. Upgrade when you need more Macs.

Every plan includes the full SonarDesk application. Professional increases the activation allowance for people working across multiple Macs.

+
+
Personal

For one Mac

£24.99 / year

or £2.99 monthly

  • Full SonarDesk feature set
  • 1 active Mac
  • 7-day free trial
  • Licence key delivered by email
  • Customer licence portal
+ +
+ +
+ +
Desktop distribution

Ready when you are.

Download the latest signed SonarDesk release for macOS, review what changed, verify the package checksum and browse previous versions from one dedicated release centre.

@if($latestRelease)Latest stableVersion {{ $latestRelease->version }}Build {{ $latestRelease->build_number }} · {{ $latestRelease->formatted_size }} · macOS {{ $latestRelease->minimum_macos ?: 'supported versions' }}
SHA-256{{ Str::limit($latestRelease->sha256, 34) }}
@elseSonarDesk releasesDistribution centrePublished builds will appear here automatically.@endif
+ +
SonarDesk for macOS

Make SonarQube easier to keep an eye on.

Try SonarDesk for seven days and connect it to your existing SonarQube environment.

Start free trial
+@endsection diff --git a/resources/themes/sonardesk/views/release.blade.php b/resources/themes/sonardesk/views/release.blade.php new file mode 100644 index 0000000..b43d39b --- /dev/null +++ b/resources/themes/sonardesk/views/release.blade.php @@ -0,0 +1,16 @@ +@extends('layouts.app', ['title' => 'SonarDesk 1.0 — Release', 'description' => 'SonarDesk 1.0 for macOS: a native desktop companion for SonarQube project health, quality gates and issues.']) +@section('content') +
Introducing SonarDesk 1.0

SonarQube monitoring, built for the Mac.

SonarDesk brings project health, quality gates, bugs, vulnerabilities, code smells and measures into a dedicated native macOS workspace.

+
+
+ Version 1.0

Why SonarDesk

+

SonarQube is an essential part of many development workflows, but checking it throughout the day often means returning to another browser tab, navigating back to a project and rebuilding context. SonarDesk is designed to make that recurring check faster and more focused on macOS.

+

It does not replace SonarQube. Your SonarQube server remains the source of truth. SonarDesk gives you a dedicated desktop view of the information you monitor most often.

+

What is included

+
  • Project dashboards and project health views
  • Quality gate status and important measures
  • Issue browsing for bugs, vulnerabilities and code smells
  • Detailed issue views and practical filtering
  • Server-side pagination for larger issue sets
  • Support for multiple SonarQube server configurations
  • SonarQube token storage using macOS Keychain
  • Direct licensing with a self-service customer portal
+

Who it is for

SonarDesk is for developers, technical leads and engineering teams who already use SonarQube and want a faster, dedicated way to keep project quality visible while working on a Mac.

+

Launch pricing

Personal is £24.99/year or £2.99/month for one active Mac. Professional is £44.99/year or £4.99/month for up to three active Macs. Both include a 7-day free trial and the complete SonarDesk feature set.

+
+ +
+@endsection diff --git a/resources/themes/sonardesk/views/seo/sitemap.blade.php b/resources/themes/sonardesk/views/seo/sitemap.blade.php new file mode 100644 index 0000000..517eb1c --- /dev/null +++ b/resources/themes/sonardesk/views/seo/sitemap.blade.php @@ -0,0 +1,18 @@ +{!! '' !!} + +@foreach($static as $item) + + {{ route($item['route']) }} + {{ $item['frequency'] }} + {{ $item['priority'] }} + +@endforeach +@foreach($pages as $page) + + {{ route('cms.show', $page->slug) }} + {{ $page->updated_at->toAtomString() }} + {{ $page->sitemap_change_frequency }} + {{ number_format((float) $page->sitemap_priority, 1, '.', '') }} + +@endforeach + diff --git a/resources/themes/sonardesk/views/support.blade.php b/resources/themes/sonardesk/views/support.blade.php new file mode 100755 index 0000000..6bdee43 --- /dev/null +++ b/resources/themes/sonardesk/views/support.blade.php @@ -0,0 +1,100 @@ +@extends('layouts.app', [ + 'title' => 'Support — SonarDesk', + 'description' => 'Contact SonarDesk support for technical, licence, billing and privacy enquiries.' +]) + +@section('content') +
+ Support +

How can we help?

+

Send us the details below. Your request is saved securely, assigned a reference and emailed to the support team.

+
+ +
+
+
+
+

Contact support

+

We will reply to the email address you provide.

+
+ Support ticket +
+ +
+ @csrf + + +
+
+ + + @error('name')
{{ $message }}
@enderror +
+
+ + + @error('email')
{{ $message }}
@enderror +
+
+ +
+
+ + + @error('category')
{{ $message }}
@enderror +
+
+ + + @error('subject')
{{ $message }}
@enderror +
+
+ +
+ + +
Do not include passwords, private SSH keys or your full licence token.
+ @error('message')
{{ $message }}
@enderror +
+ + + @error('privacy_notice')
{{ $message }}
@enderror + +
+ + @auth + @if(auth()->user()->hasVerifiedEmail())Open licence portal@endif + @endauth +
+
+
+ + +
+@endsection diff --git a/resources/themes/sonardesk/views/welcome.blade.php b/resources/themes/sonardesk/views/welcome.blade.php new file mode 100755 index 0000000..26e294a --- /dev/null +++ b/resources/themes/sonardesk/views/welcome.blade.php @@ -0,0 +1,223 @@ + + + + + + + {{ config('app.name', 'Laravel') }} + + @fonts + + + @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) + @vite(['resources/css/app.css', 'resources/js/app.js']) + @else + + @endif + + +
+ @if (Route::has('login')) + + @endif +
+
+
+
+

Let's get started

+

With so many options available to you,
we suggest you start with the following:

+ + + +

+ v{{ app()->version() }} + + View changelog + + + + +

+
+
+ {{-- Laravel Logo --}} + + + + + + + + + + + {{-- 13 --}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + @if (Route::has('login')) + + @endif + + diff --git a/scripts/scan.sh b/scripts/scan.sh old mode 100755 new mode 100644