diff --git a/app/DataTables/WorklistDataTable.php b/app/DataTables/WorklistDataTable.php index 11bc796..2f69df3 100644 --- a/app/DataTables/WorklistDataTable.php +++ b/app/DataTables/WorklistDataTable.php @@ -507,18 +507,14 @@ private function renderImageLink(string $data_id, string $img_src, string $data_ private function generateViewerButtons(Study $study): string { $btns = []; - $btns[] = $this->renderButton($study->hash, 'fa-eye', 'btn-outline', '', route('viewer.ohif', $study->hash), true); - $btns[] = Blade::render('staff.worklist.partials._dropdown-menu', - [ - 'items' => [ - [ - 'url' => route('viewer.stone', $study->hash), - 'text' => 'Stone', - 'blank' => true, - 'icon' => 'fa-circle-info', - ], - ], - ] + $btns[] = $this->renderImageLink( + $study->hash, 'eye.png', '', 'OHIF Viewer', route('viewer.ohif', $study->hash), true + ); + $btns[] = $this->renderImageLink( + $study->hash, 'weasis.png', '', 'WEASIS Viewer', $study->links()['weasis'], false + ); + $btns[] = $this->renderImageLink( + $study->hash, 'live.png', '', 'STONE Viewer', route('viewer.stone', $study->hash), true ); return implode("\r", $btns); diff --git a/app/Filament/Resources/DicomServerResource.php b/app/Filament/Resources/DicomServerResource.php new file mode 100644 index 0000000..84e9edb --- /dev/null +++ b/app/Filament/Resources/DicomServerResource.php @@ -0,0 +1,76 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('id') + ->label('ID') + ->sortable(), + Tables\Columns\CheckboxColumn::make('is_active') + ->label('Active') + ->disabled() + ->sortable(), + Tables\Columns\TextColumn::make('server_name') + ->label('Name') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('host') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('port') + ->searchable() + ->sortable(), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListDicomServers::route('/'), + 'create' => Pages\CreateDicomServer::route('/create'), + 'edit' => Pages\EditDicomServer::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/DicomServerResource/Pages/CreateDicomServer.php b/app/Filament/Resources/DicomServerResource/Pages/CreateDicomServer.php new file mode 100644 index 0000000..2674c65 --- /dev/null +++ b/app/Filament/Resources/DicomServerResource/Pages/CreateDicomServer.php @@ -0,0 +1,11 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('id') + ->label('ID') + ->sortable(), + Tables\Columns\CheckboxColumn::make('is_active') + ->label('Active') + ->disabled() + ->sortable(), + Tables\Columns\TextColumn::make('name') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('institute.name') + ->name('Institute') + ->searchable() + ->sortable(), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListFacilities::route('/'), + 'create' => Pages\CreateFacility::route('/create'), + 'edit' => Pages\EditFacility::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/FacilityResource/Pages/CreateFacility.php b/app/Filament/Resources/FacilityResource/Pages/CreateFacility.php new file mode 100644 index 0000000..3857a24 --- /dev/null +++ b/app/Filament/Resources/FacilityResource/Pages/CreateFacility.php @@ -0,0 +1,11 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('id') + ->label('ID') + ->sortable(), + Tables\Columns\CheckboxColumn::make('is_active') + ->label('Active') + ->disabled() + ->sortable(), + Tables\Columns\TextColumn::make('name') + ->searchable() + ->sortable(), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListInstitutes::route('/'), + 'create' => Pages\CreateInstitute::route('/create'), + 'edit' => Pages\EditInstitute::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/InstituteResource/Pages/CreateInstitute.php b/app/Filament/Resources/InstituteResource/Pages/CreateInstitute.php new file mode 100644 index 0000000..64f5957 --- /dev/null +++ b/app/Filament/Resources/InstituteResource/Pages/CreateInstitute.php @@ -0,0 +1,11 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + // + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListRadiologists::route('/'), + 'create' => Pages\CreateRadiologist::route('/create'), + 'edit' => Pages\EditRadiologist::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/RadiologistResource/Pages/CreateRadiologist.php b/app/Filament/Resources/RadiologistResource/Pages/CreateRadiologist.php new file mode 100644 index 0000000..c70440a --- /dev/null +++ b/app/Filament/Resources/RadiologistResource/Pages/CreateRadiologist.php @@ -0,0 +1,11 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('id') + ->label('ID') + ->sortable(), + Tables\Columns\CheckboxColumn::make('is_active') + ->label('Active') + ->disabled() + ->sortable(), + Tables\Columns\TextColumn::make('username') + ->label('Username') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('display_name') + ->label('Name') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('institute.name') + ->label('Institute') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('facility.name') + ->label('Fac') + ->searchable() + ->sortable(), + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListUsers::route('/'), + 'create' => Pages\CreateUser::route('/create'), + 'edit' => Pages\EditUser::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/UserResource/Pages/CreateUser.php b/app/Filament/Resources/UserResource/Pages/CreateUser.php new file mode 100644 index 0000000..78a3894 --- /dev/null +++ b/app/Filament/Resources/UserResource/Pages/CreateUser.php @@ -0,0 +1,11 @@ +restEndpoint($this->ohif_viewer_path); } + public function wadoUrl(): Uri + { + return $this->restEndpoint($this->wado_path); + } + private function restEndpoint(string $path): Uri { return Uri::of($this->rest_api_endpoint)->withPath($path); diff --git a/app/Models/Institute.php b/app/Models/Institute.php index d080d4d..da99505 100644 --- a/app/Models/Institute.php +++ b/app/Models/Institute.php @@ -4,12 +4,18 @@ use App\Models\Traits\Active; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; class Institute extends BaseModel { use Active; use HasFactory; + public function facilities(): HasMany + { + return $this->hasMany(Facility::class); + } + protected function casts(): array { return [ diff --git a/app/Models/Study.php b/app/Models/Study.php index 83b18dc..c0a6305 100644 --- a/app/Models/Study.php +++ b/app/Models/Study.php @@ -237,6 +237,15 @@ public function getOhifMprLink(): ?string return null; } + public function getWeasisLink(): ?string + { + if (me()->may(Permission::StudyDownload)) { + return PacsUrlGen::weasisUrl($this->dicomServer, $this); + } + + return null; + } + public function links(): array { return [ @@ -245,6 +254,7 @@ public function links(): array 'zip' => $this->getArchiveLink(), 'stone' => $this->getStoneLink(), 'ohif' => $this->getOhifLink(), + 'weasis' => $this->getWeasisLink(), 'ohif.mpr' => $this->getOhifMprLink(), 'ohif.seg' => $this->getOhifSegmentationLink(), ]; @@ -256,6 +266,7 @@ public function allowed(): array 'zip' => filled($this->getArchiveLink()), 'stone' => filled($this->getStoneLink()), 'ohif' => filled($this->getOhifLink()), + 'weasis' => filled($this->getWeasisLink()), 'ohif.mpr' => filled($this->getOhifMprLink()), 'ohif.seg' => filled($this->getOhifSegmentationLink()), ]; diff --git a/app/Models/User.php b/app/Models/User.php index d9635f8..18c1f22 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -9,8 +9,10 @@ use App\Services\UserService; use Carbon\Carbon; use Database\Factories\UserFactory; +use Filament\Models\Contracts\FilamentUser; use Filament\Panel; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -156,6 +158,21 @@ public function radiologistProfile(): HasOne return $this->hasOne(RadiologistProfile::class); } + public function institute(): BelongsTo + { + return $this->belongsTo(Institute::class); + } + + public function facility(): BelongsTo + { + return $this->belongsTo(Facility::class); + } + + public function canAccessPanel(Panel $panel): bool + { + return $this->isAdmin(); + } + /** * Get the attributes that should be cast. * @@ -170,9 +187,4 @@ protected function casts(): array 'password' => 'hashed', ]; } - - public function canAccessPanel(Panel $panel): bool - { - return $this->isAdmin(); - } } diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php new file mode 100644 index 0000000..6694332 --- /dev/null +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -0,0 +1,58 @@ +default() + ->id('admin') + ->path('admin') + ->login() + ->colors([ + 'primary' => Color::Amber, + ]) + ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') + ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') + ->pages([ + Pages\Dashboard::class, + ]) + ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') + ->widgets([ + Widgets\AccountWidget::class, + Widgets\FilamentInfoWidget::class, + ]) + ->middleware([ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + AuthenticateSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + DisableBladeIconComponents::class, + DispatchServingFilamentEvent::class, + ]) + ->authMiddleware([ + Authenticate::class, + ]); + } +} diff --git a/app/Services/Pacs/PacsUrlGen.php b/app/Services/Pacs/PacsUrlGen.php index b07fa54..d61b244 100644 --- a/app/Services/Pacs/PacsUrlGen.php +++ b/app/Services/Pacs/PacsUrlGen.php @@ -4,7 +4,7 @@ use App\Models\DicomServer; use App\Models\Study; -use Uri; +use Illuminate\Support\Uri; final class PacsUrlGen { @@ -34,6 +34,19 @@ public static function ohifViewerMpr(DicomServer $host, Study $study): string return (string) $url; } + public static function weasisUrl(DicomServer $host, Study $study, bool $close = false): string + { + $parts = []; + if ($close) { + $parts[] = '$dicom:close --all'; + } + $parts[] = sprintf('$dicom:rs --url "%s"', $host->wadoUrl()); + $parts[] = sprintf('-r "studyUID=%s"', $study->study_instance_uid); + // $parts[] = '--query-ext "&includedefaults=false"'; + + return sprintf('weasis://%s', urlencode(implode(' ', $parts))); + } + public static function ohifSegmentation(DicomServer $host, Study $study): string { $url = $host->ohifViewerUrl() diff --git a/database/migrations/0000_01_31_074312_create_dicom_servers_table.php b/database/migrations/0000_01_31_074312_create_dicom_servers_table.php index 21d8fcc..83f695a 100644 --- a/database/migrations/0000_01_31_074312_create_dicom_servers_table.php +++ b/database/migrations/0000_01_31_074312_create_dicom_servers_table.php @@ -20,6 +20,7 @@ public function up(): void $table->string('ae_title')->nullable(); $table->string('username')->nullable(); $table->string('password')->nullable(); + $table->string('wado_path')->nullable(); $table->string('stone_viewer_path')->nullable(); $table->string('ohif_viewer_path')->nullable(); $table->string('meddream_viewer_path')->nullable(); diff --git a/database/seeders/InstituteSeeder.php b/database/seeders/InstituteSeeder.php index 786210d..34161a4 100644 --- a/database/seeders/InstituteSeeder.php +++ b/database/seeders/InstituteSeeder.php @@ -63,6 +63,7 @@ public function run(): void 'host' => 'pacs.mylabctg.com', 'port' => 8042, 'rest_api_endpoint' => 'http://pacs.mylabctg.com:8042/', + 'wado_path' => 'dicom-web', 'ae_title' => 'RADFUSION', 'stone_viewer_path' => 'stone-webviewer/index.html', 'ohif_viewer_path' => 'ohif/viewer', @@ -76,6 +77,7 @@ public function run(): void 'port' => 8043, 'rest_api_endpoint' => 'http://pacs.mylabctg.com:8042/', 'ae_title' => 'RADFUSION', + 'wado_path' => 'dicom-web', 'stone_viewer_path' => 'stone-webviewer/index.html', 'ohif_viewer_path' => 'ohif/viewer', 'institute_id' => $chev->id, diff --git a/resources/views/_partials/_img-tooltip.blade.php b/resources/views/_partials/_img-tooltip.blade.php index ba6d37a..9d8ea6f 100644 --- a/resources/views/_partials/_img-tooltip.blade.php +++ b/resources/views/_partials/_img-tooltip.blade.php @@ -1 +1 @@ - + diff --git a/resources/views/_partials/_tooltip.blade.php b/resources/views/_partials/_tooltip.blade.php index 022e100..a4ea385 100644 --- a/resources/views/_partials/_tooltip.blade.php +++ b/resources/views/_partials/_tooltip.blade.php @@ -1 +1 @@ -data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{{ $tip }}" +data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{{ $tip }}" title="{{ $tip }}" diff --git a/resources/views/staff/studies/index.blade.php b/resources/views/staff/studies/index.blade.php index f4fdf99..acc2769 100644 --- a/resources/views/staff/studies/index.blade.php +++ b/resources/views/staff/studies/index.blade.php @@ -132,6 +132,9 @@ class="d-flex justify-content-between align-items-start border-end pb-4 pb-sm-0 @if ($study->allowed()['ohif']) O | @endif + @if ($study->allowed()['weasis']) + O | + @endif @if ($study->allowed()['ohif.mpr']) OM | @endif diff --git a/resources/views/staff/worklist/partials/_image-link.blade.php b/resources/views/staff/worklist/partials/_image-link.blade.php index 750309a..b8a52a0 100644 --- a/resources/views/staff/worklist/partials/_image-link.blade.php +++ b/resources/views/staff/worklist/partials/_image-link.blade.php @@ -1,3 +1,4 @@ - $text])> + $text]) data-id="{{ $data_id }}" class="me-2 {{ $data_class }}"> +