2023-09-22 09:23:49 +00:00
< ? php
2023-12-07 18:06:32 +00:00
namespace App\Livewire\Project\Service ;
2023-09-22 09:23:49 +00:00
2024-10-22 10:29:48 +00:00
use App\Models\InstanceSettings ;
2023-09-22 09:23:49 +00:00
use App\Models\ServiceApplication ;
2025-08-26 08:27:31 +00:00
use Illuminate\Foundation\Auth\Access\AuthorizesRequests ;
2024-09-05 15:54:32 +00:00
use Illuminate\Support\Facades\Auth ;
2025-04-30 15:39:41 +00:00
use Illuminate\Support\Facades\DB ;
2024-09-19 08:27:44 +00:00
use Illuminate\Support\Facades\Hash ;
2025-11-04 08:18:05 +00:00
use Livewire\Attributes\Validate ;
2024-09-19 08:27:44 +00:00
use Livewire\Component ;
2024-10-11 08:14:27 +00:00
use Spatie\Url\Url ;
2023-09-22 09:23:49 +00:00
2024-02-05 13:40:54 +00:00
class ServiceApplicationView extends Component
2023-09-22 09:23:49 +00:00
{
2025-08-26 08:27:31 +00:00
use AuthorizesRequests ;
2023-09-22 09:23:49 +00:00
public ServiceApplication $application ;
2024-06-10 20:43:34 +00:00
2023-09-25 13:48:43 +00:00
public $parameters ;
2024-06-10 20:43:34 +00:00
2024-09-05 15:54:32 +00:00
public $docker_cleanup = true ;
2024-09-19 08:27:44 +00:00
2024-09-05 15:54:32 +00:00
public $delete_volumes = true ;
2025-08-28 08:52:41 +00:00
public $domainConflicts = [];
public $showDomainConflictModal = false ;
public $forceSaveDomains = false ;
2025-11-06 13:30:39 +00:00
public $showPortWarningModal = false ;
public $forceRemovePort = false ;
public $requiredPort = null ;
2025-11-04 08:18:05 +00:00
#[Validate(['nullable'])]
2025-10-13 13:38:59 +00:00
public ? string $humanName = null ;
2025-11-04 08:18:05 +00:00
#[Validate(['nullable'])]
2025-10-13 13:38:59 +00:00
public ? string $description = null ;
2025-11-04 08:18:05 +00:00
#[Validate(['nullable'])]
2025-10-13 13:38:59 +00:00
public ? string $fqdn = null ;
2025-11-04 08:18:05 +00:00
#[Validate(['string', 'nullable'])]
2025-10-13 13:38:59 +00:00
public ? string $image = null ;
2025-11-04 08:18:05 +00:00
#[Validate(['required', 'boolean'])]
2025-10-13 13:38:59 +00:00
public bool $excludeFromStatus = false ;
2025-11-04 08:18:05 +00:00
#[Validate(['nullable', 'boolean'])]
2025-10-13 13:38:59 +00:00
public bool $isLogDrainEnabled = false ;
2025-11-04 08:18:05 +00:00
#[Validate(['nullable', 'boolean'])]
2025-10-13 13:38:59 +00:00
public bool $isGzipEnabled = false ;
2025-11-04 08:18:05 +00:00
#[Validate(['nullable', 'boolean'])]
2025-10-13 13:38:59 +00:00
public bool $isStripprefixEnabled = false ;
2023-09-22 09:23:49 +00:00
protected $rules = [
2025-10-13 13:38:59 +00:00
'humanName' => 'nullable' ,
'description' => 'nullable' ,
'fqdn' => 'nullable' ,
'image' => 'string|nullable' ,
'excludeFromStatus' => 'required|boolean' ,
2023-09-26 12:45:52 +00:00
'application.required_fqdn' => 'required|boolean' ,
2025-10-13 13:38:59 +00:00
'isLogDrainEnabled' => 'nullable|boolean' ,
'isGzipEnabled' => 'nullable|boolean' ,
'isStripprefixEnabled' => 'nullable|boolean' ,
2023-09-22 09:23:49 +00:00
];
2024-06-10 20:43:34 +00:00
2023-09-26 12:45:52 +00:00
public function instantSave ()
{
2025-08-26 08:27:31 +00:00
try {
$this -> authorize ( 'update' , $this -> application );
$this -> submit ();
} catch ( \Throwable $e ) {
return handleError ( $e , $this );
}
2023-09-25 13:48:43 +00:00
}
2024-06-10 20:43:34 +00:00
2025-12-01 12:39:15 +00:00
public function instantSaveSettings ()
{
try {
$this -> authorize ( 'update' , $this -> application );
// Save checkbox states without port validation
$this -> application -> is_gzip_enabled = $this -> isGzipEnabled ;
$this -> application -> is_stripprefix_enabled = $this -> isStripprefixEnabled ;
$this -> application -> exclude_from_status = $this -> excludeFromStatus ;
$this -> application -> save ();
$this -> dispatch ( 'success' , 'Settings saved.' );
} catch ( \Throwable $e ) {
return handleError ( $e , $this );
}
}
2023-11-17 19:08:21 +00:00
public function instantSaveAdvanced ()
{
2025-08-26 08:27:31 +00:00
try {
$this -> authorize ( 'update' , $this -> application );
if ( ! $this -> application -> service -> destination -> server -> isLogDrainEnabled ()) {
2025-10-13 13:38:59 +00:00
$this -> isLogDrainEnabled = false ;
2025-08-26 08:27:31 +00:00
$this -> dispatch ( 'error' , 'Log drain is not enabled on the server. Please enable it first.' );
2024-06-10 20:43:34 +00:00
2025-08-26 08:27:31 +00:00
return ;
}
2025-11-04 08:18:05 +00:00
// Sync component properties to model
$this -> application -> human_name = $this -> humanName ;
$this -> application -> description = $this -> description ;
$this -> application -> fqdn = $this -> fqdn ;
$this -> application -> image = $this -> image ;
$this -> application -> exclude_from_status = $this -> excludeFromStatus ;
$this -> application -> is_log_drain_enabled = $this -> isLogDrainEnabled ;
$this -> application -> is_gzip_enabled = $this -> isGzipEnabled ;
$this -> application -> is_stripprefix_enabled = $this -> isStripprefixEnabled ;
2025-08-26 08:27:31 +00:00
$this -> application -> save ();
$this -> dispatch ( 'success' , 'You need to restart the service for the changes to take effect.' );
} catch ( \Throwable $e ) {
return handleError ( $e , $this );
2023-11-22 13:21:03 +00:00
}
2023-11-17 19:08:21 +00:00
}
2024-06-10 20:43:34 +00:00
2024-09-05 15:54:32 +00:00
public function delete ( $password )
2023-09-25 13:48:43 +00:00
{
2025-08-26 08:27:31 +00:00
try {
$this -> authorize ( 'delete' , $this -> application );
2024-09-19 08:27:44 +00:00
2025-08-26 08:27:31 +00:00
if ( ! data_get ( InstanceSettings :: get (), 'disable_two_step_confirmation' )) {
if ( ! Hash :: check ( $password , Auth :: user () -> password )) {
$this -> addError ( 'password' , 'The provided password is incorrect.' );
return ;
}
2024-10-22 10:29:48 +00:00
}
2024-09-05 15:54:32 +00:00
2023-09-25 13:48:43 +00:00
$this -> application -> delete ();
2024-02-22 13:53:42 +00:00
$this -> dispatch ( 'success' , 'Application deleted.' );
2024-06-10 20:43:34 +00:00
2023-12-27 15:45:01 +00:00
return redirect () -> route ( 'project.service.configuration' , $this -> parameters );
2023-09-25 13:48:43 +00:00
} catch ( \Throwable $e ) {
return handleError ( $e , $this );
}
}
2024-06-10 20:43:34 +00:00
2023-09-25 10:49:55 +00:00
public function mount ()
{
2025-08-26 08:27:31 +00:00
try {
$this -> parameters = get_route_parameters ();
$this -> authorize ( 'view' , $this -> application );
2025-11-10 13:15:53 +00:00
$this -> requiredPort = $this -> application -> getRequiredPort ();
2025-11-04 08:18:05 +00:00
$this -> syncData ();
2025-08-26 08:27:31 +00:00
} catch ( \Throwable $e ) {
return handleError ( $e , $this );
}
2023-09-25 10:49:55 +00:00
}
2024-06-10 20:43:34 +00:00
2025-11-06 13:30:39 +00:00
public function confirmRemovePort ()
{
$this -> forceRemovePort = true ;
$this -> showPortWarningModal = false ;
$this -> submit ();
}
public function cancelRemovePort ()
{
$this -> showPortWarningModal = false ;
$this -> syncData (); // Reset to original FQDN
}
2025-11-04 08:18:05 +00:00
public function syncData ( bool $toModel = false ) : void
2025-10-13 13:38:59 +00:00
{
2025-11-04 08:18:05 +00:00
if ( $toModel ) {
$this -> validate ();
// Sync to model
$this -> application -> human_name = $this -> humanName ;
$this -> application -> description = $this -> description ;
$this -> application -> fqdn = $this -> fqdn ;
$this -> application -> image = $this -> image ;
$this -> application -> exclude_from_status = $this -> excludeFromStatus ;
$this -> application -> is_log_drain_enabled = $this -> isLogDrainEnabled ;
$this -> application -> is_gzip_enabled = $this -> isGzipEnabled ;
$this -> application -> is_stripprefix_enabled = $this -> isStripprefixEnabled ;
$this -> application -> save ();
} else {
// Sync from model
$this -> humanName = $this -> application -> human_name ;
$this -> description = $this -> application -> description ;
$this -> fqdn = $this -> application -> fqdn ;
$this -> image = $this -> application -> image ;
2025-11-04 09:51:41 +00:00
$this -> excludeFromStatus = data_get ( $this -> application , 'exclude_from_status' , false );
$this -> isLogDrainEnabled = data_get ( $this -> application , 'is_log_drain_enabled' , false );
$this -> isGzipEnabled = data_get ( $this -> application , 'is_gzip_enabled' , true );
$this -> isStripprefixEnabled = data_get ( $this -> application , 'is_stripprefix_enabled' , true );
2025-11-04 08:18:05 +00:00
}
2025-10-13 13:38:59 +00:00
}
2025-04-29 12:27:17 +00:00
public function convertToDatabase ()
{
try {
2025-08-26 08:27:31 +00:00
$this -> authorize ( 'update' , $this -> application );
2025-04-29 12:27:17 +00:00
$service = $this -> application -> service ;
$serviceApplication = $this -> application ;
2025-04-30 16:30:33 +00:00
// Check if database with same name already exists
if ( $service -> databases () -> where ( 'name' , $serviceApplication -> name ) -> exists ()) {
throw new \Exception ( 'A database with this name already exists.' );
}
2025-04-29 12:27:17 +00:00
2025-04-30 16:30:33 +00:00
$redirectParams = collect ( $this -> parameters )
-> except ( 'database_uuid' )
-> all ();
DB :: transaction ( function () use ( $service , $serviceApplication ) {
$service -> databases () -> create ([
'name' => $serviceApplication -> name ,
'human_name' => $serviceApplication -> human_name ,
'description' => $serviceApplication -> description ,
'exclude_from_status' => $serviceApplication -> exclude_from_status ,
'is_log_drain_enabled' => $serviceApplication -> is_log_drain_enabled ,
'image' => $serviceApplication -> image ,
'service_id' => $service -> id ,
'is_migrated' => true ,
]);
$serviceApplication -> delete ();
});
return redirect () -> route ( 'project.service.configuration' , $redirectParams );
} catch ( \Throwable $e ) {
2025-04-29 12:27:17 +00:00
return handleError ( $e , $this );
}
}
2025-08-28 08:52:41 +00:00
public function confirmDomainUsage ()
{
$this -> forceSaveDomains = true ;
$this -> showDomainConflictModal = false ;
$this -> submit ();
}
2023-09-22 09:23:49 +00:00
public function submit ()
{
try {
2025-08-26 08:27:31 +00:00
$this -> authorize ( 'update' , $this -> application );
2025-10-13 13:38:59 +00:00
$this -> fqdn = str ( $this -> fqdn ) -> replaceEnd ( ',' , '' ) -> trim () -> toString ();
$this -> fqdn = str ( $this -> fqdn ) -> replaceStart ( ',' , '' ) -> trim () -> toString ();
$domains = str ( $this -> fqdn ) -> trim () -> explode ( ',' ) -> map ( function ( $domain ) {
2025-08-29 13:09:03 +00:00
$domain = trim ( $domain );
2024-10-11 08:14:27 +00:00
Url :: fromString ( $domain , [ 'http' , 'https' ]);
2024-10-17 20:08:23 +00:00
2025-08-29 13:09:03 +00:00
return str ( $domain ) -> lower ();
2024-10-11 08:14:27 +00:00
});
2025-10-13 13:38:59 +00:00
$this -> fqdn = $domains -> unique () -> implode ( ',' );
$warning = sslipDomainWarning ( $this -> fqdn );
2024-10-20 20:15:31 +00:00
if ( $warning ) {
$this -> dispatch ( 'warning' , __ ( 'warning.sslipdomain' ));
}
2025-11-04 08:18:05 +00:00
// Sync to model for domain conflict check (without validation)
$this -> application -> human_name = $this -> humanName ;
$this -> application -> description = $this -> description ;
$this -> application -> fqdn = $this -> fqdn ;
$this -> application -> image = $this -> image ;
$this -> application -> exclude_from_status = $this -> excludeFromStatus ;
$this -> application -> is_log_drain_enabled = $this -> isLogDrainEnabled ;
$this -> application -> is_gzip_enabled = $this -> isGzipEnabled ;
$this -> application -> is_stripprefix_enabled = $this -> isStripprefixEnabled ;
2025-08-28 08:52:41 +00:00
// Check for domain conflicts if not forcing save
if ( ! $this -> forceSaveDomains ) {
$result = checkDomainUsage ( resource : $this -> application );
if ( $result [ 'hasConflicts' ]) {
$this -> domainConflicts = $result [ 'conflicts' ];
$this -> showDomainConflictModal = true ;
return ;
}
} else {
// Reset the force flag after using it
$this -> forceSaveDomains = false ;
}
2025-11-06 13:30:39 +00:00
// Check for required port
if ( ! $this -> forceRemovePort ) {
2025-11-10 13:15:53 +00:00
$requiredPort = $this -> application -> getRequiredPort ();
2025-11-06 13:30:39 +00:00
if ( $requiredPort !== null ) {
// Check if all FQDNs have a port
$fqdns = str ( $this -> fqdn ) -> trim () -> explode ( ',' );
$missingPort = false ;
foreach ( $fqdns as $fqdn ) {
$fqdn = trim ( $fqdn );
if ( empty ( $fqdn )) {
continue ;
}
$port = ServiceApplication :: extractPortFromUrl ( $fqdn );
if ( $port === null ) {
$missingPort = true ;
break ;
}
}
if ( $missingPort ) {
$this -> requiredPort = $requiredPort ;
$this -> showPortWarningModal = true ;
return ;
}
}
} else {
// Reset the force flag after using it
$this -> forceRemovePort = false ;
}
2023-09-22 09:23:49 +00:00
$this -> validate ();
$this -> application -> save ();
2025-10-13 13:38:59 +00:00
$this -> application -> refresh ();
2025-11-04 08:18:05 +00:00
$this -> syncData ();
2023-09-27 10:45:53 +00:00
updateCompose ( $this -> application );
2024-04-02 13:15:43 +00:00
if ( str ( $this -> application -> fqdn ) -> contains ( ',' )) {
2024-04-08 09:16:25 +00:00
$this -> dispatch ( 'warning' , 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.' );
2024-04-02 13:15:43 +00:00
} else {
2024-10-20 20:15:31 +00:00
! $warning && $this -> dispatch ( 'success' , 'Service saved.' );
2024-04-02 13:15:43 +00:00
}
2024-10-11 08:14:27 +00:00
$this -> dispatch ( 'generateDockerCompose' );
2023-09-22 09:23:49 +00:00
} catch ( \Throwable $e ) {
2024-10-11 08:14:27 +00:00
$originalFqdn = $this -> application -> getOriginal ( 'fqdn' );
if ( $originalFqdn !== $this -> application -> fqdn ) {
$this -> application -> fqdn = $originalFqdn ;
2025-11-04 08:18:05 +00:00
$this -> syncData ();
2024-10-11 08:14:27 +00:00
}
2024-10-17 20:08:23 +00:00
2023-09-27 10:45:53 +00:00
return handleError ( $e , $this );
2023-09-22 09:23:49 +00:00
}
}
2024-09-05 15:54:32 +00:00
public function render ()
{
return view ( 'livewire.project.service.service-application-view' , [
'checkboxes' => [
2024-09-19 08:27:44 +00:00
[ 'id' => 'delete_volumes' , 'label' => __ ( 'resource.delete_volumes' )],
[ 'id' => 'docker_cleanup' , 'label' => __ ( 'resource.docker_cleanup' )],
2024-09-05 15:54:32 +00:00
// ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'],
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'],
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.']
2024-09-19 08:27:44 +00:00
],
2024-09-05 15:54:32 +00:00
]);
}
2023-09-22 09:23:49 +00:00
}