Merge branch 'issue/fix-3' of https://github.com/pelican-dev/panel into issue/fix-3
This commit is contained in:
		
						commit
						693c65995d
					
				
							
								
								
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,2 @@ | |||||||
|  | github: pelican-dev | ||||||
| custom: [https://buy.stripe.com/14kdU99SI4UT7ni9AB, https://buy.stripe.com/14kaHXc0Q9b9372eUU] | custom: [https://buy.stripe.com/14kdU99SI4UT7ni9AB, https://buy.stripe.com/14kaHXc0Q9b9372eUU] | ||||||
|  | |||||||
							
								
								
									
										24
									
								
								.github/docker/default.conf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/docker/default.conf
									
									
									
									
										vendored
									
									
								
							| @ -4,6 +4,30 @@ | |||||||
| # If using CentOS this file should be placed in: | # If using CentOS this file should be placed in: | ||||||
| #				 /etc/nginx/conf.d/ | #				 /etc/nginx/conf.d/ | ||||||
| # | # | ||||||
|  | 
 | ||||||
|  | # The MIT License (MIT) | ||||||
|  | # | ||||||
|  | # Pterodactyl® | ||||||
|  | # Copyright © Dane Everitt <dane@daneeveritt.com> and contributors | ||||||
|  | # | ||||||
|  | # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | # of this software and associated documentation files (the "Software"), to deal | ||||||
|  | # in the Software without restriction, including without limitation the rights | ||||||
|  | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | # copies of the Software, and to permit persons to whom the Software is | ||||||
|  | # furnished to do so, subject to the following conditions: | ||||||
|  | # | ||||||
|  | # The above copyright notice and this permission notice shall be included in all | ||||||
|  | # copies or substantial portions of the Software. | ||||||
|  | # | ||||||
|  | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | # SOFTWARE. | ||||||
|  | 
 | ||||||
| server { | server { | ||||||
|     listen 80; |     listen 80; | ||||||
|     server_name _; |     server_name _; | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							| @ -1,9 +1,6 @@ | |||||||
| name: Build | name: Build | ||||||
| 
 | 
 | ||||||
| on: | on: | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - '**' |  | ||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - '**' |       - '**' | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| name: Tests | name: Tests | ||||||
| 
 | 
 | ||||||
| on: | on: | ||||||
|   push: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - '**' |       - '**' | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								.github/workflows/cla.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/cla.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | name: "CLA Assistant" | ||||||
|  | on: | ||||||
|  |   issue_comment: | ||||||
|  |     types: [created] | ||||||
|  |   pull_request_target: | ||||||
|  |     types: [opened,closed,synchronize] | ||||||
|  | 
 | ||||||
|  | permissions: | ||||||
|  |   actions: write | ||||||
|  |   contents: write | ||||||
|  |   pull-requests: write | ||||||
|  |   statuses: write | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   CLAAssistant: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: "CLA Assistant" | ||||||
|  |         if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' | ||||||
|  |         uses: contributor-assistant/github-action@v2.3.0 | ||||||
|  |         env: | ||||||
|  |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |           PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | ||||||
|  |         with: | ||||||
|  |           path-to-signatures: 'version1/cla.json' | ||||||
|  |           path-to-document: 'https://github.com/pelican-dev/panel/blob/3.x/contributor_license_agreement.md' | ||||||
|  |           branch: 'main' | ||||||
|  |           allowlist: dependabot[bot] | ||||||
|  |           remote-organization-name: pelican-dev | ||||||
|  |           remote-repository-name: cla-signatures | ||||||
							
								
								
									
										3
									
								
								.github/workflows/lint.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/lint.yaml
									
									
									
									
										vendored
									
									
								
							| @ -1,9 +1,6 @@ | |||||||
| name: Lint | name: Lint | ||||||
| 
 | 
 | ||||||
| on: | on: | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - '**' |  | ||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - '**' |       - '**' | ||||||
|  | |||||||
| @ -44,7 +44,6 @@ class InfoCommand extends Command | |||||||
|             ['Session Driver', config('session.driver')], |             ['Session Driver', config('session.driver')], | ||||||
|             ['Filesystem Driver', config('filesystems.default')], |             ['Filesystem Driver', config('filesystems.default')], | ||||||
|             ['Default Theme', config('themes.active')], |             ['Default Theme', config('themes.active')], | ||||||
|             ['Proxies', config('trustedproxies.proxies')], |  | ||||||
|         ], 'compact'); |         ], 'compact'); | ||||||
| 
 | 
 | ||||||
|         $this->output->title('Database Configuration'); |         $this->output->title('Database Configuration'); | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								app/Enums/ContainerStatus.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								app/Enums/ContainerStatus.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Enums; | ||||||
|  | 
 | ||||||
|  | enum ContainerStatus: string | ||||||
|  | { | ||||||
|  |     case Created = 'created'; | ||||||
|  |     case Running = 'running'; | ||||||
|  |     case Restarting = 'restarting'; | ||||||
|  |     case Exited = 'exited'; | ||||||
|  |     case Paused = 'paused'; | ||||||
|  |     case Dead = 'dead'; | ||||||
|  |     case Removing = 'removing'; | ||||||
|  |     case Missing = 'missing'; | ||||||
|  | 
 | ||||||
|  |     public function icon(): string | ||||||
|  |     { | ||||||
|  |         return match ($this) { | ||||||
|  |             self::Created => 'tabler-heart-plus', | ||||||
|  |             self::Running => 'tabler-heartbeat', | ||||||
|  |             self::Restarting => 'tabler-heart-bolt', | ||||||
|  |             self::Exited => 'tabler-heart-exclamation', | ||||||
|  |             self::Paused => 'tabler-heart-pause', | ||||||
|  |             self::Dead => 'tabler-heart-x', | ||||||
|  |             self::Removing => 'tabler-heart-down', | ||||||
|  |             self::Missing => 'tabler-heart-question', | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function color(): string | ||||||
|  |     { | ||||||
|  |         return match ($this) { | ||||||
|  |             self::Created => 'primary', | ||||||
|  |             self::Running => 'success', | ||||||
|  |             self::Restarting => 'info', | ||||||
|  |             self::Exited => 'danger', | ||||||
|  |             self::Paused => 'warning', | ||||||
|  |             self::Dead => 'danger', | ||||||
|  |             self::Removing => 'warning', | ||||||
|  |             self::Missing => 'gray', | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								app/Enums/ServerState.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/Enums/ServerState.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Enums; | ||||||
|  | 
 | ||||||
|  | enum ServerState: string | ||||||
|  | { | ||||||
|  |     case None = 'none'; | ||||||
|  |     case Installing = 'installing'; | ||||||
|  |     case InstallFailed = 'install_failed'; | ||||||
|  |     case ReinstallFailed = 'reinstall_failed'; | ||||||
|  |     case Suspended = 'suspended'; | ||||||
|  |     case RestoringBackup = 'restoring_backup'; | ||||||
|  | 
 | ||||||
|  |     public function icon(): string | ||||||
|  |     { | ||||||
|  |         return match ($this) { | ||||||
|  |             self::None => 'tabler-heart', | ||||||
|  |             self::Installing => 'tabler-heart-bolt', | ||||||
|  |             self::InstallFailed => 'tabler-heart-x', | ||||||
|  |             self::ReinstallFailed => 'tabler-heart-x', | ||||||
|  |             self::Suspended => 'tabler-heart-cancel', | ||||||
|  |             self::RestoringBackup => 'tabler-heart-up', | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function color(): string | ||||||
|  |     { | ||||||
|  |         return match ($this) { | ||||||
|  |             self::None => 'primary', | ||||||
|  |             self::Installing => 'info', | ||||||
|  |             self::InstallFailed => 'danger', | ||||||
|  |             self::ReinstallFailed => 'danger', | ||||||
|  |             self::Suspended => 'danger', | ||||||
|  |             self::RestoringBackup => 'info', | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Exceptions\Http\Server; | namespace App\Exceptions\Http\Server; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use Symfony\Component\HttpKernel\Exception\ConflictHttpException; | use Symfony\Component\HttpKernel\Exception\ConflictHttpException; | ||||||
| 
 | 
 | ||||||
| @ -20,7 +21,7 @@ class ServerStateConflictException extends ConflictHttpException | |||||||
|             $message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.'; |             $message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.'; | ||||||
|         } elseif (!$server->isInstalled()) { |         } elseif (!$server->isInstalled()) { | ||||||
|             $message = 'This server has not yet completed its installation process, please try again later.'; |             $message = 'This server has not yet completed its installation process, please try again later.'; | ||||||
|         } elseif ($server->status === Server::STATUS_RESTORING_BACKUP) { |         } elseif ($server->status === ServerState::RestoringBackup) { | ||||||
|             $message = 'This server is currently restoring from a backup, please try again later.'; |             $message = 'This server is currently restoring from a backup, please try again later.'; | ||||||
|         } elseif (!is_null($server->transfer)) { |         } elseif (!is_null($server->transfer)) { | ||||||
|             $message = 'This server is currently being transferred to a new machine, please try again later.'; |             $message = 'This server is currently being transferred to a new machine, please try again later.'; | ||||||
|  | |||||||
| @ -1,5 +1,28 @@ | |||||||
| <?php | <?php | ||||||
| 
 | 
 | ||||||
|  | /* The MIT License (MIT) | ||||||
|  | 
 | ||||||
|  |  Pterodactyl® | ||||||
|  |  Copyright © Dane Everitt <dane@daneeveritt.com> and contributors | ||||||
|  | 
 | ||||||
|  |  Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  in the Software without restriction, including without limitation the rights | ||||||
|  |  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  |  The above copyright notice and this permission notice shall be included in all | ||||||
|  |  copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  |  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  |  SOFTWARE. */ | ||||||
|  | 
 | ||||||
| namespace App\Extensions\Filesystem; | namespace App\Extensions\Filesystem; | ||||||
| 
 | 
 | ||||||
| use Aws\S3\S3ClientInterface; | use Aws\S3\S3ClientInterface; | ||||||
|  | |||||||
							
								
								
									
										82
									
								
								app/Filament/Pages/Dashboard.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								app/Filament/Pages/Dashboard.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\NodeResource\Pages\ListNodes; | ||||||
|  | use App\Models\Egg; | ||||||
|  | use App\Models\Node; | ||||||
|  | use App\Models\Server; | ||||||
|  | use App\Models\User; | ||||||
|  | use Filament\Actions\CreateAction; | ||||||
|  | use Filament\Pages\Page; | ||||||
|  | 
 | ||||||
|  | class Dashboard extends Page | ||||||
|  | { | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-layout-dashboard'; | ||||||
|  | 
 | ||||||
|  |     protected static string $view = 'filament.pages.dashboard'; | ||||||
|  | 
 | ||||||
|  |     protected ?string $heading = ''; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $title = 'Dashboard'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $slug = '/'; | ||||||
|  | 
 | ||||||
|  |     public string $activeTab = 'nodes'; | ||||||
|  | 
 | ||||||
|  |     public function getViewData(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'inDevelopment' => config('app.version') === 'canary', | ||||||
|  |             'eggsCount' => Egg::query()->count(), | ||||||
|  |             'nodesList' => ListNodes::getUrl(), | ||||||
|  |             'nodesCount' => Node::query()->count(), | ||||||
|  |             'serversCount' => Server::query()->count(), | ||||||
|  |             'usersCount' => User::query()->count(), | ||||||
|  | 
 | ||||||
|  |             'devActions' => [ | ||||||
|  |                 CreateAction::make() | ||||||
|  |                     ->label('Create Issue') | ||||||
|  |                     ->icon('tabler-brand-github') | ||||||
|  |                     ->url('https://github.com/pelican-dev/panel/issues/new/choose', true) | ||||||
|  |                     ->color('warning'), | ||||||
|  |                 CreateAction::make() | ||||||
|  |                     ->label('Discuss Features') | ||||||
|  |                     ->icon('tabler-brand-github') | ||||||
|  |                     ->url('https://github.com/pelican-dev/panel/discussions', true) | ||||||
|  |                     ->color('primary'), | ||||||
|  |             ], | ||||||
|  |             'nodeActions' => [ | ||||||
|  |                 CreateAction::make() | ||||||
|  |                     ->label('Create first Node in Pelican') | ||||||
|  |                     ->icon('tabler-server-2') | ||||||
|  |                     ->url(route('filament.admin.resources.nodes.create')) | ||||||
|  |                     ->color('primary'), | ||||||
|  |             ], | ||||||
|  |             'supportActions' => [ | ||||||
|  |                 CreateAction::make() | ||||||
|  |                     ->label('Help Translate') | ||||||
|  |                     ->icon('tabler-language') | ||||||
|  |                     ->url('https://crowdin.com/project/pelican-dev', true) | ||||||
|  |                     ->color('info'), | ||||||
|  |                 CreateAction::make() | ||||||
|  |                     ->label('Donate Directly') | ||||||
|  |                     ->icon('tabler-cash') | ||||||
|  |                     ->url('https://pelican.dev/donate', true) | ||||||
|  |                     ->color('success'), | ||||||
|  |             ], | ||||||
|  |             'helpActions' => [ | ||||||
|  |                 CreateAction::make() | ||||||
|  |                     ->label('Read Documentation') | ||||||
|  |                     ->icon('tabler-speedboat') | ||||||
|  |                     ->url('https://pelican.dev/docs', true) | ||||||
|  |                     ->color('info'), | ||||||
|  |                 CreateAction::make() | ||||||
|  |                     ->label('Get Help in Discord') | ||||||
|  |                     ->icon('tabler-brand-discord') | ||||||
|  |                     ->url('https://discord.gg/pelican-panel', true) | ||||||
|  |                     ->color('primary'), | ||||||
|  |             ], | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										194
									
								
								app/Filament/Resources/ApiKeyResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								app/Filament/Resources/ApiKeyResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,194 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\ApiKeyResource\Pages; | ||||||
|  | use App\Models\ApiKey; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Components\Tab; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | use Illuminate\Database\Eloquent\Builder; | ||||||
|  | 
 | ||||||
|  | class ApiKeyResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = ApiKey::class; | ||||||
|  |     protected static ?string $label = 'API Key'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-key'; | ||||||
|  | 
 | ||||||
|  |     public static function canEdit($record): bool | ||||||
|  |     { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getTabs(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'all' => Tab::make('All Keys'), | ||||||
|  |             'application' => Tab::make('Application Keys') | ||||||
|  |                 ->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getDefaultActiveTab(): string|int|null | ||||||
|  |     { | ||||||
|  |         return 'application'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->schema([ | ||||||
|  |                 Forms\Components\Hidden::make('identifier')->default(ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION)), | ||||||
|  |                 Forms\Components\Hidden::make('token')->default(encrypt(str_random(ApiKey::KEY_LENGTH))), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Select::make('user_id') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->preload() | ||||||
|  |                     ->relationship('user', 'username') | ||||||
|  |                     ->default(auth()->user()->id) | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Select::make('key_type') | ||||||
|  |                     ->options(function (ApiKey $apiKey) { | ||||||
|  |                         $originalOptions = [ | ||||||
|  |                             ApiKey::TYPE_NONE => 'None', | ||||||
|  |                             ApiKey::TYPE_ACCOUNT => 'Account', | ||||||
|  |                             ApiKey::TYPE_APPLICATION => 'Application', | ||||||
|  |                             ApiKey::TYPE_DAEMON_USER => 'Daemon User', | ||||||
|  |                             ApiKey::TYPE_DAEMON_APPLICATION => 'Daemon Application', | ||||||
|  |                         ]; | ||||||
|  | 
 | ||||||
|  |                         return collect($originalOptions) | ||||||
|  |                             ->filter(fn ($value, $key) => $key <= ApiKey::TYPE_APPLICATION || $apiKey->key_type === $key) | ||||||
|  |                             ->all(); | ||||||
|  |                     }) | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->selectablePlaceholder(false) | ||||||
|  |                     ->required() | ||||||
|  |                     ->default(ApiKey::TYPE_APPLICATION), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Fieldset::make('Permissions') | ||||||
|  |                     ->columns([ | ||||||
|  |                         'default' => 1, | ||||||
|  |                         'sm' => 1, | ||||||
|  |                         'md' => 2, | ||||||
|  |                     ]) | ||||||
|  |                     ->schema( | ||||||
|  |                         collect(ApiKey::RESOURCES)->map(fn ($resource) => Forms\Components\ToggleButtons::make("r_$resource") | ||||||
|  |                             ->label(str($resource)->replace('_', ' ')->title()) | ||||||
|  |                             ->options([ | ||||||
|  |                                 0 => 'None', | ||||||
|  |                                 1 => 'Read', | ||||||
|  |                                 // 2 => 'Write',
 | ||||||
|  |                                 3 => 'Read & Write', | ||||||
|  |                             ]) | ||||||
|  |                             ->icons([ | ||||||
|  |                                 0 => 'tabler-book-off', | ||||||
|  |                                 1 => 'tabler-book', | ||||||
|  |                                 2 => 'tabler-writing', | ||||||
|  |                                 3 => 'tabler-writing', | ||||||
|  |                             ]) | ||||||
|  |                             ->colors([ | ||||||
|  |                                 0 => 'success', | ||||||
|  |                                 1 => 'warning', | ||||||
|  |                                 2 => 'danger', | ||||||
|  |                                 3 => 'danger', | ||||||
|  |                             ]) | ||||||
|  |                             ->inline() | ||||||
|  |                             ->required() | ||||||
|  |                             ->disabledOn('edit') | ||||||
|  |                             ->columnSpan([ | ||||||
|  |                                 'default' => 1, | ||||||
|  |                                 'sm' => 1, | ||||||
|  |                                 'md' => 1, | ||||||
|  |                             ]) | ||||||
|  |                             ->default(0), | ||||||
|  |                         )->all(), | ||||||
|  |                     ), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\TagsInput::make('allowed_ips') | ||||||
|  |                     ->placeholder('Example: 127.0.0.1 or 192.168.1.1') | ||||||
|  |                     ->label('Whitelisted IPv4 Addresses') | ||||||
|  |                     ->helperText('Press enter to add a new IP address or leave blank to allow any IP address') | ||||||
|  |                     ->columnSpanFull() | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->default(null), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Textarea::make('memo') | ||||||
|  |                     ->required() | ||||||
|  |                     ->label('Description') | ||||||
|  |                     ->helperText(' | ||||||
|  |                         Once you have assigned permissions and created this set of credentials you will be unable to come back and edit it. | ||||||
|  |                         If you need to make changes down the road you will need to create a new set of credentials. | ||||||
|  |                     ') | ||||||
|  |                     ->columnSpanFull(), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->searchable(false) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('user.username') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->sortable(), | ||||||
|  | 
 | ||||||
|  |                 Tables\Columns\TextColumn::make('key') | ||||||
|  |                     ->copyable() | ||||||
|  |                     ->icon('tabler-clipboard-text') | ||||||
|  |                     ->state(fn (ApiKey $key) => $key->identifier . decrypt($key->token)), | ||||||
|  | 
 | ||||||
|  |                 Tables\Columns\TextColumn::make('memo') | ||||||
|  |                     ->label('Description') | ||||||
|  |                     ->wrap() | ||||||
|  |                     ->limit(50), | ||||||
|  | 
 | ||||||
|  |                 Tables\Columns\TextColumn::make('identifier') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->searchable(), | ||||||
|  | 
 | ||||||
|  |                 Tables\Columns\TextColumn::make('last_used_at') | ||||||
|  |                     ->label('Last Used') | ||||||
|  |                     ->dateTime() | ||||||
|  |                     ->sortable(), | ||||||
|  | 
 | ||||||
|  |                 Tables\Columns\TextColumn::make('created_at') | ||||||
|  |                     ->label('Created') | ||||||
|  |                     ->dateTime() | ||||||
|  |                     ->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\ListApiKeys::route('/'), | ||||||
|  |             'create' => Pages\CreateApiKey::route('/create'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\ApiKeyResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\ApiKeyResource; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | 
 | ||||||
|  | class CreateApiKey extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = ApiKeyResource::class; | ||||||
|  | 
 | ||||||
|  |     protected ?string $heading = 'Create Application API Key'; | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								app/Filament/Resources/ApiKeyResource/Pages/ListApiKeys.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/Filament/Resources/ApiKeyResource/Pages/ListApiKeys.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\ApiKeyResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\ApiKeyResource; | ||||||
|  | use App\Models\ApiKey; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Components\Tab; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | use Illuminate\Database\Eloquent\Builder; | ||||||
|  | 
 | ||||||
|  | class ListApiKeys extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = ApiKeyResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getTabs(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'all' => Tab::make('All Keys'), | ||||||
|  |             'application' => Tab::make('Application Keys') | ||||||
|  |                 ->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION) | ||||||
|  |                 ), | ||||||
|  |             'account' => Tab::make('Account Keys') | ||||||
|  |                 ->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_ACCOUNT) | ||||||
|  |                 ), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getDefaultActiveTab(): string|int|null | ||||||
|  |     { | ||||||
|  |         return 'application'; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										116
									
								
								app/Filament/Resources/DatabaseHostResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								app/Filament/Resources/DatabaseHostResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseHostResource\Pages; | ||||||
|  | use App\Models\DatabaseHost; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Components\Section; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | 
 | ||||||
|  | class DatabaseHostResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = DatabaseHost::class; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $label = 'Databases'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-database'; | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->schema([ | ||||||
|  |                 Section::make()->schema([ | ||||||
|  |                     Forms\Components\TextInput::make('host') | ||||||
|  |                         ->helperText('The IP address or Domain name that should be used when attempting to connect to this MySQL host from this Panel to create new databases.') | ||||||
|  |                         ->required() | ||||||
|  |                         ->live() | ||||||
|  |                         ->debounce(500) | ||||||
|  |                         ->afterStateUpdated(fn ($state, Forms\Set $set) => $set('name', $state)) | ||||||
|  |                         ->maxLength(191), | ||||||
|  |                     Forms\Components\TextInput::make('port') | ||||||
|  |                         ->helperText('The port that MySQL is running on for this host.') | ||||||
|  |                         ->required() | ||||||
|  |                         ->numeric() | ||||||
|  |                         ->default(3306) | ||||||
|  |                         ->minValue(0) | ||||||
|  |                         ->maxValue(65535), | ||||||
|  |                     Forms\Components\TextInput::make('username') | ||||||
|  |                         ->helperText('The username of an account that has enough permissions to create new users and databases on the system.') | ||||||
|  |                         ->required() | ||||||
|  |                         ->maxLength(191), | ||||||
|  |                     Forms\Components\TextInput::make('password') | ||||||
|  |                         ->helperText('The password for the database user.') | ||||||
|  |                         ->password() | ||||||
|  |                         ->revealable() | ||||||
|  |                         ->maxLength(191) | ||||||
|  |                         ->required(), | ||||||
|  |                     Forms\Components\TextInput::make('name') | ||||||
|  |                         ->helperText('A short identifier used to distinguish this location from others. Must be between 1 and 60 characters, for example, us.nyc.lvl3.') | ||||||
|  |                         ->required() | ||||||
|  |                         ->maxLength(60), | ||||||
|  |                     Forms\Components\Select::make('node_id') | ||||||
|  |                         ->searchable() | ||||||
|  |                         ->preload() | ||||||
|  |                         ->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.') | ||||||
|  |                         ->label('Linked Node') | ||||||
|  |                         ->relationship('node', 'name'), | ||||||
|  |                 ])->columns([ | ||||||
|  |                     'default' => 1, | ||||||
|  |                     'lg' => 2, | ||||||
|  |                 ]), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->searchable(false) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('name') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('host') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('port') | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('username') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('max_databases') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('node.name') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->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\ListDatabaseHosts::route('/'), | ||||||
|  |             'create' => Pages\CreateDatabaseHost::route('/create'), | ||||||
|  |             'edit' => Pages\EditDatabaseHost::route('/{record}/edit'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,15 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\DatabaseHostResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseHostResource; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | 
 | ||||||
|  | class CreateDatabaseHost extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = DatabaseHostResource::class; | ||||||
|  | 
 | ||||||
|  |     protected ?string $heading = 'Database Hosts'; | ||||||
|  | 
 | ||||||
|  |     protected ?string $subheading = '(database servers that can have individual databases)'; | ||||||
|  | } | ||||||
| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\DatabaseHostResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseHostResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\EditRecord; | ||||||
|  | 
 | ||||||
|  | class EditDatabaseHost extends EditRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = DatabaseHostResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\DeleteAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\DatabaseHostResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseHostResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | 
 | ||||||
|  | class ListDatabaseHosts extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = DatabaseHostResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										110
									
								
								app/Filament/Resources/DatabaseResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								app/Filament/Resources/DatabaseResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,110 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseResource\Pages; | ||||||
|  | use App\Models\Database; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | 
 | ||||||
|  | class DatabaseResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = Database::class; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-database'; | ||||||
|  | 
 | ||||||
|  |     protected static bool $shouldRegisterNavigation = false; | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->schema([ | ||||||
|  |                 Forms\Components\Select::make('server_id') | ||||||
|  |                     ->relationship('server', 'name') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->preload() | ||||||
|  |                     ->required(), | ||||||
|  |                 Forms\Components\TextInput::make('database_host_id') | ||||||
|  |                     ->required() | ||||||
|  |                     ->numeric(), | ||||||
|  |                 Forms\Components\TextInput::make('database') | ||||||
|  |                     ->required() | ||||||
|  |                     ->maxLength(191), | ||||||
|  |                 Forms\Components\TextInput::make('remote') | ||||||
|  |                     ->required() | ||||||
|  |                     ->maxLength(191) | ||||||
|  |                     ->default('%'), | ||||||
|  |                 Forms\Components\TextInput::make('username') | ||||||
|  |                     ->required() | ||||||
|  |                     ->maxLength(191), | ||||||
|  |                 Forms\Components\TextInput::make('password') | ||||||
|  |                     ->password() | ||||||
|  |                     ->revealable() | ||||||
|  |                     ->required(), | ||||||
|  |                 Forms\Components\TextInput::make('max_connections') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->minValue(0) | ||||||
|  |                     ->default(0), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('server.name') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('database_host_id') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('database') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('username') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('remote') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('max_connections') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('created_at') | ||||||
|  |                     ->dateTime() | ||||||
|  |                     ->sortable() | ||||||
|  |                     ->toggleable(isToggledHiddenByDefault: true), | ||||||
|  |                 Tables\Columns\TextColumn::make('updated_at') | ||||||
|  |                     ->dateTime() | ||||||
|  |                     ->sortable() | ||||||
|  |                     ->toggleable(isToggledHiddenByDefault: true), | ||||||
|  |             ]) | ||||||
|  |             ->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\ListDatabases::route('/'), | ||||||
|  |             'create' => Pages\CreateDatabase::route('/create'), | ||||||
|  |             'edit' => Pages\EditDatabase::route('/{record}/edit'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\DatabaseResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseResource; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | 
 | ||||||
|  | class CreateDatabase extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = DatabaseResource::class; | ||||||
|  | } | ||||||
| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\DatabaseResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\EditRecord; | ||||||
|  | 
 | ||||||
|  | class EditDatabase extends EditRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = DatabaseResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\DeleteAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\DatabaseResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\DatabaseResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | 
 | ||||||
|  | class ListDatabases extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = DatabaseResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										195
									
								
								app/Filament/Resources/EggResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								app/Filament/Resources/EggResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,195 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\EggResource\Pages; | ||||||
|  | use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor; | ||||||
|  | use App\Models\Egg; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | 
 | ||||||
|  | class EggResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = Egg::class; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-eggs'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $recordTitleAttribute = 'name'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $recordRouteKeyName = 'id'; | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->schema([ | ||||||
|  |                 Forms\Components\Tabs::make()->tabs([ | ||||||
|  |                     Forms\Components\Tabs\Tab::make('Configuration') | ||||||
|  |                         ->columns(2) | ||||||
|  |                         ->schema([ | ||||||
|  |                             Forms\Components\TextInput::make('name')->required()->maxLength(191) | ||||||
|  |                                 ->helperText('A simple, human-readable name to use as an identifier for this Egg.'), | ||||||
|  |                             Forms\Components\Textarea::make('description')->rows(5) | ||||||
|  |                                 ->helperText('A description of this Egg that will be displayed throughout the Panel as needed.'), | ||||||
|  |                             Forms\Components\TextInput::make('uuid')->disabled() | ||||||
|  |                                 ->helperText('This is the globally unique identifier for this Egg which the Daemon uses as an identifier.'), | ||||||
|  |                             Forms\Components\TextInput::make('author')->required()->maxLength(191)->disabled() | ||||||
|  |                                 ->helperText('The author of this version of the Egg. Uploading a new Egg configuration from a different author will change this.'), | ||||||
|  |                             Forms\Components\Toggle::make('force_outgoing_ip')->required() | ||||||
|  |                                 ->helperText("Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP.
 | ||||||
|  |                                     Required for certain games to work properly when the Node has multiple public IP addresses. | ||||||
|  |                                     Enabling this option will disable internal networking for any servers using this egg, causing them to be unable to internally access other servers on the same node."),
 | ||||||
|  |                             Forms\Components\Textarea::make('startup')->rows(5) | ||||||
|  |                                 ->helperText('The default startup command that should be used for new servers using this Egg.'), | ||||||
|  |                             Forms\Components\KeyValue::make('docker_images') | ||||||
|  |                                 ->columnSpanFull() | ||||||
|  |                                 ->addActionLabel('Add Image') | ||||||
|  |                                 ->keyLabel('Name') | ||||||
|  |                                 ->valueLabel('Image URI') | ||||||
|  |                                 ->helperText('The docker images available to servers using this egg.'), | ||||||
|  |                         ]), | ||||||
|  | 
 | ||||||
|  |                     Forms\Components\Tabs\Tab::make('Process Management') | ||||||
|  |                         ->columns(2) | ||||||
|  |                         ->schema([ | ||||||
|  |                             Forms\Components\Select::make('config_from') | ||||||
|  |                                 ->label('Copy Settings From') | ||||||
|  |                                 ->placeholder('None') | ||||||
|  |                                 ->relationship('configFrom', 'name', ignoreRecord: true) | ||||||
|  |                                 ->helperText('If you would like to default to settings from another Egg select it from the menu above.'), | ||||||
|  | 
 | ||||||
|  |                             Forms\Components\TextInput::make('config_stop') | ||||||
|  |                                 ->maxLength(191) | ||||||
|  |                                 ->label('Stop Command') | ||||||
|  |                                 ->helperText('The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.'), | ||||||
|  | 
 | ||||||
|  |                             Forms\Components\Textarea::make('config_startup')->rows(10)->json() | ||||||
|  |                                 ->label('Start Configuration') | ||||||
|  |                                 ->helperText('List of values the daemon should be looking for when booting a server to determine completion.'), | ||||||
|  | 
 | ||||||
|  |                             Forms\Components\Textarea::make('config_files')->rows(10)->json() | ||||||
|  |                                 ->label('Configuration Files') | ||||||
|  |                                 ->helperText('This should be a JSON representation of configuration files to modify and what parts should be changed.'), | ||||||
|  | 
 | ||||||
|  |                             Forms\Components\Textarea::make('config_logs')->rows(10)->json() | ||||||
|  |                                 ->label('Log Configuration') | ||||||
|  |                                 ->helperText('This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.'), | ||||||
|  |                         ]), | ||||||
|  |                     Forms\Components\Tabs\Tab::make('Variables') | ||||||
|  |                         ->columnSpanFull() | ||||||
|  |                         // ->columns(2)
 | ||||||
|  |                         ->schema([ | ||||||
|  |                             Forms\Components\Repeater::make('Blah') | ||||||
|  |                                 ->grid(3) | ||||||
|  |                                 ->relationship('variables') | ||||||
|  |                                 ->name('name') | ||||||
|  |                                 ->columns(1) | ||||||
|  |                                 ->columnSpan(1) | ||||||
|  |                                 ->itemLabel(fn (array $state) => $state['name']) | ||||||
|  |                                 ->schema([ | ||||||
|  |                                     Forms\Components\TextInput::make('name')->live()->maxLength(191)->columnSpanFull(), | ||||||
|  |                                     Forms\Components\Textarea::make('description')->columnSpanFull(), | ||||||
|  |                                     Forms\Components\TextInput::make('env_variable')->maxLength(191), | ||||||
|  |                                     Forms\Components\TextInput::make('default_value')->maxLength(191), | ||||||
|  |                                     Forms\Components\Textarea::make('rules')->rows(3)->columnSpanFull()->required(), | ||||||
|  |                                 ]), | ||||||
|  |                         ]), | ||||||
|  |                     Forms\Components\Tabs\Tab::make('Install Script') | ||||||
|  |                         ->columns(3) | ||||||
|  |                         ->schema([ | ||||||
|  | 
 | ||||||
|  |                             Forms\Components\Select::make('copy_script_from') | ||||||
|  |                                 ->placeholder('None') | ||||||
|  |                                 ->relationship('scriptFrom', 'name', ignoreRecord: true), | ||||||
|  | 
 | ||||||
|  |                             Forms\Components\TextInput::make('script_container') | ||||||
|  |                                 ->required() | ||||||
|  |                                 ->maxLength(191) | ||||||
|  |                                 ->default('alpine:3.4'), | ||||||
|  | 
 | ||||||
|  |                             Forms\Components\TextInput::make('script_entry') | ||||||
|  |                                 ->required() | ||||||
|  |                                 ->maxLength(191) | ||||||
|  |                                 ->default('ash'), | ||||||
|  | 
 | ||||||
|  |                             MonacoEditor::make('script_install') | ||||||
|  |                                 ->columnSpanFull() | ||||||
|  |                                 ->fontSize('16px') | ||||||
|  |                                 ->language('shell') | ||||||
|  |                                 ->view('filament.plugins.monaco-editor'), | ||||||
|  |                         ]), | ||||||
|  | 
 | ||||||
|  |                 ])->columnSpanFull()->persistTabInQueryString(), | ||||||
|  | 
 | ||||||
|  |                 // Forms\Components\TagsInput::make('features'),
 | ||||||
|  |                 // Forms\Components\TagsInput::make('file_denylist')->placeholder('new-file.txt'),
 | ||||||
|  |                 // Forms\Components\TextInput::make('update_url'),
 | ||||||
|  |                 // Forms\Components\Toggle::make('script_is_privileged')->required(),
 | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->defaultPaginationPageOption(25) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('id') | ||||||
|  |                     ->label('Id') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('name') | ||||||
|  |                     ->icon('tabler-egg') | ||||||
|  |                     ->description(fn ($record): string => $record->description) | ||||||
|  |                     ->wrap() | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('author') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('servers_count') | ||||||
|  |                     ->counts('servers') | ||||||
|  |                     ->icon('tabler-server') | ||||||
|  |                     ->label('Servers'), | ||||||
|  |                 Tables\Columns\TextColumn::make('script_container') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->hidden(), | ||||||
|  |                 Tables\Columns\TextColumn::make('copyFrom.name') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('script_entry') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->searchable(), | ||||||
|  |             ]) | ||||||
|  |             ->filters([ | ||||||
|  |                 //
 | ||||||
|  |             ]) | ||||||
|  |             ->actions([ | ||||||
|  |                 Tables\Actions\EditAction::make(), | ||||||
|  |             ]) | ||||||
|  |             ->headerActions([ | ||||||
|  |                 //
 | ||||||
|  |             ]) | ||||||
|  |             ->bulkActions([ | ||||||
|  |                 Tables\Actions\BulkActionGroup::make([ | ||||||
|  |                     Tables\Actions\DeleteBulkAction::make(), | ||||||
|  |                 ]), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function getRelations(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             //
 | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function getPages(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'index' => Pages\ListEggs::route('/'), | ||||||
|  |             'create' => Pages\CreateEgg::route('/create'), | ||||||
|  |             'edit' => Pages\EditEgg::route('/{record}/edit'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								app/Filament/Resources/EggResource/Pages/CreateEgg.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Filament/Resources/EggResource/Pages/CreateEgg.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\EggResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\EggResource; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | 
 | ||||||
|  | class CreateEgg extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = EggResource::class; | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/EggResource/Pages/EditEgg.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/EggResource/Pages/EditEgg.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\EggResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\EggResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\EditRecord; | ||||||
|  | 
 | ||||||
|  | class EditEgg extends EditRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = EggResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\DeleteAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								app/Filament/Resources/EggResource/Pages/ListEggs.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/Filament/Resources/EggResource/Pages/ListEggs.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\EggResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\EggResource; | ||||||
|  | use App\Services\Eggs\Sharing\EggImporterService; | ||||||
|  | use Exception; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Notifications\Notification; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; | ||||||
|  | 
 | ||||||
|  | class ListEggs extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = EggResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  | 
 | ||||||
|  |             Actions\Action::make('import') | ||||||
|  |                 ->label('Import Egg') | ||||||
|  |                 ->form([ | ||||||
|  |                     Forms\Components\FileUpload::make('egg') | ||||||
|  |                         ->acceptedFileTypes(['application/json']) | ||||||
|  |                         ->storeFiles(false), | ||||||
|  |                 ]) | ||||||
|  |                 ->action(function (array $data): void { | ||||||
|  |                     /** @var TemporaryUploadedFile $eggFile */ | ||||||
|  |                     $eggFile = $data['egg']; | ||||||
|  | 
 | ||||||
|  |                     /** @var EggImporterService $eggImportService */ | ||||||
|  |                     $eggImportService = resolve(EggImporterService::class); | ||||||
|  | 
 | ||||||
|  |                     try { | ||||||
|  |                         $newEgg = $eggImportService->handle($eggFile); | ||||||
|  |                     } catch (Exception $exception) { | ||||||
|  |                         Notification::make() | ||||||
|  |                             ->title('Egg Import Failed') | ||||||
|  |                             ->danger() | ||||||
|  |                             ->send(); | ||||||
|  | 
 | ||||||
|  |                         report($exception); | ||||||
|  | 
 | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     Notification::make() | ||||||
|  |                         ->title("Egg Import Success: $newEgg->name") | ||||||
|  |                         ->success() | ||||||
|  |                         ->send(); | ||||||
|  | 
 | ||||||
|  |                     redirect()->route('filament.admin.resources.eggs.edit', [$newEgg]); | ||||||
|  |                 }), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										154
									
								
								app/Filament/Resources/MountResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								app/Filament/Resources/MountResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,154 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\MountResource\Pages; | ||||||
|  | use App\Models\Mount; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Components\Group; | ||||||
|  | use Filament\Forms\Components\Section; | ||||||
|  | use Filament\Forms\Components\Select; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | 
 | ||||||
|  | class MountResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = Mount::class; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-layers-linked'; | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->schema([ | ||||||
|  |                 Section::make()->schema([ | ||||||
|  |                     Forms\Components\TextInput::make('name') | ||||||
|  |                         ->required() | ||||||
|  |                         ->helperText('Unique name used to separate this mount from another.') | ||||||
|  |                         ->maxLength(64), | ||||||
|  |                     Forms\Components\ToggleButtons::make('read_only') | ||||||
|  |                         ->label('Read only?') | ||||||
|  |                         ->helperText('Is the mount read only inside the container?') | ||||||
|  |                         ->options([ | ||||||
|  |                             false => 'Writeable', | ||||||
|  |                             true => 'Read only', | ||||||
|  |                         ]) | ||||||
|  |                         ->icons([ | ||||||
|  |                             false => 'tabler-writing', | ||||||
|  |                             true => 'tabler-writing-off', | ||||||
|  |                         ]) | ||||||
|  |                         ->colors([ | ||||||
|  |                             false => 'warning', | ||||||
|  |                             true => 'success', | ||||||
|  |                         ]) | ||||||
|  |                         ->inline() | ||||||
|  |                         ->default(false) | ||||||
|  |                         ->required(), | ||||||
|  |                     Forms\Components\TextInput::make('source') | ||||||
|  |                         ->required() | ||||||
|  |                         ->helperText('File path on the host system to mount to a container.') | ||||||
|  |                         ->maxLength(191), | ||||||
|  |                     Forms\Components\TextInput::make('target') | ||||||
|  |                         ->required() | ||||||
|  |                         ->helperText('Where the mount will be accessible inside a container.') | ||||||
|  |                         ->maxLength(191), | ||||||
|  |                     Forms\Components\ToggleButtons::make('user_mountable') | ||||||
|  |                         ->hidden() | ||||||
|  |                         ->label('User mountable?') | ||||||
|  |                         ->options([ | ||||||
|  |                             false => 'No', | ||||||
|  |                             true => 'Yes', | ||||||
|  |                         ]) | ||||||
|  |                         ->icons([ | ||||||
|  |                             false => 'tabler-user-cancel', | ||||||
|  |                             true => 'tabler-user-bolt', | ||||||
|  |                         ]) | ||||||
|  |                         ->colors([ | ||||||
|  |                             false => 'success', | ||||||
|  |                             true => 'warning', | ||||||
|  |                         ]) | ||||||
|  |                         ->default(false) | ||||||
|  |                         ->inline() | ||||||
|  |                         ->required(), | ||||||
|  |                     Forms\Components\Textarea::make('description') | ||||||
|  |                         ->helperText('A longer description for this mount.') | ||||||
|  |                         ->columnSpanFull(), | ||||||
|  |                 ])->columnSpan(1)->columns([ | ||||||
|  |                     'default' => 1, | ||||||
|  |                     'lg' => 2, | ||||||
|  |                 ]), | ||||||
|  |                 Group::make()->schema([ | ||||||
|  |                     Section::make()->schema([ | ||||||
|  |                         Select::make('eggs')->multiple() | ||||||
|  |                             ->relationship('eggs', 'name') | ||||||
|  |                             ->preload(), | ||||||
|  |                         Select::make('nodes')->multiple() | ||||||
|  |                             ->relationship('nodes', 'name') | ||||||
|  |                             ->searchable(['name', 'fqdn']) | ||||||
|  |                             ->preload(), | ||||||
|  |                     ]), | ||||||
|  |                 ])->columns([ | ||||||
|  |                     'default' => 1, | ||||||
|  |                     'lg' => 2, | ||||||
|  |                 ]), | ||||||
|  |             ])->columns([ | ||||||
|  |                 'default' => 1, | ||||||
|  |                 'lg' => 2, | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->searchable(false) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('id') | ||||||
|  |                     ->label('') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('name') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('source') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('target') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\IconColumn::make('read_only') | ||||||
|  |                     ->icon(fn (bool $state) => $state ? 'tabler-circle-check-filled' : 'tabler-circle-x-filled') | ||||||
|  |                     ->color(fn (bool $state) => $state ? 'success' : 'danger') | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\IconColumn::make('user_mountable') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->icon(fn (bool $state) => $state ? 'tabler-circle-check-filled' : 'tabler-circle-x-filled') | ||||||
|  |                     ->color(fn (bool $state) => $state ? 'success' : 'danger') | ||||||
|  |                     ->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\ListMounts::route('/'), | ||||||
|  |             'create' => Pages\CreateMount::route('/create'), | ||||||
|  |             'edit' => Pages\EditMount::route('/{record}/edit'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								app/Filament/Resources/MountResource/Pages/CreateMount.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Filament/Resources/MountResource/Pages/CreateMount.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\MountResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\MountResource; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | 
 | ||||||
|  | class CreateMount extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = MountResource::class; | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/EditMount.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/EditMount.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\MountResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\MountResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\EditRecord; | ||||||
|  | 
 | ||||||
|  | class EditMount extends EditRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = MountResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\DeleteAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/ListMounts.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/ListMounts.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\MountResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\MountResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | 
 | ||||||
|  | class ListMounts extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = MountResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										163
									
								
								app/Filament/Resources/NodeResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								app/Filament/Resources/NodeResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,163 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\NodeResource\Pages; | ||||||
|  | use App\Models\Node; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | 
 | ||||||
|  | class NodeResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = Node::class; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-server-2'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $recordTitleAttribute = 'name'; | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->schema([ | ||||||
|  |                 Forms\Components\Toggle::make('behind_proxy') | ||||||
|  |                     ->helperText('If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.') | ||||||
|  |                     ->required(), | ||||||
|  |                 Forms\Components\TextInput::make('memory') | ||||||
|  |                     ->required() | ||||||
|  |                     ->numeric(), | ||||||
|  |                 Forms\Components\TextInput::make('memory_overallocate') | ||||||
|  |                     ->required() | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->default(0), | ||||||
|  |                 Forms\Components\TextInput::make('disk') | ||||||
|  |                     ->required() | ||||||
|  |                     ->numeric(), | ||||||
|  |                 Forms\Components\TextInput::make('disk_overallocate') | ||||||
|  |                     ->required() | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->default(0), | ||||||
|  |                 Forms\Components\TextInput::make('upload_size') | ||||||
|  |                     ->required() | ||||||
|  |                     ->integer() | ||||||
|  |                     ->default(100), | ||||||
|  |                 Forms\Components\TextInput::make('daemonListen') | ||||||
|  |                     ->required() | ||||||
|  |                     ->integer() | ||||||
|  |                     ->label('Daemon Port') | ||||||
|  |                     ->default(8080), | ||||||
|  |                 Forms\Components\TextInput::make('daemonSFTP') | ||||||
|  |                     ->required() | ||||||
|  |                     ->integer() | ||||||
|  |                     ->label('Daemon SFTP Port') | ||||||
|  |                     ->default(2022), | ||||||
|  |                 Forms\Components\TextInput::make('daemonBase') | ||||||
|  |                     ->required() | ||||||
|  |                     ->maxLength(191) | ||||||
|  |                     ->default('/home/daemon-files'), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\ToggleButtons::make('public') | ||||||
|  |                     ->label('Node Visibility') | ||||||
|  |                     ->inline() | ||||||
|  |                     ->default(true) | ||||||
|  |                     ->helperText('By setting a node to private you will be denying the ability to auto-deploy to this node.') | ||||||
|  |                     ->options([ | ||||||
|  |                         true => 'Public', | ||||||
|  |                         false => 'Private', | ||||||
|  |                     ]) | ||||||
|  |                     ->colors([ | ||||||
|  |                         true => 'warning', | ||||||
|  |                         false => 'danger', | ||||||
|  |                     ]) | ||||||
|  |                     ->icons([ | ||||||
|  |                         true => 'tabler-eye-check', | ||||||
|  |                         false => 'tabler-eye-cancel', | ||||||
|  |                     ]), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->searchable(false) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('uuid') | ||||||
|  |                     ->label('UUID') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->hidden(), | ||||||
|  |                 Tables\Columns\IconColumn::make('health') | ||||||
|  |                     ->alignCenter() | ||||||
|  |                     ->state(fn (Node $node) => $node) | ||||||
|  |                     ->view('livewire.columns.version-column'), | ||||||
|  |                 Tables\Columns\TextColumn::make('name') | ||||||
|  |                     ->icon('tabler-server-2') | ||||||
|  |                     ->sortable() | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('fqdn') | ||||||
|  |                     ->visibleFrom('md') | ||||||
|  |                     ->label('Address') | ||||||
|  |                     ->icon('tabler-network') | ||||||
|  |                     ->sortable() | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('memory') | ||||||
|  |                     ->visibleFrom('sm') | ||||||
|  |                     ->icon('tabler-device-desktop-analytics') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->suffix(' GB') | ||||||
|  |                     ->formatStateUsing(fn ($state) => number_format($state / 1000, 2)) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('disk') | ||||||
|  |                     ->visibleFrom('sm') | ||||||
|  |                     ->icon('tabler-file') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->suffix(' GB') | ||||||
|  |                     ->formatStateUsing(fn ($state) => number_format($state / 1000, 2)) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\IconColumn::make('scheme') | ||||||
|  |                     ->visibleFrom('xl') | ||||||
|  |                     ->label('SSL') | ||||||
|  |                     ->trueIcon('tabler-lock') | ||||||
|  |                     ->falseIcon('tabler-lock-open-off') | ||||||
|  |                     ->state(fn (Node $node) => $node->scheme === 'https'), | ||||||
|  |                 Tables\Columns\IconColumn::make('public') | ||||||
|  |                     ->visibleFrom('lg') | ||||||
|  |                     ->trueIcon('tabler-eye-check') | ||||||
|  |                     ->falseIcon('tabler-eye-cancel'), | ||||||
|  |                 Tables\Columns\TextColumn::make('servers_count') | ||||||
|  |                     ->visibleFrom('sm') | ||||||
|  |                     ->counts('servers') | ||||||
|  |                     ->label('Servers') | ||||||
|  |                     ->sortable() | ||||||
|  |                     ->icon('tabler-brand-docker'), | ||||||
|  |             ]) | ||||||
|  |             ->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\ListNodes::route('/'), | ||||||
|  |             'create' => Pages\CreateNode::route('/create'), | ||||||
|  |             'edit' => Pages\EditNode::route('/{record}/edit'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										200
									
								
								app/Filament/Resources/NodeResource/Pages/CreateNode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								app/Filament/Resources/NodeResource/Pages/CreateNode.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,200 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\NodeResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\NodeResource; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | use Illuminate\Support\HtmlString; | ||||||
|  | 
 | ||||||
|  | class CreateNode extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = NodeResource::class; | ||||||
|  | 
 | ||||||
|  |     protected static bool $canCreateAnother = false; | ||||||
|  | 
 | ||||||
|  |     protected ?string $subheading = 'which is a machine that runs your Servers'; | ||||||
|  | 
 | ||||||
|  |     public function form(Forms\Form $form): Forms\Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->columns([ | ||||||
|  |                 'default' => 2, | ||||||
|  |                 'sm' => 3, | ||||||
|  |                 'md' => 3, | ||||||
|  |                 'lg' => 4, | ||||||
|  |             ]) | ||||||
|  |             ->schema([ | ||||||
|  |                 Forms\Components\TextInput::make('fqdn') | ||||||
|  |                     ->columnSpan(2) | ||||||
|  |                     ->required() | ||||||
|  |                     ->autofocus() | ||||||
|  |                     ->live(debounce: 1500) | ||||||
|  |                     ->rule('prohibited', fn ($state) => is_ip($state) && request()->isSecure()) | ||||||
|  |                     ->label(fn ($state) => is_ip($state) ? 'IP Address' : 'Domain Name') | ||||||
|  |                     ->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com') | ||||||
|  |                     ->helperText(function ($state) { | ||||||
|  |                         if (is_ip($state)) { | ||||||
|  |                             if (request()->isSecure()) { | ||||||
|  |                                 return ' | ||||||
|  |                                     Your panel is currently secured via an SSL certificate and that means your nodes require one too. | ||||||
|  |                                     You must use a domain name, because you cannot get SSL certificates for IP Addresses | ||||||
|  |                                 '; | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  |                             return ''; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         return " | ||||||
|  |                             This is the domain name that points to your node's IP Address. | ||||||
|  |                             If you've already set up this, you can verify it by checking the next field! | ||||||
|  |                         ";
 | ||||||
|  |                     }) | ||||||
|  |                     ->hintColor('danger') | ||||||
|  |                     ->hint(function ($state) { | ||||||
|  |                         if (is_ip($state) && request()->isSecure()) { | ||||||
|  |                             return 'You cannot connect to an IP Address over SSL'; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         return ''; | ||||||
|  |                     }) | ||||||
|  |                     ->afterStateUpdated(function (Forms\Set $set, ?string $state) { | ||||||
|  |                         $set('dns', null); | ||||||
|  |                         $set('ip', null); | ||||||
|  | 
 | ||||||
|  |                         [$subdomain] = str($state)->explode('.', 2); | ||||||
|  |                         if (!is_numeric($subdomain)) { | ||||||
|  |                             $set('name', $subdomain); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         if (!$state || is_ip($state)) { | ||||||
|  |                             $set('dns', null); | ||||||
|  | 
 | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $validRecords = gethostbynamel($state); | ||||||
|  |                         if ($validRecords) { | ||||||
|  |                             $set('dns', true); | ||||||
|  | 
 | ||||||
|  |                             $set('ip', collect($validRecords)->first()); | ||||||
|  | 
 | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $set('dns', false); | ||||||
|  |                     }) | ||||||
|  |                     ->maxLength(191), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\TextInput::make('ip') | ||||||
|  |                     ->disabled() | ||||||
|  |                     ->hidden(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\ToggleButtons::make('dns') | ||||||
|  |                     ->label('DNS Record Check') | ||||||
|  |                     ->helperText('This lets you know if your DNS record correctly points to an IP Address.') | ||||||
|  |                     ->disabled() | ||||||
|  |                     ->inline() | ||||||
|  |                     ->default(null) | ||||||
|  |                     ->hint(fn (Forms\Get $get) => $get('ip')) | ||||||
|  |                     ->hintColor('success') | ||||||
|  |                     ->options([ | ||||||
|  |                         true => 'Valid', | ||||||
|  |                         false => 'Invalid', | ||||||
|  |                     ]) | ||||||
|  |                     ->colors([ | ||||||
|  |                         true => 'success', | ||||||
|  |                         false => 'danger', | ||||||
|  |                     ]) | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 1, | ||||||
|  |                         'sm' => 1, | ||||||
|  |                         'md' => 1, | ||||||
|  |                         'lg' => 1, | ||||||
|  |                     ]), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\TextInput::make('daemonListen') | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 1, | ||||||
|  |                         'sm' => 1, | ||||||
|  |                         'md' => 1, | ||||||
|  |                         'lg' => 1, | ||||||
|  |                     ]) | ||||||
|  |                     ->label('Port') | ||||||
|  |                     ->helperText('If you are running the daemon behind Cloudflare you should set the daemon port to 8443 to allow websocket proxying over SSL.') | ||||||
|  |                     ->minValue(0) | ||||||
|  |                     ->maxValue(65536) | ||||||
|  |                     ->default(8080) | ||||||
|  |                     ->required() | ||||||
|  |                     ->integer(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\TextInput::make('name') | ||||||
|  |                     ->label('Display Name') | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 1, | ||||||
|  |                         'sm' => 1, | ||||||
|  |                         'md' => 1, | ||||||
|  |                         'lg' => 2, | ||||||
|  |                     ]) | ||||||
|  |                     ->required() | ||||||
|  |                     ->regex('/[a-zA-Z0-9_\.\- ]+/') | ||||||
|  |                     ->helperText('This name is for display only and can be changed later.') | ||||||
|  |                     ->maxLength(100), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\ToggleButtons::make('scheme') | ||||||
|  |                     ->label('Communicate over SSL') | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 1, | ||||||
|  |                         'sm' => 1, | ||||||
|  |                         'md' => 1, | ||||||
|  |                         'lg' => 1, | ||||||
|  |                     ]) | ||||||
|  |                     ->required() | ||||||
|  |                     ->inline() | ||||||
|  |                     ->helperText(function (Forms\Get $get) { | ||||||
|  |                         if (request()->isSecure()) { | ||||||
|  |                             return new HtmlString('Your Panel is using a secure SSL connection,<br>so your Daemon must too.'); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         if (is_ip($get('fqdn'))) { | ||||||
|  |                             return 'An IP address cannot use SSL.'; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         return ''; | ||||||
|  |                     }) | ||||||
|  |                     ->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure()) | ||||||
|  |                     ->options([ | ||||||
|  |                         'http' => 'HTTP', | ||||||
|  |                         'https' => 'HTTPS (SSL)', | ||||||
|  |                     ]) | ||||||
|  |                     ->colors([ | ||||||
|  |                         'http' => 'warning', | ||||||
|  |                         'https' => 'success', | ||||||
|  |                     ]) | ||||||
|  |                     ->icons([ | ||||||
|  |                         'http' => 'tabler-lock-open-off', | ||||||
|  |                         'https' => 'tabler-lock', | ||||||
|  |                     ]) | ||||||
|  |                     ->default(fn () => request()->isSecure() ? 'https' : 'http'), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Textarea::make('description') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 1, | ||||||
|  |                         'sm' => 1, | ||||||
|  |                         'md' => 2, | ||||||
|  |                         'lg' => 4, | ||||||
|  |                     ]) | ||||||
|  |                     ->rows(5), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Hidden::make('skipValidation')->default(true), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function getRedirectUrlParameters(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'tab' => '-configuration-tab', | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										130
									
								
								app/Filament/Resources/NodeResource/Pages/EditNode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								app/Filament/Resources/NodeResource/Pages/EditNode.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\NodeResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\NodeResource; | ||||||
|  | use App\Models\Allocation; | ||||||
|  | use App\Models\Node; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Components\Tabs; | ||||||
|  | use Filament\Resources\Pages\EditRecord; | ||||||
|  | use Illuminate\Support\HtmlString; | ||||||
|  | use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; | ||||||
|  | 
 | ||||||
|  | class EditNode extends EditRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = NodeResource::class; | ||||||
|  | 
 | ||||||
|  |     public function form(Forms\Form $form): Forms\Form | ||||||
|  |     { | ||||||
|  |         return $form->schema([ | ||||||
|  |             Tabs::make('Tabs') | ||||||
|  |                 ->columns([ | ||||||
|  |                     'default' => 2, | ||||||
|  |                     'sm' => 3, | ||||||
|  |                     'md' => 3, | ||||||
|  |                     'lg' => 4, | ||||||
|  |                 ]) | ||||||
|  |                 ->persistTabInQueryString() | ||||||
|  |                 ->columnSpanFull() | ||||||
|  |                 ->tabs([ | ||||||
|  |                     Tabs\Tab::make('Basic Settings') | ||||||
|  |                         ->icon('tabler-server') | ||||||
|  |                         ->schema((new CreateNode())->form($form)->getComponents()), | ||||||
|  |                     Tabs\Tab::make('Advanced Settings') | ||||||
|  |                         ->icon('tabler-server-cog'), | ||||||
|  |                     Tabs\Tab::make('Configuration') | ||||||
|  |                         ->icon('tabler-code') | ||||||
|  |                         ->schema([ | ||||||
|  |                             Forms\Components\Placeholder::make('instructions') | ||||||
|  |                                 ->columnSpanFull() | ||||||
|  |                                 ->content(new HtmlString(' | ||||||
|  |                                   Save this file to your <span title="usually /etc/pelican/">daemon\'s root directory</span>, named <code>config.yml</code> | ||||||
|  |                             ')), | ||||||
|  |                             Forms\Components\Textarea::make('config') | ||||||
|  |                                 ->label('/etc/pelican/config.yml') | ||||||
|  |                                 ->disabled() | ||||||
|  |                                 ->rows(19) | ||||||
|  |                                 ->hintAction(CopyAction::make()) | ||||||
|  |                                 ->columnSpanFull(), | ||||||
|  |                         ]), | ||||||
|  |                     Tabs\Tab::make('Allocations') | ||||||
|  |                         ->icon('tabler-plug-connected') | ||||||
|  |                         ->columns(4) | ||||||
|  |                         ->schema([ | ||||||
|  |                             Forms\Components\Repeater::make('allocations') | ||||||
|  |                                 ->orderColumn('server_id') | ||||||
|  |                                 ->columnSpan(1) | ||||||
|  |                                 ->columns(4) | ||||||
|  |                                 ->relationship() | ||||||
|  |                                 ->addActionLabel('Create New Allocation') | ||||||
|  |                                 ->addAction(fn ($action) => $action->color('info')) | ||||||
|  |                                 ->schema([ | ||||||
|  |                                     Forms\Components\TextInput::make('ip') | ||||||
|  |                                         ->label('IP Address'), | ||||||
|  |                                     Forms\Components\TextInput::make('ip_alias') | ||||||
|  |                                         ->label('Alias'), | ||||||
|  |                                     Forms\Components\TextInput::make('port') | ||||||
|  |                                         ->minValue(0) | ||||||
|  |                                         ->maxValue(65535) | ||||||
|  |                                         ->numeric(), | ||||||
|  |                                     Forms\Components\TextInput::make('server') | ||||||
|  |                                         ->formatStateUsing(fn (Allocation $allocation) => $allocation->server?->name) | ||||||
|  |                                         ->readOnly() | ||||||
|  |                                         ->placeholder('No Server'), | ||||||
|  |                                 ]), | ||||||
|  |                             Forms\Components\Section::make('Assign New Allocations') | ||||||
|  |                                 ->columnSpan(2) | ||||||
|  |                                 ->inlineLabel() | ||||||
|  |                                 ->headerActions([ | ||||||
|  |                                     Forms\Components\Actions\Action::make('submit') | ||||||
|  |                                         ->color('success') | ||||||
|  |                                         ->action(function () { | ||||||
|  |                                             // ...
 | ||||||
|  |                                         }), | ||||||
|  |                                 ]) | ||||||
|  |                                 ->schema([ | ||||||
|  |                                     Forms\Components\TextInput::make('ip') | ||||||
|  |                                         ->label('IP Address') | ||||||
|  |                                         ->placeholder('0.0.0.0') | ||||||
|  |                                         ->helperText('IP address to assign ports to') | ||||||
|  |                                         ->columnSpanFull(), | ||||||
|  |                                     Forms\Components\TextInput::make('ip_alias') | ||||||
|  |                                         ->label('Alias') | ||||||
|  |                                         ->placeholder('minecraft') | ||||||
|  |                                         ->helperText('Display name to help you remember') | ||||||
|  |                                         ->columnSpanFull(), | ||||||
|  |                                     Forms\Components\TextInput::make('ports') | ||||||
|  |                                         ->label('Ports') | ||||||
|  |                                         ->placeholder('25565') | ||||||
|  |                                         ->helperText('Individual ports or port ranges here separated by commas or spaces') | ||||||
|  |                                         ->columnSpanFull(), | ||||||
|  |                                 ]), | ||||||
|  |                         ]), | ||||||
|  |                 ]), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function mutateFormDataBeforeFill(array $data): array | ||||||
|  |     { | ||||||
|  |         $node = Node::findOrFail($data['id']); | ||||||
|  | 
 | ||||||
|  |         $data['config'] = $node->getYamlConfiguration(); | ||||||
|  | 
 | ||||||
|  |         return $data; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function getSteps(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\DeleteAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/NodeResource/Pages/ListNodes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/NodeResource/Pages/ListNodes.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\NodeResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\NodeResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | 
 | ||||||
|  | class ListNodes extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = NodeResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										661
									
								
								app/Filament/Resources/ServerResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										661
									
								
								app/Filament/Resources/ServerResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,661 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Enums\ContainerStatus; | ||||||
|  | use App\Enums\ServerState; | ||||||
|  | use App\Filament\Resources\ServerResource\Pages; | ||||||
|  | use App\Models\Allocation; | ||||||
|  | use App\Models\Egg; | ||||||
|  | use App\Models\Node; | ||||||
|  | use App\Models\Server; | ||||||
|  | use App\Repositories\Daemon\DaemonServerRepository; | ||||||
|  | use App\Services\Allocations\AssignmentService; | ||||||
|  | use Closure; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | use Illuminate\Database\Eloquent\Builder; | ||||||
|  | use Illuminate\Support\Arr; | ||||||
|  | use Illuminate\Support\Facades\Validator; | ||||||
|  | 
 | ||||||
|  | class ServerResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = Server::class; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-brand-docker'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $recordTitleAttribute = 'name'; | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->columns([ | ||||||
|  |                 'default' => 2, | ||||||
|  |                 'sm' => 2, | ||||||
|  |                 'md' => 4, | ||||||
|  |                 'lg' => 6, | ||||||
|  |             ]) | ||||||
|  |             ->schema([ | ||||||
|  |                 Forms\Components\ToggleButtons::make('docker') | ||||||
|  |                     ->label('Container Status') | ||||||
|  |                     ->hiddenOn('create') | ||||||
|  |                     ->disableOptionWhen(fn ($state, $value) => $state !== $value) | ||||||
|  |                     ->formatStateUsing(function ($state, Server $server) { | ||||||
|  |                         if ($server->node_id === null) { | ||||||
|  |                             return 'unknown'; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         /** @var DaemonServerRepository $service */ | ||||||
|  |                         $service = resolve(DaemonServerRepository::class); | ||||||
|  |                         $details = $service->setServer($server)->getDetails(); | ||||||
|  | 
 | ||||||
|  |                         return $details['state'] ?? 'unknown'; | ||||||
|  |                     }) | ||||||
|  |                     ->options(collect(ContainerStatus::cases())->mapWithKeys( | ||||||
|  |                         fn (ContainerStatus $status) => [$status->value => str($status->value)->ucwords()] | ||||||
|  |                     )) | ||||||
|  |                     ->colors(collect(ContainerStatus::cases())->mapWithKeys( | ||||||
|  |                         fn (ContainerStatus $status) => [$status->value => $status->color()] | ||||||
|  |                     )) | ||||||
|  |                     ->icons(collect(ContainerStatus::cases())->mapWithKeys( | ||||||
|  |                         fn (ContainerStatus $status) => [$status->value => $status->icon()] | ||||||
|  |                     )) | ||||||
|  |                     ->grouped() | ||||||
|  |                     ->columnSpanFull() | ||||||
|  |                     ->inline(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\ToggleButtons::make('status') | ||||||
|  |                     ->label('Server State') | ||||||
|  |                     ->helperText('') | ||||||
|  |                     ->hiddenOn('create') | ||||||
|  |                     ->disableOptionWhen(fn ($state, $value) => $state !== $value) | ||||||
|  |                     ->formatStateUsing(fn ($state) => $state ?? 'none') | ||||||
|  |                     ->options(collect(ServerState::cases())->mapWithKeys( | ||||||
|  |                         fn (ServerState $state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()] | ||||||
|  |                     )) | ||||||
|  |                     ->colors(collect(ServerState::cases())->mapWithKeys( | ||||||
|  |                         fn (ServerState $state) => [$state->value => $state->color()] | ||||||
|  |                     )) | ||||||
|  |                     ->icons(collect(ServerState::cases())->mapWithKeys( | ||||||
|  |                         fn (ServerState $state) => [$state->value => $state->icon()] | ||||||
|  |                     )) | ||||||
|  |                     ->grouped() | ||||||
|  |                     ->columnSpanFull() | ||||||
|  |                     ->inline(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\TextInput::make('external_id') | ||||||
|  |                     ->maxLength(191) | ||||||
|  |                     ->hidden(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\TextInput::make('name') | ||||||
|  |                     ->prefixIcon('tabler-server') | ||||||
|  |                     ->label('Display Name') | ||||||
|  |                     ->suffixAction(Forms\Components\Actions\Action::make('random') | ||||||
|  |                         ->icon('tabler-dice-' . random_int(1, 6)) | ||||||
|  |                         ->color('primary') | ||||||
|  |                         ->action(function (Forms\Set $set, Forms\Get $get) { | ||||||
|  |                             $egg = Egg::find($get('egg_id')); | ||||||
|  |                             $prefix = $egg ? str($egg->name)->lower()->kebab() . '-' : ''; | ||||||
|  | 
 | ||||||
|  |                             $set('name', $prefix . fake()->domainWord); | ||||||
|  |                         })) | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 4, | ||||||
|  |                         'md' => 2, | ||||||
|  |                         'lg' => 3, | ||||||
|  |                     ]) | ||||||
|  |                     ->required() | ||||||
|  |                     ->maxLength(191), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Select::make('owner_id') | ||||||
|  |                     ->prefixIcon('tabler-user') | ||||||
|  |                     ->default(auth()->user()->id) | ||||||
|  |                     ->label('Owner') | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 4, | ||||||
|  |                         'md' => 2, | ||||||
|  |                         'lg' => 3, | ||||||
|  |                     ]) | ||||||
|  |                     ->relationship('user', 'username') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->preload() | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Select::make('node_id') | ||||||
|  |                     ->disabledOn('edit') | ||||||
|  |                     ->prefixIcon('tabler-server-2') | ||||||
|  |                     ->default(fn () => Node::query()->latest()->first()?->id) | ||||||
|  |                     ->columnSpan(2) | ||||||
|  |                     ->live() | ||||||
|  |                     ->relationship('node', 'name') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->preload() | ||||||
|  |                     ->afterStateUpdated(fn (Forms\Set $set) => $set('allocation_id', null)) | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Select::make('allocation_id') | ||||||
|  |                     ->preload() | ||||||
|  |                     ->live() | ||||||
|  |                     ->prefixIcon('tabler-network') | ||||||
|  |                     ->label('Primary Allocation') | ||||||
|  |                     ->columnSpan(2) | ||||||
|  |                     ->disabled(fn (Forms\Get $get) => $get('node_id') === null) | ||||||
|  |                     ->searchable(['ip', 'port', 'ip_alias']) | ||||||
|  |                     ->afterStateUpdated(function (Forms\Set $set) { | ||||||
|  |                         $set('allocation_additional', null); | ||||||
|  |                         $set('allocation_additional.needstobeastringhere.extra_allocations', null); | ||||||
|  |                     }) | ||||||
|  |                     ->getOptionLabelFromRecordUsing( | ||||||
|  |                         fn (Allocation $allocation) => "$allocation->ip:$allocation->port" . | ||||||
|  |                             ($allocation->ip_alias ? " ($allocation->ip_alias)" : '') | ||||||
|  |                     ) | ||||||
|  |                     ->placeholder(function (Forms\Get $get) { | ||||||
|  |                         $node = Node::find($get('node_id')); | ||||||
|  | 
 | ||||||
|  |                         if ($node?->allocations) { | ||||||
|  |                             return 'Select an Allocation'; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         return 'Create a New Allocation'; | ||||||
|  |                     }) | ||||||
|  |                     ->relationship( | ||||||
|  |                         'allocation', | ||||||
|  |                         'ip', | ||||||
|  |                         fn (Builder $query, Forms\Get $get) => $query | ||||||
|  |                             ->where('node_id', $get('node_id')) | ||||||
|  |                             ->whereNull('server_id'), | ||||||
|  |                     ) | ||||||
|  |                     ->createOptionForm(fn (Forms\Get $get) => [ | ||||||
|  |                         Forms\Components\TextInput::make('allocation_ip') | ||||||
|  |                             ->ipv4() | ||||||
|  |                             ->datalist(function () use ($get) { | ||||||
|  |                                 $node = Node::find($get('node_id')); | ||||||
|  |                                 if (is_ip($node->fqdn)) { | ||||||
|  |                                     return [$node->fqdn]; | ||||||
|  |                                 } | ||||||
|  | 
 | ||||||
|  |                                 $validRecords = gethostbynamel($node->fqdn); | ||||||
|  |                                 if (!$validRecords) { | ||||||
|  |                                     return []; | ||||||
|  |                                 } | ||||||
|  | 
 | ||||||
|  |                                 return $validRecords ?: []; | ||||||
|  |                             }) | ||||||
|  |                             ->label('IP Address') | ||||||
|  |                             ->helperText("Usually your machine's public IP unless you are port forwarding.") | ||||||
|  |                             ->required(), | ||||||
|  |                         Forms\Components\TextInput::make('allocation_alias') | ||||||
|  |                             ->label('Alias') | ||||||
|  |                             ->default(null) | ||||||
|  |                             ->datalist([ | ||||||
|  |                                 $get('name'), | ||||||
|  |                                 Egg::find($get('egg_id'))?->name, | ||||||
|  |                             ]) | ||||||
|  |                             ->helperText('This is just a display only name to help you recognize what this Allocation is used for.') | ||||||
|  |                             ->required(false), | ||||||
|  |                         Forms\Components\TagsInput::make('allocation_ports') | ||||||
|  |                             ->placeholder('Examples: 27015, 27017-27019') | ||||||
|  |                             ->helperText(' | ||||||
|  |                                 These are the ports that users can connect to this Server through. | ||||||
|  |                                 They usually consist of the port forwarded ones. | ||||||
|  |                             ') | ||||||
|  |                             ->label('Ports') | ||||||
|  |                             ->required(), | ||||||
|  |                     ]) | ||||||
|  |                     ->createOptionUsing(function (array $data, Forms\Get $get): int { | ||||||
|  |                         return collect( | ||||||
|  |                             resolve(AssignmentService::class)->handle(Node::find($get('node_id')), $data) | ||||||
|  |                         )->first(); | ||||||
|  |                     }) | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Repeater::make('allocation_additional') | ||||||
|  |                     ->label('Additional Allocations') | ||||||
|  |                     ->columnSpan(2) | ||||||
|  |                     ->addActionLabel('Add Allocation') | ||||||
|  |                     ->disabled(fn (Forms\Get $get) => $get('allocation_id') === null) | ||||||
|  |                     // ->addable() TODO disable when all allocations are taken
 | ||||||
|  |                     // ->addable() TODO disable until first additional allocation is selected
 | ||||||
|  |                     ->simple( | ||||||
|  |                         Forms\Components\Select::make('extra_allocations') | ||||||
|  |                             ->live() | ||||||
|  |                             ->preload() | ||||||
|  |                             ->disableOptionsWhenSelectedInSiblingRepeaterItems() | ||||||
|  |                             ->prefixIcon('tabler-network') | ||||||
|  |                             ->label('Additional Allocations') | ||||||
|  |                             ->columnSpan(2) | ||||||
|  |                             ->disabled(fn (Forms\Get $get) => $get('../../node_id') === null) | ||||||
|  |                             ->searchable(['ip', 'port', 'ip_alias']) | ||||||
|  |                             ->getOptionLabelFromRecordUsing( | ||||||
|  |                                 fn (Allocation $allocation) => "$allocation->ip:$allocation->port" . | ||||||
|  |                                     ($allocation->ip_alias ? " ($allocation->ip_alias)" : '') | ||||||
|  |                             ) | ||||||
|  |                             ->placeholder('Select additional Allocations') | ||||||
|  |                             ->relationship( | ||||||
|  |                                 'allocations', | ||||||
|  |                                 'ip', | ||||||
|  |                                 fn (Builder $query, Forms\Get $get, Forms\Components\Select $component, $state) => $query | ||||||
|  |                                     ->where('node_id', $get('../../node_id')) | ||||||
|  |                                     ->whereNotIn( | ||||||
|  |                                         'id', | ||||||
|  |                                         collect(($repeater = $component->getParentRepeater())->getState()) | ||||||
|  |                                             ->pluck( | ||||||
|  |                                                 (string) str($component->getStatePath()) | ||||||
|  |                                                     ->after("{$repeater->getStatePath()}.") | ||||||
|  |                                                     ->after('.'), | ||||||
|  |                                             ) | ||||||
|  |                                             ->flatten() | ||||||
|  |                                             ->diff(Arr::wrap($state)) | ||||||
|  |                                             ->filter(fn (mixed $siblingItemState): bool => filled($siblingItemState)) | ||||||
|  |                                             ->add($get('../../allocation_id')) | ||||||
|  |                                     ) | ||||||
|  |                                     ->whereNull('server_id'), | ||||||
|  |                             ), | ||||||
|  |                     ), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Textarea::make('description') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->default('') | ||||||
|  |                     ->required() | ||||||
|  |                     ->columnSpanFull(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Select::make('egg_id') | ||||||
|  |                     ->disabledOn('edit') | ||||||
|  |                     ->prefixIcon('tabler-egg') | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 2, | ||||||
|  |                         'md' => 2, | ||||||
|  |                         'lg' => 6, | ||||||
|  |                     ]) | ||||||
|  |                     ->relationship('egg', 'name') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->preload() | ||||||
|  |                     ->live() | ||||||
|  |                     ->afterStateUpdated(function ($state, Forms\Set $set) { | ||||||
|  |                         $egg = Egg::find($state); | ||||||
|  |                         $set('startup', $egg->startup); | ||||||
|  | 
 | ||||||
|  |                         $variables = $egg->variables ?? []; | ||||||
|  |                         $serverVariables = collect(); | ||||||
|  |                         foreach ($variables as $variable) { | ||||||
|  |                             $serverVariables->add($variable->toArray()); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $variables = []; | ||||||
|  |                         $set($path = 'server_variables', $serverVariables->all()); | ||||||
|  |                         for ($i = 0; $i < $serverVariables->count(); $i++) { | ||||||
|  |                             $set("$path.$i.variable_value", $serverVariables[$i]['default_value']); | ||||||
|  |                             $set("$path.$i.variable_id", $serverVariables[$i]['id']); | ||||||
|  |                             $variables[$serverVariables[$i]['env_variable']] = $serverVariables[$i]['default_value']; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $set('environment', $variables); | ||||||
|  |                     }) | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\ToggleButtons::make('skip_scripts') | ||||||
|  |                     ->label('Run Egg Install Script?') | ||||||
|  |                     ->default(false) | ||||||
|  |                     ->options([ | ||||||
|  |                         false => 'Yes', | ||||||
|  |                         true => 'Skip', | ||||||
|  |                     ]) | ||||||
|  |                     ->colors([ | ||||||
|  |                         false => 'primary', | ||||||
|  |                         true => 'danger', | ||||||
|  |                     ]) | ||||||
|  |                     ->icons([ | ||||||
|  |                         false => 'tabler-code', | ||||||
|  |                         true => 'tabler-code-off', | ||||||
|  |                     ]) | ||||||
|  |                     ->inline() | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\ToggleButtons::make('custom_image') | ||||||
|  |                     ->live() | ||||||
|  |                     ->label('Custom Image?') | ||||||
|  |                     ->default(false) | ||||||
|  |                     ->formatStateUsing(function ($state, Forms\Get $get) { | ||||||
|  |                         if ($state !== null) { | ||||||
|  |                             return $state; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $images = Egg::find($get('egg_id'))->docker_images ?? []; | ||||||
|  | 
 | ||||||
|  |                         return !in_array($get('image'), $images); | ||||||
|  |                     }) | ||||||
|  |                     ->options([ | ||||||
|  |                         false => 'No', | ||||||
|  |                         true => 'Yes', | ||||||
|  |                     ]) | ||||||
|  |                     ->colors([ | ||||||
|  |                         false => 'primary', | ||||||
|  |                         true => 'danger', | ||||||
|  |                     ]) | ||||||
|  |                     ->icons([ | ||||||
|  |                         false => 'tabler-settings-cancel', | ||||||
|  |                         true => 'tabler-settings-check', | ||||||
|  |                     ]) | ||||||
|  |                     ->inline(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\TextInput::make('image') | ||||||
|  |                     ->hidden(fn (Forms\Get $get) => !$get('custom_image')) | ||||||
|  |                     ->disabled(fn (Forms\Get $get) => !$get('custom_image')) | ||||||
|  |                     ->label('Docker Image') | ||||||
|  |                     ->placeholder('Enter a custom Image') | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 2, | ||||||
|  |                         'md' => 2, | ||||||
|  |                         'lg' => 4, | ||||||
|  |                     ]) | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Select::make('image') | ||||||
|  |                     ->hidden(fn (Forms\Get $get) => $get('custom_image')) | ||||||
|  |                     ->disabled(fn (Forms\Get $get) => $get('custom_image')) | ||||||
|  |                     ->label('Docker Image') | ||||||
|  |                     ->prefixIcon('tabler-brand-docker') | ||||||
|  |                     ->options(function (Forms\Get $get, Forms\Set $set) { | ||||||
|  |                         $images = Egg::find($get('egg_id'))->docker_images ?? []; | ||||||
|  | 
 | ||||||
|  |                         $set('image', collect($images)->first()); | ||||||
|  | 
 | ||||||
|  |                         return $images; | ||||||
|  |                     }) | ||||||
|  |                     ->disabled(fn (Forms\Components\Select $component) => empty($component->getOptions())) | ||||||
|  |                     ->selectablePlaceholder(false) | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 2, | ||||||
|  |                         'md' => 2, | ||||||
|  |                         'lg' => 4, | ||||||
|  |                     ]) | ||||||
|  |                     ->required(), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Fieldset::make('Application Feature Limits') | ||||||
|  |                     ->inlineLabel() | ||||||
|  |                     ->hiddenOn('create') | ||||||
|  |                     ->columns(3) | ||||||
|  |                     ->schema([ | ||||||
|  |                         Forms\Components\TextInput::make('allocation_limit') | ||||||
|  |                             ->suffixIcon('tabler-network') | ||||||
|  |                             ->required() | ||||||
|  |                             ->numeric() | ||||||
|  |                             ->default(0), | ||||||
|  |                         Forms\Components\TextInput::make('database_limit') | ||||||
|  |                             ->suffixIcon('tabler-database') | ||||||
|  |                             ->required() | ||||||
|  |                             ->numeric() | ||||||
|  |                             ->default(0), | ||||||
|  |                         Forms\Components\TextInput::make('backup_limit') | ||||||
|  |                             ->suffixIcon('tabler-copy-check') | ||||||
|  |                             ->required() | ||||||
|  |                             ->numeric() | ||||||
|  |                             ->default(0), | ||||||
|  |                     ]), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Textarea::make('startup') | ||||||
|  |                     ->hintIcon('tabler-code') | ||||||
|  |                     ->label('Startup Command') | ||||||
|  |                     ->required() | ||||||
|  |                     ->live() | ||||||
|  |                     ->columnSpan([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 4, | ||||||
|  |                         'md' => 4, | ||||||
|  |                         'lg' => 6, | ||||||
|  |                     ]) | ||||||
|  |                     ->rows(function ($state) { | ||||||
|  |                         return str($state)->explode("\n")->reduce( | ||||||
|  |                             fn (int $carry, $line) => $carry + floor(strlen($line) / 125), | ||||||
|  |                             0 | ||||||
|  |                         ); | ||||||
|  |                     }), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Hidden::make('environment')->default([]), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Hidden::make('start_on_completion')->default(true), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Section::make('Egg Variables') | ||||||
|  |                     ->icon('tabler-eggs') | ||||||
|  |                     ->iconColor('primary') | ||||||
|  |                     ->collapsible() | ||||||
|  |                     ->collapsed() | ||||||
|  |                     ->columnSpan(([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 4, | ||||||
|  |                         'md' => 4, | ||||||
|  |                         'lg' => 6, | ||||||
|  |                     ])) | ||||||
|  |                     ->schema([ | ||||||
|  |                         Forms\Components\Placeholder::make('Select an egg first to show its variables!') | ||||||
|  |                             ->hidden(fn (Forms\Get $get) => !empty($get('server_variables'))), | ||||||
|  | 
 | ||||||
|  |                         Forms\Components\Repeater::make('server_variables') | ||||||
|  |                             ->relationship('serverVariables') | ||||||
|  |                             ->grid(2) | ||||||
|  |                             ->reorderable(false) | ||||||
|  |                             ->addable(false) | ||||||
|  |                             ->deletable(false) | ||||||
|  |                             ->default([]) | ||||||
|  |                             ->hidden(fn ($state) => empty($state)) | ||||||
|  |                             ->schema([ | ||||||
|  |                                 Forms\Components\TextInput::make('variable_value') | ||||||
|  |                                     ->rules([ | ||||||
|  |                                         fn (Forms\Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) { | ||||||
|  |                                             $validator = Validator::make(['validatorkey' => $value], [ | ||||||
|  |                                                 'validatorkey' => $get('rules'), | ||||||
|  |                                             ]); | ||||||
|  | 
 | ||||||
|  |                                             if ($validator->fails()) { | ||||||
|  |                                                 $message = str($validator->errors()->first())->replace('validatorkey', $get('name')); | ||||||
|  | 
 | ||||||
|  |                                                 $fail($message); | ||||||
|  |                                             } | ||||||
|  |                                         }, | ||||||
|  |                                     ]) | ||||||
|  |                                     ->label(fn (Forms\Get $get) => $get('name')) | ||||||
|  |                                     //->hint('Rule')
 | ||||||
|  |                                     ->hintIcon('tabler-code') | ||||||
|  |                                     ->hintIconTooltip(fn (Forms\Get $get) => $get('rules')) | ||||||
|  |                                     ->prefix(fn (Forms\Get $get) => '{{' . $get('env_variable') . '}}') | ||||||
|  |                                     ->helperText(fn (Forms\Get $get) => empty($get('description')) ? '—' : $get('description')) | ||||||
|  |                                     ->maxLength(191), | ||||||
|  | 
 | ||||||
|  |                                 Forms\Components\Hidden::make('variable_id')->default(0), | ||||||
|  |                             ]) | ||||||
|  |                             ->columnSpan(2), | ||||||
|  |                     ]), | ||||||
|  | 
 | ||||||
|  |                 Forms\Components\Section::make('Resource Management') | ||||||
|  |                     // ->hiddenOn('create')
 | ||||||
|  |                     ->collapsed() | ||||||
|  |                     ->icon('tabler-server-cog') | ||||||
|  |                     ->iconColor('primary') | ||||||
|  |                     ->columns(2) | ||||||
|  |                     ->columnSpan(([ | ||||||
|  |                         'default' => 2, | ||||||
|  |                         'sm' => 4, | ||||||
|  |                         'md' => 4, | ||||||
|  |                         'lg' => 6, | ||||||
|  |                     ])) | ||||||
|  |                     ->schema([ | ||||||
|  |                         Forms\Components\TextInput::make('memory') | ||||||
|  |                             ->default(0) | ||||||
|  |                             ->label('Allocated Memory') | ||||||
|  |                             ->suffix('MB') | ||||||
|  |                             ->required() | ||||||
|  |                             ->numeric(), | ||||||
|  | 
 | ||||||
|  |                         Forms\Components\TextInput::make('swap') | ||||||
|  |                             ->default(0) | ||||||
|  |                             ->label('Swap Memory') | ||||||
|  |                             ->suffix('MB') | ||||||
|  |                             ->helperText('0 disables swap and -1 allows unlimited swap') | ||||||
|  |                             ->minValue(-1) | ||||||
|  |                             ->required() | ||||||
|  |                             ->numeric(), | ||||||
|  | 
 | ||||||
|  |                         Forms\Components\TextInput::make('disk') | ||||||
|  |                             ->default(0) | ||||||
|  |                             ->label('Disk Space Limit') | ||||||
|  |                             ->suffix('MB') | ||||||
|  |                             ->required() | ||||||
|  |                             ->numeric(), | ||||||
|  | 
 | ||||||
|  |                         Forms\Components\TextInput::make('cpu') | ||||||
|  |                             ->default(0) | ||||||
|  |                             ->label('CPU Limit') | ||||||
|  |                             ->suffix('%') | ||||||
|  |                             ->required() | ||||||
|  |                             ->numeric(), | ||||||
|  | 
 | ||||||
|  |                         Forms\Components\TextInput::make('threads') | ||||||
|  |                             ->hint('Advanced') | ||||||
|  |                             ->hintColor('danger') | ||||||
|  |                             ->helperText('Examples: 0, 0-1,3, or 0,1,3,4') | ||||||
|  |                             ->label('CPU Pinning') | ||||||
|  |                             ->suffixIcon('tabler-cpu') | ||||||
|  |                             ->maxLength(191), | ||||||
|  | 
 | ||||||
|  |                         Forms\Components\TextInput::make('io') | ||||||
|  |                             ->helperText('The IO performance relative to other running containers') | ||||||
|  |                             ->label('Block IO Proportion') | ||||||
|  |                             ->required() | ||||||
|  |                             ->minValue(0) | ||||||
|  |                             ->maxValue(1000) | ||||||
|  |                             ->step(10) | ||||||
|  |                             ->default(0) | ||||||
|  |                             ->numeric(), | ||||||
|  | 
 | ||||||
|  |                         Forms\Components\ToggleButtons::make('oom_disabled') | ||||||
|  |                             ->label('OOM Killer') | ||||||
|  |                             ->inline() | ||||||
|  |                             ->default(false) | ||||||
|  |                             ->options([ | ||||||
|  |                                 false => 'Disabled', | ||||||
|  |                                 true => 'Enabled', | ||||||
|  |                             ]) | ||||||
|  |                             ->colors([ | ||||||
|  |                                 false => 'success', | ||||||
|  |                                 true => 'danger', | ||||||
|  |                             ]) | ||||||
|  |                             ->icons([ | ||||||
|  |                                 false => 'tabler-sword-off', | ||||||
|  |                                 true => 'tabler-sword', | ||||||
|  |                             ]) | ||||||
|  |                             ->required(), | ||||||
|  |                     ]), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->searchable(false) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('status') | ||||||
|  |                     ->default('unknown') | ||||||
|  |                     ->badge() | ||||||
|  |                     ->default(function (Server $server) { | ||||||
|  |                         if ($server->status !== null) { | ||||||
|  |                             return $server->status; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $statuses = collect($server->retrieveStatus()) | ||||||
|  |                             ->mapWithKeys(function ($status) { | ||||||
|  |                                 return [$status['configuration']['uuid'] => $status['state']]; | ||||||
|  |                             })->all(); | ||||||
|  | 
 | ||||||
|  |                         return $statuses[$server->uuid] ?? 'node_fail'; | ||||||
|  |                     }) | ||||||
|  |                     ->icon(fn ($state) => match ($state) { | ||||||
|  |                         'node_fail' => 'tabler-server-off', | ||||||
|  |                         'running' => 'tabler-heartbeat', | ||||||
|  |                         'removing' => 'tabler-heart-x', | ||||||
|  |                         'offline' => 'tabler-heart-off', | ||||||
|  |                         'paused' => 'tabler-heart-pause', | ||||||
|  |                         'installing' => 'tabler-heart-bolt', | ||||||
|  |                         'suspended' => 'tabler-heart-cancel', | ||||||
|  |                         default => 'tabler-heart-question', | ||||||
|  |                     }) | ||||||
|  |                     ->color(fn (string $state): string => match ($state) { | ||||||
|  |                         'running' => 'success', | ||||||
|  |                         'installing', 'restarting' => 'primary', | ||||||
|  |                         'paused', 'removing' => 'warning', | ||||||
|  |                         'node_fail', 'install_failed', 'suspended' => 'danger', | ||||||
|  |                         default => 'gray', | ||||||
|  |                     }), | ||||||
|  | 
 | ||||||
|  |                 Tables\Columns\TextColumn::make('uuid') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->label('UUID') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('name') | ||||||
|  |                     ->icon('tabler-brand-docker') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('node.name') | ||||||
|  |                     ->icon('tabler-server-2') | ||||||
|  |                     ->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node])) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('egg.name') | ||||||
|  |                     ->icon('tabler-egg') | ||||||
|  |                     ->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg])) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('user.username') | ||||||
|  |                     ->icon('tabler-user') | ||||||
|  |                     ->label('Owner') | ||||||
|  |                     ->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user])) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\SelectColumn::make('allocation_id') | ||||||
|  |                     ->label('Primary Allocation') | ||||||
|  |                     ->options(fn ($state, Server $server) => $server->allocations->mapWithKeys( | ||||||
|  |                         fn ($allocation) => [$allocation->id => $allocation->address]) | ||||||
|  |                     ) | ||||||
|  |                     ->selectablePlaceholder(false) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('image')->hidden(), | ||||||
|  |                 Tables\Columns\TextColumn::make('backups_count') | ||||||
|  |                     ->counts('backups') | ||||||
|  |                     ->label('Backups') | ||||||
|  |                     ->icon('tabler-file-download') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->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\ListServers::route('/'), | ||||||
|  |             'create' => Pages\CreateServer::route('/create'), | ||||||
|  |             'edit' => Pages\EditServer::route('/{record}/edit'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								app/Filament/Resources/ServerResource/Pages/CreateServer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/Filament/Resources/ServerResource/Pages/CreateServer.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\ServerResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\ServerResource; | ||||||
|  | use App\Services\Servers\ServerCreationService; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | 
 | ||||||
|  | class CreateServer extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = ServerResource::class; | ||||||
|  |     protected static bool $canCreateAnother = false; | ||||||
|  | 
 | ||||||
|  |     protected function handleRecordCreation(array $data): Model | ||||||
|  |     { | ||||||
|  |         $data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all(); | ||||||
|  | 
 | ||||||
|  |         /** @var ServerCreationService $service */ | ||||||
|  |         $service = resolve(ServerCreationService::class); | ||||||
|  | 
 | ||||||
|  |         $server = $service->handle($data); | ||||||
|  | 
 | ||||||
|  |         return $server; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //    protected function getRedirectUrl(): string
 | ||||||
|  |     //    {
 | ||||||
|  |     //        return $this->getResource()::getUrl('edit');
 | ||||||
|  |     //    }
 | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								app/Filament/Resources/ServerResource/Pages/EditServer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/Filament/Resources/ServerResource/Pages/EditServer.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\ServerResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\ServerResource; | ||||||
|  | use App\Models\Server; | ||||||
|  | use App\Services\Servers\ServerDeletionService; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\EditRecord; | ||||||
|  | 
 | ||||||
|  | class EditServer extends EditRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = ServerResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\Action::make('Delete') | ||||||
|  |                 ->successRedirectUrl($this->getResource()::getUrl('index')) | ||||||
|  |                 ->color('danger') | ||||||
|  |                 ->action(fn (Server $server) => resolve(ServerDeletionService::class)->handle($server)) | ||||||
|  |                 ->requiresConfirmation(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/ServerResource/Pages/ListServers.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/ServerResource/Pages/ListServers.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\ServerResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\ServerResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | 
 | ||||||
|  | class ListServers extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = ServerResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										153
									
								
								app/Filament/Resources/UserResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								app/Filament/Resources/UserResource.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\UserResource\Pages; | ||||||
|  | use App\Filament\Resources\UserResource\RelationManagers; | ||||||
|  | use App\Models\User; | ||||||
|  | use Filament\Forms; | ||||||
|  | use Filament\Forms\Components\Section; | ||||||
|  | use Filament\Forms\Form; | ||||||
|  | use Filament\Resources\Resource; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | use Illuminate\Support\Facades\Hash; | ||||||
|  | 
 | ||||||
|  | class UserResource extends Resource | ||||||
|  | { | ||||||
|  |     protected static ?string $model = User::class; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $navigationIcon = 'tabler-users'; | ||||||
|  | 
 | ||||||
|  |     protected static ?string $recordTitleAttribute = 'username'; | ||||||
|  | 
 | ||||||
|  |     public static function form(Form $form): Form | ||||||
|  |     { | ||||||
|  |         return $form | ||||||
|  |             ->schema([ | ||||||
|  |                 Section::make()->schema([ | ||||||
|  |                     Forms\Components\TextInput::make('username')->required()->maxLength(191), | ||||||
|  |                     Forms\Components\TextInput::make('email')->email()->required()->maxLength(191), | ||||||
|  | 
 | ||||||
|  |                     Forms\Components\TextInput::make('name_first') | ||||||
|  |                         ->maxLength(191) | ||||||
|  |                         ->hidden(fn (string $operation): bool => $operation === 'create') | ||||||
|  |                         ->label('First Name'), | ||||||
|  |                     Forms\Components\TextInput::make('name_last') | ||||||
|  |                         ->maxLength(191) | ||||||
|  |                         ->hidden(fn (string $operation): bool => $operation === 'create') | ||||||
|  |                         ->label('Last Name'), | ||||||
|  | 
 | ||||||
|  |                     Forms\Components\TextInput::make('password') | ||||||
|  |                         ->dehydrateStateUsing(fn (string $state): string => Hash::make($state)) | ||||||
|  |                         ->dehydrated(fn (?string $state): bool => filled($state)) | ||||||
|  |                         ->required(fn (string $operation): bool => $operation === 'create') | ||||||
|  |                         ->password(), | ||||||
|  | 
 | ||||||
|  |                     Forms\Components\ToggleButtons::make('root_admin') | ||||||
|  |                         ->label('Administrator (Root)') | ||||||
|  |                         ->options([ | ||||||
|  |                             false => 'No', | ||||||
|  |                             true => 'Admin', | ||||||
|  |                         ]) | ||||||
|  |                         ->colors([ | ||||||
|  |                             false => 'primary', | ||||||
|  |                             true => 'danger', | ||||||
|  |                         ]) | ||||||
|  |                         ->disableOptionWhen(function (string $operation, $value, User $user) { | ||||||
|  |                             if ($operation !== 'edit' || $value) { | ||||||
|  |                                 return false; | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  |                             return $user->isLastRootAdmin(); | ||||||
|  |                         }) | ||||||
|  |                         ->hint(fn (User $user) => $user->isLastRootAdmin() ? 'This is the last root administrator!' : '') | ||||||
|  |                         ->helperText(fn (User $user) => $user->isLastRootAdmin() ? 'You must have at least one root administrator in your system.' : '') | ||||||
|  |                         ->hintColor('warning') | ||||||
|  |                         ->inline() | ||||||
|  |                         ->required() | ||||||
|  |                         ->default(false), | ||||||
|  | 
 | ||||||
|  |                     Forms\Components\Hidden::make('skipValidation')->default(true), | ||||||
|  |                     Forms\Components\Select::make('language') | ||||||
|  |                         ->required() | ||||||
|  |                         ->hidden() | ||||||
|  |                         ->default('en') | ||||||
|  |                         ->options(fn (User $user) => $user->getAvailableLanguages()), | ||||||
|  |                 ])->columns(2), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->searchable(false) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\ImageColumn::make('picture') | ||||||
|  |                     ->visibleFrom('lg') | ||||||
|  |                     ->label('') | ||||||
|  |                     ->extraImgAttributes(['class' => 'rounded-full']) | ||||||
|  |                     ->defaultImageUrl(fn (User $user) => 'https://gravatar.com/avatar/' . md5(strtolower($user->email))), | ||||||
|  |                 Tables\Columns\TextColumn::make('external_id') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->hidden(), | ||||||
|  |                 Tables\Columns\TextColumn::make('uuid') | ||||||
|  |                     ->label('UUID') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('username') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('email') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->icon('tabler-mail'), | ||||||
|  |                 Tables\Columns\IconColumn::make('root_admin') | ||||||
|  |                     ->visibleFrom('md') | ||||||
|  |                     ->label('Admin') | ||||||
|  |                     ->boolean() | ||||||
|  |                     ->trueIcon('tabler-star') | ||||||
|  |                     ->falseIcon('tabler-star-off') | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\IconColumn::make('use_totp')->label('2FA') | ||||||
|  |                     ->visibleFrom('lg') | ||||||
|  |                     ->icon(fn (User $user) => $user->use_totp ? 'tabler-lock' : 'tabler-lock-open-off') | ||||||
|  |                     ->boolean()->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('servers_count') | ||||||
|  |                     ->counts('servers') | ||||||
|  |                     ->icon('tabler-server') | ||||||
|  |                     ->label('Servers'), | ||||||
|  |                 Tables\Columns\TextColumn::make('subusers_count') | ||||||
|  |                     ->visibleFrom('sm') | ||||||
|  |                     ->counts('subusers') | ||||||
|  |                     ->icon('tabler-users') | ||||||
|  |                     // ->formatStateUsing(fn (string $state, $record): string => (string) ($record->servers_count + $record->subusers_count))
 | ||||||
|  |                     ->label('Subuser Accounts'), | ||||||
|  |             ]) | ||||||
|  |             ->filters([ | ||||||
|  |                 //
 | ||||||
|  |             ]) | ||||||
|  |             ->actions([ | ||||||
|  |                 Tables\Actions\EditAction::make(), | ||||||
|  |             ]) | ||||||
|  |             ->bulkActions([ | ||||||
|  |                 Tables\Actions\BulkActionGroup::make([ | ||||||
|  |                     Tables\Actions\DeleteBulkAction::make(), | ||||||
|  |                 ]), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function getRelations(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             RelationManagers\ServersRelationManager::class, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static function getPages(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'index' => Pages\ListUsers::route('/'), | ||||||
|  |             'create' => Pages\CreateUser::route('/create'), | ||||||
|  |             'edit' => Pages\EditUser::route('/{record}/edit'), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								app/Filament/Resources/UserResource/Pages/CreateUser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Filament/Resources/UserResource/Pages/CreateUser.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\UserResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\UserResource; | ||||||
|  | use Filament\Resources\Pages\CreateRecord; | ||||||
|  | 
 | ||||||
|  | class CreateUser extends CreateRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = UserResource::class; | ||||||
|  | } | ||||||
							
								
								
									
										183
									
								
								app/Filament/Resources/UserResource/Pages/EditProfile.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								app/Filament/Resources/UserResource/Pages/EditProfile.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,183 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\UserResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Models\ActivityLog; | ||||||
|  | use App\Models\User; | ||||||
|  | use App\Services\Users\TwoFactorSetupService; | ||||||
|  | use chillerlan\QRCode\Common\EccLevel; | ||||||
|  | use chillerlan\QRCode\Common\Version; | ||||||
|  | use chillerlan\QRCode\QRCode; | ||||||
|  | use chillerlan\QRCode\QROptions; | ||||||
|  | use Filament\Forms\Components\Placeholder; | ||||||
|  | use Filament\Forms\Components\Repeater; | ||||||
|  | use Filament\Forms\Components\Select; | ||||||
|  | use Filament\Forms\Components\Tabs; | ||||||
|  | use Filament\Forms\Components\TagsInput; | ||||||
|  | use Filament\Forms\Components\Tabs\Tab; | ||||||
|  | use Filament\Forms\Components\TextInput; | ||||||
|  | use Filament\Forms\Get; | ||||||
|  | use Illuminate\Support\Facades\Hash; | ||||||
|  | use Illuminate\Support\HtmlString; | ||||||
|  | use Illuminate\Validation\Rules\Password; | ||||||
|  | 
 | ||||||
|  | class EditProfile extends \Filament\Pages\Auth\EditProfile | ||||||
|  | { | ||||||
|  |     protected function getForms(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'form' => $this->form( | ||||||
|  |                 $this->makeForm() | ||||||
|  |                     ->schema([ | ||||||
|  |                         Tabs::make()->schema([ | ||||||
|  |                             Tab::make('Account') | ||||||
|  |                                 ->icon('tabler-user') | ||||||
|  |                                 ->schema([ | ||||||
|  |                                     TextInput::make('username') | ||||||
|  |                                         ->disabled() | ||||||
|  |                                         ->readOnly() | ||||||
|  |                                         ->maxLength(191) | ||||||
|  |                                         ->unique(ignoreRecord: true) | ||||||
|  |                                         ->autofocus(), | ||||||
|  | 
 | ||||||
|  |                                     TextInput::make('email') | ||||||
|  |                                         ->prefixIcon('tabler-mail') | ||||||
|  |                                         ->email() | ||||||
|  |                                         ->required() | ||||||
|  |                                         ->maxLength(191) | ||||||
|  |                                         ->unique(ignoreRecord: true), | ||||||
|  | 
 | ||||||
|  |                                     TextInput::make('password') | ||||||
|  |                                         ->password() | ||||||
|  |                                         ->prefixIcon('tabler-password') | ||||||
|  |                                         ->revealable(filament()->arePasswordsRevealable()) | ||||||
|  |                                         ->rule(Password::default()) | ||||||
|  |                                         ->autocomplete('new-password') | ||||||
|  |                                         ->dehydrated(fn ($state): bool => filled($state)) | ||||||
|  |                                         ->dehydrateStateUsing(fn ($state): string => Hash::make($state)) | ||||||
|  |                                         ->live(debounce: 500) | ||||||
|  |                                         ->same('passwordConfirmation'), | ||||||
|  | 
 | ||||||
|  |                                     TextInput::make('passwordConfirmation') | ||||||
|  |                                         ->password() | ||||||
|  |                                         ->prefixIcon('tabler-password-fingerprint') | ||||||
|  |                                         ->revealable(filament()->arePasswordsRevealable()) | ||||||
|  |                                         ->required() | ||||||
|  |                                         ->visible(fn (Get $get): bool => filled($get('password'))) | ||||||
|  |                                         ->dehydrated(false), | ||||||
|  | 
 | ||||||
|  |                                     Select::make('language') | ||||||
|  |                                         ->required() | ||||||
|  |                                         ->prefixIcon('tabler-flag') | ||||||
|  |                                         ->live() | ||||||
|  |                                         ->default('en') | ||||||
|  |                                         ->helperText(fn (User $user, $state) => new HtmlString($user->isLanguageTranslated($state) ? '' : " | ||||||
|  |                                                 Your language ($state) has not been translated yet! | ||||||
|  |                                                 But never fear, you can help fix that by | ||||||
|  |                                                 <a style='color: rgb(56, 189, 248)' href='https://crowdin.com/project/pelican-dev'>contributing directly here</a>. | ||||||
|  |                                             ")
 | ||||||
|  |                                         ) | ||||||
|  |                                         ->options(fn (User $user) => $user->getAvailableLanguages()), | ||||||
|  |                                 ]), | ||||||
|  | 
 | ||||||
|  |                             Tab::make('2FA') | ||||||
|  |                                 ->icon('tabler-shield-lock') | ||||||
|  |                                 ->schema(function () { | ||||||
|  | 
 | ||||||
|  |                                     if ($this->getUser()->use_totp) { | ||||||
|  |                                         return [ | ||||||
|  |                                             Placeholder::make('2FA already enabled!'), | ||||||
|  |                                         ]; | ||||||
|  |                                     } | ||||||
|  |                                     $setupService = app(TwoFactorSetupService::class); | ||||||
|  | 
 | ||||||
|  |                                     ['image_url_data' => $url] = $setupService->handle($this->getUser()); | ||||||
|  | 
 | ||||||
|  |                                     $options = new QROptions([ | ||||||
|  |                                         'svgLogo' => public_path('pelican.svg'), | ||||||
|  |                                         'addLogoSpace' => true, | ||||||
|  |                                         'logoSpaceWidth' => 13, | ||||||
|  |                                         'logoSpaceHeight' => 13, | ||||||
|  |                                     ]); | ||||||
|  | 
 | ||||||
|  |                                     // https://github.com/chillerlan/php-qrcode/blob/main/examples/svgWithLogo.php
 | ||||||
|  | 
 | ||||||
|  |                                     // SVG logo options (see extended class)
 | ||||||
|  |                                     $options->svgLogo = public_path('pelican.svg'); // logo from: https://github.com/simple-icons/simple-icons
 | ||||||
|  |                                     $options->svgLogoScale = 0.05; | ||||||
|  |                                     // $options->svgLogoCssClass     = 'dark';
 | ||||||
|  | 
 | ||||||
|  |                                     // QROptions
 | ||||||
|  |                                     $options->version = Version::AUTO; | ||||||
|  |                                     // $options->outputInterface     = QRSvgWithLogo::class;
 | ||||||
|  |                                     $options->outputBase64 = false; | ||||||
|  |                                     $options->eccLevel = EccLevel::H; // ECC level H is necessary when using logos
 | ||||||
|  |                                     $options->addQuietzone = true; | ||||||
|  |                                     // $options->drawLightModules    = true;
 | ||||||
|  |                                     $options->connectPaths = true; | ||||||
|  |                                     $options->drawCircularModules = true; | ||||||
|  |                                     // $options->circleRadius        = 0.45;
 | ||||||
|  | 
 | ||||||
|  |                                     $options->svgDefs = '<linearGradient id="gradient" x1="100%" y2="100%"> | ||||||
|  |                                             <stop stop-color="#7dd4fc" offset="0"/> | ||||||
|  |                                             <stop stop-color="#38bdf8" offset="0.5"/> | ||||||
|  |                                             <stop stop-color="#0369a1" offset="1"/> | ||||||
|  |                                         </linearGradient> | ||||||
|  |                                         <style><![CDATA[ | ||||||
|  |                                             .dark{fill: url(#gradient);}
 | ||||||
|  |                                             .light{fill: #000;}
 | ||||||
|  |                                         ]]></style>'; | ||||||
|  | 
 | ||||||
|  |                                     $image = (new QRCode($options))->render($url); | ||||||
|  | 
 | ||||||
|  |                                     return [ | ||||||
|  |                                         Placeholder::make('qr') | ||||||
|  |                                             ->label('Scan QR Code') | ||||||
|  |                                             ->content(fn () => new HtmlString(" | ||||||
|  |                                                 <div style='width: 300px'>$image</div> | ||||||
|  |                                             "))
 | ||||||
|  |                                             ->default('asdfasdf'), | ||||||
|  |                                     ]; | ||||||
|  |                                 }), | ||||||
|  | 
 | ||||||
|  |                             Tab::make('API Keys') | ||||||
|  |                                 ->icon('tabler-key') | ||||||
|  |                                 ->schema([ | ||||||
|  |                                     Placeholder::make('Coming soon!'), | ||||||
|  |                                     TagsInput::make('allowed_ips') | ||||||
|  |                                         ->placeholder('Example: 127.0.0.1 or 192.168.1.1') | ||||||
|  |                                         ->label('Whitelisted IPv4 Addresses') | ||||||
|  |                                         ->helperText('Press enter to add a new IP address or leave blank to allow any IP address') | ||||||
|  |                                         ->columnSpanFull() | ||||||
|  |                                         ->hidden() | ||||||
|  |                                         ->default(null), | ||||||
|  |                                 ]), | ||||||
|  | 
 | ||||||
|  |                             Tab::make('SSH Keys') | ||||||
|  |                                 ->icon('tabler-lock-code') | ||||||
|  |                                 ->schema([ | ||||||
|  |                                     Placeholder::make('Coming soon!'), | ||||||
|  |                                 ]), | ||||||
|  | 
 | ||||||
|  |                             Tab::make('Activity') | ||||||
|  |                                 ->icon('tabler-history') | ||||||
|  |                                 ->schema([ | ||||||
|  |                                     Repeater::make('activity') | ||||||
|  |                                         ->deletable(false) | ||||||
|  |                                         ->addable(false) | ||||||
|  |                                         ->relationship() | ||||||
|  | 
 | ||||||
|  |                                         ->schema([ | ||||||
|  |                                             Placeholder::make('activity!')->label('')->content(fn (ActivityLog $log) => new HtmlString($log->htmlable())), | ||||||
|  |                                         ]), | ||||||
|  |                                 ]), | ||||||
|  |                         ]), | ||||||
|  |                     ]) | ||||||
|  |                     ->operation('edit') | ||||||
|  |                     ->model($this->getUser()) | ||||||
|  |                     ->statePath('data') | ||||||
|  |                     ->inlineLabel(!static::isSimple()), | ||||||
|  |             ), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/EditUser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/EditUser.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\UserResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\UserResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\EditRecord; | ||||||
|  | 
 | ||||||
|  | class EditUser extends EditRecord | ||||||
|  | { | ||||||
|  |     protected static string $resource = UserResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\DeleteAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/ListUsers.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/ListUsers.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\UserResource\Pages; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\UserResource; | ||||||
|  | use Filament\Actions; | ||||||
|  | use Filament\Resources\Pages\ListRecords; | ||||||
|  | 
 | ||||||
|  | class ListUsers extends ListRecords | ||||||
|  | { | ||||||
|  |     protected static string $resource = UserResource::class; | ||||||
|  | 
 | ||||||
|  |     protected function getHeaderActions(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             Actions\CreateAction::make(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,55 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Filament\Resources\UserResource\RelationManagers; | ||||||
|  | 
 | ||||||
|  | use App\Models\Server; | ||||||
|  | use Filament\Tables; | ||||||
|  | use Filament\Tables\Table; | ||||||
|  | use Filament\Resources\RelationManagers\RelationManager; | ||||||
|  | 
 | ||||||
|  | class ServersRelationManager extends RelationManager | ||||||
|  | { | ||||||
|  |     protected static string $relationship = 'servers'; | ||||||
|  | 
 | ||||||
|  |     public function table(Table $table): Table | ||||||
|  |     { | ||||||
|  |         return $table | ||||||
|  |             ->searchable(false) | ||||||
|  |             ->columns([ | ||||||
|  |                 Tables\Columns\TextColumn::make('uuid') | ||||||
|  |                     ->hidden() | ||||||
|  |                     ->label('UUID') | ||||||
|  |                     ->searchable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('name') | ||||||
|  |                     ->icon('tabler-brand-docker') | ||||||
|  |                     ->searchable() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('node.name') | ||||||
|  |                     ->icon('tabler-server-2') | ||||||
|  |                     ->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node])) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('egg.name') | ||||||
|  |                     ->icon('tabler-egg') | ||||||
|  |                     ->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg])) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\SelectColumn::make('allocation.id') | ||||||
|  |                     ->label('Primary Allocation') | ||||||
|  |                     ->options(fn ($state, Server $server) => [$server->allocation->id => $server->allocation->address]) | ||||||
|  |                     ->selectablePlaceholder(false) | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('image')->hidden(), | ||||||
|  |                 Tables\Columns\TextColumn::make('databases_count') | ||||||
|  |                     ->counts('databases') | ||||||
|  |                     ->label('Databases') | ||||||
|  |                     ->icon('tabler-database') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->sortable(), | ||||||
|  |                 Tables\Columns\TextColumn::make('backups_count') | ||||||
|  |                     ->counts('backups') | ||||||
|  |                     ->label('Backups') | ||||||
|  |                     ->icon('tabler-file-download') | ||||||
|  |                     ->numeric() | ||||||
|  |                     ->sortable(), | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,152 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Http\Controllers\Admin; |  | ||||||
| 
 |  | ||||||
| use App\Models\Egg; |  | ||||||
| use Ramsey\Uuid\Uuid; |  | ||||||
| use Illuminate\View\View; |  | ||||||
| use Illuminate\Http\Request; |  | ||||||
| use Illuminate\Http\Response; |  | ||||||
| use App\Models\Mount; |  | ||||||
| use Illuminate\Http\RedirectResponse; |  | ||||||
| use Prologue\Alerts\AlertsMessageBag; |  | ||||||
| use Illuminate\View\Factory as ViewFactory; |  | ||||||
| use App\Http\Controllers\Controller; |  | ||||||
| use App\Http\Requests\Admin\MountFormRequest; |  | ||||||
| 
 |  | ||||||
| class MountController extends Controller |  | ||||||
| { |  | ||||||
|     /** |  | ||||||
|      * MountController constructor. |  | ||||||
|      */ |  | ||||||
|     public function __construct( |  | ||||||
|         protected AlertsMessageBag $alert, |  | ||||||
|         protected ViewFactory $view |  | ||||||
|     ) { |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the mount overview page. |  | ||||||
|      */ |  | ||||||
|     public function index(): View |  | ||||||
|     { |  | ||||||
|         return view('admin.mounts.index', [ |  | ||||||
|             'mounts' => Mount::query()->withCount(['eggs', 'nodes'])->get(), |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the mount view page. |  | ||||||
|      */ |  | ||||||
|     public function view(string $id): View |  | ||||||
|     { |  | ||||||
|         return view('admin.mounts.view', [ |  | ||||||
|             'mount' => Mount::with(['eggs', 'nodes'])->findOrFail($id), |  | ||||||
|             'eggs' => Egg::all(), |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Handle request to create new mount. |  | ||||||
|      * |  | ||||||
|      * @throws \Throwable |  | ||||||
|      */ |  | ||||||
|     public function create(MountFormRequest $request): RedirectResponse |  | ||||||
|     { |  | ||||||
|         $model = (new Mount())->fill($request->validated()); |  | ||||||
|         $model->forceFill(['uuid' => Uuid::uuid4()->toString()]); |  | ||||||
| 
 |  | ||||||
|         $model->saveOrFail(); |  | ||||||
|         $mount = $model->fresh(); |  | ||||||
| 
 |  | ||||||
|         $this->alert->success('Mount was created successfully.')->flash(); |  | ||||||
| 
 |  | ||||||
|         return redirect()->route('admin.mounts.view', $mount->id); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Handle request to update or delete location. |  | ||||||
|      * |  | ||||||
|      * @throws \Throwable |  | ||||||
|      */ |  | ||||||
|     public function update(MountFormRequest $request, Mount $mount): RedirectResponse |  | ||||||
|     { |  | ||||||
|         if ($request->input('action') === 'delete') { |  | ||||||
|             return $this->delete($mount); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $mount->forceFill($request->validated())->save(); |  | ||||||
| 
 |  | ||||||
|         $this->alert->success('Mount was updated successfully.')->flash(); |  | ||||||
| 
 |  | ||||||
|         return redirect()->route('admin.mounts.view', $mount->id); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Delete a location from the system. |  | ||||||
|      * |  | ||||||
|      * @throws \Exception |  | ||||||
|      */ |  | ||||||
|     public function delete(Mount $mount): RedirectResponse |  | ||||||
|     { |  | ||||||
|         $mount->delete(); |  | ||||||
| 
 |  | ||||||
|         return redirect()->route('admin.mounts'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Adds eggs to the mount's many-to-many relation. |  | ||||||
|      */ |  | ||||||
|     public function addEggs(Request $request, Mount $mount): RedirectResponse |  | ||||||
|     { |  | ||||||
|         $validatedData = $request->validate([ |  | ||||||
|             'eggs' => 'required|exists:eggs,id', |  | ||||||
|         ]); |  | ||||||
| 
 |  | ||||||
|         $eggs = $validatedData['eggs'] ?? []; |  | ||||||
|         if (count($eggs) > 0) { |  | ||||||
|             $mount->eggs()->attach($eggs); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $this->alert->success('Mount was updated successfully.')->flash(); |  | ||||||
| 
 |  | ||||||
|         return redirect()->route('admin.mounts.view', $mount->id); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Adds nodes to the mount's many-to-many relation. |  | ||||||
|      */ |  | ||||||
|     public function addNodes(Request $request, Mount $mount): RedirectResponse |  | ||||||
|     { |  | ||||||
|         $data = $request->validate(['nodes' => 'required|exists:nodes,id']); |  | ||||||
| 
 |  | ||||||
|         $nodes = $data['nodes'] ?? []; |  | ||||||
|         if (count($nodes) > 0) { |  | ||||||
|             $mount->nodes()->attach($nodes); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $this->alert->success('Mount was updated successfully.')->flash(); |  | ||||||
| 
 |  | ||||||
|         return redirect()->route('admin.mounts.view', $mount->id); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Deletes an egg from the mount's many-to-many relation. |  | ||||||
|      */ |  | ||||||
|     public function deleteEgg(Mount $mount, int $egg_id): Response |  | ||||||
|     { |  | ||||||
|         $mount->eggs()->detach($egg_id); |  | ||||||
| 
 |  | ||||||
|         return response('', 204); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Deletes a node from the mount's many-to-many relation. |  | ||||||
|      */ |  | ||||||
|     public function deleteNode(Mount $mount, int $node_id): Response |  | ||||||
|     { |  | ||||||
|         $mount->nodes()->detach($node_id); |  | ||||||
| 
 |  | ||||||
|         return response('', 204); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -39,29 +39,8 @@ class NodeViewController extends Controller | |||||||
|             ->where('node_id', '=', $node->id) |             ->where('node_id', '=', $node->id) | ||||||
|             ->first(); |             ->first(); | ||||||
| 
 | 
 | ||||||
|         $usageStats = Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory]) |  | ||||||
|             ->mapWithKeys(function ($value, $key) use ($node) { |  | ||||||
|                 $maxUsage = $node->{$key}; |  | ||||||
|                 if ($node->{$key . '_overallocate'} > 0) { |  | ||||||
|                     $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 $percent = ($value / $maxUsage) * 100; |  | ||||||
| 
 |  | ||||||
|                 return [ |  | ||||||
|                     $key => [ |  | ||||||
|                         'value' => number_format($value), |  | ||||||
|                         'max' => number_format($maxUsage), |  | ||||||
|                         'percent' => $percent, |  | ||||||
|                         'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'), |  | ||||||
|                     ], |  | ||||||
|                 ]; |  | ||||||
|             }) |  | ||||||
|             ->toArray(); |  | ||||||
| 
 |  | ||||||
|         return view('admin.nodes.view.index', [ |         return view('admin.nodes.view.index', [ | ||||||
|             'node' => $node, |             'node' => $node, | ||||||
|             'stats' => $usageStats, |  | ||||||
|             'version' => $this->versionService, |             'version' => $this->versionService, | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,12 +2,12 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\Controllers\Admin\Servers; | namespace App\Http\Controllers\Admin\Servers; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use App\Models\DatabaseHost; | use App\Models\DatabaseHost; | ||||||
| use App\Models\Egg; | use App\Models\Egg; | ||||||
| use App\Models\Mount; | use App\Models\Mount; | ||||||
| use App\Models\Node; | use App\Models\Node; | ||||||
| use Illuminate\View\View; | use Illuminate\View\View; | ||||||
| use Illuminate\Http\Request; |  | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Exceptions\DisplayException; | use App\Exceptions\DisplayException; | ||||||
| use App\Http\Controllers\Controller; | use App\Http\Controllers\Controller; | ||||||
| @ -22,14 +22,14 @@ class ServerViewController extends Controller | |||||||
|      * ServerViewController constructor. |      * ServerViewController constructor. | ||||||
|      */ |      */ | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         private EnvironmentService $environmentService, |         private readonly EnvironmentService $environmentService, | ||||||
|     ) { |     ) { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns the index view for a server. |      * Returns the index view for a server. | ||||||
|      */ |      */ | ||||||
|     public function index(Request $request, Server $server): View |     public function index(Server $server): View | ||||||
|     { |     { | ||||||
|         return view('admin.servers.view.index', compact('server')); |         return view('admin.servers.view.index', compact('server')); | ||||||
|     } |     } | ||||||
| @ -37,7 +37,7 @@ class ServerViewController extends Controller | |||||||
|     /** |     /** | ||||||
|      * Returns the server details page. |      * Returns the server details page. | ||||||
|      */ |      */ | ||||||
|     public function details(Request $request, Server $server): View |     public function details(Server $server): View | ||||||
|     { |     { | ||||||
|         return view('admin.servers.view.details', compact('server')); |         return view('admin.servers.view.details', compact('server')); | ||||||
|     } |     } | ||||||
| @ -45,7 +45,7 @@ class ServerViewController extends Controller | |||||||
|     /** |     /** | ||||||
|      * Returns a view of server build settings. |      * Returns a view of server build settings. | ||||||
|      */ |      */ | ||||||
|     public function build(Request $request, Server $server): View |     public function build(Server $server): View | ||||||
|     { |     { | ||||||
|         $allocations = $server->node->allocations->toBase(); |         $allocations = $server->node->allocations->toBase(); | ||||||
| 
 | 
 | ||||||
| @ -59,7 +59,7 @@ class ServerViewController extends Controller | |||||||
|     /** |     /** | ||||||
|      * Returns the server startup management page. |      * Returns the server startup management page. | ||||||
|      */ |      */ | ||||||
|     public function startup(Request $request, Server $server): View |     public function startup(Server $server): View | ||||||
|     { |     { | ||||||
|         $variables = $this->environmentService->handle($server); |         $variables = $this->environmentService->handle($server); | ||||||
|         $eggs = Egg::all()->keyBy('id'); |         $eggs = Egg::all()->keyBy('id'); | ||||||
| @ -76,7 +76,7 @@ class ServerViewController extends Controller | |||||||
|     /** |     /** | ||||||
|      * Returns all the databases that exist for the server. |      * Returns all the databases that exist for the server. | ||||||
|      */ |      */ | ||||||
|     public function database(Request $request, Server $server): View |     public function database(Server $server): View | ||||||
|     { |     { | ||||||
|         return view('admin.servers.view.database', [ |         return view('admin.servers.view.database', [ | ||||||
|             'hosts' => DatabaseHost::all(), |             'hosts' => DatabaseHost::all(), | ||||||
| @ -87,7 +87,7 @@ class ServerViewController extends Controller | |||||||
|     /** |     /** | ||||||
|      * Returns all the mounts that exist for the server. |      * Returns all the mounts that exist for the server. | ||||||
|      */ |      */ | ||||||
|     public function mounts(Request $request, Server $server): View |     public function mounts(Server $server): View | ||||||
|     { |     { | ||||||
|         $server->load('mounts'); |         $server->load('mounts'); | ||||||
| 
 | 
 | ||||||
| @ -108,9 +108,9 @@ class ServerViewController extends Controller | |||||||
|      * |      * | ||||||
|      * @throws \App\Exceptions\DisplayException |      * @throws \App\Exceptions\DisplayException | ||||||
|      */ |      */ | ||||||
|     public function manage(Request $request, Server $server): View |     public function manage(Server $server): View | ||||||
|     { |     { | ||||||
|         if ($server->status === Server::STATUS_INSTALL_FAILED) { |         if ($server->status === ServerState::InstallFailed) { | ||||||
|             throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.'); |             throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -135,7 +135,7 @@ class ServerViewController extends Controller | |||||||
|     /** |     /** | ||||||
|      * Returns the server deletion page. |      * Returns the server deletion page. | ||||||
|      */ |      */ | ||||||
|     public function delete(Request $request, Server $server): View |     public function delete(Server $server): View | ||||||
|     { |     { | ||||||
|         return view('admin.servers.view.delete', compact('server')); |         return view('admin.servers.view.delete', compact('server')); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\Controllers\Admin; | namespace App\Http\Controllers\Admin; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use App\Models\User; | use App\Models\User; | ||||||
| use Illuminate\Http\Response; | use Illuminate\Http\Response; | ||||||
| @ -71,11 +72,11 @@ class ServersController extends Controller | |||||||
|      */ |      */ | ||||||
|     public function toggleInstall(Server $server): RedirectResponse |     public function toggleInstall(Server $server): RedirectResponse | ||||||
|     { |     { | ||||||
|         if ($server->status === Server::STATUS_INSTALL_FAILED) { |         if ($server->status === ServerState::InstallFailed) { | ||||||
|             throw new DisplayException(trans('admin/server.exceptions.marked_as_failed')); |             throw new DisplayException(trans('admin/server.exceptions.marked_as_failed')); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $server->status = $server->isInstalled() ? Server::STATUS_INSTALLING : null; |         $server->status = $server->isInstalled() ? ServerState::Installing : null; | ||||||
|         $server->save(); |         $server->save(); | ||||||
| 
 | 
 | ||||||
|         $this->alert->success(trans('admin/server.alerts.install_toggled'))->flash(); |         $this->alert->success(trans('admin/server.alerts.install_toggled'))->flash(); | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\Controllers\Api\Client\Servers; | namespace App\Http\Controllers\Api\Client\Servers; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use App\Models\Backup; | use App\Models\Backup; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| @ -212,7 +213,7 @@ class BackupController extends ClientApiController | |||||||
| 
 | 
 | ||||||
|             // Update the status right away for the server so that we know not to allow certain
 |             // Update the status right away for the server so that we know not to allow certain
 | ||||||
|             // actions against it via the Panel API.
 |             // actions against it via the Panel API.
 | ||||||
|             $server->update(['status' => Server::STATUS_RESTORING_BACKUP]); |             $server->update(['status' => ServerState::RestoringBackup]); | ||||||
| 
 | 
 | ||||||
|             $this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate')); |             $this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate')); | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\Controllers\Api\Remote\Servers; | namespace App\Http\Controllers\Api\Remote\Servers; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use App\Models\Backup; | use App\Models\Backup; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| @ -81,7 +82,7 @@ class ServerDetailsController extends Controller | |||||||
|                     ->latest('timestamp'), |                     ->latest('timestamp'), | ||||||
|             ]) |             ]) | ||||||
|             ->where('node_id', $node->id) |             ->where('node_id', $node->id) | ||||||
|             ->where('status', Server::STATUS_RESTORING_BACKUP) |             ->where('status', ServerState::RestoringBackup) | ||||||
|             ->get(); |             ->get(); | ||||||
| 
 | 
 | ||||||
|         $this->connection->transaction(function () use ($node, $servers) { |         $this->connection->transaction(function () use ($node, $servers) { | ||||||
| @ -108,7 +109,7 @@ class ServerDetailsController extends Controller | |||||||
|             // Update any server marked as installing or restoring as being in a normal state
 |             // Update any server marked as installing or restoring as being in a normal state
 | ||||||
|             // at this point in the process.
 |             // at this point in the process.
 | ||||||
|             Server::query()->where('node_id', $node->id) |             Server::query()->where('node_id', $node->id) | ||||||
|                 ->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP]) |                 ->whereIn('status', [ServerState::Installing, ServerState::RestoringBackup]) | ||||||
|                 ->update(['status' => null]); |                 ->update(['status' => null]); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\Controllers\Api\Remote\Servers; | namespace App\Http\Controllers\Api\Remote\Servers; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use Illuminate\Http\Response; | use Illuminate\Http\Response; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use Illuminate\Http\JsonResponse; | use Illuminate\Http\JsonResponse; | ||||||
| @ -36,16 +37,16 @@ class ServerInstallController extends Controller | |||||||
| 
 | 
 | ||||||
|         // Make sure the type of failure is accurate
 |         // Make sure the type of failure is accurate
 | ||||||
|         if (!$request->boolean('successful')) { |         if (!$request->boolean('successful')) { | ||||||
|             $status = Server::STATUS_INSTALL_FAILED; |             $status = ServerState::InstallFailed; | ||||||
| 
 | 
 | ||||||
|             if ($request->boolean('reinstall')) { |             if ($request->boolean('reinstall')) { | ||||||
|                 $status = Server::STATUS_REINSTALL_FAILED; |                 $status = ServerState::ReinstallFailed; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Keep the server suspended if it's already suspended
 |         // Keep the server suspended if it's already suspended
 | ||||||
|         if ($server->status === Server::STATUS_SUSPENDED) { |         if ($server->status === ServerState::Suspended) { | ||||||
|             $status = Server::STATUS_SUSPENDED; |             $status = ServerState::Suspended; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $previouslyInstalledAt = $server->installed_at; |         $previouslyInstalledAt = $server->installed_at; | ||||||
|  | |||||||
| @ -1,23 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Http\Requests\Admin; |  | ||||||
| 
 |  | ||||||
| use App\Models\Mount; |  | ||||||
| 
 |  | ||||||
| class MountFormRequest extends AdminFormRequest |  | ||||||
| { |  | ||||||
|     /** |  | ||||||
|      * Set up the validation rules to use for these requests. |  | ||||||
|      */ |  | ||||||
|     public function rules(): array |  | ||||||
|     { |  | ||||||
|         if ($this->method() === 'PATCH') { |  | ||||||
|             /** @var Mount $mount */ |  | ||||||
|             $mount = $this->route()->parameter('mount'); |  | ||||||
| 
 |  | ||||||
|             return Mount::getRulesForUpdate($mount->id); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return Mount::getRules(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -2,7 +2,6 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Http\Requests\Admin\Node; | namespace App\Http\Requests\Admin\Node; | ||||||
| 
 | 
 | ||||||
| use App\Rules\Fqdn; |  | ||||||
| use App\Models\Node; | use App\Models\Node; | ||||||
| use App\Http\Requests\Admin\AdminFormRequest; | use App\Http\Requests\Admin\AdminFormRequest; | ||||||
| 
 | 
 | ||||||
| @ -17,9 +16,6 @@ class NodeFormRequest extends AdminFormRequest | |||||||
|             return Node::getRulesForUpdate($this->route()->parameter('node')); |             return Node::getRulesForUpdate($this->route()->parameter('node')); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $data = Node::getRules(); |         return Node::getRules(); | ||||||
|         $data['fqdn'][] = Fqdn::make('scheme'); |  | ||||||
| 
 |  | ||||||
|         return $data; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								app/Livewire/NodeSystemInformation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/Livewire/NodeSystemInformation.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Livewire; | ||||||
|  | 
 | ||||||
|  | use App\Models\Node; | ||||||
|  | use Livewire\Component; | ||||||
|  | 
 | ||||||
|  | class NodeSystemInformation extends Component | ||||||
|  | { | ||||||
|  |     public Node $node; | ||||||
|  |     public string $sizeClasses; | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return view('livewire.node-system-information'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function placeholder() | ||||||
|  |     { | ||||||
|  |         return <<<'HTML' | ||||||
|  |         <div> | ||||||
|  |             <x-filament::icon | ||||||
|  |                 :icon="'tabler-heart-question'" | ||||||
|  |                 @class(['fi-ta-icon-item', $sizeClasses, 'fi-color-custom text-custom-500 dark:text-custom-400', 'fi-color-warning']) | ||||||
|  |                 @style([\Filament\Support\get_color_css_variables('warning', shades: [400, 500], alias: 'tables::columns.icon-column.item')]) | ||||||
|  |             /> | ||||||
|  |         </div> | ||||||
|  |         HTML; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -144,4 +144,29 @@ class ActivityLog extends Model | |||||||
|             Event::dispatch(new ActivityLogged($model)); |             Event::dispatch(new ActivityLogged($model)); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function htmlable() | ||||||
|  |     { | ||||||
|  |         $user = $this->actor; | ||||||
|  |         if (!$user instanceof User) { | ||||||
|  |             $user = new User([ | ||||||
|  |                 'email' => 'system@pelican.dev', | ||||||
|  |                 'username' => 'system', | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $event = __('activity.'.str($this->event)->replace(':', '.')); | ||||||
|  | 
 | ||||||
|  |         return " | ||||||
|  |             <div style='display: flex; align-items: center;'> | ||||||
|  |                 <img width='50px' height='50px' src='{$user->getFilamentAvatarUrl()}' style='margin-right: 15px' /> | ||||||
|  | 
 | ||||||
|  |                 <div> | ||||||
|  |                     <p>$user->username — $this->event</p> | ||||||
|  |                     <p>$event</p> | ||||||
|  |                     <p>$this->ip — <span title='{$this->timestamp->format('M j, Y g:ia')}'>{$this->timestamp->diffForHumans()}</span></p> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         ";
 | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| namespace App\Models; | namespace App\Models; | ||||||
| 
 | 
 | ||||||
| use App\Exceptions\Service\Allocation\ServerUsingAllocationException; | use App\Exceptions\Service\Allocation\ServerUsingAllocationException; | ||||||
|  | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -111,9 +112,16 @@ class Allocation extends Model | |||||||
|         return !is_null($this->ip_alias); |         return !is_null($this->ip_alias); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function address(): Attribute | ||||||
|  |     { | ||||||
|  |         return Attribute::make( | ||||||
|  |             get: fn () => "$this->ip:$this->port", | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function toString(): string |     public function toString(): string | ||||||
|     { |     { | ||||||
|         return sprintf('%s:%s', $this->ip, $this->port); |         return $this->address; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -83,6 +83,8 @@ class ApiKey extends Model | |||||||
|      */ |      */ | ||||||
|     public const KEY_LENGTH = 32; |     public const KEY_LENGTH = 32; | ||||||
| 
 | 
 | ||||||
|  |     public const RESOURCES = ['servers', 'nodes', 'allocations', 'users', 'eggs', 'database_hosts', 'server_databases']; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * The table associated with the model. |      * The table associated with the model. | ||||||
|      */ |      */ | ||||||
| @ -92,12 +94,21 @@ class ApiKey extends Model | |||||||
|      * Fields that are mass assignable. |      * Fields that are mass assignable. | ||||||
|      */ |      */ | ||||||
|     protected $fillable = [ |     protected $fillable = [ | ||||||
|  |         'user_id', | ||||||
|  |         'key_type', | ||||||
|         'identifier', |         'identifier', | ||||||
|         'token', |         'token', | ||||||
|         'allowed_ips', |         'allowed_ips', | ||||||
|         'memo', |         'memo', | ||||||
|         'last_used_at', |         'last_used_at', | ||||||
|         'expires_at', |         'expires_at', | ||||||
|  |         'r_' . AdminAcl::RESOURCE_USERS, | ||||||
|  |         'r_' . AdminAcl::RESOURCE_ALLOCATIONS, | ||||||
|  |         'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS, | ||||||
|  |         'r_' . AdminAcl::RESOURCE_SERVER_DATABASES, | ||||||
|  |         'r_' . AdminAcl::RESOURCE_EGGS, | ||||||
|  |         'r_' . AdminAcl::RESOURCE_NODES, | ||||||
|  |         'r_' . AdminAcl::RESOURCE_SERVERS, | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -187,7 +198,7 @@ class ApiKey extends Model | |||||||
|     { |     { | ||||||
|         Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]); |         Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]); | ||||||
| 
 | 
 | ||||||
|         return $type === self::TYPE_ACCOUNT ? 'ptlc_' : 'ptla_'; |         return $type === self::TYPE_ACCOUNT ? 'plcn_' : 'peli_'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -65,6 +65,11 @@ class DatabaseHost extends Model | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function getRouteKeyName(): string | ||||||
|  |     { | ||||||
|  |         return 'id'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Gets the node associated with a database host. |      * Gets the node associated with a database host. | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -152,6 +152,11 @@ class Egg extends Model | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function getRouteKeyName(): string | ||||||
|  |     { | ||||||
|  |         return 'id'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns the install script for the egg; if egg is copying from another |      * Returns the install script for the egg; if egg is copying from another | ||||||
|      * it will return the copied script. |      * it will return the copied script. | ||||||
|  | |||||||
| @ -71,8 +71,8 @@ class Mount extends Model | |||||||
|      * Blacklisted source paths. |      * Blacklisted source paths. | ||||||
|      */ |      */ | ||||||
|     public static $invalidSourcePaths = [ |     public static $invalidSourcePaths = [ | ||||||
|         '/etc/panel', |         '/etc/pelican', | ||||||
|         '/var/lib/panel/volumes', |         '/var/lib/pelican/volumes', | ||||||
|         '/srv/daemon-data', |         '/srv/daemon-data', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
| @ -115,4 +115,9 @@ class Mount extends Model | |||||||
|     { |     { | ||||||
|         return $this->belongsToMany(Server::class); |         return $this->belongsToMany(Server::class); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getRouteKeyName(): string | ||||||
|  |     { | ||||||
|  |         return 'id'; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,10 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Models; | namespace App\Models; | ||||||
| 
 | 
 | ||||||
|  | use App\Exceptions\Service\HasActiveServersException; | ||||||
|  | use App\Repositories\Daemon\DaemonConfigurationRepository; | ||||||
|  | use Exception; | ||||||
|  | use Illuminate\Support\Facades\Http; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
| use Symfony\Component\Yaml\Yaml; | use Symfony\Component\Yaml\Yaml; | ||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
| @ -79,9 +83,9 @@ class Node extends Model | |||||||
|         'fqdn' => 'required|string', |         'fqdn' => 'required|string', | ||||||
|         'scheme' => 'required', |         'scheme' => 'required', | ||||||
|         'behind_proxy' => 'boolean', |         'behind_proxy' => 'boolean', | ||||||
|         'memory' => 'required|numeric|min:1', |         'memory' => 'required|numeric|min:0', | ||||||
|         'memory_overallocate' => 'required|numeric|min:-1', |         'memory_overallocate' => 'required|numeric|min:-1', | ||||||
|         'disk' => 'required|numeric|min:1', |         'disk' => 'required|numeric|min:0', | ||||||
|         'disk_overallocate' => 'required|numeric|min:-1', |         'disk_overallocate' => 'required|numeric|min:-1', | ||||||
|         'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', |         'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', | ||||||
|         'daemon_sftp' => 'required|numeric|between:1,65535', |         'daemon_sftp' => 'required|numeric|between:1,65535', | ||||||
| @ -96,7 +100,9 @@ class Node extends Model | |||||||
|     protected $attributes = [ |     protected $attributes = [ | ||||||
|         'public' => true, |         'public' => true, | ||||||
|         'behind_proxy' => false, |         'behind_proxy' => false, | ||||||
|  |         'memory' => 0, | ||||||
|         'memory_overallocate' => 0, |         'memory_overallocate' => 0, | ||||||
|  |         'disk' => 0, | ||||||
|         'disk_overallocate' => 0, |         'disk_overallocate' => 0, | ||||||
|         'daemon_base' => '/var/lib/pelican/volumes', |         'daemon_base' => '/var/lib/pelican/volumes', | ||||||
|         'daemon_sftp' => 2022, |         'daemon_sftp' => 2022, | ||||||
| @ -117,6 +123,26 @@ class Node extends Model | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function getRouteKeyName(): string | ||||||
|  |     { | ||||||
|  |         return 'id'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected static function booted(): void | ||||||
|  |     { | ||||||
|  |         static::creating(function (self $node) { | ||||||
|  |             $node->uuid = Str::uuid(); | ||||||
|  |             $node->daemon_token = encrypt(Str::random(self::DAEMON_TOKEN_LENGTH)); | ||||||
|  |             $node->daemon_token_id = Str::random(self::DAEMON_TOKEN_ID_LENGTH); | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         static::deleting(function (self $node) { | ||||||
|  |             throw_if($node->servers()->count(), new HasActiveServersException(trans('exceptions.egg.delete_has_servers'))); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Get the connection address to use when making calls to this node. |      * Get the connection address to use when making calls to this node. | ||||||
|      */ |      */ | ||||||
| @ -240,4 +266,40 @@ class Node extends Model | |||||||
|             ]; |             ]; | ||||||
|         })->values(); |         })->values(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function systemInformation(): array | ||||||
|  |     { | ||||||
|  |         return once(function () { | ||||||
|  |             try { | ||||||
|  |                 return resolve(DaemonConfigurationRepository::class) | ||||||
|  |                     ->setNode($this) | ||||||
|  |                     ->getSystemInformation(connectTimeout: 3); | ||||||
|  |             } catch (Exception $exception) { | ||||||
|  |                 $message = str($exception->getMessage()); | ||||||
|  | 
 | ||||||
|  |                 if ($message->startsWith('cURL error 6: Could not resolve host')) { | ||||||
|  |                     $message = str('Could not resolve host'); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if ($message->startsWith('cURL error 28: Failed to connect to ')) { | ||||||
|  |                     $message = $message->after('cURL error 28: ')->before(' after '); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return ['exception' => $message->toString()]; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function serverStatuses(): array | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             /** @var \Illuminate\Http\Client\Response $response */ | ||||||
|  |             $response = Http::daemon($this)->connectTimeout(1)->timeout(1)->get('/api/servers'); | ||||||
|  |             $statuses = $response->json(); | ||||||
|  |         } catch (Exception) { | ||||||
|  |             $statuses = []; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return cache()->remember("nodes.$this->id.servers", now()->addSeconds(2), fn () => $statuses); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Models; | namespace App\Models; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use App\Exceptions\Http\Connection\DaemonConnectionException; | use App\Exceptions\Http\Connection\DaemonConnectionException; | ||||||
| use GuzzleHttp\Exception\GuzzleException; | use GuzzleHttp\Exception\GuzzleException; | ||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
| @ -112,12 +113,6 @@ class Server extends Model | |||||||
|      */ |      */ | ||||||
|     public const RESOURCE_NAME = 'server'; |     public const RESOURCE_NAME = 'server'; | ||||||
| 
 | 
 | ||||||
|     public const STATUS_INSTALLING = 'installing'; |  | ||||||
|     public const STATUS_INSTALL_FAILED = 'install_failed'; |  | ||||||
|     public const STATUS_REINSTALL_FAILED = 'reinstall_failed'; |  | ||||||
|     public const STATUS_SUSPENDED = 'suspended'; |  | ||||||
|     public const STATUS_RESTORING_BACKUP = 'restoring_backup'; |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * The table associated with the model. |      * The table associated with the model. | ||||||
|      */ |      */ | ||||||
| @ -128,7 +123,7 @@ class Server extends Model | |||||||
|      * on server instances unless the user specifies otherwise in the request. |      * on server instances unless the user specifies otherwise in the request. | ||||||
|      */ |      */ | ||||||
|     protected $attributes = [ |     protected $attributes = [ | ||||||
|         'status' => self::STATUS_INSTALLING, |         'status' => ServerState::Installing, | ||||||
|         'oom_disabled' => true, |         'oom_disabled' => true, | ||||||
|         'installed_at' => null, |         'installed_at' => null, | ||||||
|     ]; |     ]; | ||||||
| @ -152,7 +147,7 @@ class Server extends Model | |||||||
|         'status' => 'nullable|string', |         'status' => 'nullable|string', | ||||||
|         'memory' => 'required|numeric|min:0', |         'memory' => 'required|numeric|min:0', | ||||||
|         'swap' => 'required|numeric|min:-1', |         'swap' => 'required|numeric|min:-1', | ||||||
|         'io' => 'required|numeric|between:10,1000', |         'io' => 'required|numeric|between:0,1000', | ||||||
|         'cpu' => 'required|numeric|min:0', |         'cpu' => 'required|numeric|min:0', | ||||||
|         'threads' => 'nullable|regex:/^[0-9-,]+$/', |         'threads' => 'nullable|regex:/^[0-9-,]+$/', | ||||||
|         'oom_disabled' => 'sometimes|boolean', |         'oom_disabled' => 'sometimes|boolean', | ||||||
| @ -171,6 +166,7 @@ class Server extends Model | |||||||
|     { |     { | ||||||
|         return [ |         return [ | ||||||
|             'node_id' => 'integer', |             'node_id' => 'integer', | ||||||
|  |             'status' => ServerState::class, | ||||||
|             'skip_scripts' => 'boolean', |             'skip_scripts' => 'boolean', | ||||||
|             'owner_id' => 'integer', |             'owner_id' => 'integer', | ||||||
|             'memory' => 'integer', |             'memory' => 'integer', | ||||||
| @ -203,12 +199,12 @@ class Server extends Model | |||||||
| 
 | 
 | ||||||
|     public function isInstalled(): bool |     public function isInstalled(): bool | ||||||
|     { |     { | ||||||
|         return $this->status !== self::STATUS_INSTALLING && $this->status !== self::STATUS_INSTALL_FAILED; |         return $this->status !== ServerState::Installing && $this->status !== ServerState::InstallFailed; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function isSuspended(): bool |     public function isSuspended(): bool | ||||||
|     { |     { | ||||||
|         return $this->status === self::STATUS_SUSPENDED; |         return $this->status === ServerState::Suspended; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -251,6 +247,11 @@ class Server extends Model | |||||||
|         return $this->hasOne(Egg::class, 'id', 'egg_id'); |         return $this->hasOne(Egg::class, 'id', 'egg_id'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function eggVariables(): HasMany | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Gets information for the egg variables associated with this server. |      * Gets information for the egg variables associated with this server. | ||||||
|      */ |      */ | ||||||
| @ -267,6 +268,11 @@ class Server extends Model | |||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function serverVariables(): HasMany | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(ServerVariable::class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Gets information for the node associated with this server. |      * Gets information for the node associated with this server. | ||||||
|      */ |      */ | ||||||
| @ -349,7 +355,7 @@ class Server extends Model | |||||||
|             $this->isSuspended() || |             $this->isSuspended() || | ||||||
|             $this->node->isUnderMaintenance() || |             $this->node->isUnderMaintenance() || | ||||||
|             !$this->isInstalled() || |             !$this->isInstalled() || | ||||||
|             $this->status === self::STATUS_RESTORING_BACKUP || |             $this->status === ServerState::RestoringBackup || | ||||||
|             !is_null($this->transfer) |             !is_null($this->transfer) | ||||||
|         ) { |         ) { | ||||||
|             throw new ServerStateConflictException($this); |             throw new ServerStateConflictException($this); | ||||||
| @ -366,7 +372,7 @@ class Server extends Model | |||||||
|     { |     { | ||||||
|         if ( |         if ( | ||||||
|             !$this->isInstalled() || |             !$this->isInstalled() || | ||||||
|             $this->status === self::STATUS_RESTORING_BACKUP || |             $this->status === ServerState::RestoringBackup || | ||||||
|             !is_null($this->transfer) |             !is_null($this->transfer) | ||||||
|         ) { |         ) { | ||||||
|             throw new ServerStateConflictException($this); |             throw new ServerStateConflictException($this); | ||||||
| @ -388,4 +394,9 @@ class Server extends Model | |||||||
|             throw new DaemonConnectionException($exception); |             throw new DaemonConnectionException($exception); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function retrieveStatus() | ||||||
|  |     { | ||||||
|  |         return $this->node->serverStatuses(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,7 +5,11 @@ namespace App\Models; | |||||||
| use App\Exceptions\DisplayException; | use App\Exceptions\DisplayException; | ||||||
| use App\Rules\Username; | use App\Rules\Username; | ||||||
| use App\Facades\Activity; | use App\Facades\Activity; | ||||||
| use Illuminate\Support\Collection; | use Filament\Models\Contracts\FilamentUser; | ||||||
|  | use Filament\Models\Contracts\HasAvatar; | ||||||
|  | use Filament\Models\Contracts\HasName; | ||||||
|  | use Filament\Panel; | ||||||
|  | use Illuminate\Support\Str; | ||||||
| use Illuminate\Validation\Rules\In; | use Illuminate\Validation\Rules\In; | ||||||
| use Illuminate\Auth\Authenticatable; | use Illuminate\Auth\Authenticatable; | ||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
| @ -79,7 +83,7 @@ use App\Notifications\SendPasswordReset as ResetPasswordNotification; | |||||||
|  * |  * | ||||||
|  * @mixin \Eloquent |  * @mixin \Eloquent | ||||||
|  */ |  */ | ||||||
| class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract | class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName | ||||||
| { | { | ||||||
|     use Authenticatable; |     use Authenticatable; | ||||||
|     use Authorizable {can as protected canned; } |     use Authorizable {can as protected canned; } | ||||||
| @ -139,18 +143,20 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | |||||||
|         'language' => 'en', |         'language' => 'en', | ||||||
|         'use_totp' => false, |         'use_totp' => false, | ||||||
|         'totp_secret' => null, |         'totp_secret' => null, | ||||||
|  |         'name_first' => '', | ||||||
|  |         'name_last' => '', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Rules verifying that the data being stored matches the expectations of the database. |      * Rules verifying that the data being stored matches the expectations of the database. | ||||||
|      */ |      */ | ||||||
|     public static array $validationRules = [ |     public static array $validationRules = [ | ||||||
|         'uuid' => 'required|string|size:36|unique:users,uuid', |         'uuid' => 'nullable|string|size:36|unique:users,uuid', | ||||||
|         'email' => 'required|email|between:1,191|unique:users,email', |         'email' => 'required|email|between:1,191|unique:users,email', | ||||||
|         'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', |         'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', | ||||||
|         'username' => 'required|between:1,191|unique:users,username', |         'username' => 'required|between:1,191|unique:users,username', | ||||||
|         'name_first' => 'required|string|between:1,191', |         'name_first' => 'nullable|string|between:0,191', | ||||||
|         'name_last' => 'required|string|between:1,191', |         'name_last' => 'nullable|string|between:0,191', | ||||||
|         'password' => 'sometimes|nullable|string', |         'password' => 'sometimes|nullable|string', | ||||||
|         'root_admin' => 'boolean', |         'root_admin' => 'boolean', | ||||||
|         'language' => 'string', |         'language' => 'string', | ||||||
| @ -170,6 +176,12 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | |||||||
| 
 | 
 | ||||||
|     protected static function booted(): void |     protected static function booted(): void | ||||||
|     { |     { | ||||||
|  |         static::creating(function (self $user) { | ||||||
|  |             $user->uuid = Str::uuid(); | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         static::deleting(function (self $user) { |         static::deleting(function (self $user) { | ||||||
|             throw_if($user->servers()->count() > 0, new DisplayException(__('admin/user.exceptions.user_has_servers'))); |             throw_if($user->servers()->count() > 0, new DisplayException(__('admin/user.exceptions.user_has_servers'))); | ||||||
| 
 | 
 | ||||||
| @ -177,6 +189,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function getRouteKeyName(): string | ||||||
|  |     { | ||||||
|  |         return 'id'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Implement language verification by overriding Eloquence's gather |      * Implement language verification by overriding Eloquence's gather | ||||||
|      * rules function. |      * rules function. | ||||||
| @ -196,7 +213,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | |||||||
|      */ |      */ | ||||||
|     public function toVueObject(): array |     public function toVueObject(): array | ||||||
|     { |     { | ||||||
|         return Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); |         return collect($this->toArray())->except(['id', 'external_id'])->toArray(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -278,6 +295,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | |||||||
|             ->groupBy('servers.id'); |             ->groupBy('servers.id'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function subusers(): HasMany | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(Subuser::class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     protected function checkPermission(Server $server, string $permission = ''): bool |     protected function checkPermission(Server $server, string $permission = ''): bool | ||||||
|     { |     { | ||||||
|         if ($this->root_admin || $server->owner_id === $this->id) { |         if ($this->root_admin || $server->owner_id === $this->id) { | ||||||
| @ -313,4 +335,26 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | |||||||
| 
 | 
 | ||||||
|         return $this->canned($abilities, $arguments); |         return $this->canned($abilities, $arguments); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function isLastRootAdmin(): bool | ||||||
|  |     { | ||||||
|  |         $rootAdmins = User::query()->where('root_admin', true)->limit(2)->get(); | ||||||
|  | 
 | ||||||
|  |         return once(fn () => $rootAdmins->count() === 1 && $rootAdmins->first()->is($this)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function canAccessPanel(Panel $panel): bool | ||||||
|  |     { | ||||||
|  |         return $this->root_admin; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getFilamentName(): string | ||||||
|  |     { | ||||||
|  |         return $this->name_first ?: $this->username; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getFilamentAvatarUrl(): ?string | ||||||
|  |     { | ||||||
|  |         return 'https://gravatar.com/avatar/' . md5(strtolower($this->email)); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								app/Policies/EggPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/Policies/EggPolicy.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Policies; | ||||||
|  | 
 | ||||||
|  | use App\Models\User; | ||||||
|  | 
 | ||||||
|  | class EggPolicy | ||||||
|  | { | ||||||
|  |     public function create(User $user): bool | ||||||
|  |     { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										75
									
								
								app/Providers/Filament/AdminPanelProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								app/Providers/Filament/AdminPanelProvider.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Providers\Filament; | ||||||
|  | 
 | ||||||
|  | use App\Filament\Resources\UserResource\Pages\EditProfile; | ||||||
|  | use Filament\Http\Middleware\Authenticate; | ||||||
|  | use Filament\Http\Middleware\DisableBladeIconComponents; | ||||||
|  | use Filament\Http\Middleware\DispatchServingFilamentEvent; | ||||||
|  | use Filament\Panel; | ||||||
|  | use Filament\PanelProvider; | ||||||
|  | use Filament\Support\Colors\Color; | ||||||
|  | use Filament\Support\Facades\FilamentAsset; | ||||||
|  | use Filament\Widgets; | ||||||
|  | use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; | ||||||
|  | use Illuminate\Cookie\Middleware\EncryptCookies; | ||||||
|  | use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; | ||||||
|  | use Illuminate\Routing\Middleware\SubstituteBindings; | ||||||
|  | use Illuminate\Session\Middleware\AuthenticateSession; | ||||||
|  | use Illuminate\Session\Middleware\StartSession; | ||||||
|  | use Illuminate\View\Middleware\ShareErrorsFromSession; | ||||||
|  | 
 | ||||||
|  | class AdminPanelProvider extends PanelProvider | ||||||
|  | { | ||||||
|  |     public function boot() | ||||||
|  |     { | ||||||
|  |         FilamentAsset::registerCssVariables([ | ||||||
|  |             'sidebar-width' => '12rem !important', | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function panel(Panel $panel): Panel | ||||||
|  |     { | ||||||
|  |         return $panel | ||||||
|  |             ->default() | ||||||
|  |             ->id('admin') | ||||||
|  |             ->path('panel') | ||||||
|  |             ->login() | ||||||
|  |             ->brandName('Pelican') | ||||||
|  |             ->homeUrl('/') | ||||||
|  |             ->favicon('/pelican.ico') | ||||||
|  |             ->profile(EditProfile::class, false) | ||||||
|  |             ->colors([ | ||||||
|  |                 'danger' => Color::Red, | ||||||
|  |                 'gray' => Color::Zinc, | ||||||
|  |                 'info' => Color::Blue, | ||||||
|  |                 'primary' => Color::Sky, | ||||||
|  |                 'success' => Color::Green, | ||||||
|  |                 'warning' => 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, | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -35,11 +35,9 @@ class DaemonBackupRepository extends DaemonRepository | |||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/backup', $this->server->uuid), |                 sprintf('/api/servers/%s/backup', $this->server->uuid), | ||||||
|                 [ |                 [ | ||||||
|                     'json' => [ |                     'adapter' => $this->adapter ?? config('backups.default'), | ||||||
|                         'adapter' => $this->adapter ?? config('backups.default'), |                     'uuid' => $backup->uuid, | ||||||
|                         'uuid' => $backup->uuid, |                     'ignore' => implode("\n", $backup->ignored_files), | ||||||
|                         'ignore' => implode("\n", $backup->ignored_files), |  | ||||||
|                     ], |  | ||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
| @ -60,11 +58,9 @@ class DaemonBackupRepository extends DaemonRepository | |||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid), |                 sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid), | ||||||
|                 [ |                 [ | ||||||
|                     'json' => [ |                     'adapter' => $backup->disk, | ||||||
|                         'adapter' => $backup->disk, |                     'truncate_directory' => $truncate, | ||||||
|                         'truncate_directory' => $truncate, |                     'download_url' => $url ?? '', | ||||||
|                         'download_url' => $url ?? '', |  | ||||||
|                     ], |  | ||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
|  | |||||||
| @ -13,10 +13,13 @@ class DaemonConfigurationRepository extends DaemonRepository | |||||||
|      * |      * | ||||||
|      * @throws \App\Exceptions\Http\Connection\DaemonConnectionException |      * @throws \App\Exceptions\Http\Connection\DaemonConnectionException | ||||||
|      */ |      */ | ||||||
|     public function getSystemInformation(?int $version = null): array |     public function getSystemInformation(?int $version = null, $connectTimeout = 5): array | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $response = $this->getHttpClient()->get('/api/system' . (!is_null($version) ? '?v=' . $version : '')); |             $response = $this | ||||||
|  |                 ->getHttpClient() | ||||||
|  |                 ->connectTimeout($connectTimeout) | ||||||
|  |                 ->get('/api/system' . (!is_null($version) ? '?v=' . $version : '')); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
|             throw new DaemonConnectionException($exception); |             throw new DaemonConnectionException($exception); | ||||||
|         } |         } | ||||||
| @ -36,7 +39,7 @@ class DaemonConfigurationRepository extends DaemonRepository | |||||||
|         try { |         try { | ||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 '/api/update', |                 '/api/update', | ||||||
|                 ['json' => $node->getConfiguration()] |                 $node->getConfiguration(), | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
|             throw new DaemonConnectionException($exception); |             throw new DaemonConnectionException($exception); | ||||||
|  | |||||||
| @ -103,10 +103,8 @@ class DaemonFileRepository extends DaemonRepository | |||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/files/create-directory', $this->server->uuid), |                 sprintf('/api/servers/%s/files/create-directory', $this->server->uuid), | ||||||
|                 [ |                 [ | ||||||
|                     'json' => [ |                     'name' => $name, | ||||||
|                         'name' => $name, |                     'path' => $path, | ||||||
|                         'path' => $path, |  | ||||||
|                     ], |  | ||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
| @ -151,9 +149,7 @@ class DaemonFileRepository extends DaemonRepository | |||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/files/copy', $this->server->uuid), |                 sprintf('/api/servers/%s/files/copy', $this->server->uuid), | ||||||
|                 [ |                 [ | ||||||
|                     'json' => [ |                     'location' => $location, | ||||||
|                         'location' => $location, |  | ||||||
|                     ], |  | ||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
| @ -174,10 +170,8 @@ class DaemonFileRepository extends DaemonRepository | |||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/files/delete', $this->server->uuid), |                 sprintf('/api/servers/%s/files/delete', $this->server->uuid), | ||||||
|                 [ |                 [ | ||||||
|                     'json' => [ |                     'root' => $root ?? '/', | ||||||
|                         'root' => $root ?? '/', |                     'files' => $files, | ||||||
|                         'files' => $files, |  | ||||||
|                     ], |  | ||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
| @ -195,18 +189,17 @@ class DaemonFileRepository extends DaemonRepository | |||||||
|         Assert::isInstanceOf($this->server, Server::class); |         Assert::isInstanceOf($this->server, Server::class); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             $response = $this->getHttpClient()->post( |             $response = $this->getHttpClient() | ||||||
|                 sprintf('/api/servers/%s/files/compress', $this->server->uuid), |                 // Wait for up to 15 minutes for the archive to be completed when calling this endpoint
 | ||||||
|                 [ |                 // since it will likely take quite awhile for large directories.
 | ||||||
|                     'json' => [ |                 ->timeout(60 * 15) | ||||||
|  |                 ->post( | ||||||
|  |                     sprintf('/api/servers/%s/files/compress', $this->server->uuid), | ||||||
|  |                     [ | ||||||
|                         'root' => $root ?? '/', |                         'root' => $root ?? '/', | ||||||
|                         'files' => $files, |                         'files' => $files, | ||||||
|                     ], |                     ] | ||||||
|                     // Wait for up to 15 minutes for the archive to be completed when calling this endpoint
 |                 ); | ||||||
|                     // since it will likely take quite awhile for large directories.
 |  | ||||||
|                     'timeout' => 60 * 15, |  | ||||||
|                 ] |  | ||||||
|             ); |  | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
|             throw new DaemonConnectionException($exception); |             throw new DaemonConnectionException($exception); | ||||||
|         } |         } | ||||||
| @ -224,18 +217,17 @@ class DaemonFileRepository extends DaemonRepository | |||||||
|         Assert::isInstanceOf($this->server, Server::class); |         Assert::isInstanceOf($this->server, Server::class); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient() | ||||||
|                 sprintf('/api/servers/%s/files/decompress', $this->server->uuid), |                 // Wait for up to 15 minutes for the decompress to be completed when calling this endpoint
 | ||||||
|                 [ |                 // since it will likely take quite awhile for large directories.
 | ||||||
|                     'json' => [ |                 ->timeout((int) CarbonInterval::minutes(15)->totalSeconds) | ||||||
|  |                 ->post( | ||||||
|  |                     sprintf('/api/servers/%s/files/decompress', $this->server->uuid), | ||||||
|  |                     [ | ||||||
|                         'root' => $root ?? '/', |                         'root' => $root ?? '/', | ||||||
|                         'file' => $file, |                         'file' => $file, | ||||||
|                     ], |                     ] | ||||||
|                     // Wait for up to 15 minutes for the decompress to be completed when calling this endpoint
 |                 ); | ||||||
|                     // since it will likely take quite awhile for large directories.
 |  | ||||||
|                     'timeout' => (int) CarbonInterval::minutes(15)->totalSeconds, |  | ||||||
|                 ] |  | ||||||
|             ); |  | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
|             throw new DaemonConnectionException($exception); |             throw new DaemonConnectionException($exception); | ||||||
|         } |         } | ||||||
| @ -254,10 +246,8 @@ class DaemonFileRepository extends DaemonRepository | |||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/files/chmod', $this->server->uuid), |                 sprintf('/api/servers/%s/files/chmod', $this->server->uuid), | ||||||
|                 [ |                 [ | ||||||
|                     'json' => [ |                     'root' => $root ?? '/', | ||||||
|                         'root' => $root ?? '/', |                     'files' => $files, | ||||||
|                         'files' => $files, |  | ||||||
|                     ], |  | ||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
| @ -286,7 +276,7 @@ class DaemonFileRepository extends DaemonRepository | |||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/files/pull', $this->server->uuid), |                 sprintf('/api/servers/%s/files/pull', $this->server->uuid), | ||||||
|                 [ |                 [ | ||||||
|                     'json' => array_filter($attributes, fn ($value) => !is_null($value)), |                     array_filter($attributes, fn ($value) => !is_null($value)), | ||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ class DaemonPowerRepository extends DaemonRepository | |||||||
|         try { |         try { | ||||||
|             return $this->getHttpClient()->post( |             return $this->getHttpClient()->post( | ||||||
|                 sprintf('/api/servers/%s/power', $this->server->uuid), |                 sprintf('/api/servers/%s/power', $this->server->uuid), | ||||||
|                 ['json' => ['action' => $action]] |                 ['action' => $action], | ||||||
|             ); |             ); | ||||||
|         } catch (TransferException $exception) { |         } catch (TransferException $exception) { | ||||||
|             throw new DaemonConnectionException($exception); |             throw new DaemonConnectionException($exception); | ||||||
|  | |||||||
| @ -40,11 +40,9 @@ class DaemonServerRepository extends DaemonRepository | |||||||
|         Assert::isInstanceOf($this->server, Server::class); |         Assert::isInstanceOf($this->server, Server::class); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             $this->getHttpClient()->post('/api/servers', [ |             $response = $this->getHttpClient()->post('/api/servers', [ | ||||||
|                 'json' => [ |                 'uuid' => $this->server->uuid, | ||||||
|                     'uuid' => $this->server->uuid, |                 'start_on_completion' => $startOnCompletion, | ||||||
|                     'start_on_completion' => $startOnCompletion, |  | ||||||
|                 ], |  | ||||||
|             ]); |             ]); | ||||||
|         } catch (GuzzleException $exception) { |         } catch (GuzzleException $exception) { | ||||||
|             throw new DaemonConnectionException($exception); |             throw new DaemonConnectionException($exception); | ||||||
|  | |||||||
| @ -1,80 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Rules; |  | ||||||
| 
 |  | ||||||
| use Illuminate\Support\Arr; |  | ||||||
| use Illuminate\Contracts\Validation\Rule; |  | ||||||
| use Illuminate\Contracts\Validation\DataAwareRule; |  | ||||||
| 
 |  | ||||||
| class Fqdn implements DataAwareRule, Rule |  | ||||||
| { |  | ||||||
|     protected array $data = []; |  | ||||||
|     protected string $message = ''; |  | ||||||
|     protected ?string $schemeField = null; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * @param array $data |  | ||||||
|      */ |  | ||||||
|     public function setData($data): self |  | ||||||
|     { |  | ||||||
|         $this->data = $data; |  | ||||||
| 
 |  | ||||||
|         return $this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Validates that the value provided resolves to an IP address. If a scheme is |  | ||||||
|      * specified when this rule is created additional checks will be applied. |  | ||||||
|      * |  | ||||||
|      * @param string $attribute |  | ||||||
|      * @param mixed $value |  | ||||||
|      */ |  | ||||||
|     public function passes($attribute, $value): bool |  | ||||||
|     { |  | ||||||
|         if (filter_var($value, FILTER_VALIDATE_IP)) { |  | ||||||
|             // Check if the scheme is set to HTTPS.
 |  | ||||||
|             //
 |  | ||||||
|             // Unless someone owns their IP blocks and decides to pay who knows how much for a
 |  | ||||||
|             // custom SSL cert, IPs will not be able to use HTTPS.  This should prevent most
 |  | ||||||
|             // home users from making this mistake and wondering why their node is not working.
 |  | ||||||
|             if ($this->schemeField && Arr::get($this->data, $this->schemeField) === 'https') { |  | ||||||
|                 $this->message = 'The :attribute must not be an IP address when HTTPS is enabled.'; |  | ||||||
| 
 |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Lookup A and AAAA DNS records for the FQDN. Note, this function will also resolve CNAMEs
 |  | ||||||
|         // for us automatically, there is no need to manually resolve them here.
 |  | ||||||
|         //
 |  | ||||||
|         // The error suppression is intentional, see https://bugs.php.net/bug.php?id=73149
 |  | ||||||
|         $records = @dns_get_record($value, DNS_A + DNS_AAAA); |  | ||||||
|         // If no records were returned fall back to trying to resolve the value using the hosts DNS
 |  | ||||||
|         // resolution. This will not work for IPv6 which is why we prefer to use `dns_get_record`
 |  | ||||||
|         // first.
 |  | ||||||
|         if (!empty($records) || filter_var(gethostbyname($value), FILTER_VALIDATE_IP)) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $this->message = 'The :attribute could not be resolved to a valid IP address.'; |  | ||||||
| 
 |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function message(): string |  | ||||||
|     { |  | ||||||
|         return $this->message; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns a new instance of the rule with a defined scheme set. |  | ||||||
|      */ |  | ||||||
|     public static function make(string $schemeField = null): self |  | ||||||
|     { |  | ||||||
|         return tap(new self(), function ($fqdn) use ($schemeField) { |  | ||||||
|             $fqdn->schemeField = $schemeField; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -37,7 +37,7 @@ class AssignmentService | |||||||
|      * @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException |      * @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException | ||||||
|      * @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException |      * @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException | ||||||
|      */ |      */ | ||||||
|     public function handle(Node $node, array $data): void |     public function handle(Node $node, array $data): array | ||||||
|     { |     { | ||||||
|         $explode = explode('/', $data['allocation_ip']); |         $explode = explode('/', $data['allocation_ip']); | ||||||
|         if (count($explode) !== 1) { |         if (count($explode) !== 1) { | ||||||
| @ -58,6 +58,8 @@ class AssignmentService | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $this->connection->beginTransaction(); |         $this->connection->beginTransaction(); | ||||||
|  | 
 | ||||||
|  |         $ids = []; | ||||||
|         foreach ($parsed as $ip) { |         foreach ($parsed as $ip) { | ||||||
|             foreach ($data['allocation_ports'] as $port) { |             foreach ($data['allocation_ports'] as $port) { | ||||||
|                 if (!is_digit($port) && !preg_match(self::PORT_RANGE_REGEX, $port)) { |                 if (!is_digit($port) && !preg_match(self::PORT_RANGE_REGEX, $port)) { | ||||||
| @ -99,10 +101,12 @@ class AssignmentService | |||||||
|                     ]; |                     ]; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 Allocation::query()->insertOrIgnore($insertData); |                 $ids[] = Allocation::query()->insertOrIgnore($insertData); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $this->connection->commit(); |         $this->connection->commit(); | ||||||
|  | 
 | ||||||
|  |         return $ids; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -18,17 +18,19 @@ class EggParserService | |||||||
|      */ |      */ | ||||||
|     public function handle(UploadedFile $file): array |     public function handle(UploadedFile $file): array | ||||||
|     { |     { | ||||||
|         if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { |         if ($file->getError() !== UPLOAD_ERR_OK) { | ||||||
|             throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.'); |             throw new InvalidFileUploadException('The selected file was not uploaded successfully'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /** @var array $parsed */ |         $parsed = json_decode($file->getContent(), true, 512, JSON_THROW_ON_ERROR); | ||||||
|         $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); |  | ||||||
|         if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { |  | ||||||
|             throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.'); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return $this->convertToV2($parsed); |         $version = $parsed['meta']['version'] ?? ''; | ||||||
|  | 
 | ||||||
|  |         return match ($version) { | ||||||
|  |             'PTDL_v1' => $this->convertToV2($parsed), | ||||||
|  |             'PTDL_v2' => $parsed, | ||||||
|  |             default => throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.') | ||||||
|  |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -62,10 +64,6 @@ class EggParserService | |||||||
|      */ |      */ | ||||||
|     protected function convertToV2(array $parsed): array |     protected function convertToV2(array $parsed): array | ||||||
|     { |     { | ||||||
|         if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) { |  | ||||||
|             return $parsed; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Maintain backwards compatability for eggs that are still using the old single image
 |         // Maintain backwards compatability for eggs that are still using the old single image
 | ||||||
|         // string format. New eggs can provide an array of Docker images that can be used.
 |         // string format. New eggs can provide an array of Docker images that can be used.
 | ||||||
|         if (!isset($parsed['images'])) { |         if (!isset($parsed['images'])) { | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Services\Servers; | namespace App\Services\Servers; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use Illuminate\Database\ConnectionInterface; | use Illuminate\Database\ConnectionInterface; | ||||||
| use App\Repositories\Daemon\DaemonServerRepository; | use App\Repositories\Daemon\DaemonServerRepository; | ||||||
| @ -25,7 +26,7 @@ class ReinstallServerService | |||||||
|     public function handle(Server $server): Server |     public function handle(Server $server): Server | ||||||
|     { |     { | ||||||
|         return $this->connection->transaction(function () use ($server) { |         return $this->connection->transaction(function () use ($server) { | ||||||
|             $server->fill(['status' => Server::STATUS_INSTALLING])->save(); |             $server->fill(['status' => ServerState::Installing])->save(); | ||||||
| 
 | 
 | ||||||
|             $this->daemonServerRepository->setServer($server)->reinstall(); |             $this->daemonServerRepository->setServer($server)->reinstall(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Services\Servers; | namespace App\Services\Servers; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use App\Models\ServerVariable; | use App\Models\ServerVariable; | ||||||
| use Ramsey\Uuid\Uuid; | use Ramsey\Uuid\Uuid; | ||||||
| use Illuminate\Support\Arr; | use Illuminate\Support\Arr; | ||||||
| @ -132,7 +133,7 @@ class ServerCreationService | |||||||
|             'node_id' => Arr::get($data, 'node_id'), |             'node_id' => Arr::get($data, 'node_id'), | ||||||
|             'name' => Arr::get($data, 'name'), |             'name' => Arr::get($data, 'name'), | ||||||
|             'description' => Arr::get($data, 'description') ?? '', |             'description' => Arr::get($data, 'description') ?? '', | ||||||
|             'status' => Server::STATUS_INSTALLING, |             'status' => ServerState::Installing, | ||||||
|             'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']), |             'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']), | ||||||
|             'owner_id' => Arr::get($data, 'owner_id'), |             'owner_id' => Arr::get($data, 'owner_id'), | ||||||
|             'memory' => Arr::get($data, 'memory'), |             'memory' => Arr::get($data, 'memory'), | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Services\Servers; | namespace App\Services\Servers; | ||||||
| 
 | 
 | ||||||
|  | use App\Enums\ServerState; | ||||||
| use Webmozart\Assert\Assert; | use Webmozart\Assert\Assert; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Repositories\Daemon\DaemonServerRepository; | use App\Repositories\Daemon\DaemonServerRepository; | ||||||
| @ -44,7 +45,7 @@ class SuspensionService | |||||||
| 
 | 
 | ||||||
|         // Update the server's suspension status.
 |         // Update the server's suspension status.
 | ||||||
|         $server->update([ |         $server->update([ | ||||||
|             'status' => $isSuspending ? Server::STATUS_SUSPENDED : null, |             'status' => $isSuspending ? ServerState::Suspended : null, | ||||||
|         ]); |         ]); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
| @ -53,7 +54,7 @@ class SuspensionService | |||||||
|         } catch (\Exception $exception) { |         } catch (\Exception $exception) { | ||||||
|             // Rollback the server's suspension status if daemon fails to sync the server.
 |             // Rollback the server's suspension status if daemon fails to sync the server.
 | ||||||
|             $server->update([ |             $server->update([ | ||||||
|                 'status' => $isSuspending ? null : Server::STATUS_SUSPENDED, |                 'status' => $isSuspending ? null : ServerState::Suspended, | ||||||
|             ]); |             ]); | ||||||
|             throw $exception; |             throw $exception; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -9,6 +9,15 @@ trait AvailableLanguages | |||||||
| { | { | ||||||
|     private ?Filesystem $filesystem = null; |     private ?Filesystem $filesystem = null; | ||||||
| 
 | 
 | ||||||
|  |     public const TRANSLATED = [ | ||||||
|  |         'cz', | ||||||
|  |         'da', | ||||||
|  |         'de', | ||||||
|  |         'en', | ||||||
|  |         'es', | ||||||
|  |         'tr', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Return all the available languages on the Panel based on those |      * Return all the available languages on the Panel based on those | ||||||
|      * that are present in the language folder. |      * that are present in the language folder. | ||||||
| @ -18,12 +27,17 @@ trait AvailableLanguages | |||||||
|         return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) { |         return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) { | ||||||
|             $code = basename($path); |             $code = basename($path); | ||||||
| 
 | 
 | ||||||
|             $value = Locale::getDisplayName($code, app()->currentLocale()); |             $value = Locale::getDisplayName($code, $code); | ||||||
| 
 | 
 | ||||||
|             return [$code => title_case($value)]; |             return [$code => title_case($value)]; | ||||||
|         })->toArray(); |         })->toArray(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function isLanguageTranslated(string $countryCode = 'en'): bool | ||||||
|  |     { | ||||||
|  |         return in_array($countryCode, self::TRANSLATED, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Return an instance of the filesystem for getting a folder listing. |      * Return an instance of the filesystem for getting a folder listing. | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -11,6 +11,13 @@ if (!function_exists('is_digit')) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | if (!function_exists('is_ip')) { | ||||||
|  |     function is_ip(?string $address): bool | ||||||
|  |     { | ||||||
|  |         return $address !== null && filter_var($address, FILTER_VALIDATE_IP) !== false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| if (!function_exists('object_get_strict')) { | if (!function_exists('object_get_strict')) { | ||||||
|     /** |     /** | ||||||
|      * Get an object using dot notation. An object key with a value of null is still considered valid |      * Get an object using dot notation. An object key with a value of null is still considered valid | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ return [ | |||||||
|     App\Providers\BackupsServiceProvider::class, |     App\Providers\BackupsServiceProvider::class, | ||||||
|     App\Providers\BladeServiceProvider::class, |     App\Providers\BladeServiceProvider::class, | ||||||
|     App\Providers\EventServiceProvider::class, |     App\Providers\EventServiceProvider::class, | ||||||
|  |     App\Providers\Filament\AdminPanelProvider::class, | ||||||
|     App\Providers\HashidsServiceProvider::class, |     App\Providers\HashidsServiceProvider::class, | ||||||
|     App\Providers\RouteServiceProvider::class, |     App\Providers\RouteServiceProvider::class, | ||||||
|     App\Providers\ViewComposerServiceProvider::class, |     App\Providers\ViewComposerServiceProvider::class, | ||||||
|  | |||||||
| @ -9,8 +9,11 @@ | |||||||
|         "ext-pdo": "*", |         "ext-pdo": "*", | ||||||
|         "ext-pdo_mysql": "*", |         "ext-pdo_mysql": "*", | ||||||
|         "ext-zip": "*", |         "ext-zip": "*", | ||||||
|  |         "abdelhamiderrahmouni/filament-monaco-editor": "^0.2.0", | ||||||
|         "aws/aws-sdk-php": "~3.288.1", |         "aws/aws-sdk-php": "~3.288.1", | ||||||
|  |         "chillerlan/php-qrcode": "^5.0", | ||||||
|         "doctrine/dbal": "~3.6.0", |         "doctrine/dbal": "~3.6.0", | ||||||
|  |         "filament/filament": "^3.2", | ||||||
|         "guzzlehttp/guzzle": "^7.5", |         "guzzlehttp/guzzle": "^7.5", | ||||||
|         "hashids/hashids": "~5.0.0", |         "hashids/hashids": "~5.0.0", | ||||||
|         "laracasts/utilities": "~3.2.2", |         "laracasts/utilities": "~3.2.2", | ||||||
| @ -26,12 +29,14 @@ | |||||||
|         "pragmarx/google2fa": "~8.0.0", |         "pragmarx/google2fa": "~8.0.0", | ||||||
|         "predis/predis": "~2.1.1", |         "predis/predis": "~2.1.1", | ||||||
|         "prologue/alerts": "^1.2", |         "prologue/alerts": "^1.2", | ||||||
|  |         "ryangjchandler/blade-tabler-icons": "^2.3", | ||||||
|         "s1lentium/iptools": "~1.2.0", |         "s1lentium/iptools": "~1.2.0", | ||||||
|         "spatie/laravel-fractal": "^6.1", |         "spatie/laravel-fractal": "^6.1", | ||||||
|         "spatie/laravel-query-builder": "^5.8", |         "spatie/laravel-query-builder": "^5.8", | ||||||
|         "symfony/mailgun-mailer": "^7.0", |         "symfony/mailgun-mailer": "^7.0", | ||||||
|         "symfony/postmark-mailer": "^7.0", |         "symfony/postmark-mailer": "^7.0", | ||||||
|         "symfony/yaml": "^7.0", |         "symfony/yaml": "^7.0", | ||||||
|  |         "webbingbrasil/filament-copyactions": "^3.0", | ||||||
|         "webmozart/assert": "~1.11.0" |         "webmozart/assert": "~1.11.0" | ||||||
|     }, |     }, | ||||||
|     "require-dev": { |     "require-dev": { | ||||||
| @ -66,7 +71,8 @@ | |||||||
|         "cs:check": "php-cs-fixer fix --dry-run --diff --verbose", |         "cs:check": "php-cs-fixer fix --dry-run --diff --verbose", | ||||||
|         "post-autoload-dump": [ |         "post-autoload-dump": [ | ||||||
|             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", |             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", | ||||||
|             "@php artisan package:discover --ansi || true" |             "@php artisan package:discover --ansi || true", | ||||||
|  |             "@php artisan filament:upgrade" | ||||||
|         ], |         ], | ||||||
|         "post-root-package-install": [ |         "post-root-package-install": [ | ||||||
|             "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" |             "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" | ||||||
|  | |||||||
							
								
								
									
										2352
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2352
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,16 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| return [ |  | ||||||
|     /* |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | Egg Feature: EULA Popup |  | ||||||
|     |-------------------------------------------------------------------------- |  | ||||||
|     | |  | ||||||
|     | This popup is enabled for Minecraft eggs and allows a custom frontend |  | ||||||
|     | hook to run that monitors the console output of the server and pops up |  | ||||||
|     | a modal asking the user to accept it if necessary. |  | ||||||
|     | |  | ||||||
|     | There is no additional configuration necessary. |  | ||||||
|     | |  | ||||||
|     */ |  | ||||||
| ]; |  | ||||||
							
								
								
									
										270
									
								
								config/filament-monaco-editor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								config/filament-monaco-editor.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,270 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | return [ | ||||||
|  |     'general' => [ | ||||||
|  |         'enable-preview' => true, | ||||||
|  |         'show-full-screen-toggle' => true, | ||||||
|  |         'show-placeholder' => true, | ||||||
|  |         'placeholder-text' => 'Your code here...', | ||||||
|  |         'show-loader' => true, | ||||||
|  |         'font-size' => '15px', | ||||||
|  |         'line-numbers-min-chars' => true, | ||||||
|  |         'automatic-layout' => true, | ||||||
|  |         'default-theme' => 'iPlastic', | ||||||
|  |     ], | ||||||
|  |     'themes' => [ | ||||||
|  |         'blackboard' => [ | ||||||
|  |             'base' => 'vs-dark', | ||||||
|  |             'inherit' => true, | ||||||
|  |             'rules' => [ | ||||||
|  |                 [ | ||||||
|  |                     'background' => '0C1021', | ||||||
|  |                     'token' => '', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'aeaeae', | ||||||
|  |                     'token' => 'comment', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'd8fa3c', | ||||||
|  |                     'token' => 'constant', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ff6400', | ||||||
|  |                     'token' => 'entity', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'fbde2d', | ||||||
|  |                     'token' => 'keyword', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'fbde2d', | ||||||
|  |                     'token' => 'storage', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '61ce3c', | ||||||
|  |                     'token' => 'string', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '61ce3c', | ||||||
|  |                     'token' => 'meta.verbatim', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '8da6ce', | ||||||
|  |                     'token' => 'support', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ab2a1d', | ||||||
|  |                     'fontStyle' => 'italic', | ||||||
|  |                     'token' => 'invalid.deprecated', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'f8f8f8', | ||||||
|  |                     'background' => '9d1e15', | ||||||
|  |                     'token' => 'invalid.illegal', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ff6400', | ||||||
|  |                     'fontStyle' => 'italic', | ||||||
|  |                     'token' => 'entity.other.inherited-class', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ff6400', | ||||||
|  |                     'token' => 'string constant.other.placeholder', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'becde6', | ||||||
|  |                     'token' => 'meta.function-call.py', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '7f90aa', | ||||||
|  |                     'token' => 'meta.tag', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '7f90aa', | ||||||
|  |                     'token' => 'meta.tag entity', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ffffff', | ||||||
|  |                     'token' => 'entity.name.section', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'd5e0f3', | ||||||
|  |                     'token' => 'keyword.type.variant', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'f8f8f8', | ||||||
|  |                     'token' => 'source.ocaml keyword.operator.symbol', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '8da6ce', | ||||||
|  |                     'token' => 'source.ocaml keyword.operator.symbol.infix', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '8da6ce', | ||||||
|  |                     'token' => 'source.ocaml keyword.operator.symbol.prefix', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'underline', | ||||||
|  |                     'token' => 'source.ocaml keyword.operator.symbol.infix.floating-point', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'underline', | ||||||
|  |                     'token' => 'source.ocaml keyword.operator.symbol.prefix.floating-point', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'underline', | ||||||
|  |                     'token' => 'source.ocaml constant.numeric.floating-point', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'background' => 'ffffff08', | ||||||
|  |                     'token' => 'text.tex.latex meta.function.environment', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'background' => '7a96fa08', | ||||||
|  |                     'token' => 'text.tex.latex meta.function.environment meta.function.environment', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'fbde2d', | ||||||
|  |                     'token' => 'text.tex.latex support.function', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ffffff', | ||||||
|  |                     'token' => 'source.plist string.unquoted', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ffffff', | ||||||
|  |                     'token' => 'source.plist keyword.operator', | ||||||
|  |                 ], | ||||||
|  |             ], | ||||||
|  |             'colors' => [ | ||||||
|  |                 'editor.foreground' => '#F8F8F8', | ||||||
|  |                 'editor.background' => '#0C1021', | ||||||
|  |                 'editor.selectionBackground' => '#253B76', | ||||||
|  |                 'editor.lineHighlightBackground' => '#FFFFFF0F', | ||||||
|  |                 'editorCursor.foreground' => '#FFFFFFA6', | ||||||
|  |                 'editorWhitespace.foreground' => '#FFFFFF40', | ||||||
|  |             ], | ||||||
|  |         ], | ||||||
|  |         'iPlastic' => [ | ||||||
|  |             'base' => 'vs', | ||||||
|  |             'inherit' => true, | ||||||
|  |             'rules' => [ | ||||||
|  |                 [ | ||||||
|  |                     'background' => 'EEEEEEEB', | ||||||
|  |                     'token' => '', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '009933', | ||||||
|  |                     'token' => 'string', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '0066ff', | ||||||
|  |                     'token' => 'constant.numeric', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ff0080', | ||||||
|  |                     'token' => 'string.regexp', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '0000ff', | ||||||
|  |                     'token' => 'keyword', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '9700cc', | ||||||
|  |                     'token' => 'constant.language', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '990000', | ||||||
|  |                     'token' => 'support.class.exception', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ff8000', | ||||||
|  |                     'token' => 'entity.name.function', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'bold underline', | ||||||
|  |                     'token' => 'entity.name.type', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'italic', | ||||||
|  |                     'token' => 'variable.parameter', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '0066ff', | ||||||
|  |                     'fontStyle' => 'italic', | ||||||
|  |                     'token' => 'comment', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => 'ff0000', | ||||||
|  |                     'background' => 'e71a114d', | ||||||
|  |                     'token' => 'invalid', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'background' => 'e71a1100', | ||||||
|  |                     'token' => 'invalid.deprecated.trailing-whitespace', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '000000', | ||||||
|  |                     'background' => 'fafafafc', | ||||||
|  |                     'token' => 'text source', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '0033cc', | ||||||
|  |                     'token' => 'meta.tag', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '0033cc', | ||||||
|  |                     'token' => 'declaration.tag', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '6782d3', | ||||||
|  |                     'token' => 'constant', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '6782d3', | ||||||
|  |                     'token' => 'support.constant', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '3333ff', | ||||||
|  |                     'fontStyle' => 'bold', | ||||||
|  |                     'token' => 'support', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'bold', | ||||||
|  |                     'token' => 'storage', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'bold underline', | ||||||
|  |                     'token' => 'entity.name.section', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '000000', | ||||||
|  |                     'fontStyle' => 'bold', | ||||||
|  |                     'token' => 'entity.name.function.frame', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '333333', | ||||||
|  |                     'token' => 'meta.tag.preprocessor.xml', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'foreground' => '3366cc', | ||||||
|  |                     'fontStyle' => 'italic', | ||||||
|  |                     'token' => 'entity.other.attribute-name', | ||||||
|  |                 ], | ||||||
|  |                 [ | ||||||
|  |                     'fontStyle' => 'bold', | ||||||
|  |                     'token' => 'entity.name.tag', | ||||||
|  |                 ], | ||||||
|  |             ], | ||||||
|  |             'colors' => [ | ||||||
|  |                 'editor.foreground' => '#000000', | ||||||
|  |                 'editor.background' => '#EEEEEEEB', | ||||||
|  |                 'editor.selectionBackground' => '#BAD6FD', | ||||||
|  |                 'editor.lineHighlightBackground' => '#0000001A', | ||||||
|  |                 'editorCursor.foreground' => '#000000', | ||||||
|  |                 'editorWhitespace.foreground' => '#B3B3B3F4', | ||||||
|  |             ], | ||||||
|  |         ], | ||||||
|  |     ], | ||||||
|  | ]; | ||||||
| @ -24,7 +24,7 @@ return [ | |||||||
|     */ |     */ | ||||||
| 
 | 
 | ||||||
|     'service' => [ |     'service' => [ | ||||||
|         'author' => env('APP_SERVICE_AUTHOR', 'unknown@unknown.com'), |         'author' => env('APP_SERVICE_AUTHOR', 'unknown@example.com'), | ||||||
|     ], |     ], | ||||||
| 
 | 
 | ||||||
|     /* |     /* | ||||||
|  | |||||||
| @ -1,28 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| return [ |  | ||||||
|     /* |  | ||||||
|      * Set trusted proxy IP addresses. |  | ||||||
|      * |  | ||||||
|      * Both IPv4 and IPv6 addresses are |  | ||||||
|      * supported, along with CIDR notation. |  | ||||||
|      * |  | ||||||
|      * The "*" character is syntactic sugar |  | ||||||
|      * within TrustedProxy to trust any proxy |  | ||||||
|      * that connects directly to your server, |  | ||||||
|      * a requirement when you cannot know the address |  | ||||||
|      * of your proxy (e.g. if using Rackspace balancers). |  | ||||||
|      * |  | ||||||
|      * The "**" character is syntactic sugar within |  | ||||||
|      * TrustedProxy to trust not just any proxy that |  | ||||||
|      * connects directly to your server, but also |  | ||||||
|      * proxies that connect to those proxies, and all |  | ||||||
|      * the way back until you reach the original source |  | ||||||
|      * IP. It will mean that $request->getClientIp() |  | ||||||
|      * always gets the originating client IP, no matter |  | ||||||
|      * how many proxies that client's request has |  | ||||||
|      * subsequently passed through. |  | ||||||
|      */ |  | ||||||
|     'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? |  | ||||||
|         env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES') ?? ''), |  | ||||||
| ]; |  | ||||||
							
								
								
									
										96
									
								
								contributor_license_agreement.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								contributor_license_agreement.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | Thank you for your interest in Pelican ("Pelican Developers"). To clarify the intellectual property license | ||||||
|  | granted with Contributions from any person or entity, the Pelican Developers | ||||||
|  | must have on file a signed Contributor License Agreement ("CLA") | ||||||
|  | from each Contributor, indicating agreement with the license | ||||||
|  | terms below. This agreement is for your protection as a Contributor | ||||||
|  | as well as the protection of the Pelican Developers and its users. It does not | ||||||
|  | change your rights to use your own Contributions for any other purpose. | ||||||
|  | 
 | ||||||
|  | You accept and agree to the following terms and conditions for Your | ||||||
|  | Contributions (present and future) that you submit to the Pelican Developers. In | ||||||
|  | return, the Pelican Developers shall not use Your Contributions in a way that | ||||||
|  | is contrary to the public benefit or inconsistent with its nonprofit | ||||||
|  | status and bylaws in effect at the time of the Contribution. Except | ||||||
|  | for the license granted herein to the Pelican Developers and recipients of | ||||||
|  | software distributed by the Pelican Developers, You reserve all right, title, | ||||||
|  | and interest in and to Your Contributions. | ||||||
|  | 1. Definitions. | ||||||
|  |    "You" (or "Your") shall mean the copyright owner or legal entity | ||||||
|  |    authorized by the copyright owner that is making this Agreement | ||||||
|  |    with the Pelican Developers. For legal entities, the entity making a | ||||||
|  |    Contribution and all other entities that control, are controlled | ||||||
|  |    by, or are under common control with that entity are considered to | ||||||
|  |    be a single Contributor. For the purposes of this definition, | ||||||
|  |    "control" means (i) the power, direct or indirect, to cause the | ||||||
|  |    direction or management of such entity, whether by contract or | ||||||
|  |    otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||||
|  |    outstanding shares, or (iii) beneficial ownership of such entity. | ||||||
|  |    "Contribution" shall mean any original work of authorship, | ||||||
|  |    including any modifications or additions to an existing work, that | ||||||
|  |    is intentionally submitted by You to the Pelican Developers for inclusion | ||||||
|  |    in, or documentation of, any of the products owned or managed by | ||||||
|  |    the Pelican Developers (the "Work"). For the purposes of this definition, | ||||||
|  |    "submitted" means any form of electronic, verbal, or written | ||||||
|  |    communication sent to the Pelican Developers or its representatives, | ||||||
|  |    including but not limited to communication on electronic mailing | ||||||
|  |    lists, source code control systems, and issue tracking systems that | ||||||
|  |    are managed by, or on behalf of, the Pelican Developers for the purpose of | ||||||
|  |    discussing and improving the Work, but excluding communication that | ||||||
|  |    is conspicuously marked or otherwise designated in writing by You | ||||||
|  |    as "Not a Contribution." | ||||||
|  | 2. Grant of Copyright License. Subject to the terms and conditions of | ||||||
|  |    this Agreement, You hereby grant to the Pelican Developers and to | ||||||
|  |    recipients of software distributed by the Pelican Developers a perpetual, | ||||||
|  |    worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||||
|  |    copyright license to reproduce, prepare derivative works of, | ||||||
|  |    publicly display, publicly perform, sublicense, and distribute Your | ||||||
|  |    Contributions and such derivative works. | ||||||
|  | 3. Grant of Patent License. Subject to the terms and conditions of | ||||||
|  |    this Agreement, You hereby grant to the Pelican Developers and to | ||||||
|  |    recipients of software distributed by the Pelican Developers a perpetual, | ||||||
|  |    worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||||
|  |    (except as stated in this section) patent license to make, have | ||||||
|  |    made, use, offer to sell, sell, import, and otherwise transfer the | ||||||
|  |    Work, where such license applies only to those patent claims | ||||||
|  |    licensable by You that are necessarily infringed by Your | ||||||
|  |    Contribution(s) alone or by combination of Your Contribution(s) | ||||||
|  |    with the Work to which such Contribution(s) was submitted. If any | ||||||
|  |    entity institutes patent litigation against You or any other entity | ||||||
|  |    (including a cross-claim or counterclaim in a lawsuit) alleging | ||||||
|  |    that your Contribution, or the Work to which you have contributed, | ||||||
|  |    constitutes direct or contributory patent infringement, then any | ||||||
|  |    patent licenses granted to that entity under this Agreement for | ||||||
|  |    that Contribution or Work shall terminate as of the date such | ||||||
|  |    litigation is filed. | ||||||
|  | 4. You represent that you are legally entitled to grant the above | ||||||
|  |    license. If your employer(s) has rights to intellectual property | ||||||
|  |    that you create that includes your Contributions, you represent | ||||||
|  |    that you have received permission to make Contributions on behalf | ||||||
|  |    of that employer, that your employer has waived such rights for | ||||||
|  |    your Contributions to the Pelican Developers, or that your employer has | ||||||
|  |    executed a separate Corporate CLA with the Pelican Developers. | ||||||
|  | 5. You represent that each of Your Contributions is Your original | ||||||
|  |    creation (see section 7 for submissions on behalf of others). You | ||||||
|  |    represent that Your Contribution submissions include complete | ||||||
|  |    details of any third-party license or other restriction (including, | ||||||
|  |    but not limited to, related patents and trademarks) of which you | ||||||
|  |    are personally aware and which are associated with any part of Your | ||||||
|  |    Contributions. | ||||||
|  | 6. You are not expected to provide support for Your Contributions, | ||||||
|  |    except to the extent You desire to provide support. You may provide | ||||||
|  |    support for free, for a fee, or not at all. Unless required by | ||||||
|  |    applicable law or agreed to in writing, You provide Your | ||||||
|  |    Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS | ||||||
|  |    OF ANY KIND, either express or implied, including, without | ||||||
|  |    limitation, any warranties or conditions of TITLE, NON- | ||||||
|  |    INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. | ||||||
|  | 7. Should You wish to submit work that is not Your original creation, | ||||||
|  |    You may submit it to the Pelican Developers separately from any | ||||||
|  |    Contribution, identifying the complete details of its source and of | ||||||
|  |    any license or other restriction (including, but not limited to, | ||||||
|  |    related patents, trademarks, and license agreements) of which you | ||||||
|  |    are personally aware, and conspicuously marking the work as | ||||||
|  |    "Submitted on behalf of a third-party: [named here]". | ||||||
|  | 8. You agree to notify the Pelican Developers of any facts or circumstances of | ||||||
|  |    which you become aware that would make these representations | ||||||
|  |    inaccurate in any respect. | ||||||
							
								
								
									
										11
									
								
								crowdin.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								crowdin.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | files: | ||||||
|  |   - source: /lang/en/*.php | ||||||
|  |     translation: /lang/%two_letters_code%/%original_file_name% | ||||||
|  |   - source: /lang/en/admin | ||||||
|  |     translation: /lang/%two_letters_code%/admin/%original_file_name% | ||||||
|  |   - source: /lang/en/command | ||||||
|  |     translation: /lang/%two_letters_code%/command/%original_file_name% | ||||||
|  |   - source: /lang/en/dashboard | ||||||
|  |     translation: /lang/%two_letters_code%/dashboard/%original_file_name% | ||||||
|  |   - source: /lang/en/server | ||||||
|  |     translation: /lang/%two_letters_code%/server/%original_file_name% | ||||||
| @ -14,10 +14,10 @@ | |||||||
|         "pid_limit" |         "pid_limit" | ||||||
|     ], |     ], | ||||||
|     "docker_images": { |     "docker_images": { | ||||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", |         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", |         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", |         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" |         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||||
|     }, |     }, | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", |     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", | ||||||
| @ -30,7 +30,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/ash\r\n# Bungeecord Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${BUNGEE_VERSION}\" ] || [ \"${BUNGEE_VERSION}\" == \"latest\" ]; then\r\n    BUNGEE_VERSION=\"lastStableBuild\"\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/ci.md-5.net\/job\/BungeeCord\/${BUNGEE_VERSION}\/artifact\/bootstrap\/target\/BungeeCord.jar", |             "script": "#!\/bin\/ash\r\n# Bungeecord Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${BUNGEE_VERSION}\" ] || [ \"${BUNGEE_VERSION}\" == \"latest\" ]; then\r\n    BUNGEE_VERSION=\"lastStableBuild\"\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/ci.md-5.net\/job\/BungeeCord\/${BUNGEE_VERSION}\/artifact\/bootstrap\/target\/BungeeCord.jar", | ||||||
|             "container": "ghcr.io\/App\/installers:alpine", |             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||||
|             "entrypoint": "ash" |             "entrypoint": "ash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -14,10 +14,10 @@ | |||||||
|         "pid_limit" |         "pid_limit" | ||||||
|     ], |     ], | ||||||
|     "docker_images": { |     "docker_images": { | ||||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", |         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", |         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", |         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" |         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||||
|     }, |     }, | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true $( [[  ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )", |     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true $( [[  ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )", | ||||||
|  | |||||||
| @ -14,10 +14,10 @@ | |||||||
|         "pid_limit" |         "pid_limit" | ||||||
|     ], |     ], | ||||||
|     "docker_images": { |     "docker_images": { | ||||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", |         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", |         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", |         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" |         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||||
|     }, |     }, | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", |     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", | ||||||
| @ -30,7 +30,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n    echo -e \"Downloading MC server.properties\"\r\n    curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi", |             "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n    echo -e \"Downloading MC server.properties\"\r\n    curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi", | ||||||
|             "container": "ghcr.io\/App\/installers:alpine", |             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||||
|             "entrypoint": "ash" |             "entrypoint": "ash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -14,9 +14,9 @@ | |||||||
|         "pid_limit" |         "pid_limit" | ||||||
|     ], |     ], | ||||||
|     "docker_images": { |     "docker_images": { | ||||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", |         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", |         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" |         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||||
|     }, |     }, | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", |     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", | ||||||
| @ -29,7 +29,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/ash\r\n# Sponge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sSL \"https:\/\/repo.spongepowered.org\/maven\/org\/spongepowered\/spongevanilla\/${SPONGE_VERSION}\/spongevanilla-${SPONGE_VERSION}.jar\" -o ${SERVER_JARFILE}", |             "script": "#!\/bin\/ash\r\n# Sponge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sSL \"https:\/\/repo.spongepowered.org\/maven\/org\/spongepowered\/spongevanilla\/${SPONGE_VERSION}\/spongevanilla-${SPONGE_VERSION}.jar\" -o ${SERVER_JARFILE}", | ||||||
|             "container": "ghcr.io\/App\/installers:alpine", |             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||||
|             "entrypoint": "ash" |             "entrypoint": "ash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -14,10 +14,10 @@ | |||||||
|         "pid_limit" |         "pid_limit" | ||||||
|     ], |     ], | ||||||
|     "docker_images": { |     "docker_images": { | ||||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", |         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", |         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", |         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" |         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||||
|     }, |     }, | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", |     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", | ||||||
| @ -30,7 +30,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\nLATEST_SNAPSHOT_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.snapshot'`\r\n\r\necho -e \"latest version is $LATEST_VERSION\"\r\necho -e \"latest snapshot is $LATEST_SNAPSHOT_VERSION\"\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelif [ \"$VANILLA_VERSION\" == \"snapshot\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_SNAPSHOT_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelse\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $VANILLA_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nfi\r\n\r\nDOWNLOAD_URL=$(curl ${MANIFEST_URL} | jq .downloads.server | jq -r '. | .url')\r\n\r\necho -e \"running: curl -o ${SERVER_JARFILE} $DOWNLOAD_URL\"\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL\r\n\r\necho -e \"Install Complete\"", |             "script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\nLATEST_SNAPSHOT_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.snapshot'`\r\n\r\necho -e \"latest version is $LATEST_VERSION\"\r\necho -e \"latest snapshot is $LATEST_SNAPSHOT_VERSION\"\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelif [ \"$VANILLA_VERSION\" == \"snapshot\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_SNAPSHOT_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelse\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $VANILLA_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nfi\r\n\r\nDOWNLOAD_URL=$(curl ${MANIFEST_URL} | jq .downloads.server | jq -r '. | .url')\r\n\r\necho -e \"running: curl -o ${SERVER_JARFILE} $DOWNLOAD_URL\"\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL\r\n\r\necho -e \"Install Complete\"", | ||||||
|             "container": "ghcr.io\/App\/installers:alpine", |             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||||
|             "entrypoint": "ash" |             "entrypoint": "ash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ | |||||||
|         "steam_disk_space" |         "steam_disk_space" | ||||||
|     ], |     ], | ||||||
|     "docker_images": { |     "docker_images": { | ||||||
|         "ghcr.io\/App\/games:rust": "ghcr.io\/App\/games:rust" |         "ghcr.io\/pterodactyl\/games:rust": "ghcr.io\/pterodactyl\/games:rust" | ||||||
|     }, |     }, | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}}  $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}", |     "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}}  $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}", | ||||||
| @ -25,7 +25,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nSRCDS_APPID=258550\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", |             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nSRCDS_APPID=258550\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", | ||||||
|             "container": "ghcr.io\/App\/installers:debian", |             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||||
|             "entrypoint": "bash" |             "entrypoint": "bash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
|         "steam_disk_space" |         "steam_disk_space" | ||||||
|     ], |     ], | ||||||
|     "images": [ |     "images": [ | ||||||
|         "ghcr.io\/App\/games:source" |         "ghcr.io\/pterodactyl\/games:source" | ||||||
|     ], |     ], | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}", |     "startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}", | ||||||
| @ -26,7 +26,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", |             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", | ||||||
|             "container": "ghcr.io\/App\/installers:debian", |             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||||
|             "entrypoint": "bash" |             "entrypoint": "bash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ | |||||||
|         "steam_disk_space" |         "steam_disk_space" | ||||||
|     ], |     ], | ||||||
|     "images": [ |     "images": [ | ||||||
|         "ghcr.io\/App\/games:source" |         "ghcr.io\/pterodactyl\/games:source" | ||||||
|     ], |     ], | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", |     "startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", | ||||||
| @ -25,7 +25,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", |             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", | ||||||
|             "container": "ghcr.io\/App\/installers:debian", |             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||||
|             "entrypoint": "bash" |             "entrypoint": "bash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
|         "steam_disk_space" |         "steam_disk_space" | ||||||
|     ], |     ], | ||||||
|     "images": [ |     "images": [ | ||||||
|         "ghcr.io\/App\/games:source" |         "ghcr.io\/pterodactyl\/games:source" | ||||||
|     ], |     ], | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}}  -tickrate {{TICKRATE}}  $( [ \"$LUA_REFRESH\" == \"1\" ] || printf %s '-disableluarefresh' )", |     "startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}}  -tickrate {{TICKRATE}}  $( [ \"$LUA_REFRESH\" == \"1\" ] || printf %s '-disableluarefresh' )", | ||||||
| @ -26,7 +26,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl   \"\"\r\nsv_downloadurl  \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t    0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg", |             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl   \"\"\r\nsv_downloadurl  \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t    0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg", | ||||||
|             "container": "ghcr.io\/App\/installers:debian", |             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||||
|             "entrypoint": "bash" |             "entrypoint": "bash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ | |||||||
|         "steam_disk_space" |         "steam_disk_space" | ||||||
|     ], |     ], | ||||||
|     "images": [ |     "images": [ | ||||||
|         "ghcr.io\/App\/games:source" |         "ghcr.io\/pterodactyl\/games:source" | ||||||
|     ], |     ], | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": ".\/srcds_run -game insurgency -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", |     "startup": ".\/srcds_run -game insurgency -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", | ||||||
| @ -25,7 +25,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login anonymous +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", |             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login anonymous +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", | ||||||
|             "container": "ghcr.io\/App\/installers:debian", |             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||||
|             "entrypoint": "bash" |             "entrypoint": "bash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
|         "steam_disk_space" |         "steam_disk_space" | ||||||
|     ], |     ], | ||||||
|     "images": [ |     "images": [ | ||||||
|         "ghcr.io\/App\/games:source" |         "ghcr.io\/pterodactyl\/games:source" | ||||||
|     ], |     ], | ||||||
|     "file_denylist": [], |     "file_denylist": [], | ||||||
|     "startup": ".\/srcds_run -game tf -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}", |     "startup": ".\/srcds_run -game tf -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}", | ||||||
| @ -26,7 +26,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", |             "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", | ||||||
|             "container": "ghcr.io\/App\/installers:debian", |             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||||
|             "entrypoint": "bash" |             "entrypoint": "bash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "installation": { |         "installation": { | ||||||
|             "script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n    mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n    echo \"Config file already exists.\"\r\nelse \r\n    echo \"Downloading the config file.\"\r\n    apk add --no-cache murmur\r\n    cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n    apk del murmur\r\nfi\r\necho \"done\"", |             "script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n    mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n    echo \"Config file already exists.\"\r\nelse \r\n    echo \"Downloading the config file.\"\r\n    apk add --no-cache murmur\r\n    cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n    apk del murmur\r\nfi\r\necho \"done\"", | ||||||
|             "container": "ghcr.io\/App\/installers:alpine", |             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||||
|             "entrypoint": "ash" |             "entrypoint": "ash" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 notCharles
						notCharles