From dd1f1449087c2f1f2e1dd1e62b5bdfebbdea4049 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 12 Oct 2026 16:23:27 +0100 Subject: [PATCH] move into sonardesk --- ...26_10_12_160000_extend_download_products.php | 8 ++++++++ .../Admin/DistributionAdminController.php | 14 +++++++++++--- .../Http/Controllers/DistributionController.php | 2 +- Modules/Licensing/Models/DownloadProduct.php | 4 ++-- .../Resources/views/admin/layout.blade.php | 11 +++++++---- .../views/distribution/admin.blade.php | 17 ++++++++++------- .../Resources/views/downloads/index.blade.php | 11 ++++------- .../Resources/views/downloads/product.blade.php | 4 +--- .../Resources/views/downloads/release.blade.php | 4 +--- Modules/Licensing/Routes/distribution.php | 4 ++++ public/css/sonardesk-absdev.css | 6 +++++- public/css/sonardesk-downloads.css | 3 +++ .../components/layouts/app/sidebar.blade.php | 3 +++ resources/themes/saasland/views/home.blade.php | 7 ++++--- .../saasland/views/partials/header.blade.php | 4 ++-- 15 files changed, 66 insertions(+), 36 deletions(-) create mode 100644 Modules/Licensing/Database/Migrations/2026_10_12_160000_extend_download_products.php create mode 100644 public/css/sonardesk-downloads.css diff --git a/Modules/Licensing/Database/Migrations/2026_10_12_160000_extend_download_products.php b/Modules/Licensing/Database/Migrations/2026_10_12_160000_extend_download_products.php new file mode 100644 index 0000000..420dd74 --- /dev/null +++ b/Modules/Licensing/Database/Migrations/2026_10_12_160000_extend_download_products.php @@ -0,0 +1,8 @@ +longText('release_notes')->nullable();$table->text('minimum_system')->nullable();$table->integer('sort_order')->default(0);}); } + public function down(): void {Schema::table('download_products',function(Blueprint $table){$table->dropColumn(['release_notes','minimum_system','sort_order']);});} +}; diff --git a/Modules/Licensing/Http/Controllers/Admin/DistributionAdminController.php b/Modules/Licensing/Http/Controllers/Admin/DistributionAdminController.php index c548b10..73a9d5c 100644 --- a/Modules/Licensing/Http/Controllers/Admin/DistributionAdminController.php +++ b/Modules/Licensing/Http/Controllers/Admin/DistributionAdminController.php @@ -6,8 +6,16 @@ use Illuminate\Support\Facades\Storage; use Illuminate\Validation\Rule; use Modules\Licensing\Models\{SonarDeskRelease,DownloadProduct}; final class DistributionAdminController extends Controller { - public function index(){return view('licensing::distribution.admin', ['releases'=>SonarDeskRelease::orderByDesc('created_at')->paginate(20),'products'=>DownloadProduct::orderBy('name')->get()]);} - public function release(Request $request){$data=$request->validate(['version'=>'required|string|max:40','build_number'=>'required|integer|min:1','channel'=>['required',Rule::in(['stable','beta'])],'minimum_macos'=>'nullable|string|max:30','release_notes'=>'nullable|string','sparkle_signature'=>'nullable|string|max:2048','is_published'=>'nullable|boolean','is_featured'=>'nullable|boolean','file'=>'required|file|max:512000|mimes:dmg,zip,pkg']);$file=$request->file('file');$path=$file->store('sonardesk-distribution/releases','local');$release=SonarDeskRelease::create(['version'=>$data['version'],'build_number'=>$data['build_number'],'channel'=>$data['channel'],'minimum_macos'=>$data['minimum_macos']??null,'release_notes'=>$data['release_notes']??null,'sparkle_signature'=>$data['sparkle_signature']??null,'file_path'=>$path,'original_filename'=>$file->getClientOriginalName(),'file_size'=>$file->getSize(),'sha256'=>hash_file('sha256',$file->getRealPath()),'is_published'=>(bool)($data['is_published']??false),'is_featured'=>(bool)($data['is_featured']??false),'published_at'=>!empty($data['is_published'])?now():null]);return redirect()->route('administration.sonardesk.distribution')->with('status','Release '.$release->version.' created');} - public function product(Request $request){$data=$request->validate(['name'=>'required|string|max:150','description'=>'nullable|string','version'=>'nullable|string|max:60','platform'=>'nullable|string|max:60','is_published'=>'nullable|boolean','file'=>'required|file|max:512000']);$file=$request->file('file');$path=$file->store('sonardesk-distribution/products','local');DownloadProduct::create(['name'=>$data['name'],'description'=>$data['description']??null,'version'=>$data['version']??null,'platform'=>$data['platform']??null,'file_path'=>$path,'original_filename'=>$file->getClientOriginalName(),'file_size'=>$file->getSize(),'is_published'=>(bool)($data['is_published']??false)]);return redirect()->route('administration.sonardesk.distribution')->with('status','Download created');} + public function index(){return view('licensing::distribution.admin',['releases'=>SonarDeskRelease::orderByDesc('created_at')->paginate(20),'products'=>DownloadProduct::orderBy('name')->orderByDesc('created_at')->get()]);} + private function upload($file,string $folder):array{$path=$file->store('sonardesk-distribution/'.$folder,'local');return ['file_path'=>$path,'original_filename'=>$file->getClientOriginalName(),'file_size'=>$file->getSize()];} + private function remove(?string $path):void {if($path && str_starts_with($path,'sonardesk-distribution/') && !str_contains($path,'..'))Storage::disk('local')->delete($path);} + private function productRules(bool $new):array{return ['name'=>'required|string|max:150','description'=>'nullable|string','version'=>'nullable|string|max:60','platform'=>'nullable|string|max:60','release_notes'=>'nullable|string','minimum_system'=>'nullable|string|max:2000','sort_order'=>'nullable|integer|between:-10000,10000','is_published'=>'nullable|boolean','file'=>[$new?'required':'nullable','file','max:512000']];} + private function releaseRules(bool $new):array{return ['version'=>'required|string|max:40','build_number'=>'required|integer|min:1','channel'=>['required',Rule::in(['stable','beta'])],'minimum_macos'=>'nullable|string|max:30','release_notes'=>'nullable|string','sparkle_signature'=>'nullable|string|max:2048','is_published'=>'nullable|boolean','is_featured'=>'nullable|boolean','file'=>[$new?'required':'nullable','file','max:512000','mimes:dmg,zip,pkg']];} + public function product(Request $request){$data=$request->validate($this->productRules(true));$file=$request->file('file');$attributes=$this->upload($file,'products');unset($data['file']);DownloadProduct::create(array_merge($data,$attributes,['is_published'=>$request->boolean('is_published'),'sort_order'=>(int)($data['sort_order']??0)]));return back()->with('status','Download added');} + public function updateProduct(Request $request,DownloadProduct $product){$data=$request->validate($this->productRules(false));unset($data['file']);$data['is_published']=$request->boolean('is_published');$data['sort_order']=(int)($data['sort_order']??0);if($request->hasFile('file')){$old=$product->file_path;$data=array_merge($data,$this->upload($request->file('file'),'products'));$product->update($data);$this->remove($old);}else{$product->update($data);}return back()->with('status','Download updated');} + public function deleteProduct(DownloadProduct $product){$path=$product->file_path;$product->delete();$this->remove($path);return back()->with('status','Download removed');} + public function release(Request $request){$data=$request->validate($this->releaseRules(true));$file=$request->file('file');unset($data['file']);$release=SonarDeskRelease::create(array_merge($data,$this->upload($file,'releases'),['sha256'=>hash_file('sha256',$file->getRealPath()),'is_published'=>$request->boolean('is_published'),'is_featured'=>$request->boolean('is_featured'),'published_at'=>$request->boolean('is_published')?now():null]));return back()->with('status','Release '.$release->version.' created');} + public function updateRelease(Request $request,SonarDeskRelease $release){$data=$request->validate($this->releaseRules(false));unset($data['file']);$data['is_published']=$request->boolean('is_published');$data['is_featured']=$request->boolean('is_featured');$data['published_at']=$data['is_published']?($release->published_at??now()):null;if($request->hasFile('file')){$file=$request->file('file');$old=$release->file_path;$data=array_merge($data,$this->upload($file,'releases'),['sha256'=>hash_file('sha256',$file->getRealPath())]);$release->update($data);$this->remove($old);}else{$release->update($data);}return back()->with('status','Release updated');} + public function deleteRelease(SonarDeskRelease $release){$path=$release->file_path;$release->delete();$this->remove($path);return back()->with('status','Release removed');} public function publish(Request $request,SonarDeskRelease $release){$data=$request->validate(['is_published'=>'required|boolean']);$release->update(['is_published'=>(bool)$data['is_published'],'published_at'=>$data['is_published']?($release->published_at??now()):null]);return back()->with('status','Release status updated');} } diff --git a/Modules/Licensing/Http/Controllers/DistributionController.php b/Modules/Licensing/Http/Controllers/DistributionController.php index 6c36343..dadf977 100644 --- a/Modules/Licensing/Http/Controllers/DistributionController.php +++ b/Modules/Licensing/Http/Controllers/DistributionController.php @@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Storage; use Modules\Licensing\Models\{SonarDeskRelease,DownloadProduct}; use Symfony\Component\HttpFoundation\BinaryFileResponse; final class DistributionController extends Controller { - public function index(){ $latest=SonarDeskRelease::published()->where('channel','stable')->orderByDesc('published_at')->first(); $releases=SonarDeskRelease::published()->orderByDesc('published_at')->paginate(12);$products=DownloadProduct::where('is_published',true)->whereNotNull('file_path')->orderBy('name')->get();return view('licensing::downloads.index',compact('latest','releases','products')); } + public function index(){ $latest=SonarDeskRelease::published()->where('channel','stable')->orderByDesc('published_at')->first(); $releases=SonarDeskRelease::published()->orderByDesc('published_at')->paginate(12);$products=DownloadProduct::where('is_published',true)->whereNotNull('file_path')->orderBy('name')->orderBy('sort_order')->orderByDesc('created_at')->get();return view('licensing::downloads.index',compact('latest','releases','products')); } public function release(SonarDeskRelease $release){abort_unless($release->is_published && $release->published_at && $release->file_path,404);return view('licensing::downloads.release',compact('release'));} public function product(DownloadProduct $product){abort_unless($product->is_published && $product->file_path,404);return view('licensing::downloads.product',compact('product'));} public function latest():BinaryFileResponse{$r=SonarDeskRelease::published()->where('channel','stable')->orderByDesc('published_at')->firstOrFail();return $this->send($r);} diff --git a/Modules/Licensing/Models/DownloadProduct.php b/Modules/Licensing/Models/DownloadProduct.php index c8279ba..a61649a 100644 --- a/Modules/Licensing/Models/DownloadProduct.php +++ b/Modules/Licensing/Models/DownloadProduct.php @@ -3,6 +3,6 @@ namespace Modules\Licensing\Models; use Illuminate\Database\Eloquent\Model; final class DownloadProduct extends Model { protected $table='download_products'; - protected $fillable=['name','description','version','platform','file_path','original_filename','file_size','is_published']; - protected function casts():array{return ['is_published'=>'boolean','file_size'=>'integer'];} + protected $fillable=['name','description','version','platform','file_path','original_filename','file_size','is_published','release_notes','minimum_system','sort_order']; + protected function casts():array{return ['is_published'=>'boolean','file_size'=>'integer','sort_order'=>'integer'];} } diff --git a/Modules/Licensing/Resources/views/admin/layout.blade.php b/Modules/Licensing/Resources/views/admin/layout.blade.php index 88a45f3..41b64b1 100644 --- a/Modules/Licensing/Resources/views/admin/layout.blade.php +++ b/Modules/Licensing/Resources/views/admin/layout.blade.php @@ -3,13 +3,16 @@
-

ABSDev / Products

+

ABSDev / Products

SonarDesk

-

Licensing administration · Read only

+

Manage licensing, releases and product downloads

Overview Licences + @if (\Illuminate\Support\Facades\Route::has('administration.sonardesk.distribution')) + Downloads & releases + @endif
@yield('content')
@@ -17,11 +20,11 @@ +@endsection diff --git a/Modules/Licensing/Resources/views/downloads/index.blade.php b/Modules/Licensing/Resources/views/downloads/index.blade.php index f1e5644..e799f9f 100644 --- a/Modules/Licensing/Resources/views/downloads/index.blade.php +++ b/Modules/Licensing/Resources/views/downloads/index.blade.php @@ -1,7 +1,4 @@ -@extends('sonardesk.layout') -@section('title','Downloads & Release Notes') -@section('content')
Software distribution

Downloads & release notes

All published applications in one place. Download current installers, view product details and read complete release notes.

Current downloads

Available software

-@if($latest)
SonarDesk · macOS

SonarDesk

Version {{ $latest->version }} @if($latest->published_at) · {{ $latest->published_at->format('j M Y') }} @endif

{{ \Illuminate\Support\Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags(\Illuminate\Support\Str::markdown($latest->release_notes ?: 'Native SonarQube project monitoring for macOS.', ['html_input'=>'strip','allow_unsafe_links'=>false])))), 155) }}

@endif -@foreach($products as $product)
{{ $product->platform ?: 'Software' }}

{{ $product->name }}

{{ $product->version ? 'Version '.$product->version : 'Current release' }}

{{ \Illuminate\Support\Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags(\Illuminate\Support\Str::markdown($product->description ?: 'Published software download.', ['html_input'=>'strip','allow_unsafe_links'=>false])))), 155) }}

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

No published installers are available yet. Downloads appear automatically when a product or release is published in the distribution administration.

@endif
-
Release archive

SonarDesk release history

Read a summary here or open the full release page for notes, requirements and the installer.

@forelse($releases as $release)
SonarDesk {{ $release->version }}

Build {{ $release->build_number }} · {{ ucfirst($release->channel) }} @if($release->published_at) · {{ $release->published_at->format('j M Y') }} @endif

{{ \Illuminate\Support\Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags(\Illuminate\Support\Str::markdown($release->release_notes ?: 'Release information available on the details page.', ['html_input'=>'strip','allow_unsafe_links'=>false])))), 160) }}

@empty

No published releases yet.

@endforelse
{{ $releases->links() }}
@endsection +Product downloads · ABSDEV
ABSDEV
ABSDEV SOFTWARE

Downloads & releases

Get the latest versions of SonarDesk, SonarDisk and additional tools. Choose the installer that matches your system.

+@if($latest)
LATEST STABLE · SONARDESK

SonarDesk {{ $latest->version }}

{{ \Illuminate\Support\Str::limit(strip_tags($latest->release_notes ?: 'Native SonarQube monitoring for macOS.'),180) }}

SonarDeskNative code quality dashboard
@endif +@if($products->isNotEmpty())
SOFTWARE LIBRARY

Products & alternative downloads

Installers are grouped by product, with each available platform and version listed together.

@foreach($products->groupBy('name') as $name => $files)

{{ $name }}

{{ $files->first()->description ?: 'Available downloads and supporting files.' }}

{{ $files->count() }} {{ $files->count() === 1 ? 'download' : 'downloads' }}
@foreach($files as $file)
{{ $file->version ? 'Version '.$file->version : ($file->original_filename ?: 'Download') }}{{ $file->platform ?: 'All platforms' }} @if($file->file_size) · {{ number_format($file->file_size / 1048576,1) }} MB @endif
@endforeach
@endforeach
@endif +
RELEASE HISTORY

SonarDesk versions

Full release notes and system requirements are available on each release page.

@forelse($releases as $release)
SonarDesk {{ $release->version }}Build {{ $release->build_number }} · {{ ucfirst($release->channel) }}

{{ \Illuminate\Support\Str::limit(strip_tags($release->release_notes ?: 'Release information'),120) }}

@empty

No SonarDesk releases published yet.

@endforelse
{{ $releases->links() }}
\ No newline at end of file diff --git a/Modules/Licensing/Resources/views/downloads/product.blade.php b/Modules/Licensing/Resources/views/downloads/product.blade.php index a292ee6..04a4955 100644 --- a/Modules/Licensing/Resources/views/downloads/product.blade.php +++ b/Modules/Licensing/Resources/views/downloads/product.blade.php @@ -1,3 +1 @@ -@extends('sonardesk.layout') -@section('title',$product->name.' — Download') -@section('content')
← All downloads

{{ $product->platform ?: 'Software' }} · {{ $product->version ? 'Version '.$product->version : 'Current release' }}

{{ $product->name }}

Product information and installer download.

About {{ $product->name }}

{!! \Illuminate\Support\Str::markdown($product->description ?: 'Published software download.', ['html_input'=>'strip','allow_unsafe_links'=>false]) !!}

Download information

Platform: {{ $product->platform ?: 'See product documentation' }}

@if($product->version)

Version: {{ $product->version }}

@endif @if($product->file_size)

Size: {{ number_format($product->file_size / 1048576, 2) }} MB

@endifDownload installer ↓
@endsection +{{ $product->name }} · ABSDEV
ABSDEV
← All downloadsPRODUCT DOWNLOAD

{{ $product->name }}

{{ $product->description }}

Version {{ $product->version ?: 'Unspecified' }}{{ $product->platform ?: 'All platforms' }}
Download {{ $product->original_filename }} ↓@if($product->release_notes)

Release notes

{{ $product->release_notes }}
@endif@if($product->minimum_system)

System requirements

{{ $product->minimum_system }}
@endif
© {{ date('Y') }} ABSDEV
\ No newline at end of file diff --git a/Modules/Licensing/Resources/views/downloads/release.blade.php b/Modules/Licensing/Resources/views/downloads/release.blade.php index c4bdd1b..bf79a03 100644 --- a/Modules/Licensing/Resources/views/downloads/release.blade.php +++ b/Modules/Licensing/Resources/views/downloads/release.blade.php @@ -1,3 +1 @@ -@extends('sonardesk.layout') -@section('title','SonarDesk '.$release->version.' — Release Notes') -@section('content')
← All downloads

SonarDesk · {{ ucfirst($release->channel) }}

SonarDesk {{ $release->version }}

Build {{ $release->build_number }} @if($release->published_at) · Released {{ $release->published_at->format('j F Y') }} @endif

Release notes

{!! \Illuminate\Support\Str::markdown($release->release_notes ?: 'No release notes were provided for this version.', ['html_input'=>'strip','allow_unsafe_links'=>false]) !!}

System requirements

Platform: macOS

Minimum version: {{ $release->minimum_macos ?: 'See application documentation' }}


Installation

  1. Download the signed application installer.
  2. Open the downloaded disk image and move SonarDesk into Applications.
  3. Launch SonarDesk and configure your SonarQube connection.
@if($release->file_size)

Installer size: {{ number_format($release->file_size / 1048576, 2) }} MB

@endif @if($release->sha256)
SHA-256 checksum

{{ $release->sha256 }}

@endif
@endsection +SonarDesk {{ $release->version }} · ABSDEV
ABSDEV
← All downloadsSONARDESK · {{ strtoupper($release->channel) }}

SonarDesk {{ $release->version }}

Build {{ $release->build_number }}

Download {{ $release->original_filename }} ↓

Release notes

{{ $release->release_notes ?: 'No release notes provided.' }}

System requirements

macOS {{ $release->minimum_macos ?: 'See application documentation' }} or later.

@if($release->sha256)

File verification

SHA-256

{{ $release->sha256 }}
@endif
© {{ date('Y') }} ABSDEV
\ No newline at end of file diff --git a/Modules/Licensing/Routes/distribution.php b/Modules/Licensing/Routes/distribution.php index c3a094d..f664e45 100644 --- a/Modules/Licensing/Routes/distribution.php +++ b/Modules/Licensing/Routes/distribution.php @@ -15,5 +15,9 @@ Route::middleware(['web','auth','verified','can:manage-sonardesk-licensing'])->p Route::get('/distribution',[AdminDownloads::class,'index'])->name('distribution'); Route::post('/distribution/releases',[AdminDownloads::class,'release'])->name('distribution.releases.store'); Route::post('/distribution/products',[AdminDownloads::class,'product'])->name('distribution.products.store'); + Route::patch('/distribution/products/{product}',[AdminDownloads::class,'updateProduct'])->name('distribution.products.update'); + Route::delete('/distribution/products/{product}',[AdminDownloads::class,'deleteProduct'])->name('distribution.products.delete'); + Route::patch('/distribution/releases/{release}',[AdminDownloads::class,'updateRelease'])->name('distribution.releases.update'); + Route::delete('/distribution/releases/{release}',[AdminDownloads::class,'deleteRelease'])->name('distribution.releases.delete'); Route::patch('/distribution/releases/{release}/publish',[AdminDownloads::class,'publish'])->name('distribution.releases.publish'); }); diff --git a/public/css/sonardesk-absdev.css b/public/css/sonardesk-absdev.css index d53e3e1..7bed311 100644 --- a/public/css/sonardesk-absdev.css +++ b/public/css/sonardesk-absdev.css @@ -1,2 +1,6 @@ /* SonarDesk product & distribution — ABSDEV navy/gold design */ -:root{--sd-bg:#0b1729;--sd-navy:#102540;--sd-navy2:#173657;--sd-gold:#d9b36a;--sd-gold2:#f1d69b;--sd-text:#f5f7fc;--sd-muted:#b6c4d8;--sd-line:rgba(204,221,244,.17)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:#f4f7fb;color:#14263e;font:16px/1.65 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}a{color:inherit;text-decoration:none}a:hover{color:#a97924}.sd-container{width:min(1320px,calc(100% - 64px));margin-inline:auto}.sd-header{background:var(--sd-bg);color:white;border-bottom:1px solid var(--sd-line);position:relative;z-index:30}.sd-header-row{min-height:80px;display:flex;align-items:center;justify-content:space-between;gap:25px}.sd-logo{font-size:24px;font-weight:850;letter-spacing:-.04em;white-space:nowrap}.sd-logo span,.sd-footer strong span{color:var(--sd-gold)}.sd-mark{display:inline-grid;place-items:center;width:35px;height:35px;border:1px solid var(--sd-gold);border-radius:9px;margin-right:9px}.sd-mainnav{display:flex;align-items:center;gap:26px;font-size:14px;font-weight:650}.sd-mainnav>a:hover,.sd-navparent:hover{color:var(--sd-gold2)}.sd-navgroup{position:relative;padding:26px 0}.sd-subnav{position:absolute;top:100%;left:-20px;min-width:220px;padding:10px;background:#142b49;border:1px solid var(--sd-line);border-radius:12px;box-shadow:0 18px 40px #0004;display:none}.sd-navgroup:hover .sd-subnav,.sd-navgroup:focus-within .sd-subnav{display:grid}.sd-subnav a{padding:9px 12px;border-radius:7px}.sd-subnav a:hover{background:#ffffff14;color:var(--sd-gold2)}.sd-mobile{display:none}.sd-hero{background:radial-gradient(ellipse at 80% 0%,#244e78 0,transparent 45%),linear-gradient(130deg,#0a1729,#142c4a);color:var(--sd-text);padding:90px 0 92px}.sd-hero-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(280px,.75fr);gap:70px;align-items:center}.sd-eyebrow{font-size:12px;font-weight:800;letter-spacing:.19em;text-transform:uppercase;color:var(--sd-gold)}.sd-hero h1{font-size:clamp(43px,5.3vw,76px);line-height:1.09;letter-spacing:-.055em;margin:18px 0 25px}.sd-hero h1 em{color:var(--sd-gold);font-style:normal}.sd-hero p{font-size:19px;max-width:720px;color:#c7d4e4}.sd-actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:32px}.sd-btn{display:inline-flex;align-items:center;justify-content:center;padding:12px 22px;border-radius:9px;font-weight:750;font-size:14px;min-height:48px;border:1px solid transparent}.sd-btn-primary{background:var(--sd-gold);color:#10233b}.sd-btn-primary:hover{background:var(--sd-gold2);color:#10233b}.sd-btn-ghost{border-color:#ffffff65;color:white}.sd-btn-ghost:hover{background:#ffffff1c;color:white}.sd-btn-dark{background:#173658;color:white}.sd-btn-dark:hover{background:#254e79;color:white}.sd-visual{border:1px solid #ffffff32;border-radius:20px;padding:24px;background:linear-gradient(155deg,#18365a,#0b1b30);box-shadow:0 25px 65px #0004}.sd-visual-top{display:flex;gap:7px;margin-bottom:26px}.sd-visual-top i{width:9px;height:9px;border-radius:50%;background:#718ba9}.sd-visual-title{font-size:15px;color:#dbe6f5;font-weight:700}.sd-metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:17px}.sd-metric{border:1px solid #ffffff22;border-radius:10px;padding:16px;background:#ffffff08}.sd-metric b{display:block;font-size:27px;color:var(--sd-gold)}.sd-metric span{font-size:12px;color:#b4c8df}.sd-section{padding:72px 0}.sd-section-alt{background:#eaf0f7}.sd-section h2{font-size:clamp(28px,3vw,40px);line-height:1.18;letter-spacing:-.035em;margin:12px 0 18px}.sd-intro{max-width:740px;color:#52627a}.sd-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;margin-top:32px}.sd-card{background:white;border:1px solid #dce5ef;border-radius:15px;padding:28px;box-shadow:0 10px 28px #0b264009}.sd-card h3{margin:12px 0 8px;font-size:22px;line-height:1.3}.sd-card p{color:#586980}.sd-card .sd-eyebrow{color:#a67826}.sd-card a.sd-textlink{color:#173e6c;font-weight:750}.sd-card a.sd-textlink:hover{text-decoration:underline}.sd-card-actions{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:24px}.sd-meta{font-size:13px;color:#62738b}.sd-pagehead{padding:65px 0 62px;background:linear-gradient(120deg,#0b192d,#193b61);color:white}.sd-pagehead h1{font-size:clamp(36px,4.5vw,58px);letter-spacing:-.04em;margin:10px 0}.sd-pagehead p{color:#c7d4e4;max-width:780px;font-size:18px}.sd-back{font-weight:700;color:#b78a39;font-size:14px}.sd-detail{max-width:960px;margin:0 auto}.sd-prose{line-height:1.8;overflow-wrap:anywhere}.sd-prose h2,.sd-prose h3{margin-top:26px}.sd-prose pre{overflow:auto;background:#eef2f7;padding:16px;border-radius:8px}.sd-prose code{overflow-wrap:anywhere}.sd-divider{border:0;border-top:1px solid #dce5ef;margin:30px 0}.sd-list{display:grid;gap:12px;margin-top:25px}.sd-row{display:flex;align-items:center;justify-content:space-between;gap:20px;background:white;border:1px solid #dce5ef;border-radius:12px;padding:18px 22px}.sd-row p{margin:2px 0;color:#687a8d;font-size:13px}.sd-row-actions{display:flex;align-items:center;gap:18px;flex-wrap:wrap}.sd-row-actions a{color:#1a4a79;font-weight:750}.sd-note{padding:18px 22px;border-left:4px solid var(--sd-gold);background:#fff;border-radius:7px}.sd-price{font-size:35px;font-weight:850;letter-spacing:-.04em;margin:15px 0}.sd-price small{font-size:14px;font-weight:500;color:#64748b}.sd-footer{background:var(--sd-bg);color:#cad5e3;padding:42px 0}.sd-footer-row{display:flex;justify-content:space-between;gap:24px}.sd-footer strong{font-size:22px;color:white}.sd-footer p{margin:3px 0;font-size:13px}.sd-footer-row>div:last-child{display:flex;gap:24px;flex-wrap:wrap;align-items:center;font-size:14px}.sd-pagination{margin-top:25px}.sd-checksum{overflow-wrap:anywhere;font-size:13px;background:#eef2f7;padding:14px;border-radius:8px}@media(max-width:950px){.sd-hero-grid{grid-template-columns:1fr;gap:28px}.sd-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.sd-mainnav{gap:14px}}@media(max-width:700px){.sd-container{width:min(100% - 32px,1320px)}.sd-mainnav{display:none}.sd-mobile{display:block;position:relative}.sd-mobile summary{cursor:pointer;font-weight:700;list-style:none}.sd-mobile nav{position:absolute;right:0;top:40px;display:grid;min-width:235px;padding:12px;background:#132b49;border:1px solid #ffffff3b;border-radius:10px;box-shadow:0 15px 35px #0005}.sd-mobile nav a{padding:9px 12px}.sd-hero{padding:58px 0}.sd-grid{grid-template-columns:1fr}.sd-section{padding:50px 0}.sd-row{align-items:flex-start;flex-direction:column}.sd-footer-row{flex-direction:column}} +:root{--sd-bg:#0f0e0d;--sd-navy:#191714;--sd-navy2:#29231d;--sd-gold:#f97316;--sd-gold2:#fb923c;--sd-text:#f5f7fc;--sd-muted:#c8b8ab;--sd-line:rgba(204,221,244,.17)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:#faf7f3;color:#211b16;font:16px/1.65 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}a{color:inherit;text-decoration:none}a:hover{color:#ea580c}.sd-container{width:min(1320px,calc(100% - 64px));margin-inline:auto}.sd-header{background:var(--sd-bg);color:white;border-bottom:1px solid var(--sd-line);position:relative;z-index:30}.sd-header-row{min-height:80px;display:flex;align-items:center;justify-content:space-between;gap:25px}.sd-logo{font-size:24px;font-weight:850;letter-spacing:-.04em;white-space:nowrap}.sd-logo span,.sd-footer strong span{color:var(--sd-gold)}.sd-mark{display:inline-grid;place-items:center;width:35px;height:35px;border:1px solid var(--sd-gold);border-radius:9px;margin-right:9px}.sd-mainnav{display:flex;align-items:center;gap:26px;font-size:14px;font-weight:650}.sd-mainnav>a:hover,.sd-navparent:hover{color:var(--sd-gold2)}.sd-navgroup{position:relative;padding:26px 0}.sd-subnav{position:absolute;top:100%;left:-20px;min-width:220px;padding:10px;background:#142b49;border:1px solid var(--sd-line);border-radius:12px;box-shadow:0 18px 40px #0004;display:none}.sd-navgroup:hover .sd-subnav,.sd-navgroup:focus-within .sd-subnav{display:grid}.sd-subnav a{padding:9px 12px;border-radius:7px}.sd-subnav a:hover{background:#ffffff14;color:var(--sd-gold2)}.sd-mobile{display:none}.sd-hero{background:radial-gradient(ellipse at 80% 0%,#244e78 0,transparent 45%),linear-gradient(130deg,#0a1729,#142c4a);color:var(--sd-text);padding:90px 0 92px}.sd-hero-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(280px,.75fr);gap:70px;align-items:center}.sd-eyebrow{font-size:12px;font-weight:800;letter-spacing:.19em;text-transform:uppercase;color:var(--sd-gold)}.sd-hero h1{font-size:clamp(43px,5.3vw,76px);line-height:1.09;letter-spacing:-.055em;margin:18px 0 25px}.sd-hero h1 em{color:var(--sd-gold);font-style:normal}.sd-hero p{font-size:19px;max-width:720px;color:#c7d4e4}.sd-actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:32px}.sd-btn{display:inline-flex;align-items:center;justify-content:center;padding:12px 22px;border-radius:9px;font-weight:750;font-size:14px;min-height:48px;border:1px solid transparent}.sd-btn-primary{background:var(--sd-gold);color:#10233b}.sd-btn-primary:hover{background:var(--sd-gold2);color:#10233b}.sd-btn-ghost{border-color:#ffffff65;color:white}.sd-btn-ghost:hover{background:#ffffff1c;color:white}.sd-btn-dark{background:#173658;color:white}.sd-btn-dark:hover{background:#254e79;color:white}.sd-visual{border:1px solid #ffffff32;border-radius:20px;padding:24px;background:linear-gradient(155deg,#18365a,#0b1b30);box-shadow:0 25px 65px #0004}.sd-visual-top{display:flex;gap:7px;margin-bottom:26px}.sd-visual-top i{width:9px;height:9px;border-radius:50%;background:#718ba9}.sd-visual-title{font-size:15px;color:#dbe6f5;font-weight:700}.sd-metrics{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:17px}.sd-metric{border:1px solid #ffffff22;border-radius:10px;padding:16px;background:#ffffff08}.sd-metric b{display:block;font-size:27px;color:var(--sd-gold)}.sd-metric span{font-size:12px;color:#b4c8df}.sd-section{padding:72px 0}.sd-section-alt{background:#eaf0f7}.sd-section h2{font-size:clamp(28px,3vw,40px);line-height:1.18;letter-spacing:-.035em;margin:12px 0 18px}.sd-intro{max-width:740px;color:#52627a}.sd-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;margin-top:32px}.sd-card{background:white;border:1px solid #dce5ef;border-radius:15px;padding:28px;box-shadow:0 10px 28px #0b264009}.sd-card h3{margin:12px 0 8px;font-size:22px;line-height:1.3}.sd-card p{color:#586980}.sd-card .sd-eyebrow{color:#a67826}.sd-card a.sd-textlink{color:#173e6c;font-weight:750}.sd-card a.sd-textlink:hover{text-decoration:underline}.sd-card-actions{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:24px}.sd-meta{font-size:13px;color:#62738b}.sd-pagehead{padding:65px 0 62px;background:linear-gradient(120deg,#0b192d,#193b61);color:white}.sd-pagehead h1{font-size:clamp(36px,4.5vw,58px);letter-spacing:-.04em;margin:10px 0}.sd-pagehead p{color:#c7d4e4;max-width:780px;font-size:18px}.sd-back{font-weight:700;color:#b78a39;font-size:14px}.sd-detail{max-width:960px;margin:0 auto}.sd-prose{line-height:1.8;overflow-wrap:anywhere}.sd-prose h2,.sd-prose h3{margin-top:26px}.sd-prose pre{overflow:auto;background:#eef2f7;padding:16px;border-radius:8px}.sd-prose code{overflow-wrap:anywhere}.sd-divider{border:0;border-top:1px solid #dce5ef;margin:30px 0}.sd-list{display:grid;gap:12px;margin-top:25px}.sd-row{display:flex;align-items:center;justify-content:space-between;gap:20px;background:white;border:1px solid #dce5ef;border-radius:12px;padding:18px 22px}.sd-row p{margin:2px 0;color:#687a8d;font-size:13px}.sd-row-actions{display:flex;align-items:center;gap:18px;flex-wrap:wrap}.sd-row-actions a{color:#1a4a79;font-weight:750}.sd-note{padding:18px 22px;border-left:4px solid var(--sd-gold);background:#fff;border-radius:7px}.sd-price{font-size:35px;font-weight:850;letter-spacing:-.04em;margin:15px 0}.sd-price small{font-size:14px;font-weight:500;color:#64748b}.sd-footer{background:var(--sd-bg);color:#cad5e3;padding:42px 0}.sd-footer-row{display:flex;justify-content:space-between;gap:24px}.sd-footer strong{font-size:22px;color:white}.sd-footer p{margin:3px 0;font-size:13px}.sd-footer-row>div:last-child{display:flex;gap:24px;flex-wrap:wrap;align-items:center;font-size:14px}.sd-pagination{margin-top:25px}.sd-checksum{overflow-wrap:anywhere;font-size:13px;background:#eef2f7;padding:14px;border-radius:8px}@media(max-width:950px){.sd-hero-grid{grid-template-columns:1fr;gap:28px}.sd-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.sd-mainnav{gap:14px}}@media(max-width:700px){.sd-container{width:min(100% - 32px,1320px)}.sd-mainnav{display:none}.sd-mobile{display:block;position:relative}.sd-mobile summary{cursor:pointer;font-weight:700;list-style:none}.sd-mobile nav{position:absolute;right:0;top:40px;display:grid;min-width:235px;padding:12px;background:#132b49;border:1px solid #ffffff3b;border-radius:10px;box-shadow:0 15px 35px #0005}.sd-mobile nav a{padding:9px 12px}.sd-hero{padding:58px 0}.sd-grid{grid-template-columns:1fr}.sd-section{padding:50px 0}.sd-row{align-items:flex-start;flex-direction:column}.sd-footer-row{flex-direction:column}} + +/* Match the existing SaaSland orange brand instead of imposing navy/gold. */ +:root{--sd-bg:#0f0e0d;--sd-navy:#191714;--sd-navy2:#29231d;--sd-gold:#f97316;--sd-gold2:#fb923c;--sd-text:#f7f3ef;--sd-muted:#c8b8ab;--sd-line:rgba(251,146,60,.22)} +.sd-header,.sd-footer{background:#0f0e0d}.sd-logo span,.sd-footer strong span{color:#f97316} diff --git a/public/css/sonardesk-downloads.css b/public/css/sonardesk-downloads.css new file mode 100644 index 0000000..ef77d03 --- /dev/null +++ b/public/css/sonardesk-downloads.css @@ -0,0 +1,3 @@ +*{box-sizing:border-box}.sd-site{margin:0;background:#0f0e0d;color:#f4f7fd;font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.sd-site a{color:inherit;text-decoration:none}.sd-top,.sd-footer{display:flex;justify-content:space-between;align-items:center;gap:20px;padding:22px max(5vw,24px);border-bottom:1px solid #ffffff1e}.sd-top nav{display:flex;gap:26px;flex-wrap:wrap;font-size:14px;color:#d7cbc2}.sd-top nav a:hover,.sd-top nav .selected{color:#f97316}.sd-brand{font-size:24px;font-weight:850;letter-spacing:-1px}.sd-brand span,.sd-gold{color:#f97316}.sd-main{width:min(1420px,calc(100% - 64px));margin:auto;padding:50px 0 95px}.sd-intro{max-width:900px;margin-bottom:46px}.sd-kicker{color:#f97316;font-size:11px;font-weight:800;letter-spacing:.19em}.sd-site h1{font-size:clamp(42px,5.6vw,72px);line-height:1.07;letter-spacing:-.045em;margin:13px 0 19px}.sd-intro p,.sd-lead{font-size:19px;color:#b9c8dc;max-width:740px}.sd-feature{display:grid;grid-template-columns:1.3fr 1fr;gap:40px;align-items:center;background:linear-gradient(125deg,#162f4d,#0e2239);border:1px solid #ffffff24;border-radius:18px;padding:clamp(28px,4vw,54px);margin-bottom:80px}.sd-feature h2{font-size:clamp(32px,4vw,52px);margin:12px 0}.sd-feature h2 span{color:#f97316}.sd-feature p,.sd-product p,.sd-section-title p{color:#b7c6d9}.sd-feature-art{min-height:230px;display:flex;flex-direction:column;justify-content:center;align-items:center;background:radial-gradient(circle at 50% 50%,#294c73,#11243d 70%);border:1px solid #ffffff20;border-radius:14px}.sd-feature-art>span{font-size:clamp(34px,4vw,58px);font-weight:850;letter-spacing:-.05em}.sd-feature-art small{color:#aabbd0}.sd-buttons{display:flex;flex-wrap:wrap;gap:12px;margin-top:25px}.sd-btn{display:inline-flex;align-items:center;justify-content:center;background:#f97316;color:#13233a!important;padding:12px 20px;border-radius:9px;font-weight:800;font-size:14px;white-space:nowrap}.sd-btn:hover{background:#f5cb77}.sd-btn.ghost{background:transparent;color:#f5f7fb!important;border:1px solid #ffffff60}.sd-btn.small{padding:9px 15px;font-size:13px}.sd-section{margin-top:65px}.sd-section-title{margin-bottom:28px}.sd-section-title h2{font-size:clamp(27px,3vw,39px);letter-spacing:-.03em;margin:8px 0}.sd-product{border:1px solid #ffffff25;border-radius:15px;background:#ffffff08;margin:15px 0;overflow:hidden}.sd-product-head{padding:25px 30px;display:flex;justify-content:space-between;align-items:center;gap:18px}.sd-product-head h3{font-size:25px;margin:0}.sd-product-head p{margin:6px 0 0}.sd-count{color:#f97316;font-size:13px;white-space:nowrap}.sd-file{padding:19px 30px;border-top:1px solid #ffffff1c;display:flex;align-items:center;justify-content:space-between;gap:20px}.sd-file strong{display:block;font-size:16px}.sd-file small{display:block;color:#a9bad1;margin-top:3px}.sd-file p{color:#a9bad1;font-size:13px;margin:6px 0 0}.sd-file-actions{display:flex;align-items:center;gap:20px;white-space:nowrap;font-size:14px}.sd-file-actions>a:not(.sd-btn){color:#f97316}.sd-history{border:1px solid #ffffff25;border-radius:15px;overflow:hidden}.sd-history .sd-file:first-child{border-top:0}.sd-pagination{margin-top:25px}.sd-detail{max-width:1000px;min-height:65vh}.sd-back{display:block;color:#f97316!important;margin-bottom:55px}.sd-meta{display:flex;gap:12px;flex-wrap:wrap;margin:25px 0}.sd-meta span{padding:6px 12px;background:#ffffff15;border-radius:30px}.sd-detail-section{margin-top:55px;padding-top:25px;border-top:1px solid #ffffff24}.sd-detail-section h2{font-size:26px}.sd-pre{white-space:pre-wrap;overflow-wrap:anywhere;color:#cad7e8}.sd-hash{overflow-wrap:anywhere;color:#aebfd5}.sd-footer{border-top:1px solid #ffffff1e;border-bottom:0;color:#a7b9cf;font-size:13px}@media(max-width:760px){.sd-main{width:calc(100% - 36px);padding-top:36px}.sd-feature{grid-template-columns:1fr}.sd-feature-art{min-height:150px}.sd-file{padding:17px;align-items:flex-start;flex-direction:column}.sd-product-head{padding:20px;align-items:flex-start;flex-direction:column}.sd-top{align-items:flex-start;flex-direction:column}.sd-top nav{gap:16px}} +/* SaaSland orange product downloads */ +.sd-site{background:#0f0e0d;color:#f7f3ef}.sd-top,.sd-footer{background:#0f0e0d}.sd-top nav a:hover,.sd-top nav .selected,.sd-brand span,.sd-gold{color:#f97316} diff --git a/resources/themes/common/views/components/layouts/app/sidebar.blade.php b/resources/themes/common/views/components/layouts/app/sidebar.blade.php index cdbe35d..4b08355 100644 --- a/resources/themes/common/views/components/layouts/app/sidebar.blade.php +++ b/resources/themes/common/views/components/layouts/app/sidebar.blade.php @@ -56,6 +56,9 @@ @if (\Illuminate\Support\Facades\Route::has('administration.sonardesk.paddle-reconciliation')) {{ __('Paddle Reconciliation') }} @endif + @if (\Illuminate\Support\Facades\Route::has('administration.sonardesk.distribution')) + {{ __('Downloads & Releases') }} + @endif @if (\Illuminate\Support\Facades\Route::has('administration.sonardesk.subscriptions')) {{ __('Subscriptions') }} @endif diff --git a/resources/themes/saasland/views/home.blade.php b/resources/themes/saasland/views/home.blade.php index b352ce6..b346c70 100644 --- a/resources/themes/saasland/views/home.blade.php +++ b/resources/themes/saasland/views/home.blade.php @@ -23,10 +23,11 @@ : 'Streamline operations, boost productivity, and improve customer satisfaction with one connected platform built for modern teams.', ]) @endif -
+
-
ABSDEV SOFTWARE

Meet SonarDesk

Native SonarQube monitoring for macOS. Quality gates, vulnerabilities, bugs and issue details — without the browser clutter.

-

SonarDesk for macOS

Know your code health.

One focused workspace for project quality, issues and server connections.

Explore features →
+
ABSDEV SOFTWARE

Meet SonarDesk

Native SonarQube monitoring for macOS. Quality gates, vulnerabilities, bugs and issue details — without the browser clutter.

+

SonarDesk for macOS

Know your code health.

One focused workspace for project quality, issues and server connections.

Explore features →
+ @endsection diff --git a/resources/themes/saasland/views/partials/header.blade.php b/resources/themes/saasland/views/partials/header.blade.php index d4fbf61..ca2129f 100644 --- a/resources/themes/saasland/views/partials/header.blade.php +++ b/resources/themes/saasland/views/partials/header.blade.php @@ -23,10 +23,10 @@