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] | ||||
|  | ||||
							
								
								
									
										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: | ||||
| #				 /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 { | ||||
|     listen 80; | ||||
|     server_name _; | ||||
|  | ||||
							
								
								
									
										3
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							| @ -1,9 +1,6 @@ | ||||
| name: Build | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - '**' | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '**' | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| name: Tests | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|   pull_request: | ||||
|     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 | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - '**' | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - '**' | ||||
|  | ||||
| @ -44,7 +44,6 @@ class InfoCommand extends Command | ||||
|             ['Session Driver', config('session.driver')], | ||||
|             ['Filesystem Driver', config('filesystems.default')], | ||||
|             ['Default Theme', config('themes.active')], | ||||
|             ['Proxies', config('trustedproxies.proxies')], | ||||
|         ], 'compact'); | ||||
| 
 | ||||
|         $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; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use App\Models\Server; | ||||
| 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.'; | ||||
|         } elseif (!$server->isInstalled()) { | ||||
|             $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.'; | ||||
|         } elseif (!is_null($server->transfer)) { | ||||
|             $message = 'This server is currently being transferred to a new machine, please try again later.'; | ||||
|  | ||||
| @ -1,5 +1,28 @@ | ||||
| <?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; | ||||
| 
 | ||||
| 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) | ||||
|             ->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', [ | ||||
|             'node' => $node, | ||||
|             'stats' => $usageStats, | ||||
|             'version' => $this->versionService, | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
| @ -2,12 +2,12 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers\Admin\Servers; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use App\Models\DatabaseHost; | ||||
| use App\Models\Egg; | ||||
| use App\Models\Mount; | ||||
| use App\Models\Node; | ||||
| use Illuminate\View\View; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\Server; | ||||
| use App\Exceptions\DisplayException; | ||||
| use App\Http\Controllers\Controller; | ||||
| @ -22,14 +22,14 @@ class ServerViewController extends Controller | ||||
|      * ServerViewController constructor. | ||||
|      */ | ||||
|     public function __construct( | ||||
|         private EnvironmentService $environmentService, | ||||
|         private readonly EnvironmentService $environmentService, | ||||
|     ) { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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')); | ||||
|     } | ||||
| @ -37,7 +37,7 @@ class ServerViewController extends Controller | ||||
|     /** | ||||
|      * 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')); | ||||
|     } | ||||
| @ -45,7 +45,7 @@ class ServerViewController extends Controller | ||||
|     /** | ||||
|      * 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(); | ||||
| 
 | ||||
| @ -59,7 +59,7 @@ class ServerViewController extends Controller | ||||
|     /** | ||||
|      * 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); | ||||
|         $eggs = Egg::all()->keyBy('id'); | ||||
| @ -76,7 +76,7 @@ class ServerViewController extends Controller | ||||
|     /** | ||||
|      * 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', [ | ||||
|             'hosts' => DatabaseHost::all(), | ||||
| @ -87,7 +87,7 @@ class ServerViewController extends Controller | ||||
|     /** | ||||
|      * 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'); | ||||
| 
 | ||||
| @ -108,9 +108,9 @@ class ServerViewController extends Controller | ||||
|      * | ||||
|      * @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.'); | ||||
|         } | ||||
| 
 | ||||
| @ -135,7 +135,7 @@ class ServerViewController extends Controller | ||||
|     /** | ||||
|      * 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')); | ||||
|     } | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers\Admin; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\User; | ||||
| use Illuminate\Http\Response; | ||||
| @ -71,11 +72,11 @@ class ServersController extends Controller | ||||
|      */ | ||||
|     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')); | ||||
|         } | ||||
| 
 | ||||
|         $server->status = $server->isInstalled() ? Server::STATUS_INSTALLING : null; | ||||
|         $server->status = $server->isInstalled() ? ServerState::Installing : null; | ||||
|         $server->save(); | ||||
| 
 | ||||
|         $this->alert->success(trans('admin/server.alerts.install_toggled'))->flash(); | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers\Api\Client\Servers; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\Backup; | ||||
| 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
 | ||||
|             // 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')); | ||||
|         }); | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers\Api\Remote\Servers; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use App\Models\Backup; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\Server; | ||||
| @ -81,7 +82,7 @@ class ServerDetailsController extends Controller | ||||
|                     ->latest('timestamp'), | ||||
|             ]) | ||||
|             ->where('node_id', $node->id) | ||||
|             ->where('status', Server::STATUS_RESTORING_BACKUP) | ||||
|             ->where('status', ServerState::RestoringBackup) | ||||
|             ->get(); | ||||
| 
 | ||||
|         $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
 | ||||
|             // at this point in the process.
 | ||||
|             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]); | ||||
|         }); | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers\Api\Remote\Servers; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use Illuminate\Http\Response; | ||||
| use App\Models\Server; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| @ -36,16 +37,16 @@ class ServerInstallController extends Controller | ||||
| 
 | ||||
|         // Make sure the type of failure is accurate
 | ||||
|         if (!$request->boolean('successful')) { | ||||
|             $status = Server::STATUS_INSTALL_FAILED; | ||||
|             $status = ServerState::InstallFailed; | ||||
| 
 | ||||
|             if ($request->boolean('reinstall')) { | ||||
|                 $status = Server::STATUS_REINSTALL_FAILED; | ||||
|                 $status = ServerState::ReinstallFailed; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Keep the server suspended if it's already suspended
 | ||||
|         if ($server->status === Server::STATUS_SUSPENDED) { | ||||
|             $status = Server::STATUS_SUSPENDED; | ||||
|         if ($server->status === ServerState::Suspended) { | ||||
|             $status = ServerState::Suspended; | ||||
|         } | ||||
| 
 | ||||
|         $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; | ||||
| 
 | ||||
| use App\Rules\Fqdn; | ||||
| use App\Models\Node; | ||||
| use App\Http\Requests\Admin\AdminFormRequest; | ||||
| 
 | ||||
| @ -17,9 +16,6 @@ class NodeFormRequest extends AdminFormRequest | ||||
|             return Node::getRulesForUpdate($this->route()->parameter('node')); | ||||
|         } | ||||
| 
 | ||||
|         $data = Node::getRules(); | ||||
|         $data['fqdn'][] = Fqdn::make('scheme'); | ||||
| 
 | ||||
|         return $data; | ||||
|         return Node::getRules(); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										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)); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
| 
 | ||||
| use App\Exceptions\Service\Allocation\ServerUsingAllocationException; | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||
| 
 | ||||
| /** | ||||
| @ -111,9 +112,16 @@ class Allocation extends Model | ||||
|         return !is_null($this->ip_alias); | ||||
|     } | ||||
| 
 | ||||
|     public function address(): Attribute | ||||
|     { | ||||
|         return Attribute::make( | ||||
|             get: fn () => "$this->ip:$this->port", | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     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 RESOURCES = ['servers', 'nodes', 'allocations', 'users', 'eggs', 'database_hosts', 'server_databases']; | ||||
| 
 | ||||
|     /** | ||||
|      * The table associated with the model. | ||||
|      */ | ||||
| @ -92,12 +94,21 @@ class ApiKey extends Model | ||||
|      * Fields that are mass assignable. | ||||
|      */ | ||||
|     protected $fillable = [ | ||||
|         'user_id', | ||||
|         'key_type', | ||||
|         'identifier', | ||||
|         'token', | ||||
|         'allowed_ips', | ||||
|         'memo', | ||||
|         'last_used_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]); | ||||
| 
 | ||||
|         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. | ||||
|      */ | ||||
|  | ||||
| @ -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 | ||||
|      * it will return the copied script. | ||||
|  | ||||
| @ -71,8 +71,8 @@ class Mount extends Model | ||||
|      * Blacklisted source paths. | ||||
|      */ | ||||
|     public static $invalidSourcePaths = [ | ||||
|         '/etc/panel', | ||||
|         '/var/lib/panel/volumes', | ||||
|         '/etc/pelican', | ||||
|         '/var/lib/pelican/volumes', | ||||
|         '/srv/daemon-data', | ||||
|     ]; | ||||
| 
 | ||||
| @ -115,4 +115,9 @@ class Mount extends Model | ||||
|     { | ||||
|         return $this->belongsToMany(Server::class); | ||||
|     } | ||||
| 
 | ||||
|     public function getRouteKeyName(): string | ||||
|     { | ||||
|         return 'id'; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,6 +2,10 @@ | ||||
| 
 | ||||
| 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 Symfony\Component\Yaml\Yaml; | ||||
| use Illuminate\Notifications\Notifiable; | ||||
| @ -79,9 +83,9 @@ class Node extends Model | ||||
|         'fqdn' => 'required|string', | ||||
|         'scheme' => 'required', | ||||
|         'behind_proxy' => 'boolean', | ||||
|         'memory' => 'required|numeric|min:1', | ||||
|         'memory' => 'required|numeric|min:0', | ||||
|         'memory_overallocate' => 'required|numeric|min:-1', | ||||
|         'disk' => 'required|numeric|min:1', | ||||
|         'disk' => 'required|numeric|min:0', | ||||
|         'disk_overallocate' => 'required|numeric|min:-1', | ||||
|         'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', | ||||
|         'daemon_sftp' => 'required|numeric|between:1,65535', | ||||
| @ -96,7 +100,9 @@ class Node extends Model | ||||
|     protected $attributes = [ | ||||
|         'public' => true, | ||||
|         'behind_proxy' => false, | ||||
|         'memory' => 0, | ||||
|         'memory_overallocate' => 0, | ||||
|         'disk' => 0, | ||||
|         'disk_overallocate' => 0, | ||||
|         'daemon_base' => '/var/lib/pelican/volumes', | ||||
|         '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. | ||||
|      */ | ||||
| @ -240,4 +266,40 @@ class Node extends Model | ||||
|             ]; | ||||
|         })->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; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use App\Exceptions\Http\Connection\DaemonConnectionException; | ||||
| use GuzzleHttp\Exception\GuzzleException; | ||||
| use Illuminate\Notifications\Notifiable; | ||||
| @ -112,12 +113,6 @@ class Server extends Model | ||||
|      */ | ||||
|     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. | ||||
|      */ | ||||
| @ -128,7 +123,7 @@ class Server extends Model | ||||
|      * on server instances unless the user specifies otherwise in the request. | ||||
|      */ | ||||
|     protected $attributes = [ | ||||
|         'status' => self::STATUS_INSTALLING, | ||||
|         'status' => ServerState::Installing, | ||||
|         'oom_disabled' => true, | ||||
|         'installed_at' => null, | ||||
|     ]; | ||||
| @ -152,7 +147,7 @@ class Server extends Model | ||||
|         'status' => 'nullable|string', | ||||
|         'memory' => 'required|numeric|min:0', | ||||
|         'swap' => 'required|numeric|min:-1', | ||||
|         'io' => 'required|numeric|between:10,1000', | ||||
|         'io' => 'required|numeric|between:0,1000', | ||||
|         'cpu' => 'required|numeric|min:0', | ||||
|         'threads' => 'nullable|regex:/^[0-9-,]+$/', | ||||
|         'oom_disabled' => 'sometimes|boolean', | ||||
| @ -171,6 +166,7 @@ class Server extends Model | ||||
|     { | ||||
|         return [ | ||||
|             'node_id' => 'integer', | ||||
|             'status' => ServerState::class, | ||||
|             'skip_scripts' => 'boolean', | ||||
|             'owner_id' => 'integer', | ||||
|             'memory' => 'integer', | ||||
| @ -203,12 +199,12 @@ class Server extends Model | ||||
| 
 | ||||
|     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 | ||||
|     { | ||||
|         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'); | ||||
|     } | ||||
| 
 | ||||
|     public function eggVariables(): HasMany | ||||
|     { | ||||
|         return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
| @ -349,7 +355,7 @@ class Server extends Model | ||||
|             $this->isSuspended() || | ||||
|             $this->node->isUnderMaintenance() || | ||||
|             !$this->isInstalled() || | ||||
|             $this->status === self::STATUS_RESTORING_BACKUP || | ||||
|             $this->status === ServerState::RestoringBackup || | ||||
|             !is_null($this->transfer) | ||||
|         ) { | ||||
|             throw new ServerStateConflictException($this); | ||||
| @ -366,7 +372,7 @@ class Server extends Model | ||||
|     { | ||||
|         if ( | ||||
|             !$this->isInstalled() || | ||||
|             $this->status === self::STATUS_RESTORING_BACKUP || | ||||
|             $this->status === ServerState::RestoringBackup || | ||||
|             !is_null($this->transfer) | ||||
|         ) { | ||||
|             throw new ServerStateConflictException($this); | ||||
| @ -388,4 +394,9 @@ class Server extends Model | ||||
|             throw new DaemonConnectionException($exception); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function retrieveStatus() | ||||
|     { | ||||
|         return $this->node->serverStatuses(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -5,7 +5,11 @@ namespace App\Models; | ||||
| use App\Exceptions\DisplayException; | ||||
| use App\Rules\Username; | ||||
| 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\Auth\Authenticatable; | ||||
| use Illuminate\Notifications\Notifiable; | ||||
| @ -79,7 +83,7 @@ use App\Notifications\SendPasswordReset as ResetPasswordNotification; | ||||
|  * | ||||
|  * @mixin \Eloquent | ||||
|  */ | ||||
| class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract | ||||
| class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName | ||||
| { | ||||
|     use Authenticatable; | ||||
|     use Authorizable {can as protected canned; } | ||||
| @ -139,18 +143,20 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | ||||
|         'language' => 'en', | ||||
|         'use_totp' => false, | ||||
|         'totp_secret' => null, | ||||
|         'name_first' => '', | ||||
|         'name_last' => '', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * Rules verifying that the data being stored matches the expectations of the database. | ||||
|      */ | ||||
|     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', | ||||
|         'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', | ||||
|         'username' => 'required|between:1,191|unique:users,username', | ||||
|         'name_first' => 'required|string|between:1,191', | ||||
|         'name_last' => 'required|string|between:1,191', | ||||
|         'name_first' => 'nullable|string|between:0,191', | ||||
|         'name_last' => 'nullable|string|between:0,191', | ||||
|         'password' => 'sometimes|nullable|string', | ||||
|         'root_admin' => 'boolean', | ||||
|         'language' => 'string', | ||||
| @ -170,6 +176,12 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | ||||
| 
 | ||||
|     protected static function booted(): void | ||||
|     { | ||||
|         static::creating(function (self $user) { | ||||
|             $user->uuid = Str::uuid(); | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|         static::deleting(function (self $user) { | ||||
|             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 | ||||
|      * rules function. | ||||
| @ -196,7 +213,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac | ||||
|      */ | ||||
|     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'); | ||||
|     } | ||||
| 
 | ||||
|     public function subusers(): HasMany | ||||
|     { | ||||
|         return $this->hasMany(Subuser::class); | ||||
|     } | ||||
| 
 | ||||
|     protected function checkPermission(Server $server, string $permission = ''): bool | ||||
|     { | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     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( | ||||
|                 sprintf('/api/servers/%s/backup', $this->server->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|                         'adapter' => $this->adapter ?? config('backups.default'), | ||||
|                         'uuid' => $backup->uuid, | ||||
|                         'ignore' => implode("\n", $backup->ignored_files), | ||||
|                     ], | ||||
|                     'adapter' => $this->adapter ?? config('backups.default'), | ||||
|                     'uuid' => $backup->uuid, | ||||
|                     'ignore' => implode("\n", $backup->ignored_files), | ||||
|                 ] | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
| @ -60,11 +58,9 @@ class DaemonBackupRepository extends DaemonRepository | ||||
|             return $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|                         'adapter' => $backup->disk, | ||||
|                         'truncate_directory' => $truncate, | ||||
|                         'download_url' => $url ?? '', | ||||
|                     ], | ||||
|                     'adapter' => $backup->disk, | ||||
|                     'truncate_directory' => $truncate, | ||||
|                     'download_url' => $url ?? '', | ||||
|                 ] | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
|  | ||||
| @ -13,10 +13,13 @@ class DaemonConfigurationRepository extends DaemonRepository | ||||
|      * | ||||
|      * @throws \App\Exceptions\Http\Connection\DaemonConnectionException | ||||
|      */ | ||||
|     public function getSystemInformation(?int $version = null): array | ||||
|     public function getSystemInformation(?int $version = null, $connectTimeout = 5): array | ||||
|     { | ||||
|         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) { | ||||
|             throw new DaemonConnectionException($exception); | ||||
|         } | ||||
| @ -36,7 +39,7 @@ class DaemonConfigurationRepository extends DaemonRepository | ||||
|         try { | ||||
|             return $this->getHttpClient()->post( | ||||
|                 '/api/update', | ||||
|                 ['json' => $node->getConfiguration()] | ||||
|                 $node->getConfiguration(), | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
|             throw new DaemonConnectionException($exception); | ||||
|  | ||||
| @ -103,10 +103,8 @@ class DaemonFileRepository extends DaemonRepository | ||||
|             return $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/files/create-directory', $this->server->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|                         'name' => $name, | ||||
|                         'path' => $path, | ||||
|                     ], | ||||
|                     'name' => $name, | ||||
|                     'path' => $path, | ||||
|                 ] | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
| @ -151,9 +149,7 @@ class DaemonFileRepository extends DaemonRepository | ||||
|             return $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/files/copy', $this->server->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|                         'location' => $location, | ||||
|                     ], | ||||
|                     'location' => $location, | ||||
|                 ] | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
| @ -174,10 +170,8 @@ class DaemonFileRepository extends DaemonRepository | ||||
|             return $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/files/delete', $this->server->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|                         'root' => $root ?? '/', | ||||
|                         'files' => $files, | ||||
|                     ], | ||||
|                     'root' => $root ?? '/', | ||||
|                     'files' => $files, | ||||
|                 ] | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
| @ -195,18 +189,17 @@ class DaemonFileRepository extends DaemonRepository | ||||
|         Assert::isInstanceOf($this->server, Server::class); | ||||
| 
 | ||||
|         try { | ||||
|             $response = $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/files/compress', $this->server->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|             $response = $this->getHttpClient() | ||||
|                 // 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) | ||||
|                 ->post( | ||||
|                     sprintf('/api/servers/%s/files/compress', $this->server->uuid), | ||||
|                     [ | ||||
|                         'root' => $root ?? '/', | ||||
|                         '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) { | ||||
|             throw new DaemonConnectionException($exception); | ||||
|         } | ||||
| @ -224,18 +217,17 @@ class DaemonFileRepository extends DaemonRepository | ||||
|         Assert::isInstanceOf($this->server, Server::class); | ||||
| 
 | ||||
|         try { | ||||
|             return $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/files/decompress', $this->server->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|             return $this->getHttpClient() | ||||
|                 // 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) | ||||
|                 ->post( | ||||
|                     sprintf('/api/servers/%s/files/decompress', $this->server->uuid), | ||||
|                     [ | ||||
|                         'root' => $root ?? '/', | ||||
|                         '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) { | ||||
|             throw new DaemonConnectionException($exception); | ||||
|         } | ||||
| @ -254,10 +246,8 @@ class DaemonFileRepository extends DaemonRepository | ||||
|             return $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/files/chmod', $this->server->uuid), | ||||
|                 [ | ||||
|                     'json' => [ | ||||
|                         'root' => $root ?? '/', | ||||
|                         'files' => $files, | ||||
|                     ], | ||||
|                     'root' => $root ?? '/', | ||||
|                     'files' => $files, | ||||
|                 ] | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
| @ -286,7 +276,7 @@ class DaemonFileRepository extends DaemonRepository | ||||
|             return $this->getHttpClient()->post( | ||||
|                 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) { | ||||
|  | ||||
| @ -21,7 +21,7 @@ class DaemonPowerRepository extends DaemonRepository | ||||
|         try { | ||||
|             return $this->getHttpClient()->post( | ||||
|                 sprintf('/api/servers/%s/power', $this->server->uuid), | ||||
|                 ['json' => ['action' => $action]] | ||||
|                 ['action' => $action], | ||||
|             ); | ||||
|         } catch (TransferException $exception) { | ||||
|             throw new DaemonConnectionException($exception); | ||||
|  | ||||
| @ -40,11 +40,9 @@ class DaemonServerRepository extends DaemonRepository | ||||
|         Assert::isInstanceOf($this->server, Server::class); | ||||
| 
 | ||||
|         try { | ||||
|             $this->getHttpClient()->post('/api/servers', [ | ||||
|                 'json' => [ | ||||
|                     'uuid' => $this->server->uuid, | ||||
|                     'start_on_completion' => $startOnCompletion, | ||||
|                 ], | ||||
|             $response = $this->getHttpClient()->post('/api/servers', [ | ||||
|                 'uuid' => $this->server->uuid, | ||||
|                 'start_on_completion' => $startOnCompletion, | ||||
|             ]); | ||||
|         } catch (GuzzleException $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\TooManyPortsInRangeException | ||||
|      */ | ||||
|     public function handle(Node $node, array $data): void | ||||
|     public function handle(Node $node, array $data): array | ||||
|     { | ||||
|         $explode = explode('/', $data['allocation_ip']); | ||||
|         if (count($explode) !== 1) { | ||||
| @ -58,6 +58,8 @@ class AssignmentService | ||||
|         } | ||||
| 
 | ||||
|         $this->connection->beginTransaction(); | ||||
| 
 | ||||
|         $ids = []; | ||||
|         foreach ($parsed as $ip) { | ||||
|             foreach ($data['allocation_ports'] as $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(); | ||||
| 
 | ||||
|         return $ids; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -18,17 +18,19 @@ class EggParserService | ||||
|      */ | ||||
|     public function handle(UploadedFile $file): array | ||||
|     { | ||||
|         if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { | ||||
|             throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.'); | ||||
|         if ($file->getError() !== UPLOAD_ERR_OK) { | ||||
|             throw new InvalidFileUploadException('The selected file was not uploaded successfully'); | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $parsed */ | ||||
|         $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.'); | ||||
|         } | ||||
|         $parsed = json_decode($file->getContent(), true, 512, JSON_THROW_ON_ERROR); | ||||
| 
 | ||||
|         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 | ||||
|     { | ||||
|         if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) { | ||||
|             return $parsed; | ||||
|         } | ||||
| 
 | ||||
|         // 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.
 | ||||
|         if (!isset($parsed['images'])) { | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Services\Servers; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use App\Models\Server; | ||||
| use Illuminate\Database\ConnectionInterface; | ||||
| use App\Repositories\Daemon\DaemonServerRepository; | ||||
| @ -25,7 +26,7 @@ class ReinstallServerService | ||||
|     public function handle(Server $server): 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(); | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Services\Servers; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use App\Models\ServerVariable; | ||||
| use Ramsey\Uuid\Uuid; | ||||
| use Illuminate\Support\Arr; | ||||
| @ -132,7 +133,7 @@ class ServerCreationService | ||||
|             'node_id' => Arr::get($data, 'node_id'), | ||||
|             'name' => Arr::get($data, 'name'), | ||||
|             'description' => Arr::get($data, 'description') ?? '', | ||||
|             'status' => Server::STATUS_INSTALLING, | ||||
|             'status' => ServerState::Installing, | ||||
|             'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']), | ||||
|             'owner_id' => Arr::get($data, 'owner_id'), | ||||
|             'memory' => Arr::get($data, 'memory'), | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Services\Servers; | ||||
| 
 | ||||
| use App\Enums\ServerState; | ||||
| use Webmozart\Assert\Assert; | ||||
| use App\Models\Server; | ||||
| use App\Repositories\Daemon\DaemonServerRepository; | ||||
| @ -44,7 +45,7 @@ class SuspensionService | ||||
| 
 | ||||
|         // Update the server's suspension status.
 | ||||
|         $server->update([ | ||||
|             'status' => $isSuspending ? Server::STATUS_SUSPENDED : null, | ||||
|             'status' => $isSuspending ? ServerState::Suspended : null, | ||||
|         ]); | ||||
| 
 | ||||
|         try { | ||||
| @ -53,7 +54,7 @@ class SuspensionService | ||||
|         } catch (\Exception $exception) { | ||||
|             // Rollback the server's suspension status if daemon fails to sync the server.
 | ||||
|             $server->update([ | ||||
|                 'status' => $isSuspending ? null : Server::STATUS_SUSPENDED, | ||||
|                 'status' => $isSuspending ? null : ServerState::Suspended, | ||||
|             ]); | ||||
|             throw $exception; | ||||
|         } | ||||
|  | ||||
| @ -9,6 +9,15 @@ trait AvailableLanguages | ||||
| { | ||||
|     private ?Filesystem $filesystem = null; | ||||
| 
 | ||||
|     public const TRANSLATED = [ | ||||
|         'cz', | ||||
|         'da', | ||||
|         'de', | ||||
|         'en', | ||||
|         'es', | ||||
|         'tr', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * Return all the available languages on the Panel based on those | ||||
|      * that are present in the language folder. | ||||
| @ -18,12 +27,17 @@ trait AvailableLanguages | ||||
|         return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) { | ||||
|             $code = basename($path); | ||||
| 
 | ||||
|             $value = Locale::getDisplayName($code, app()->currentLocale()); | ||||
|             $value = Locale::getDisplayName($code, $code); | ||||
| 
 | ||||
|             return [$code => title_case($value)]; | ||||
|         })->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. | ||||
|      */ | ||||
|  | ||||
| @ -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')) { | ||||
|     /** | ||||
|      * 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\BladeServiceProvider::class, | ||||
|     App\Providers\EventServiceProvider::class, | ||||
|     App\Providers\Filament\AdminPanelProvider::class, | ||||
|     App\Providers\HashidsServiceProvider::class, | ||||
|     App\Providers\RouteServiceProvider::class, | ||||
|     App\Providers\ViewComposerServiceProvider::class, | ||||
|  | ||||
| @ -9,8 +9,11 @@ | ||||
|         "ext-pdo": "*", | ||||
|         "ext-pdo_mysql": "*", | ||||
|         "ext-zip": "*", | ||||
|         "abdelhamiderrahmouni/filament-monaco-editor": "^0.2.0", | ||||
|         "aws/aws-sdk-php": "~3.288.1", | ||||
|         "chillerlan/php-qrcode": "^5.0", | ||||
|         "doctrine/dbal": "~3.6.0", | ||||
|         "filament/filament": "^3.2", | ||||
|         "guzzlehttp/guzzle": "^7.5", | ||||
|         "hashids/hashids": "~5.0.0", | ||||
|         "laracasts/utilities": "~3.2.2", | ||||
| @ -26,12 +29,14 @@ | ||||
|         "pragmarx/google2fa": "~8.0.0", | ||||
|         "predis/predis": "~2.1.1", | ||||
|         "prologue/alerts": "^1.2", | ||||
|         "ryangjchandler/blade-tabler-icons": "^2.3", | ||||
|         "s1lentium/iptools": "~1.2.0", | ||||
|         "spatie/laravel-fractal": "^6.1", | ||||
|         "spatie/laravel-query-builder": "^5.8", | ||||
|         "symfony/mailgun-mailer": "^7.0", | ||||
|         "symfony/postmark-mailer": "^7.0", | ||||
|         "symfony/yaml": "^7.0", | ||||
|         "webbingbrasil/filament-copyactions": "^3.0", | ||||
|         "webmozart/assert": "~1.11.0" | ||||
|     }, | ||||
|     "require-dev": { | ||||
| @ -66,7 +71,8 @@ | ||||
|         "cs:check": "php-cs-fixer fix --dry-run --diff --verbose", | ||||
|         "post-autoload-dump": [ | ||||
|             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", | ||||
|             "@php artisan package:discover --ansi || true" | ||||
|             "@php artisan package:discover --ansi || true", | ||||
|             "@php artisan filament:upgrade" | ||||
|         ], | ||||
|         "post-root-package-install": [ | ||||
|             "@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' => [ | ||||
|         '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" | ||||
|     ], | ||||
|     "docker_images": { | ||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" | ||||
|         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||
|     }, | ||||
|     "file_denylist": [], | ||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", | ||||
| @ -30,7 +30,7 @@ | ||||
|     "scripts": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:alpine", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||
|             "entrypoint": "ash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -14,10 +14,10 @@ | ||||
|         "pid_limit" | ||||
|     ], | ||||
|     "docker_images": { | ||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" | ||||
|         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||
|     }, | ||||
|     "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\" )", | ||||
|  | ||||
| @ -14,10 +14,10 @@ | ||||
|         "pid_limit" | ||||
|     ], | ||||
|     "docker_images": { | ||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" | ||||
|         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||
|     }, | ||||
|     "file_denylist": [], | ||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", | ||||
| @ -30,7 +30,7 @@ | ||||
|     "scripts": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:alpine", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||
|             "entrypoint": "ash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -14,9 +14,9 @@ | ||||
|         "pid_limit" | ||||
|     ], | ||||
|     "docker_images": { | ||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" | ||||
|         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||
|     }, | ||||
|     "file_denylist": [], | ||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", | ||||
| @ -29,7 +29,7 @@ | ||||
|     "scripts": { | ||||
|         "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}", | ||||
|             "container": "ghcr.io\/App\/installers:alpine", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||
|             "entrypoint": "ash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -14,10 +14,10 @@ | ||||
|         "pid_limit" | ||||
|     ], | ||||
|     "docker_images": { | ||||
|         "Java 17": "ghcr.io\/App\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/App\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/App\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/App\/yolks:java_8" | ||||
|         "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17", | ||||
|         "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16", | ||||
|         "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11", | ||||
|         "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8" | ||||
|     }, | ||||
|     "file_denylist": [], | ||||
|     "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}", | ||||
| @ -30,7 +30,7 @@ | ||||
|     "scripts": { | ||||
|         "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\"", | ||||
|             "container": "ghcr.io\/App\/installers:alpine", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||
|             "entrypoint": "ash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -12,7 +12,7 @@ | ||||
|         "steam_disk_space" | ||||
|     ], | ||||
|     "docker_images": { | ||||
|         "ghcr.io\/App\/games:rust": "ghcr.io\/App\/games:rust" | ||||
|         "ghcr.io\/pterodactyl\/games:rust": "ghcr.io\/pterodactyl\/games:rust" | ||||
|     }, | ||||
|     "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}}", | ||||
| @ -25,7 +25,7 @@ | ||||
|     "scripts": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:debian", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||
|             "entrypoint": "bash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|         "steam_disk_space" | ||||
|     ], | ||||
|     "images": [ | ||||
|         "ghcr.io\/App\/games:source" | ||||
|         "ghcr.io\/pterodactyl\/games:source" | ||||
|     ], | ||||
|     "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}}", | ||||
| @ -26,7 +26,7 @@ | ||||
|     "scripts": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:debian", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||
|             "entrypoint": "bash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -12,7 +12,7 @@ | ||||
|         "steam_disk_space" | ||||
|     ], | ||||
|     "images": [ | ||||
|         "ghcr.io\/App\/games:source" | ||||
|         "ghcr.io\/pterodactyl\/games:source" | ||||
|     ], | ||||
|     "file_denylist": [], | ||||
|     "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": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:debian", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||
|             "entrypoint": "bash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|         "steam_disk_space" | ||||
|     ], | ||||
|     "images": [ | ||||
|         "ghcr.io\/App\/games:source" | ||||
|         "ghcr.io\/pterodactyl\/games:source" | ||||
|     ], | ||||
|     "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' )", | ||||
| @ -26,7 +26,7 @@ | ||||
|     "scripts": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:debian", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||
|             "entrypoint": "bash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -12,7 +12,7 @@ | ||||
|         "steam_disk_space" | ||||
|     ], | ||||
|     "images": [ | ||||
|         "ghcr.io\/App\/games:source" | ||||
|         "ghcr.io\/pterodactyl\/games:source" | ||||
|     ], | ||||
|     "file_denylist": [], | ||||
|     "startup": ".\/srcds_run -game insurgency -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart", | ||||
| @ -25,7 +25,7 @@ | ||||
|     "scripts": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:debian", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||
|             "entrypoint": "bash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|         "steam_disk_space" | ||||
|     ], | ||||
|     "images": [ | ||||
|         "ghcr.io\/App\/games:source" | ||||
|         "ghcr.io\/pterodactyl\/games:source" | ||||
|     ], | ||||
|     "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}}", | ||||
| @ -26,7 +26,7 @@ | ||||
|     "scripts": { | ||||
|         "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", | ||||
|             "container": "ghcr.io\/App\/installers:debian", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:debian", | ||||
|             "entrypoint": "bash" | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -23,7 +23,7 @@ | ||||
|     "scripts": { | ||||
|         "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\"", | ||||
|             "container": "ghcr.io\/App\/installers:alpine", | ||||
|             "container": "ghcr.io\/pterodactyl\/installers:alpine", | ||||
|             "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