mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-11-04 16:56:51 +01:00 
			
		
		
		
	Merge branch 'issue/fix-3' of https://github.com/pelican-dev/panel into issue/fix-3
This commit is contained in:
		
						commit
						693c65995d
					
				
							
								
								
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							@ -1 +1,2 @@
 | 
				
			|||||||
 | 
					github: pelican-dev
 | 
				
			||||||
custom: [https://buy.stripe.com/14kdU99SI4UT7ni9AB, https://buy.stripe.com/14kaHXc0Q9b9372eUU]
 | 
					custom: [https://buy.stripe.com/14kdU99SI4UT7ni9AB, https://buy.stripe.com/14kaHXc0Q9b9372eUU]
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								.github/docker/default.conf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/docker/default.conf
									
									
									
									
										vendored
									
									
								
							@ -4,6 +4,30 @@
 | 
				
			|||||||
# If using CentOS this file should be placed in:
 | 
					# If using CentOS this file should be placed in:
 | 
				
			||||||
#				 /etc/nginx/conf.d/
 | 
					#				 /etc/nginx/conf.d/
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The MIT License (MIT)
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Pterodactyl®
 | 
				
			||||||
 | 
					# Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					# of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					# in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					# copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					# furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					# copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					# SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
server {
 | 
					server {
 | 
				
			||||||
    listen 80;
 | 
					    listen 80;
 | 
				
			||||||
    server_name _;
 | 
					    server_name _;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							@ -1,9 +1,6 @@
 | 
				
			|||||||
name: Build
 | 
					name: Build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches:
 | 
					 | 
				
			||||||
      - '**'
 | 
					 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - '**'
 | 
					      - '**'
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							@ -1,7 +1,7 @@
 | 
				
			|||||||
name: Tests
 | 
					name: Tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					  pull_request:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - '**'
 | 
					      - '**'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										30
									
								
								.github/workflows/cla.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/cla.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					name: "CLA Assistant"
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  issue_comment:
 | 
				
			||||||
 | 
					    types: [created]
 | 
				
			||||||
 | 
					  pull_request_target:
 | 
				
			||||||
 | 
					    types: [opened,closed,synchronize]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  actions: write
 | 
				
			||||||
 | 
					  contents: write
 | 
				
			||||||
 | 
					  pull-requests: write
 | 
				
			||||||
 | 
					  statuses: write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  CLAAssistant:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: "CLA Assistant"
 | 
				
			||||||
 | 
					        if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
 | 
				
			||||||
 | 
					        uses: contributor-assistant/github-action@v2.3.0
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					          PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path-to-signatures: 'version1/cla.json'
 | 
				
			||||||
 | 
					          path-to-document: 'https://github.com/pelican-dev/panel/blob/3.x/contributor_license_agreement.md'
 | 
				
			||||||
 | 
					          branch: 'main'
 | 
				
			||||||
 | 
					          allowlist: dependabot[bot]
 | 
				
			||||||
 | 
					          remote-organization-name: pelican-dev
 | 
				
			||||||
 | 
					          remote-repository-name: cla-signatures
 | 
				
			||||||
							
								
								
									
										3
									
								
								.github/workflows/lint.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/lint.yaml
									
									
									
									
										vendored
									
									
								
							@ -1,9 +1,6 @@
 | 
				
			|||||||
name: Lint
 | 
					name: Lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches:
 | 
					 | 
				
			||||||
      - '**'
 | 
					 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - '**'
 | 
					      - '**'
 | 
				
			||||||
 | 
				
			|||||||
@ -44,7 +44,6 @@ class InfoCommand extends Command
 | 
				
			|||||||
            ['Session Driver', config('session.driver')],
 | 
					            ['Session Driver', config('session.driver')],
 | 
				
			||||||
            ['Filesystem Driver', config('filesystems.default')],
 | 
					            ['Filesystem Driver', config('filesystems.default')],
 | 
				
			||||||
            ['Default Theme', config('themes.active')],
 | 
					            ['Default Theme', config('themes.active')],
 | 
				
			||||||
            ['Proxies', config('trustedproxies.proxies')],
 | 
					 | 
				
			||||||
        ], 'compact');
 | 
					        ], 'compact');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->output->title('Database Configuration');
 | 
					        $this->output->title('Database Configuration');
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										43
									
								
								app/Enums/ContainerStatus.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								app/Enums/ContainerStatus.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Enums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ContainerStatus: string
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    case Created = 'created';
 | 
				
			||||||
 | 
					    case Running = 'running';
 | 
				
			||||||
 | 
					    case Restarting = 'restarting';
 | 
				
			||||||
 | 
					    case Exited = 'exited';
 | 
				
			||||||
 | 
					    case Paused = 'paused';
 | 
				
			||||||
 | 
					    case Dead = 'dead';
 | 
				
			||||||
 | 
					    case Removing = 'removing';
 | 
				
			||||||
 | 
					    case Missing = 'missing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function icon(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return match ($this) {
 | 
				
			||||||
 | 
					            self::Created => 'tabler-heart-plus',
 | 
				
			||||||
 | 
					            self::Running => 'tabler-heartbeat',
 | 
				
			||||||
 | 
					            self::Restarting => 'tabler-heart-bolt',
 | 
				
			||||||
 | 
					            self::Exited => 'tabler-heart-exclamation',
 | 
				
			||||||
 | 
					            self::Paused => 'tabler-heart-pause',
 | 
				
			||||||
 | 
					            self::Dead => 'tabler-heart-x',
 | 
				
			||||||
 | 
					            self::Removing => 'tabler-heart-down',
 | 
				
			||||||
 | 
					            self::Missing => 'tabler-heart-question',
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function color(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return match ($this) {
 | 
				
			||||||
 | 
					            self::Created => 'primary',
 | 
				
			||||||
 | 
					            self::Running => 'success',
 | 
				
			||||||
 | 
					            self::Restarting => 'info',
 | 
				
			||||||
 | 
					            self::Exited => 'danger',
 | 
				
			||||||
 | 
					            self::Paused => 'warning',
 | 
				
			||||||
 | 
					            self::Dead => 'danger',
 | 
				
			||||||
 | 
					            self::Removing => 'warning',
 | 
				
			||||||
 | 
					            self::Missing => 'gray',
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								app/Enums/ServerState.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/Enums/ServerState.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Enums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ServerState: string
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    case None = 'none';
 | 
				
			||||||
 | 
					    case Installing = 'installing';
 | 
				
			||||||
 | 
					    case InstallFailed = 'install_failed';
 | 
				
			||||||
 | 
					    case ReinstallFailed = 'reinstall_failed';
 | 
				
			||||||
 | 
					    case Suspended = 'suspended';
 | 
				
			||||||
 | 
					    case RestoringBackup = 'restoring_backup';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function icon(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return match ($this) {
 | 
				
			||||||
 | 
					            self::None => 'tabler-heart',
 | 
				
			||||||
 | 
					            self::Installing => 'tabler-heart-bolt',
 | 
				
			||||||
 | 
					            self::InstallFailed => 'tabler-heart-x',
 | 
				
			||||||
 | 
					            self::ReinstallFailed => 'tabler-heart-x',
 | 
				
			||||||
 | 
					            self::Suspended => 'tabler-heart-cancel',
 | 
				
			||||||
 | 
					            self::RestoringBackup => 'tabler-heart-up',
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function color(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return match ($this) {
 | 
				
			||||||
 | 
					            self::None => 'primary',
 | 
				
			||||||
 | 
					            self::Installing => 'info',
 | 
				
			||||||
 | 
					            self::InstallFailed => 'danger',
 | 
				
			||||||
 | 
					            self::ReinstallFailed => 'danger',
 | 
				
			||||||
 | 
					            self::Suspended => 'danger',
 | 
				
			||||||
 | 
					            self::RestoringBackup => 'info',
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Exceptions\Http\Server;
 | 
					namespace App\Exceptions\Http\Server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use App\Models\Server;
 | 
					use App\Models\Server;
 | 
				
			||||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
 | 
					use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,7 +21,7 @@ class ServerStateConflictException extends ConflictHttpException
 | 
				
			|||||||
            $message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
 | 
					            $message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
 | 
				
			||||||
        } elseif (!$server->isInstalled()) {
 | 
					        } elseif (!$server->isInstalled()) {
 | 
				
			||||||
            $message = 'This server has not yet completed its installation process, please try again later.';
 | 
					            $message = 'This server has not yet completed its installation process, please try again later.';
 | 
				
			||||||
        } elseif ($server->status === Server::STATUS_RESTORING_BACKUP) {
 | 
					        } elseif ($server->status === ServerState::RestoringBackup) {
 | 
				
			||||||
            $message = 'This server is currently restoring from a backup, please try again later.';
 | 
					            $message = 'This server is currently restoring from a backup, please try again later.';
 | 
				
			||||||
        } elseif (!is_null($server->transfer)) {
 | 
					        } elseif (!is_null($server->transfer)) {
 | 
				
			||||||
            $message = 'This server is currently being transferred to a new machine, please try again later.';
 | 
					            $message = 'This server is currently being transferred to a new machine, please try again later.';
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,28 @@
 | 
				
			|||||||
<?php
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Pterodactyl®
 | 
				
			||||||
 | 
					 Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					 of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					 in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					 copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					 furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					 copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					 SOFTWARE. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace App\Extensions\Filesystem;
 | 
					namespace App\Extensions\Filesystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Aws\S3\S3ClientInterface;
 | 
					use Aws\S3\S3ClientInterface;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										82
									
								
								app/Filament/Pages/Dashboard.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								app/Filament/Pages/Dashboard.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\NodeResource\Pages\ListNodes;
 | 
				
			||||||
 | 
					use App\Models\Egg;
 | 
				
			||||||
 | 
					use App\Models\Node;
 | 
				
			||||||
 | 
					use App\Models\Server;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use Filament\Actions\CreateAction;
 | 
				
			||||||
 | 
					use Filament\Pages\Page;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Dashboard extends Page
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-layout-dashboard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static string $view = 'filament.pages.dashboard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ?string $heading = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $title = 'Dashboard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $slug = '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string $activeTab = 'nodes';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getViewData(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'inDevelopment' => config('app.version') === 'canary',
 | 
				
			||||||
 | 
					            'eggsCount' => Egg::query()->count(),
 | 
				
			||||||
 | 
					            'nodesList' => ListNodes::getUrl(),
 | 
				
			||||||
 | 
					            'nodesCount' => Node::query()->count(),
 | 
				
			||||||
 | 
					            'serversCount' => Server::query()->count(),
 | 
				
			||||||
 | 
					            'usersCount' => User::query()->count(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            'devActions' => [
 | 
				
			||||||
 | 
					                CreateAction::make()
 | 
				
			||||||
 | 
					                    ->label('Create Issue')
 | 
				
			||||||
 | 
					                    ->icon('tabler-brand-github')
 | 
				
			||||||
 | 
					                    ->url('https://github.com/pelican-dev/panel/issues/new/choose', true)
 | 
				
			||||||
 | 
					                    ->color('warning'),
 | 
				
			||||||
 | 
					                CreateAction::make()
 | 
				
			||||||
 | 
					                    ->label('Discuss Features')
 | 
				
			||||||
 | 
					                    ->icon('tabler-brand-github')
 | 
				
			||||||
 | 
					                    ->url('https://github.com/pelican-dev/panel/discussions', true)
 | 
				
			||||||
 | 
					                    ->color('primary'),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            'nodeActions' => [
 | 
				
			||||||
 | 
					                CreateAction::make()
 | 
				
			||||||
 | 
					                    ->label('Create first Node in Pelican')
 | 
				
			||||||
 | 
					                    ->icon('tabler-server-2')
 | 
				
			||||||
 | 
					                    ->url(route('filament.admin.resources.nodes.create'))
 | 
				
			||||||
 | 
					                    ->color('primary'),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            'supportActions' => [
 | 
				
			||||||
 | 
					                CreateAction::make()
 | 
				
			||||||
 | 
					                    ->label('Help Translate')
 | 
				
			||||||
 | 
					                    ->icon('tabler-language')
 | 
				
			||||||
 | 
					                    ->url('https://crowdin.com/project/pelican-dev', true)
 | 
				
			||||||
 | 
					                    ->color('info'),
 | 
				
			||||||
 | 
					                CreateAction::make()
 | 
				
			||||||
 | 
					                    ->label('Donate Directly')
 | 
				
			||||||
 | 
					                    ->icon('tabler-cash')
 | 
				
			||||||
 | 
					                    ->url('https://pelican.dev/donate', true)
 | 
				
			||||||
 | 
					                    ->color('success'),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            'helpActions' => [
 | 
				
			||||||
 | 
					                CreateAction::make()
 | 
				
			||||||
 | 
					                    ->label('Read Documentation')
 | 
				
			||||||
 | 
					                    ->icon('tabler-speedboat')
 | 
				
			||||||
 | 
					                    ->url('https://pelican.dev/docs', true)
 | 
				
			||||||
 | 
					                    ->color('info'),
 | 
				
			||||||
 | 
					                CreateAction::make()
 | 
				
			||||||
 | 
					                    ->label('Get Help in Discord')
 | 
				
			||||||
 | 
					                    ->icon('tabler-brand-discord')
 | 
				
			||||||
 | 
					                    ->url('https://discord.gg/pelican-panel', true)
 | 
				
			||||||
 | 
					                    ->color('primary'),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										194
									
								
								app/Filament/Resources/ApiKeyResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								app/Filament/Resources/ApiKeyResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\ApiKeyResource\Pages;
 | 
				
			||||||
 | 
					use App\Models\ApiKey;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Components\Tab;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ApiKeyResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = ApiKey::class;
 | 
				
			||||||
 | 
					    protected static ?string $label = 'API Key';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-key';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function canEdit($record): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getTabs(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'all' => Tab::make('All Keys'),
 | 
				
			||||||
 | 
					            'application' => Tab::make('Application Keys')
 | 
				
			||||||
 | 
					                ->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getDefaultActiveTab(): string|int|null
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'application';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Forms\Components\Hidden::make('identifier')->default(ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION)),
 | 
				
			||||||
 | 
					                Forms\Components\Hidden::make('token')->default(encrypt(str_random(ApiKey::KEY_LENGTH))),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('user_id')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->preload()
 | 
				
			||||||
 | 
					                    ->relationship('user', 'username')
 | 
				
			||||||
 | 
					                    ->default(auth()->user()->id)
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('key_type')
 | 
				
			||||||
 | 
					                    ->options(function (ApiKey $apiKey) {
 | 
				
			||||||
 | 
					                        $originalOptions = [
 | 
				
			||||||
 | 
					                            ApiKey::TYPE_NONE => 'None',
 | 
				
			||||||
 | 
					                            ApiKey::TYPE_ACCOUNT => 'Account',
 | 
				
			||||||
 | 
					                            ApiKey::TYPE_APPLICATION => 'Application',
 | 
				
			||||||
 | 
					                            ApiKey::TYPE_DAEMON_USER => 'Daemon User',
 | 
				
			||||||
 | 
					                            ApiKey::TYPE_DAEMON_APPLICATION => 'Daemon Application',
 | 
				
			||||||
 | 
					                        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return collect($originalOptions)
 | 
				
			||||||
 | 
					                            ->filter(fn ($value, $key) => $key <= ApiKey::TYPE_APPLICATION || $apiKey->key_type === $key)
 | 
				
			||||||
 | 
					                            ->all();
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->selectablePlaceholder(false)
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->default(ApiKey::TYPE_APPLICATION),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Fieldset::make('Permissions')
 | 
				
			||||||
 | 
					                    ->columns([
 | 
				
			||||||
 | 
					                        'default' => 1,
 | 
				
			||||||
 | 
					                        'sm' => 1,
 | 
				
			||||||
 | 
					                        'md' => 2,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->schema(
 | 
				
			||||||
 | 
					                        collect(ApiKey::RESOURCES)->map(fn ($resource) => Forms\Components\ToggleButtons::make("r_$resource")
 | 
				
			||||||
 | 
					                            ->label(str($resource)->replace('_', ' ')->title())
 | 
				
			||||||
 | 
					                            ->options([
 | 
				
			||||||
 | 
					                                0 => 'None',
 | 
				
			||||||
 | 
					                                1 => 'Read',
 | 
				
			||||||
 | 
					                                // 2 => 'Write',
 | 
				
			||||||
 | 
					                                3 => 'Read & Write',
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->icons([
 | 
				
			||||||
 | 
					                                0 => 'tabler-book-off',
 | 
				
			||||||
 | 
					                                1 => 'tabler-book',
 | 
				
			||||||
 | 
					                                2 => 'tabler-writing',
 | 
				
			||||||
 | 
					                                3 => 'tabler-writing',
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->colors([
 | 
				
			||||||
 | 
					                                0 => 'success',
 | 
				
			||||||
 | 
					                                1 => 'warning',
 | 
				
			||||||
 | 
					                                2 => 'danger',
 | 
				
			||||||
 | 
					                                3 => 'danger',
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->inline()
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->disabledOn('edit')
 | 
				
			||||||
 | 
					                            ->columnSpan([
 | 
				
			||||||
 | 
					                                'default' => 1,
 | 
				
			||||||
 | 
					                                'sm' => 1,
 | 
				
			||||||
 | 
					                                'md' => 1,
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->default(0),
 | 
				
			||||||
 | 
					                        )->all(),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\TagsInput::make('allowed_ips')
 | 
				
			||||||
 | 
					                    ->placeholder('Example: 127.0.0.1 or 192.168.1.1')
 | 
				
			||||||
 | 
					                    ->label('Whitelisted IPv4 Addresses')
 | 
				
			||||||
 | 
					                    ->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
 | 
				
			||||||
 | 
					                    ->columnSpanFull()
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->default(null),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Textarea::make('memo')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->label('Description')
 | 
				
			||||||
 | 
					                    ->helperText('
 | 
				
			||||||
 | 
					                        Once you have assigned permissions and created this set of credentials you will be unable to come back and edit it.
 | 
				
			||||||
 | 
					                        If you need to make changes down the road you will need to create a new set of credentials.
 | 
				
			||||||
 | 
					                    ')
 | 
				
			||||||
 | 
					                    ->columnSpanFull(),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->searchable(false)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('user.username')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('key')
 | 
				
			||||||
 | 
					                    ->copyable()
 | 
				
			||||||
 | 
					                    ->icon('tabler-clipboard-text')
 | 
				
			||||||
 | 
					                    ->state(fn (ApiKey $key) => $key->identifier . decrypt($key->token)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('memo')
 | 
				
			||||||
 | 
					                    ->label('Description')
 | 
				
			||||||
 | 
					                    ->wrap()
 | 
				
			||||||
 | 
					                    ->limit(50),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('identifier')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('last_used_at')
 | 
				
			||||||
 | 
					                    ->label('Last Used')
 | 
				
			||||||
 | 
					                    ->dateTime()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('created_at')
 | 
				
			||||||
 | 
					                    ->label('Created')
 | 
				
			||||||
 | 
					                    ->dateTime()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListApiKeys::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateApiKey::route('/create'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\ApiKeyResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\ApiKeyResource;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateApiKey extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = ApiKeyResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ?string $heading = 'Create Application API Key';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								app/Filament/Resources/ApiKeyResource/Pages/ListApiKeys.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/Filament/Resources/ApiKeyResource/Pages/ListApiKeys.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\ApiKeyResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\ApiKeyResource;
 | 
				
			||||||
 | 
					use App\Models\ApiKey;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Components\Tab;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListApiKeys extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = ApiKeyResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getTabs(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'all' => Tab::make('All Keys'),
 | 
				
			||||||
 | 
					            'application' => Tab::make('Application Keys')
 | 
				
			||||||
 | 
					                ->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            'account' => Tab::make('Account Keys')
 | 
				
			||||||
 | 
					                ->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_ACCOUNT)
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getDefaultActiveTab(): string|int|null
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'application';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										116
									
								
								app/Filament/Resources/DatabaseHostResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								app/Filament/Resources/DatabaseHostResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseHostResource\Pages;
 | 
				
			||||||
 | 
					use App\Models\DatabaseHost;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Section;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DatabaseHostResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = DatabaseHost::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $label = 'Databases';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-database';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Section::make()->schema([
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('host')
 | 
				
			||||||
 | 
					                        ->helperText('The IP address or Domain name that should be used when attempting to connect to this MySQL host from this Panel to create new databases.')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->live()
 | 
				
			||||||
 | 
					                        ->debounce(500)
 | 
				
			||||||
 | 
					                        ->afterStateUpdated(fn ($state, Forms\Set $set) => $set('name', $state))
 | 
				
			||||||
 | 
					                        ->maxLength(191),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('port')
 | 
				
			||||||
 | 
					                        ->helperText('The port that MySQL is running on for this host.')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->numeric()
 | 
				
			||||||
 | 
					                        ->default(3306)
 | 
				
			||||||
 | 
					                        ->minValue(0)
 | 
				
			||||||
 | 
					                        ->maxValue(65535),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('username')
 | 
				
			||||||
 | 
					                        ->helperText('The username of an account that has enough permissions to create new users and databases on the system.')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->maxLength(191),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('password')
 | 
				
			||||||
 | 
					                        ->helperText('The password for the database user.')
 | 
				
			||||||
 | 
					                        ->password()
 | 
				
			||||||
 | 
					                        ->revealable()
 | 
				
			||||||
 | 
					                        ->maxLength(191)
 | 
				
			||||||
 | 
					                        ->required(),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('name')
 | 
				
			||||||
 | 
					                        ->helperText('A short identifier used to distinguish this location from others. Must be between 1 and 60 characters, for example, us.nyc.lvl3.')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->maxLength(60),
 | 
				
			||||||
 | 
					                    Forms\Components\Select::make('node_id')
 | 
				
			||||||
 | 
					                        ->searchable()
 | 
				
			||||||
 | 
					                        ->preload()
 | 
				
			||||||
 | 
					                        ->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
 | 
				
			||||||
 | 
					                        ->label('Linked Node')
 | 
				
			||||||
 | 
					                        ->relationship('node', 'name'),
 | 
				
			||||||
 | 
					                ])->columns([
 | 
				
			||||||
 | 
					                    'default' => 1,
 | 
				
			||||||
 | 
					                    'lg' => 2,
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->searchable(false)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('name')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('host')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('port')
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('username')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('max_databases')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('node.name')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListDatabaseHosts::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateDatabaseHost::route('/create'),
 | 
				
			||||||
 | 
					            'edit' => Pages\EditDatabaseHost::route('/{record}/edit'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\DatabaseHostResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseHostResource;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateDatabaseHost extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = DatabaseHostResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ?string $heading = 'Database Hosts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ?string $subheading = '(database servers that can have individual databases)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\DatabaseHostResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseHostResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\EditRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditDatabaseHost extends EditRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = DatabaseHostResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\DeleteAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\DatabaseHostResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseHostResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListDatabaseHosts extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = DatabaseHostResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										110
									
								
								app/Filament/Resources/DatabaseResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								app/Filament/Resources/DatabaseResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseResource\Pages;
 | 
				
			||||||
 | 
					use App\Models\Database;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DatabaseResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = Database::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-database';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static bool $shouldRegisterNavigation = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('server_id')
 | 
				
			||||||
 | 
					                    ->relationship('server', 'name')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->preload()
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('database_host_id')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->numeric(),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('database')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->maxLength(191),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('remote')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->maxLength(191)
 | 
				
			||||||
 | 
					                    ->default('%'),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('username')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->maxLength(191),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('password')
 | 
				
			||||||
 | 
					                    ->password()
 | 
				
			||||||
 | 
					                    ->revealable()
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('max_connections')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->minValue(0)
 | 
				
			||||||
 | 
					                    ->default(0),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('server.name')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('database_host_id')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('database')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('username')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('remote')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('max_connections')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('created_at')
 | 
				
			||||||
 | 
					                    ->dateTime()
 | 
				
			||||||
 | 
					                    ->sortable()
 | 
				
			||||||
 | 
					                    ->toggleable(isToggledHiddenByDefault: true),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('updated_at')
 | 
				
			||||||
 | 
					                    ->dateTime()
 | 
				
			||||||
 | 
					                    ->sortable()
 | 
				
			||||||
 | 
					                    ->toggleable(isToggledHiddenByDefault: true),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListDatabases::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateDatabase::route('/create'),
 | 
				
			||||||
 | 
					            'edit' => Pages\EditDatabase::route('/{record}/edit'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\DatabaseResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseResource;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateDatabase extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = DatabaseResource::class;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\DatabaseResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\EditRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditDatabase extends EditRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = DatabaseResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\DeleteAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\DatabaseResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\DatabaseResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListDatabases extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = DatabaseResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										195
									
								
								app/Filament/Resources/EggResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								app/Filament/Resources/EggResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,195 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\EggResource\Pages;
 | 
				
			||||||
 | 
					use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
 | 
				
			||||||
 | 
					use App\Models\Egg;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EggResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = Egg::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-eggs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $recordTitleAttribute = 'name';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $recordRouteKeyName = 'id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Forms\Components\Tabs::make()->tabs([
 | 
				
			||||||
 | 
					                    Forms\Components\Tabs\Tab::make('Configuration')
 | 
				
			||||||
 | 
					                        ->columns(2)
 | 
				
			||||||
 | 
					                        ->schema([
 | 
				
			||||||
 | 
					                            Forms\Components\TextInput::make('name')->required()->maxLength(191)
 | 
				
			||||||
 | 
					                                ->helperText('A simple, human-readable name to use as an identifier for this Egg.'),
 | 
				
			||||||
 | 
					                            Forms\Components\Textarea::make('description')->rows(5)
 | 
				
			||||||
 | 
					                                ->helperText('A description of this Egg that will be displayed throughout the Panel as needed.'),
 | 
				
			||||||
 | 
					                            Forms\Components\TextInput::make('uuid')->disabled()
 | 
				
			||||||
 | 
					                                ->helperText('This is the globally unique identifier for this Egg which the Daemon uses as an identifier.'),
 | 
				
			||||||
 | 
					                            Forms\Components\TextInput::make('author')->required()->maxLength(191)->disabled()
 | 
				
			||||||
 | 
					                                ->helperText('The author of this version of the Egg. Uploading a new Egg configuration from a different author will change this.'),
 | 
				
			||||||
 | 
					                            Forms\Components\Toggle::make('force_outgoing_ip')->required()
 | 
				
			||||||
 | 
					                                ->helperText("Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP.
 | 
				
			||||||
 | 
					                                    Required for certain games to work properly when the Node has multiple public IP addresses.
 | 
				
			||||||
 | 
					                                    Enabling this option will disable internal networking for any servers using this egg, causing them to be unable to internally access other servers on the same node."),
 | 
				
			||||||
 | 
					                            Forms\Components\Textarea::make('startup')->rows(5)
 | 
				
			||||||
 | 
					                                ->helperText('The default startup command that should be used for new servers using this Egg.'),
 | 
				
			||||||
 | 
					                            Forms\Components\KeyValue::make('docker_images')
 | 
				
			||||||
 | 
					                                ->columnSpanFull()
 | 
				
			||||||
 | 
					                                ->addActionLabel('Add Image')
 | 
				
			||||||
 | 
					                                ->keyLabel('Name')
 | 
				
			||||||
 | 
					                                ->valueLabel('Image URI')
 | 
				
			||||||
 | 
					                                ->helperText('The docker images available to servers using this egg.'),
 | 
				
			||||||
 | 
					                        ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Forms\Components\Tabs\Tab::make('Process Management')
 | 
				
			||||||
 | 
					                        ->columns(2)
 | 
				
			||||||
 | 
					                        ->schema([
 | 
				
			||||||
 | 
					                            Forms\Components\Select::make('config_from')
 | 
				
			||||||
 | 
					                                ->label('Copy Settings From')
 | 
				
			||||||
 | 
					                                ->placeholder('None')
 | 
				
			||||||
 | 
					                                ->relationship('configFrom', 'name', ignoreRecord: true)
 | 
				
			||||||
 | 
					                                ->helperText('If you would like to default to settings from another Egg select it from the menu above.'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Forms\Components\TextInput::make('config_stop')
 | 
				
			||||||
 | 
					                                ->maxLength(191)
 | 
				
			||||||
 | 
					                                ->label('Stop Command')
 | 
				
			||||||
 | 
					                                ->helperText('The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Forms\Components\Textarea::make('config_startup')->rows(10)->json()
 | 
				
			||||||
 | 
					                                ->label('Start Configuration')
 | 
				
			||||||
 | 
					                                ->helperText('List of values the daemon should be looking for when booting a server to determine completion.'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Forms\Components\Textarea::make('config_files')->rows(10)->json()
 | 
				
			||||||
 | 
					                                ->label('Configuration Files')
 | 
				
			||||||
 | 
					                                ->helperText('This should be a JSON representation of configuration files to modify and what parts should be changed.'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Forms\Components\Textarea::make('config_logs')->rows(10)->json()
 | 
				
			||||||
 | 
					                                ->label('Log Configuration')
 | 
				
			||||||
 | 
					                                ->helperText('This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.'),
 | 
				
			||||||
 | 
					                        ]),
 | 
				
			||||||
 | 
					                    Forms\Components\Tabs\Tab::make('Variables')
 | 
				
			||||||
 | 
					                        ->columnSpanFull()
 | 
				
			||||||
 | 
					                        // ->columns(2)
 | 
				
			||||||
 | 
					                        ->schema([
 | 
				
			||||||
 | 
					                            Forms\Components\Repeater::make('Blah')
 | 
				
			||||||
 | 
					                                ->grid(3)
 | 
				
			||||||
 | 
					                                ->relationship('variables')
 | 
				
			||||||
 | 
					                                ->name('name')
 | 
				
			||||||
 | 
					                                ->columns(1)
 | 
				
			||||||
 | 
					                                ->columnSpan(1)
 | 
				
			||||||
 | 
					                                ->itemLabel(fn (array $state) => $state['name'])
 | 
				
			||||||
 | 
					                                ->schema([
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('name')->live()->maxLength(191)->columnSpanFull(),
 | 
				
			||||||
 | 
					                                    Forms\Components\Textarea::make('description')->columnSpanFull(),
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('env_variable')->maxLength(191),
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('default_value')->maxLength(191),
 | 
				
			||||||
 | 
					                                    Forms\Components\Textarea::make('rules')->rows(3)->columnSpanFull()->required(),
 | 
				
			||||||
 | 
					                                ]),
 | 
				
			||||||
 | 
					                        ]),
 | 
				
			||||||
 | 
					                    Forms\Components\Tabs\Tab::make('Install Script')
 | 
				
			||||||
 | 
					                        ->columns(3)
 | 
				
			||||||
 | 
					                        ->schema([
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Forms\Components\Select::make('copy_script_from')
 | 
				
			||||||
 | 
					                                ->placeholder('None')
 | 
				
			||||||
 | 
					                                ->relationship('scriptFrom', 'name', ignoreRecord: true),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Forms\Components\TextInput::make('script_container')
 | 
				
			||||||
 | 
					                                ->required()
 | 
				
			||||||
 | 
					                                ->maxLength(191)
 | 
				
			||||||
 | 
					                                ->default('alpine:3.4'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Forms\Components\TextInput::make('script_entry')
 | 
				
			||||||
 | 
					                                ->required()
 | 
				
			||||||
 | 
					                                ->maxLength(191)
 | 
				
			||||||
 | 
					                                ->default('ash'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            MonacoEditor::make('script_install')
 | 
				
			||||||
 | 
					                                ->columnSpanFull()
 | 
				
			||||||
 | 
					                                ->fontSize('16px')
 | 
				
			||||||
 | 
					                                ->language('shell')
 | 
				
			||||||
 | 
					                                ->view('filament.plugins.monaco-editor'),
 | 
				
			||||||
 | 
					                        ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ])->columnSpanFull()->persistTabInQueryString(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Forms\Components\TagsInput::make('features'),
 | 
				
			||||||
 | 
					                // Forms\Components\TagsInput::make('file_denylist')->placeholder('new-file.txt'),
 | 
				
			||||||
 | 
					                // Forms\Components\TextInput::make('update_url'),
 | 
				
			||||||
 | 
					                // Forms\Components\Toggle::make('script_is_privileged')->required(),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->defaultPaginationPageOption(25)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('id')
 | 
				
			||||||
 | 
					                    ->label('Id')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-egg')
 | 
				
			||||||
 | 
					                    ->description(fn ($record): string => $record->description)
 | 
				
			||||||
 | 
					                    ->wrap()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('author')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('servers_count')
 | 
				
			||||||
 | 
					                    ->counts('servers')
 | 
				
			||||||
 | 
					                    ->icon('tabler-server')
 | 
				
			||||||
 | 
					                    ->label('Servers'),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('script_container')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->hidden(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('copyFrom.name')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('script_entry')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->headerActions([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListEggs::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateEgg::route('/create'),
 | 
				
			||||||
 | 
					            'edit' => Pages\EditEgg::route('/{record}/edit'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								app/Filament/Resources/EggResource/Pages/CreateEgg.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Filament/Resources/EggResource/Pages/CreateEgg.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\EggResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\EggResource;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateEgg extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = EggResource::class;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/EggResource/Pages/EditEgg.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/EggResource/Pages/EditEgg.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\EggResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\EggResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\EditRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditEgg extends EditRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = EggResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\DeleteAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								app/Filament/Resources/EggResource/Pages/ListEggs.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/Filament/Resources/EggResource/Pages/ListEggs.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\EggResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\EggResource;
 | 
				
			||||||
 | 
					use App\Services\Eggs\Sharing\EggImporterService;
 | 
				
			||||||
 | 
					use Exception;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Notifications\Notification;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListEggs extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = EggResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Actions\Action::make('import')
 | 
				
			||||||
 | 
					                ->label('Import Egg')
 | 
				
			||||||
 | 
					                ->form([
 | 
				
			||||||
 | 
					                    Forms\Components\FileUpload::make('egg')
 | 
				
			||||||
 | 
					                        ->acceptedFileTypes(['application/json'])
 | 
				
			||||||
 | 
					                        ->storeFiles(false),
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                ->action(function (array $data): void {
 | 
				
			||||||
 | 
					                    /** @var TemporaryUploadedFile $eggFile */
 | 
				
			||||||
 | 
					                    $eggFile = $data['egg'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    /** @var EggImporterService $eggImportService */
 | 
				
			||||||
 | 
					                    $eggImportService = resolve(EggImporterService::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        $newEgg = $eggImportService->handle($eggFile);
 | 
				
			||||||
 | 
					                    } catch (Exception $exception) {
 | 
				
			||||||
 | 
					                        Notification::make()
 | 
				
			||||||
 | 
					                            ->title('Egg Import Failed')
 | 
				
			||||||
 | 
					                            ->danger()
 | 
				
			||||||
 | 
					                            ->send();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        report($exception);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Notification::make()
 | 
				
			||||||
 | 
					                        ->title("Egg Import Success: $newEgg->name")
 | 
				
			||||||
 | 
					                        ->success()
 | 
				
			||||||
 | 
					                        ->send();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    redirect()->route('filament.admin.resources.eggs.edit', [$newEgg]);
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										154
									
								
								app/Filament/Resources/MountResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								app/Filament/Resources/MountResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,154 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\MountResource\Pages;
 | 
				
			||||||
 | 
					use App\Models\Mount;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Group;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Section;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Select;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MountResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = Mount::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-layers-linked';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Section::make()->schema([
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('name')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->helperText('Unique name used to separate this mount from another.')
 | 
				
			||||||
 | 
					                        ->maxLength(64),
 | 
				
			||||||
 | 
					                    Forms\Components\ToggleButtons::make('read_only')
 | 
				
			||||||
 | 
					                        ->label('Read only?')
 | 
				
			||||||
 | 
					                        ->helperText('Is the mount read only inside the container?')
 | 
				
			||||||
 | 
					                        ->options([
 | 
				
			||||||
 | 
					                            false => 'Writeable',
 | 
				
			||||||
 | 
					                            true => 'Read only',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->icons([
 | 
				
			||||||
 | 
					                            false => 'tabler-writing',
 | 
				
			||||||
 | 
					                            true => 'tabler-writing-off',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->colors([
 | 
				
			||||||
 | 
					                            false => 'warning',
 | 
				
			||||||
 | 
					                            true => 'success',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->inline()
 | 
				
			||||||
 | 
					                        ->default(false)
 | 
				
			||||||
 | 
					                        ->required(),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('source')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->helperText('File path on the host system to mount to a container.')
 | 
				
			||||||
 | 
					                        ->maxLength(191),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('target')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->helperText('Where the mount will be accessible inside a container.')
 | 
				
			||||||
 | 
					                        ->maxLength(191),
 | 
				
			||||||
 | 
					                    Forms\Components\ToggleButtons::make('user_mountable')
 | 
				
			||||||
 | 
					                        ->hidden()
 | 
				
			||||||
 | 
					                        ->label('User mountable?')
 | 
				
			||||||
 | 
					                        ->options([
 | 
				
			||||||
 | 
					                            false => 'No',
 | 
				
			||||||
 | 
					                            true => 'Yes',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->icons([
 | 
				
			||||||
 | 
					                            false => 'tabler-user-cancel',
 | 
				
			||||||
 | 
					                            true => 'tabler-user-bolt',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->colors([
 | 
				
			||||||
 | 
					                            false => 'success',
 | 
				
			||||||
 | 
					                            true => 'warning',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->default(false)
 | 
				
			||||||
 | 
					                        ->inline()
 | 
				
			||||||
 | 
					                        ->required(),
 | 
				
			||||||
 | 
					                    Forms\Components\Textarea::make('description')
 | 
				
			||||||
 | 
					                        ->helperText('A longer description for this mount.')
 | 
				
			||||||
 | 
					                        ->columnSpanFull(),
 | 
				
			||||||
 | 
					                ])->columnSpan(1)->columns([
 | 
				
			||||||
 | 
					                    'default' => 1,
 | 
				
			||||||
 | 
					                    'lg' => 2,
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					                Group::make()->schema([
 | 
				
			||||||
 | 
					                    Section::make()->schema([
 | 
				
			||||||
 | 
					                        Select::make('eggs')->multiple()
 | 
				
			||||||
 | 
					                            ->relationship('eggs', 'name')
 | 
				
			||||||
 | 
					                            ->preload(),
 | 
				
			||||||
 | 
					                        Select::make('nodes')->multiple()
 | 
				
			||||||
 | 
					                            ->relationship('nodes', 'name')
 | 
				
			||||||
 | 
					                            ->searchable(['name', 'fqdn'])
 | 
				
			||||||
 | 
					                            ->preload(),
 | 
				
			||||||
 | 
					                    ]),
 | 
				
			||||||
 | 
					                ])->columns([
 | 
				
			||||||
 | 
					                    'default' => 1,
 | 
				
			||||||
 | 
					                    'lg' => 2,
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ])->columns([
 | 
				
			||||||
 | 
					                'default' => 1,
 | 
				
			||||||
 | 
					                'lg' => 2,
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->searchable(false)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('id')
 | 
				
			||||||
 | 
					                    ->label('')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('name')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('source')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('target')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\IconColumn::make('read_only')
 | 
				
			||||||
 | 
					                    ->icon(fn (bool $state) => $state ? 'tabler-circle-check-filled' : 'tabler-circle-x-filled')
 | 
				
			||||||
 | 
					                    ->color(fn (bool $state) => $state ? 'success' : 'danger')
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\IconColumn::make('user_mountable')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->icon(fn (bool $state) => $state ? 'tabler-circle-check-filled' : 'tabler-circle-x-filled')
 | 
				
			||||||
 | 
					                    ->color(fn (bool $state) => $state ? 'success' : 'danger')
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListMounts::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateMount::route('/create'),
 | 
				
			||||||
 | 
					            'edit' => Pages\EditMount::route('/{record}/edit'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								app/Filament/Resources/MountResource/Pages/CreateMount.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Filament/Resources/MountResource/Pages/CreateMount.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\MountResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\MountResource;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateMount extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = MountResource::class;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/EditMount.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/EditMount.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\MountResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\MountResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\EditRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditMount extends EditRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = MountResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\DeleteAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/ListMounts.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/MountResource/Pages/ListMounts.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\MountResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\MountResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListMounts extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = MountResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										163
									
								
								app/Filament/Resources/NodeResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								app/Filament/Resources/NodeResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,163 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\NodeResource\Pages;
 | 
				
			||||||
 | 
					use App\Models\Node;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NodeResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = Node::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-server-2';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $recordTitleAttribute = 'name';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Forms\Components\Toggle::make('behind_proxy')
 | 
				
			||||||
 | 
					                    ->helperText('If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.')
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('memory')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->numeric(),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('memory_overallocate')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->default(0),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('disk')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->numeric(),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('disk_overallocate')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->default(0),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('upload_size')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->integer()
 | 
				
			||||||
 | 
					                    ->default(100),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('daemonListen')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->integer()
 | 
				
			||||||
 | 
					                    ->label('Daemon Port')
 | 
				
			||||||
 | 
					                    ->default(8080),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('daemonSFTP')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->integer()
 | 
				
			||||||
 | 
					                    ->label('Daemon SFTP Port')
 | 
				
			||||||
 | 
					                    ->default(2022),
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('daemonBase')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->maxLength(191)
 | 
				
			||||||
 | 
					                    ->default('/home/daemon-files'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\ToggleButtons::make('public')
 | 
				
			||||||
 | 
					                    ->label('Node Visibility')
 | 
				
			||||||
 | 
					                    ->inline()
 | 
				
			||||||
 | 
					                    ->default(true)
 | 
				
			||||||
 | 
					                    ->helperText('By setting a node to private you will be denying the ability to auto-deploy to this node.')
 | 
				
			||||||
 | 
					                    ->options([
 | 
				
			||||||
 | 
					                        true => 'Public',
 | 
				
			||||||
 | 
					                        false => 'Private',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->colors([
 | 
				
			||||||
 | 
					                        true => 'warning',
 | 
				
			||||||
 | 
					                        false => 'danger',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->icons([
 | 
				
			||||||
 | 
					                        true => 'tabler-eye-check',
 | 
				
			||||||
 | 
					                        false => 'tabler-eye-cancel',
 | 
				
			||||||
 | 
					                    ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->searchable(false)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('uuid')
 | 
				
			||||||
 | 
					                    ->label('UUID')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->hidden(),
 | 
				
			||||||
 | 
					                Tables\Columns\IconColumn::make('health')
 | 
				
			||||||
 | 
					                    ->alignCenter()
 | 
				
			||||||
 | 
					                    ->state(fn (Node $node) => $node)
 | 
				
			||||||
 | 
					                    ->view('livewire.columns.version-column'),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-server-2')
 | 
				
			||||||
 | 
					                    ->sortable()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('fqdn')
 | 
				
			||||||
 | 
					                    ->visibleFrom('md')
 | 
				
			||||||
 | 
					                    ->label('Address')
 | 
				
			||||||
 | 
					                    ->icon('tabler-network')
 | 
				
			||||||
 | 
					                    ->sortable()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('memory')
 | 
				
			||||||
 | 
					                    ->visibleFrom('sm')
 | 
				
			||||||
 | 
					                    ->icon('tabler-device-desktop-analytics')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->suffix(' GB')
 | 
				
			||||||
 | 
					                    ->formatStateUsing(fn ($state) => number_format($state / 1000, 2))
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('disk')
 | 
				
			||||||
 | 
					                    ->visibleFrom('sm')
 | 
				
			||||||
 | 
					                    ->icon('tabler-file')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->suffix(' GB')
 | 
				
			||||||
 | 
					                    ->formatStateUsing(fn ($state) => number_format($state / 1000, 2))
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\IconColumn::make('scheme')
 | 
				
			||||||
 | 
					                    ->visibleFrom('xl')
 | 
				
			||||||
 | 
					                    ->label('SSL')
 | 
				
			||||||
 | 
					                    ->trueIcon('tabler-lock')
 | 
				
			||||||
 | 
					                    ->falseIcon('tabler-lock-open-off')
 | 
				
			||||||
 | 
					                    ->state(fn (Node $node) => $node->scheme === 'https'),
 | 
				
			||||||
 | 
					                Tables\Columns\IconColumn::make('public')
 | 
				
			||||||
 | 
					                    ->visibleFrom('lg')
 | 
				
			||||||
 | 
					                    ->trueIcon('tabler-eye-check')
 | 
				
			||||||
 | 
					                    ->falseIcon('tabler-eye-cancel'),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('servers_count')
 | 
				
			||||||
 | 
					                    ->visibleFrom('sm')
 | 
				
			||||||
 | 
					                    ->counts('servers')
 | 
				
			||||||
 | 
					                    ->label('Servers')
 | 
				
			||||||
 | 
					                    ->sortable()
 | 
				
			||||||
 | 
					                    ->icon('tabler-brand-docker'),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListNodes::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateNode::route('/create'),
 | 
				
			||||||
 | 
					            'edit' => Pages\EditNode::route('/{record}/edit'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										200
									
								
								app/Filament/Resources/NodeResource/Pages/CreateNode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								app/Filament/Resources/NodeResource/Pages/CreateNode.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,200 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\NodeResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\NodeResource;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					use Illuminate\Support\HtmlString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateNode extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = NodeResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static bool $canCreateAnother = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ?string $subheading = 'which is a machine that runs your Servers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function form(Forms\Form $form): Forms\Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                'default' => 2,
 | 
				
			||||||
 | 
					                'sm' => 3,
 | 
				
			||||||
 | 
					                'md' => 3,
 | 
				
			||||||
 | 
					                'lg' => 4,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('fqdn')
 | 
				
			||||||
 | 
					                    ->columnSpan(2)
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->autofocus()
 | 
				
			||||||
 | 
					                    ->live(debounce: 1500)
 | 
				
			||||||
 | 
					                    ->rule('prohibited', fn ($state) => is_ip($state) && request()->isSecure())
 | 
				
			||||||
 | 
					                    ->label(fn ($state) => is_ip($state) ? 'IP Address' : 'Domain Name')
 | 
				
			||||||
 | 
					                    ->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
 | 
				
			||||||
 | 
					                    ->helperText(function ($state) {
 | 
				
			||||||
 | 
					                        if (is_ip($state)) {
 | 
				
			||||||
 | 
					                            if (request()->isSecure()) {
 | 
				
			||||||
 | 
					                                return '
 | 
				
			||||||
 | 
					                                    Your panel is currently secured via an SSL certificate and that means your nodes require one too.
 | 
				
			||||||
 | 
					                                    You must use a domain name, because you cannot get SSL certificates for IP Addresses
 | 
				
			||||||
 | 
					                                ';
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return '';
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return "
 | 
				
			||||||
 | 
					                            This is the domain name that points to your node's IP Address.
 | 
				
			||||||
 | 
					                            If you've already set up this, you can verify it by checking the next field!
 | 
				
			||||||
 | 
					                        ";
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->hintColor('danger')
 | 
				
			||||||
 | 
					                    ->hint(function ($state) {
 | 
				
			||||||
 | 
					                        if (is_ip($state) && request()->isSecure()) {
 | 
				
			||||||
 | 
					                            return 'You cannot connect to an IP Address over SSL';
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return '';
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->afterStateUpdated(function (Forms\Set $set, ?string $state) {
 | 
				
			||||||
 | 
					                        $set('dns', null);
 | 
				
			||||||
 | 
					                        $set('ip', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        [$subdomain] = str($state)->explode('.', 2);
 | 
				
			||||||
 | 
					                        if (!is_numeric($subdomain)) {
 | 
				
			||||||
 | 
					                            $set('name', $subdomain);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (!$state || is_ip($state)) {
 | 
				
			||||||
 | 
					                            $set('dns', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $validRecords = gethostbynamel($state);
 | 
				
			||||||
 | 
					                        if ($validRecords) {
 | 
				
			||||||
 | 
					                            $set('dns', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            $set('ip', collect($validRecords)->first());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $set('dns', false);
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->maxLength(191),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('ip')
 | 
				
			||||||
 | 
					                    ->disabled()
 | 
				
			||||||
 | 
					                    ->hidden(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\ToggleButtons::make('dns')
 | 
				
			||||||
 | 
					                    ->label('DNS Record Check')
 | 
				
			||||||
 | 
					                    ->helperText('This lets you know if your DNS record correctly points to an IP Address.')
 | 
				
			||||||
 | 
					                    ->disabled()
 | 
				
			||||||
 | 
					                    ->inline()
 | 
				
			||||||
 | 
					                    ->default(null)
 | 
				
			||||||
 | 
					                    ->hint(fn (Forms\Get $get) => $get('ip'))
 | 
				
			||||||
 | 
					                    ->hintColor('success')
 | 
				
			||||||
 | 
					                    ->options([
 | 
				
			||||||
 | 
					                        true => 'Valid',
 | 
				
			||||||
 | 
					                        false => 'Invalid',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->colors([
 | 
				
			||||||
 | 
					                        true => 'success',
 | 
				
			||||||
 | 
					                        false => 'danger',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 1,
 | 
				
			||||||
 | 
					                        'sm' => 1,
 | 
				
			||||||
 | 
					                        'md' => 1,
 | 
				
			||||||
 | 
					                        'lg' => 1,
 | 
				
			||||||
 | 
					                    ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('daemonListen')
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 1,
 | 
				
			||||||
 | 
					                        'sm' => 1,
 | 
				
			||||||
 | 
					                        'md' => 1,
 | 
				
			||||||
 | 
					                        'lg' => 1,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->label('Port')
 | 
				
			||||||
 | 
					                    ->helperText('If you are running the daemon behind Cloudflare you should set the daemon port to 8443 to allow websocket proxying over SSL.')
 | 
				
			||||||
 | 
					                    ->minValue(0)
 | 
				
			||||||
 | 
					                    ->maxValue(65536)
 | 
				
			||||||
 | 
					                    ->default(8080)
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->integer(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('name')
 | 
				
			||||||
 | 
					                    ->label('Display Name')
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 1,
 | 
				
			||||||
 | 
					                        'sm' => 1,
 | 
				
			||||||
 | 
					                        'md' => 1,
 | 
				
			||||||
 | 
					                        'lg' => 2,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->regex('/[a-zA-Z0-9_\.\- ]+/')
 | 
				
			||||||
 | 
					                    ->helperText('This name is for display only and can be changed later.')
 | 
				
			||||||
 | 
					                    ->maxLength(100),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\ToggleButtons::make('scheme')
 | 
				
			||||||
 | 
					                    ->label('Communicate over SSL')
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 1,
 | 
				
			||||||
 | 
					                        'sm' => 1,
 | 
				
			||||||
 | 
					                        'md' => 1,
 | 
				
			||||||
 | 
					                        'lg' => 1,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->inline()
 | 
				
			||||||
 | 
					                    ->helperText(function (Forms\Get $get) {
 | 
				
			||||||
 | 
					                        if (request()->isSecure()) {
 | 
				
			||||||
 | 
					                            return new HtmlString('Your Panel is using a secure SSL connection,<br>so your Daemon must too.');
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (is_ip($get('fqdn'))) {
 | 
				
			||||||
 | 
					                            return 'An IP address cannot use SSL.';
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return '';
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
 | 
				
			||||||
 | 
					                    ->options([
 | 
				
			||||||
 | 
					                        'http' => 'HTTP',
 | 
				
			||||||
 | 
					                        'https' => 'HTTPS (SSL)',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->colors([
 | 
				
			||||||
 | 
					                        'http' => 'warning',
 | 
				
			||||||
 | 
					                        'https' => 'success',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->icons([
 | 
				
			||||||
 | 
					                        'http' => 'tabler-lock-open-off',
 | 
				
			||||||
 | 
					                        'https' => 'tabler-lock',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->default(fn () => request()->isSecure() ? 'https' : 'http'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Textarea::make('description')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 1,
 | 
				
			||||||
 | 
					                        'sm' => 1,
 | 
				
			||||||
 | 
					                        'md' => 2,
 | 
				
			||||||
 | 
					                        'lg' => 4,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->rows(5),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Hidden::make('skipValidation')->default(true),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getRedirectUrlParameters(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'tab' => '-configuration-tab',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										130
									
								
								app/Filament/Resources/NodeResource/Pages/EditNode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								app/Filament/Resources/NodeResource/Pages/EditNode.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\NodeResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\NodeResource;
 | 
				
			||||||
 | 
					use App\Models\Allocation;
 | 
				
			||||||
 | 
					use App\Models\Node;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Tabs;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\EditRecord;
 | 
				
			||||||
 | 
					use Illuminate\Support\HtmlString;
 | 
				
			||||||
 | 
					use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditNode extends EditRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = NodeResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function form(Forms\Form $form): Forms\Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form->schema([
 | 
				
			||||||
 | 
					            Tabs::make('Tabs')
 | 
				
			||||||
 | 
					                ->columns([
 | 
				
			||||||
 | 
					                    'default' => 2,
 | 
				
			||||||
 | 
					                    'sm' => 3,
 | 
				
			||||||
 | 
					                    'md' => 3,
 | 
				
			||||||
 | 
					                    'lg' => 4,
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                ->persistTabInQueryString()
 | 
				
			||||||
 | 
					                ->columnSpanFull()
 | 
				
			||||||
 | 
					                ->tabs([
 | 
				
			||||||
 | 
					                    Tabs\Tab::make('Basic Settings')
 | 
				
			||||||
 | 
					                        ->icon('tabler-server')
 | 
				
			||||||
 | 
					                        ->schema((new CreateNode())->form($form)->getComponents()),
 | 
				
			||||||
 | 
					                    Tabs\Tab::make('Advanced Settings')
 | 
				
			||||||
 | 
					                        ->icon('tabler-server-cog'),
 | 
				
			||||||
 | 
					                    Tabs\Tab::make('Configuration')
 | 
				
			||||||
 | 
					                        ->icon('tabler-code')
 | 
				
			||||||
 | 
					                        ->schema([
 | 
				
			||||||
 | 
					                            Forms\Components\Placeholder::make('instructions')
 | 
				
			||||||
 | 
					                                ->columnSpanFull()
 | 
				
			||||||
 | 
					                                ->content(new HtmlString('
 | 
				
			||||||
 | 
					                                  Save this file to your <span title="usually /etc/pelican/">daemon\'s root directory</span>, named <code>config.yml</code>
 | 
				
			||||||
 | 
					                            ')),
 | 
				
			||||||
 | 
					                            Forms\Components\Textarea::make('config')
 | 
				
			||||||
 | 
					                                ->label('/etc/pelican/config.yml')
 | 
				
			||||||
 | 
					                                ->disabled()
 | 
				
			||||||
 | 
					                                ->rows(19)
 | 
				
			||||||
 | 
					                                ->hintAction(CopyAction::make())
 | 
				
			||||||
 | 
					                                ->columnSpanFull(),
 | 
				
			||||||
 | 
					                        ]),
 | 
				
			||||||
 | 
					                    Tabs\Tab::make('Allocations')
 | 
				
			||||||
 | 
					                        ->icon('tabler-plug-connected')
 | 
				
			||||||
 | 
					                        ->columns(4)
 | 
				
			||||||
 | 
					                        ->schema([
 | 
				
			||||||
 | 
					                            Forms\Components\Repeater::make('allocations')
 | 
				
			||||||
 | 
					                                ->orderColumn('server_id')
 | 
				
			||||||
 | 
					                                ->columnSpan(1)
 | 
				
			||||||
 | 
					                                ->columns(4)
 | 
				
			||||||
 | 
					                                ->relationship()
 | 
				
			||||||
 | 
					                                ->addActionLabel('Create New Allocation')
 | 
				
			||||||
 | 
					                                ->addAction(fn ($action) => $action->color('info'))
 | 
				
			||||||
 | 
					                                ->schema([
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('ip')
 | 
				
			||||||
 | 
					                                        ->label('IP Address'),
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('ip_alias')
 | 
				
			||||||
 | 
					                                        ->label('Alias'),
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('port')
 | 
				
			||||||
 | 
					                                        ->minValue(0)
 | 
				
			||||||
 | 
					                                        ->maxValue(65535)
 | 
				
			||||||
 | 
					                                        ->numeric(),
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('server')
 | 
				
			||||||
 | 
					                                        ->formatStateUsing(fn (Allocation $allocation) => $allocation->server?->name)
 | 
				
			||||||
 | 
					                                        ->readOnly()
 | 
				
			||||||
 | 
					                                        ->placeholder('No Server'),
 | 
				
			||||||
 | 
					                                ]),
 | 
				
			||||||
 | 
					                            Forms\Components\Section::make('Assign New Allocations')
 | 
				
			||||||
 | 
					                                ->columnSpan(2)
 | 
				
			||||||
 | 
					                                ->inlineLabel()
 | 
				
			||||||
 | 
					                                ->headerActions([
 | 
				
			||||||
 | 
					                                    Forms\Components\Actions\Action::make('submit')
 | 
				
			||||||
 | 
					                                        ->color('success')
 | 
				
			||||||
 | 
					                                        ->action(function () {
 | 
				
			||||||
 | 
					                                            // ...
 | 
				
			||||||
 | 
					                                        }),
 | 
				
			||||||
 | 
					                                ])
 | 
				
			||||||
 | 
					                                ->schema([
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('ip')
 | 
				
			||||||
 | 
					                                        ->label('IP Address')
 | 
				
			||||||
 | 
					                                        ->placeholder('0.0.0.0')
 | 
				
			||||||
 | 
					                                        ->helperText('IP address to assign ports to')
 | 
				
			||||||
 | 
					                                        ->columnSpanFull(),
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('ip_alias')
 | 
				
			||||||
 | 
					                                        ->label('Alias')
 | 
				
			||||||
 | 
					                                        ->placeholder('minecraft')
 | 
				
			||||||
 | 
					                                        ->helperText('Display name to help you remember')
 | 
				
			||||||
 | 
					                                        ->columnSpanFull(),
 | 
				
			||||||
 | 
					                                    Forms\Components\TextInput::make('ports')
 | 
				
			||||||
 | 
					                                        ->label('Ports')
 | 
				
			||||||
 | 
					                                        ->placeholder('25565')
 | 
				
			||||||
 | 
					                                        ->helperText('Individual ports or port ranges here separated by commas or spaces')
 | 
				
			||||||
 | 
					                                        ->columnSpanFull(),
 | 
				
			||||||
 | 
					                                ]),
 | 
				
			||||||
 | 
					                        ]),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function mutateFormDataBeforeFill(array $data): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $node = Node::findOrFail($data['id']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data['config'] = $node->getYamlConfiguration();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getSteps(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\DeleteAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/NodeResource/Pages/ListNodes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/NodeResource/Pages/ListNodes.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\NodeResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\NodeResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListNodes extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = NodeResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										661
									
								
								app/Filament/Resources/ServerResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										661
									
								
								app/Filament/Resources/ServerResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,661 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ContainerStatus;
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
 | 
					use App\Filament\Resources\ServerResource\Pages;
 | 
				
			||||||
 | 
					use App\Models\Allocation;
 | 
				
			||||||
 | 
					use App\Models\Egg;
 | 
				
			||||||
 | 
					use App\Models\Node;
 | 
				
			||||||
 | 
					use App\Models\Server;
 | 
				
			||||||
 | 
					use App\Repositories\Daemon\DaemonServerRepository;
 | 
				
			||||||
 | 
					use App\Services\Allocations\AssignmentService;
 | 
				
			||||||
 | 
					use Closure;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Builder;
 | 
				
			||||||
 | 
					use Illuminate\Support\Arr;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Validator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ServerResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = Server::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-brand-docker';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $recordTitleAttribute = 'name';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                'default' => 2,
 | 
				
			||||||
 | 
					                'sm' => 2,
 | 
				
			||||||
 | 
					                'md' => 4,
 | 
				
			||||||
 | 
					                'lg' => 6,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Forms\Components\ToggleButtons::make('docker')
 | 
				
			||||||
 | 
					                    ->label('Container Status')
 | 
				
			||||||
 | 
					                    ->hiddenOn('create')
 | 
				
			||||||
 | 
					                    ->disableOptionWhen(fn ($state, $value) => $state !== $value)
 | 
				
			||||||
 | 
					                    ->formatStateUsing(function ($state, Server $server) {
 | 
				
			||||||
 | 
					                        if ($server->node_id === null) {
 | 
				
			||||||
 | 
					                            return 'unknown';
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /** @var DaemonServerRepository $service */
 | 
				
			||||||
 | 
					                        $service = resolve(DaemonServerRepository::class);
 | 
				
			||||||
 | 
					                        $details = $service->setServer($server)->getDetails();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return $details['state'] ?? 'unknown';
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->options(collect(ContainerStatus::cases())->mapWithKeys(
 | 
				
			||||||
 | 
					                        fn (ContainerStatus $status) => [$status->value => str($status->value)->ucwords()]
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                    ->colors(collect(ContainerStatus::cases())->mapWithKeys(
 | 
				
			||||||
 | 
					                        fn (ContainerStatus $status) => [$status->value => $status->color()]
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                    ->icons(collect(ContainerStatus::cases())->mapWithKeys(
 | 
				
			||||||
 | 
					                        fn (ContainerStatus $status) => [$status->value => $status->icon()]
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                    ->grouped()
 | 
				
			||||||
 | 
					                    ->columnSpanFull()
 | 
				
			||||||
 | 
					                    ->inline(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\ToggleButtons::make('status')
 | 
				
			||||||
 | 
					                    ->label('Server State')
 | 
				
			||||||
 | 
					                    ->helperText('')
 | 
				
			||||||
 | 
					                    ->hiddenOn('create')
 | 
				
			||||||
 | 
					                    ->disableOptionWhen(fn ($state, $value) => $state !== $value)
 | 
				
			||||||
 | 
					                    ->formatStateUsing(fn ($state) => $state ?? 'none')
 | 
				
			||||||
 | 
					                    ->options(collect(ServerState::cases())->mapWithKeys(
 | 
				
			||||||
 | 
					                        fn (ServerState $state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()]
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                    ->colors(collect(ServerState::cases())->mapWithKeys(
 | 
				
			||||||
 | 
					                        fn (ServerState $state) => [$state->value => $state->color()]
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                    ->icons(collect(ServerState::cases())->mapWithKeys(
 | 
				
			||||||
 | 
					                        fn (ServerState $state) => [$state->value => $state->icon()]
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                    ->grouped()
 | 
				
			||||||
 | 
					                    ->columnSpanFull()
 | 
				
			||||||
 | 
					                    ->inline(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('external_id')
 | 
				
			||||||
 | 
					                    ->maxLength(191)
 | 
				
			||||||
 | 
					                    ->hidden(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('name')
 | 
				
			||||||
 | 
					                    ->prefixIcon('tabler-server')
 | 
				
			||||||
 | 
					                    ->label('Display Name')
 | 
				
			||||||
 | 
					                    ->suffixAction(Forms\Components\Actions\Action::make('random')
 | 
				
			||||||
 | 
					                        ->icon('tabler-dice-' . random_int(1, 6))
 | 
				
			||||||
 | 
					                        ->color('primary')
 | 
				
			||||||
 | 
					                        ->action(function (Forms\Set $set, Forms\Get $get) {
 | 
				
			||||||
 | 
					                            $egg = Egg::find($get('egg_id'));
 | 
				
			||||||
 | 
					                            $prefix = $egg ? str($egg->name)->lower()->kebab() . '-' : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            $set('name', $prefix . fake()->domainWord);
 | 
				
			||||||
 | 
					                        }))
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 4,
 | 
				
			||||||
 | 
					                        'md' => 2,
 | 
				
			||||||
 | 
					                        'lg' => 3,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->maxLength(191),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('owner_id')
 | 
				
			||||||
 | 
					                    ->prefixIcon('tabler-user')
 | 
				
			||||||
 | 
					                    ->default(auth()->user()->id)
 | 
				
			||||||
 | 
					                    ->label('Owner')
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 4,
 | 
				
			||||||
 | 
					                        'md' => 2,
 | 
				
			||||||
 | 
					                        'lg' => 3,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->relationship('user', 'username')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->preload()
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('node_id')
 | 
				
			||||||
 | 
					                    ->disabledOn('edit')
 | 
				
			||||||
 | 
					                    ->prefixIcon('tabler-server-2')
 | 
				
			||||||
 | 
					                    ->default(fn () => Node::query()->latest()->first()?->id)
 | 
				
			||||||
 | 
					                    ->columnSpan(2)
 | 
				
			||||||
 | 
					                    ->live()
 | 
				
			||||||
 | 
					                    ->relationship('node', 'name')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->preload()
 | 
				
			||||||
 | 
					                    ->afterStateUpdated(fn (Forms\Set $set) => $set('allocation_id', null))
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('allocation_id')
 | 
				
			||||||
 | 
					                    ->preload()
 | 
				
			||||||
 | 
					                    ->live()
 | 
				
			||||||
 | 
					                    ->prefixIcon('tabler-network')
 | 
				
			||||||
 | 
					                    ->label('Primary Allocation')
 | 
				
			||||||
 | 
					                    ->columnSpan(2)
 | 
				
			||||||
 | 
					                    ->disabled(fn (Forms\Get $get) => $get('node_id') === null)
 | 
				
			||||||
 | 
					                    ->searchable(['ip', 'port', 'ip_alias'])
 | 
				
			||||||
 | 
					                    ->afterStateUpdated(function (Forms\Set $set) {
 | 
				
			||||||
 | 
					                        $set('allocation_additional', null);
 | 
				
			||||||
 | 
					                        $set('allocation_additional.needstobeastringhere.extra_allocations', null);
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->getOptionLabelFromRecordUsing(
 | 
				
			||||||
 | 
					                        fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
 | 
				
			||||||
 | 
					                            ($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    ->placeholder(function (Forms\Get $get) {
 | 
				
			||||||
 | 
					                        $node = Node::find($get('node_id'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if ($node?->allocations) {
 | 
				
			||||||
 | 
					                            return 'Select an Allocation';
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return 'Create a New Allocation';
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->relationship(
 | 
				
			||||||
 | 
					                        'allocation',
 | 
				
			||||||
 | 
					                        'ip',
 | 
				
			||||||
 | 
					                        fn (Builder $query, Forms\Get $get) => $query
 | 
				
			||||||
 | 
					                            ->where('node_id', $get('node_id'))
 | 
				
			||||||
 | 
					                            ->whereNull('server_id'),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    ->createOptionForm(fn (Forms\Get $get) => [
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('allocation_ip')
 | 
				
			||||||
 | 
					                            ->ipv4()
 | 
				
			||||||
 | 
					                            ->datalist(function () use ($get) {
 | 
				
			||||||
 | 
					                                $node = Node::find($get('node_id'));
 | 
				
			||||||
 | 
					                                if (is_ip($node->fqdn)) {
 | 
				
			||||||
 | 
					                                    return [$node->fqdn];
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                $validRecords = gethostbynamel($node->fqdn);
 | 
				
			||||||
 | 
					                                if (!$validRecords) {
 | 
				
			||||||
 | 
					                                    return [];
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                return $validRecords ?: [];
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					                            ->label('IP Address')
 | 
				
			||||||
 | 
					                            ->helperText("Usually your machine's public IP unless you are port forwarding.")
 | 
				
			||||||
 | 
					                            ->required(),
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('allocation_alias')
 | 
				
			||||||
 | 
					                            ->label('Alias')
 | 
				
			||||||
 | 
					                            ->default(null)
 | 
				
			||||||
 | 
					                            ->datalist([
 | 
				
			||||||
 | 
					                                $get('name'),
 | 
				
			||||||
 | 
					                                Egg::find($get('egg_id'))?->name,
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->helperText('This is just a display only name to help you recognize what this Allocation is used for.')
 | 
				
			||||||
 | 
					                            ->required(false),
 | 
				
			||||||
 | 
					                        Forms\Components\TagsInput::make('allocation_ports')
 | 
				
			||||||
 | 
					                            ->placeholder('Examples: 27015, 27017-27019')
 | 
				
			||||||
 | 
					                            ->helperText('
 | 
				
			||||||
 | 
					                                These are the ports that users can connect to this Server through.
 | 
				
			||||||
 | 
					                                They usually consist of the port forwarded ones.
 | 
				
			||||||
 | 
					                            ')
 | 
				
			||||||
 | 
					                            ->label('Ports')
 | 
				
			||||||
 | 
					                            ->required(),
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->createOptionUsing(function (array $data, Forms\Get $get): int {
 | 
				
			||||||
 | 
					                        return collect(
 | 
				
			||||||
 | 
					                            resolve(AssignmentService::class)->handle(Node::find($get('node_id')), $data)
 | 
				
			||||||
 | 
					                        )->first();
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Repeater::make('allocation_additional')
 | 
				
			||||||
 | 
					                    ->label('Additional Allocations')
 | 
				
			||||||
 | 
					                    ->columnSpan(2)
 | 
				
			||||||
 | 
					                    ->addActionLabel('Add Allocation')
 | 
				
			||||||
 | 
					                    ->disabled(fn (Forms\Get $get) => $get('allocation_id') === null)
 | 
				
			||||||
 | 
					                    // ->addable() TODO disable when all allocations are taken
 | 
				
			||||||
 | 
					                    // ->addable() TODO disable until first additional allocation is selected
 | 
				
			||||||
 | 
					                    ->simple(
 | 
				
			||||||
 | 
					                        Forms\Components\Select::make('extra_allocations')
 | 
				
			||||||
 | 
					                            ->live()
 | 
				
			||||||
 | 
					                            ->preload()
 | 
				
			||||||
 | 
					                            ->disableOptionsWhenSelectedInSiblingRepeaterItems()
 | 
				
			||||||
 | 
					                            ->prefixIcon('tabler-network')
 | 
				
			||||||
 | 
					                            ->label('Additional Allocations')
 | 
				
			||||||
 | 
					                            ->columnSpan(2)
 | 
				
			||||||
 | 
					                            ->disabled(fn (Forms\Get $get) => $get('../../node_id') === null)
 | 
				
			||||||
 | 
					                            ->searchable(['ip', 'port', 'ip_alias'])
 | 
				
			||||||
 | 
					                            ->getOptionLabelFromRecordUsing(
 | 
				
			||||||
 | 
					                                fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
 | 
				
			||||||
 | 
					                                    ($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                            ->placeholder('Select additional Allocations')
 | 
				
			||||||
 | 
					                            ->relationship(
 | 
				
			||||||
 | 
					                                'allocations',
 | 
				
			||||||
 | 
					                                'ip',
 | 
				
			||||||
 | 
					                                fn (Builder $query, Forms\Get $get, Forms\Components\Select $component, $state) => $query
 | 
				
			||||||
 | 
					                                    ->where('node_id', $get('../../node_id'))
 | 
				
			||||||
 | 
					                                    ->whereNotIn(
 | 
				
			||||||
 | 
					                                        'id',
 | 
				
			||||||
 | 
					                                        collect(($repeater = $component->getParentRepeater())->getState())
 | 
				
			||||||
 | 
					                                            ->pluck(
 | 
				
			||||||
 | 
					                                                (string) str($component->getStatePath())
 | 
				
			||||||
 | 
					                                                    ->after("{$repeater->getStatePath()}.")
 | 
				
			||||||
 | 
					                                                    ->after('.'),
 | 
				
			||||||
 | 
					                                            )
 | 
				
			||||||
 | 
					                                            ->flatten()
 | 
				
			||||||
 | 
					                                            ->diff(Arr::wrap($state))
 | 
				
			||||||
 | 
					                                            ->filter(fn (mixed $siblingItemState): bool => filled($siblingItemState))
 | 
				
			||||||
 | 
					                                            ->add($get('../../allocation_id'))
 | 
				
			||||||
 | 
					                                    )
 | 
				
			||||||
 | 
					                                    ->whereNull('server_id'),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Textarea::make('description')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->default('')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->columnSpanFull(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('egg_id')
 | 
				
			||||||
 | 
					                    ->disabledOn('edit')
 | 
				
			||||||
 | 
					                    ->prefixIcon('tabler-egg')
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 2,
 | 
				
			||||||
 | 
					                        'md' => 2,
 | 
				
			||||||
 | 
					                        'lg' => 6,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->relationship('egg', 'name')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->preload()
 | 
				
			||||||
 | 
					                    ->live()
 | 
				
			||||||
 | 
					                    ->afterStateUpdated(function ($state, Forms\Set $set) {
 | 
				
			||||||
 | 
					                        $egg = Egg::find($state);
 | 
				
			||||||
 | 
					                        $set('startup', $egg->startup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $variables = $egg->variables ?? [];
 | 
				
			||||||
 | 
					                        $serverVariables = collect();
 | 
				
			||||||
 | 
					                        foreach ($variables as $variable) {
 | 
				
			||||||
 | 
					                            $serverVariables->add($variable->toArray());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $variables = [];
 | 
				
			||||||
 | 
					                        $set($path = 'server_variables', $serverVariables->all());
 | 
				
			||||||
 | 
					                        for ($i = 0; $i < $serverVariables->count(); $i++) {
 | 
				
			||||||
 | 
					                            $set("$path.$i.variable_value", $serverVariables[$i]['default_value']);
 | 
				
			||||||
 | 
					                            $set("$path.$i.variable_id", $serverVariables[$i]['id']);
 | 
				
			||||||
 | 
					                            $variables[$serverVariables[$i]['env_variable']] = $serverVariables[$i]['default_value'];
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $set('environment', $variables);
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\ToggleButtons::make('skip_scripts')
 | 
				
			||||||
 | 
					                    ->label('Run Egg Install Script?')
 | 
				
			||||||
 | 
					                    ->default(false)
 | 
				
			||||||
 | 
					                    ->options([
 | 
				
			||||||
 | 
					                        false => 'Yes',
 | 
				
			||||||
 | 
					                        true => 'Skip',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->colors([
 | 
				
			||||||
 | 
					                        false => 'primary',
 | 
				
			||||||
 | 
					                        true => 'danger',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->icons([
 | 
				
			||||||
 | 
					                        false => 'tabler-code',
 | 
				
			||||||
 | 
					                        true => 'tabler-code-off',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->inline()
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\ToggleButtons::make('custom_image')
 | 
				
			||||||
 | 
					                    ->live()
 | 
				
			||||||
 | 
					                    ->label('Custom Image?')
 | 
				
			||||||
 | 
					                    ->default(false)
 | 
				
			||||||
 | 
					                    ->formatStateUsing(function ($state, Forms\Get $get) {
 | 
				
			||||||
 | 
					                        if ($state !== null) {
 | 
				
			||||||
 | 
					                            return $state;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $images = Egg::find($get('egg_id'))->docker_images ?? [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return !in_array($get('image'), $images);
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->options([
 | 
				
			||||||
 | 
					                        false => 'No',
 | 
				
			||||||
 | 
					                        true => 'Yes',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->colors([
 | 
				
			||||||
 | 
					                        false => 'primary',
 | 
				
			||||||
 | 
					                        true => 'danger',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->icons([
 | 
				
			||||||
 | 
					                        false => 'tabler-settings-cancel',
 | 
				
			||||||
 | 
					                        true => 'tabler-settings-check',
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->inline(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\TextInput::make('image')
 | 
				
			||||||
 | 
					                    ->hidden(fn (Forms\Get $get) => !$get('custom_image'))
 | 
				
			||||||
 | 
					                    ->disabled(fn (Forms\Get $get) => !$get('custom_image'))
 | 
				
			||||||
 | 
					                    ->label('Docker Image')
 | 
				
			||||||
 | 
					                    ->placeholder('Enter a custom Image')
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 2,
 | 
				
			||||||
 | 
					                        'md' => 2,
 | 
				
			||||||
 | 
					                        'lg' => 4,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Select::make('image')
 | 
				
			||||||
 | 
					                    ->hidden(fn (Forms\Get $get) => $get('custom_image'))
 | 
				
			||||||
 | 
					                    ->disabled(fn (Forms\Get $get) => $get('custom_image'))
 | 
				
			||||||
 | 
					                    ->label('Docker Image')
 | 
				
			||||||
 | 
					                    ->prefixIcon('tabler-brand-docker')
 | 
				
			||||||
 | 
					                    ->options(function (Forms\Get $get, Forms\Set $set) {
 | 
				
			||||||
 | 
					                        $images = Egg::find($get('egg_id'))->docker_images ?? [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $set('image', collect($images)->first());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return $images;
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->disabled(fn (Forms\Components\Select $component) => empty($component->getOptions()))
 | 
				
			||||||
 | 
					                    ->selectablePlaceholder(false)
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 2,
 | 
				
			||||||
 | 
					                        'md' => 2,
 | 
				
			||||||
 | 
					                        'lg' => 4,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->required(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Fieldset::make('Application Feature Limits')
 | 
				
			||||||
 | 
					                    ->inlineLabel()
 | 
				
			||||||
 | 
					                    ->hiddenOn('create')
 | 
				
			||||||
 | 
					                    ->columns(3)
 | 
				
			||||||
 | 
					                    ->schema([
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('allocation_limit')
 | 
				
			||||||
 | 
					                            ->suffixIcon('tabler-network')
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->numeric()
 | 
				
			||||||
 | 
					                            ->default(0),
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('database_limit')
 | 
				
			||||||
 | 
					                            ->suffixIcon('tabler-database')
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->numeric()
 | 
				
			||||||
 | 
					                            ->default(0),
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('backup_limit')
 | 
				
			||||||
 | 
					                            ->suffixIcon('tabler-copy-check')
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->numeric()
 | 
				
			||||||
 | 
					                            ->default(0),
 | 
				
			||||||
 | 
					                    ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Textarea::make('startup')
 | 
				
			||||||
 | 
					                    ->hintIcon('tabler-code')
 | 
				
			||||||
 | 
					                    ->label('Startup Command')
 | 
				
			||||||
 | 
					                    ->required()
 | 
				
			||||||
 | 
					                    ->live()
 | 
				
			||||||
 | 
					                    ->columnSpan([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 4,
 | 
				
			||||||
 | 
					                        'md' => 4,
 | 
				
			||||||
 | 
					                        'lg' => 6,
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->rows(function ($state) {
 | 
				
			||||||
 | 
					                        return str($state)->explode("\n")->reduce(
 | 
				
			||||||
 | 
					                            fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
 | 
				
			||||||
 | 
					                            0
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Hidden::make('environment')->default([]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Hidden::make('start_on_completion')->default(true),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Section::make('Egg Variables')
 | 
				
			||||||
 | 
					                    ->icon('tabler-eggs')
 | 
				
			||||||
 | 
					                    ->iconColor('primary')
 | 
				
			||||||
 | 
					                    ->collapsible()
 | 
				
			||||||
 | 
					                    ->collapsed()
 | 
				
			||||||
 | 
					                    ->columnSpan(([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 4,
 | 
				
			||||||
 | 
					                        'md' => 4,
 | 
				
			||||||
 | 
					                        'lg' => 6,
 | 
				
			||||||
 | 
					                    ]))
 | 
				
			||||||
 | 
					                    ->schema([
 | 
				
			||||||
 | 
					                        Forms\Components\Placeholder::make('Select an egg first to show its variables!')
 | 
				
			||||||
 | 
					                            ->hidden(fn (Forms\Get $get) => !empty($get('server_variables'))),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Forms\Components\Repeater::make('server_variables')
 | 
				
			||||||
 | 
					                            ->relationship('serverVariables')
 | 
				
			||||||
 | 
					                            ->grid(2)
 | 
				
			||||||
 | 
					                            ->reorderable(false)
 | 
				
			||||||
 | 
					                            ->addable(false)
 | 
				
			||||||
 | 
					                            ->deletable(false)
 | 
				
			||||||
 | 
					                            ->default([])
 | 
				
			||||||
 | 
					                            ->hidden(fn ($state) => empty($state))
 | 
				
			||||||
 | 
					                            ->schema([
 | 
				
			||||||
 | 
					                                Forms\Components\TextInput::make('variable_value')
 | 
				
			||||||
 | 
					                                    ->rules([
 | 
				
			||||||
 | 
					                                        fn (Forms\Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) {
 | 
				
			||||||
 | 
					                                            $validator = Validator::make(['validatorkey' => $value], [
 | 
				
			||||||
 | 
					                                                'validatorkey' => $get('rules'),
 | 
				
			||||||
 | 
					                                            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                            if ($validator->fails()) {
 | 
				
			||||||
 | 
					                                                $message = str($validator->errors()->first())->replace('validatorkey', $get('name'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                                $fail($message);
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                        },
 | 
				
			||||||
 | 
					                                    ])
 | 
				
			||||||
 | 
					                                    ->label(fn (Forms\Get $get) => $get('name'))
 | 
				
			||||||
 | 
					                                    //->hint('Rule')
 | 
				
			||||||
 | 
					                                    ->hintIcon('tabler-code')
 | 
				
			||||||
 | 
					                                    ->hintIconTooltip(fn (Forms\Get $get) => $get('rules'))
 | 
				
			||||||
 | 
					                                    ->prefix(fn (Forms\Get $get) => '{{' . $get('env_variable') . '}}')
 | 
				
			||||||
 | 
					                                    ->helperText(fn (Forms\Get $get) => empty($get('description')) ? '—' : $get('description'))
 | 
				
			||||||
 | 
					                                    ->maxLength(191),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                Forms\Components\Hidden::make('variable_id')->default(0),
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->columnSpan(2),
 | 
				
			||||||
 | 
					                    ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Forms\Components\Section::make('Resource Management')
 | 
				
			||||||
 | 
					                    // ->hiddenOn('create')
 | 
				
			||||||
 | 
					                    ->collapsed()
 | 
				
			||||||
 | 
					                    ->icon('tabler-server-cog')
 | 
				
			||||||
 | 
					                    ->iconColor('primary')
 | 
				
			||||||
 | 
					                    ->columns(2)
 | 
				
			||||||
 | 
					                    ->columnSpan(([
 | 
				
			||||||
 | 
					                        'default' => 2,
 | 
				
			||||||
 | 
					                        'sm' => 4,
 | 
				
			||||||
 | 
					                        'md' => 4,
 | 
				
			||||||
 | 
					                        'lg' => 6,
 | 
				
			||||||
 | 
					                    ]))
 | 
				
			||||||
 | 
					                    ->schema([
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('memory')
 | 
				
			||||||
 | 
					                            ->default(0)
 | 
				
			||||||
 | 
					                            ->label('Allocated Memory')
 | 
				
			||||||
 | 
					                            ->suffix('MB')
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->numeric(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('swap')
 | 
				
			||||||
 | 
					                            ->default(0)
 | 
				
			||||||
 | 
					                            ->label('Swap Memory')
 | 
				
			||||||
 | 
					                            ->suffix('MB')
 | 
				
			||||||
 | 
					                            ->helperText('0 disables swap and -1 allows unlimited swap')
 | 
				
			||||||
 | 
					                            ->minValue(-1)
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->numeric(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('disk')
 | 
				
			||||||
 | 
					                            ->default(0)
 | 
				
			||||||
 | 
					                            ->label('Disk Space Limit')
 | 
				
			||||||
 | 
					                            ->suffix('MB')
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->numeric(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('cpu')
 | 
				
			||||||
 | 
					                            ->default(0)
 | 
				
			||||||
 | 
					                            ->label('CPU Limit')
 | 
				
			||||||
 | 
					                            ->suffix('%')
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->numeric(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('threads')
 | 
				
			||||||
 | 
					                            ->hint('Advanced')
 | 
				
			||||||
 | 
					                            ->hintColor('danger')
 | 
				
			||||||
 | 
					                            ->helperText('Examples: 0, 0-1,3, or 0,1,3,4')
 | 
				
			||||||
 | 
					                            ->label('CPU Pinning')
 | 
				
			||||||
 | 
					                            ->suffixIcon('tabler-cpu')
 | 
				
			||||||
 | 
					                            ->maxLength(191),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Forms\Components\TextInput::make('io')
 | 
				
			||||||
 | 
					                            ->helperText('The IO performance relative to other running containers')
 | 
				
			||||||
 | 
					                            ->label('Block IO Proportion')
 | 
				
			||||||
 | 
					                            ->required()
 | 
				
			||||||
 | 
					                            ->minValue(0)
 | 
				
			||||||
 | 
					                            ->maxValue(1000)
 | 
				
			||||||
 | 
					                            ->step(10)
 | 
				
			||||||
 | 
					                            ->default(0)
 | 
				
			||||||
 | 
					                            ->numeric(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Forms\Components\ToggleButtons::make('oom_disabled')
 | 
				
			||||||
 | 
					                            ->label('OOM Killer')
 | 
				
			||||||
 | 
					                            ->inline()
 | 
				
			||||||
 | 
					                            ->default(false)
 | 
				
			||||||
 | 
					                            ->options([
 | 
				
			||||||
 | 
					                                false => 'Disabled',
 | 
				
			||||||
 | 
					                                true => 'Enabled',
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->colors([
 | 
				
			||||||
 | 
					                                false => 'success',
 | 
				
			||||||
 | 
					                                true => 'danger',
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->icons([
 | 
				
			||||||
 | 
					                                false => 'tabler-sword-off',
 | 
				
			||||||
 | 
					                                true => 'tabler-sword',
 | 
				
			||||||
 | 
					                            ])
 | 
				
			||||||
 | 
					                            ->required(),
 | 
				
			||||||
 | 
					                    ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->searchable(false)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('status')
 | 
				
			||||||
 | 
					                    ->default('unknown')
 | 
				
			||||||
 | 
					                    ->badge()
 | 
				
			||||||
 | 
					                    ->default(function (Server $server) {
 | 
				
			||||||
 | 
					                        if ($server->status !== null) {
 | 
				
			||||||
 | 
					                            return $server->status;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $statuses = collect($server->retrieveStatus())
 | 
				
			||||||
 | 
					                            ->mapWithKeys(function ($status) {
 | 
				
			||||||
 | 
					                                return [$status['configuration']['uuid'] => $status['state']];
 | 
				
			||||||
 | 
					                            })->all();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return $statuses[$server->uuid] ?? 'node_fail';
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->icon(fn ($state) => match ($state) {
 | 
				
			||||||
 | 
					                        'node_fail' => 'tabler-server-off',
 | 
				
			||||||
 | 
					                        'running' => 'tabler-heartbeat',
 | 
				
			||||||
 | 
					                        'removing' => 'tabler-heart-x',
 | 
				
			||||||
 | 
					                        'offline' => 'tabler-heart-off',
 | 
				
			||||||
 | 
					                        'paused' => 'tabler-heart-pause',
 | 
				
			||||||
 | 
					                        'installing' => 'tabler-heart-bolt',
 | 
				
			||||||
 | 
					                        'suspended' => 'tabler-heart-cancel',
 | 
				
			||||||
 | 
					                        default => 'tabler-heart-question',
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    ->color(fn (string $state): string => match ($state) {
 | 
				
			||||||
 | 
					                        'running' => 'success',
 | 
				
			||||||
 | 
					                        'installing', 'restarting' => 'primary',
 | 
				
			||||||
 | 
					                        'paused', 'removing' => 'warning',
 | 
				
			||||||
 | 
					                        'node_fail', 'install_failed', 'suspended' => 'danger',
 | 
				
			||||||
 | 
					                        default => 'gray',
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('uuid')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->label('UUID')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-brand-docker')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('node.name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-server-2')
 | 
				
			||||||
 | 
					                    ->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('egg.name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-egg')
 | 
				
			||||||
 | 
					                    ->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('user.username')
 | 
				
			||||||
 | 
					                    ->icon('tabler-user')
 | 
				
			||||||
 | 
					                    ->label('Owner')
 | 
				
			||||||
 | 
					                    ->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\SelectColumn::make('allocation_id')
 | 
				
			||||||
 | 
					                    ->label('Primary Allocation')
 | 
				
			||||||
 | 
					                    ->options(fn ($state, Server $server) => $server->allocations->mapWithKeys(
 | 
				
			||||||
 | 
					                        fn ($allocation) => [$allocation->id => $allocation->address])
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    ->selectablePlaceholder(false)
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('image')->hidden(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('backups_count')
 | 
				
			||||||
 | 
					                    ->counts('backups')
 | 
				
			||||||
 | 
					                    ->label('Backups')
 | 
				
			||||||
 | 
					                    ->icon('tabler-file-download')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    // Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListServers::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateServer::route('/create'),
 | 
				
			||||||
 | 
					            'edit' => Pages\EditServer::route('/{record}/edit'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								app/Filament/Resources/ServerResource/Pages/CreateServer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/Filament/Resources/ServerResource/Pages/CreateServer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\ServerResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\ServerResource;
 | 
				
			||||||
 | 
					use App\Services\Servers\ServerCreationService;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateServer extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = ServerResource::class;
 | 
				
			||||||
 | 
					    protected static bool $canCreateAnother = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function handleRecordCreation(array $data): Model
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** @var ServerCreationService $service */
 | 
				
			||||||
 | 
					        $service = resolve(ServerCreationService::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $server = $service->handle($data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $server;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //    protected function getRedirectUrl(): string
 | 
				
			||||||
 | 
					    //    {
 | 
				
			||||||
 | 
					    //        return $this->getResource()::getUrl('edit');
 | 
				
			||||||
 | 
					    //    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								app/Filament/Resources/ServerResource/Pages/EditServer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/Filament/Resources/ServerResource/Pages/EditServer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\ServerResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\ServerResource;
 | 
				
			||||||
 | 
					use App\Models\Server;
 | 
				
			||||||
 | 
					use App\Services\Servers\ServerDeletionService;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\EditRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditServer extends EditRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = ServerResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\Action::make('Delete')
 | 
				
			||||||
 | 
					                ->successRedirectUrl($this->getResource()::getUrl('index'))
 | 
				
			||||||
 | 
					                ->color('danger')
 | 
				
			||||||
 | 
					                ->action(fn (Server $server) => resolve(ServerDeletionService::class)->handle($server))
 | 
				
			||||||
 | 
					                ->requiresConfirmation(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/ServerResource/Pages/ListServers.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/ServerResource/Pages/ListServers.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\ServerResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\ServerResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListServers extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = ServerResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										153
									
								
								app/Filament/Resources/UserResource.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								app/Filament/Resources/UserResource.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\UserResource\Pages;
 | 
				
			||||||
 | 
					use App\Filament\Resources\UserResource\RelationManagers;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use Filament\Forms;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Section;
 | 
				
			||||||
 | 
					use Filament\Forms\Form;
 | 
				
			||||||
 | 
					use Filament\Resources\Resource;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Hash;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserResource extends Resource
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static ?string $model = User::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $navigationIcon = 'tabler-users';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static ?string $recordTitleAttribute = 'username';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function form(Form $form): Form
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $form
 | 
				
			||||||
 | 
					            ->schema([
 | 
				
			||||||
 | 
					                Section::make()->schema([
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('username')->required()->maxLength(191),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('email')->email()->required()->maxLength(191),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('name_first')
 | 
				
			||||||
 | 
					                        ->maxLength(191)
 | 
				
			||||||
 | 
					                        ->hidden(fn (string $operation): bool => $operation === 'create')
 | 
				
			||||||
 | 
					                        ->label('First Name'),
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('name_last')
 | 
				
			||||||
 | 
					                        ->maxLength(191)
 | 
				
			||||||
 | 
					                        ->hidden(fn (string $operation): bool => $operation === 'create')
 | 
				
			||||||
 | 
					                        ->label('Last Name'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Forms\Components\TextInput::make('password')
 | 
				
			||||||
 | 
					                        ->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
 | 
				
			||||||
 | 
					                        ->dehydrated(fn (?string $state): bool => filled($state))
 | 
				
			||||||
 | 
					                        ->required(fn (string $operation): bool => $operation === 'create')
 | 
				
			||||||
 | 
					                        ->password(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Forms\Components\ToggleButtons::make('root_admin')
 | 
				
			||||||
 | 
					                        ->label('Administrator (Root)')
 | 
				
			||||||
 | 
					                        ->options([
 | 
				
			||||||
 | 
					                            false => 'No',
 | 
				
			||||||
 | 
					                            true => 'Admin',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->colors([
 | 
				
			||||||
 | 
					                            false => 'primary',
 | 
				
			||||||
 | 
					                            true => 'danger',
 | 
				
			||||||
 | 
					                        ])
 | 
				
			||||||
 | 
					                        ->disableOptionWhen(function (string $operation, $value, User $user) {
 | 
				
			||||||
 | 
					                            if ($operation !== 'edit' || $value) {
 | 
				
			||||||
 | 
					                                return false;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return $user->isLastRootAdmin();
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        ->hint(fn (User $user) => $user->isLastRootAdmin() ? 'This is the last root administrator!' : '')
 | 
				
			||||||
 | 
					                        ->helperText(fn (User $user) => $user->isLastRootAdmin() ? 'You must have at least one root administrator in your system.' : '')
 | 
				
			||||||
 | 
					                        ->hintColor('warning')
 | 
				
			||||||
 | 
					                        ->inline()
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->default(false),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Forms\Components\Hidden::make('skipValidation')->default(true),
 | 
				
			||||||
 | 
					                    Forms\Components\Select::make('language')
 | 
				
			||||||
 | 
					                        ->required()
 | 
				
			||||||
 | 
					                        ->hidden()
 | 
				
			||||||
 | 
					                        ->default('en')
 | 
				
			||||||
 | 
					                        ->options(fn (User $user) => $user->getAvailableLanguages()),
 | 
				
			||||||
 | 
					                ])->columns(2),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->searchable(false)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\ImageColumn::make('picture')
 | 
				
			||||||
 | 
					                    ->visibleFrom('lg')
 | 
				
			||||||
 | 
					                    ->label('')
 | 
				
			||||||
 | 
					                    ->extraImgAttributes(['class' => 'rounded-full'])
 | 
				
			||||||
 | 
					                    ->defaultImageUrl(fn (User $user) => 'https://gravatar.com/avatar/' . md5(strtolower($user->email))),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('external_id')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->hidden(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('uuid')
 | 
				
			||||||
 | 
					                    ->label('UUID')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('username')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('email')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->icon('tabler-mail'),
 | 
				
			||||||
 | 
					                Tables\Columns\IconColumn::make('root_admin')
 | 
				
			||||||
 | 
					                    ->visibleFrom('md')
 | 
				
			||||||
 | 
					                    ->label('Admin')
 | 
				
			||||||
 | 
					                    ->boolean()
 | 
				
			||||||
 | 
					                    ->trueIcon('tabler-star')
 | 
				
			||||||
 | 
					                    ->falseIcon('tabler-star-off')
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\IconColumn::make('use_totp')->label('2FA')
 | 
				
			||||||
 | 
					                    ->visibleFrom('lg')
 | 
				
			||||||
 | 
					                    ->icon(fn (User $user) => $user->use_totp ? 'tabler-lock' : 'tabler-lock-open-off')
 | 
				
			||||||
 | 
					                    ->boolean()->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('servers_count')
 | 
				
			||||||
 | 
					                    ->counts('servers')
 | 
				
			||||||
 | 
					                    ->icon('tabler-server')
 | 
				
			||||||
 | 
					                    ->label('Servers'),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('subusers_count')
 | 
				
			||||||
 | 
					                    ->visibleFrom('sm')
 | 
				
			||||||
 | 
					                    ->counts('subusers')
 | 
				
			||||||
 | 
					                    ->icon('tabler-users')
 | 
				
			||||||
 | 
					                    // ->formatStateUsing(fn (string $state, $record): string => (string) ($record->servers_count + $record->subusers_count))
 | 
				
			||||||
 | 
					                    ->label('Subuser Accounts'),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->filters([
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->actions([
 | 
				
			||||||
 | 
					                Tables\Actions\EditAction::make(),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->bulkActions([
 | 
				
			||||||
 | 
					                Tables\Actions\BulkActionGroup::make([
 | 
				
			||||||
 | 
					                    Tables\Actions\DeleteBulkAction::make(),
 | 
				
			||||||
 | 
					                ]),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getRelations(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            RelationManagers\ServersRelationManager::class,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getPages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'index' => Pages\ListUsers::route('/'),
 | 
				
			||||||
 | 
					            'create' => Pages\CreateUser::route('/create'),
 | 
				
			||||||
 | 
					            'edit' => Pages\EditUser::route('/{record}/edit'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								app/Filament/Resources/UserResource/Pages/CreateUser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Filament/Resources/UserResource/Pages/CreateUser.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\UserResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\UserResource;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\CreateRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateUser extends CreateRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = UserResource::class;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										183
									
								
								app/Filament/Resources/UserResource/Pages/EditProfile.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								app/Filament/Resources/UserResource/Pages/EditProfile.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,183 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\UserResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\ActivityLog;
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use App\Services\Users\TwoFactorSetupService;
 | 
				
			||||||
 | 
					use chillerlan\QRCode\Common\EccLevel;
 | 
				
			||||||
 | 
					use chillerlan\QRCode\Common\Version;
 | 
				
			||||||
 | 
					use chillerlan\QRCode\QRCode;
 | 
				
			||||||
 | 
					use chillerlan\QRCode\QROptions;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Placeholder;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Repeater;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Select;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Tabs;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\TagsInput;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\Tabs\Tab;
 | 
				
			||||||
 | 
					use Filament\Forms\Components\TextInput;
 | 
				
			||||||
 | 
					use Filament\Forms\Get;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Hash;
 | 
				
			||||||
 | 
					use Illuminate\Support\HtmlString;
 | 
				
			||||||
 | 
					use Illuminate\Validation\Rules\Password;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditProfile extends \Filament\Pages\Auth\EditProfile
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected function getForms(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'form' => $this->form(
 | 
				
			||||||
 | 
					                $this->makeForm()
 | 
				
			||||||
 | 
					                    ->schema([
 | 
				
			||||||
 | 
					                        Tabs::make()->schema([
 | 
				
			||||||
 | 
					                            Tab::make('Account')
 | 
				
			||||||
 | 
					                                ->icon('tabler-user')
 | 
				
			||||||
 | 
					                                ->schema([
 | 
				
			||||||
 | 
					                                    TextInput::make('username')
 | 
				
			||||||
 | 
					                                        ->disabled()
 | 
				
			||||||
 | 
					                                        ->readOnly()
 | 
				
			||||||
 | 
					                                        ->maxLength(191)
 | 
				
			||||||
 | 
					                                        ->unique(ignoreRecord: true)
 | 
				
			||||||
 | 
					                                        ->autofocus(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    TextInput::make('email')
 | 
				
			||||||
 | 
					                                        ->prefixIcon('tabler-mail')
 | 
				
			||||||
 | 
					                                        ->email()
 | 
				
			||||||
 | 
					                                        ->required()
 | 
				
			||||||
 | 
					                                        ->maxLength(191)
 | 
				
			||||||
 | 
					                                        ->unique(ignoreRecord: true),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    TextInput::make('password')
 | 
				
			||||||
 | 
					                                        ->password()
 | 
				
			||||||
 | 
					                                        ->prefixIcon('tabler-password')
 | 
				
			||||||
 | 
					                                        ->revealable(filament()->arePasswordsRevealable())
 | 
				
			||||||
 | 
					                                        ->rule(Password::default())
 | 
				
			||||||
 | 
					                                        ->autocomplete('new-password')
 | 
				
			||||||
 | 
					                                        ->dehydrated(fn ($state): bool => filled($state))
 | 
				
			||||||
 | 
					                                        ->dehydrateStateUsing(fn ($state): string => Hash::make($state))
 | 
				
			||||||
 | 
					                                        ->live(debounce: 500)
 | 
				
			||||||
 | 
					                                        ->same('passwordConfirmation'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    TextInput::make('passwordConfirmation')
 | 
				
			||||||
 | 
					                                        ->password()
 | 
				
			||||||
 | 
					                                        ->prefixIcon('tabler-password-fingerprint')
 | 
				
			||||||
 | 
					                                        ->revealable(filament()->arePasswordsRevealable())
 | 
				
			||||||
 | 
					                                        ->required()
 | 
				
			||||||
 | 
					                                        ->visible(fn (Get $get): bool => filled($get('password')))
 | 
				
			||||||
 | 
					                                        ->dehydrated(false),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    Select::make('language')
 | 
				
			||||||
 | 
					                                        ->required()
 | 
				
			||||||
 | 
					                                        ->prefixIcon('tabler-flag')
 | 
				
			||||||
 | 
					                                        ->live()
 | 
				
			||||||
 | 
					                                        ->default('en')
 | 
				
			||||||
 | 
					                                        ->helperText(fn (User $user, $state) => new HtmlString($user->isLanguageTranslated($state) ? '' : "
 | 
				
			||||||
 | 
					                                                Your language ($state) has not been translated yet!
 | 
				
			||||||
 | 
					                                                But never fear, you can help fix that by
 | 
				
			||||||
 | 
					                                                <a style='color: rgb(56, 189, 248)' href='https://crowdin.com/project/pelican-dev'>contributing directly here</a>.
 | 
				
			||||||
 | 
					                                            ")
 | 
				
			||||||
 | 
					                                        )
 | 
				
			||||||
 | 
					                                        ->options(fn (User $user) => $user->getAvailableLanguages()),
 | 
				
			||||||
 | 
					                                ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Tab::make('2FA')
 | 
				
			||||||
 | 
					                                ->icon('tabler-shield-lock')
 | 
				
			||||||
 | 
					                                ->schema(function () {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    if ($this->getUser()->use_totp) {
 | 
				
			||||||
 | 
					                                        return [
 | 
				
			||||||
 | 
					                                            Placeholder::make('2FA already enabled!'),
 | 
				
			||||||
 | 
					                                        ];
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                    $setupService = app(TwoFactorSetupService::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    ['image_url_data' => $url] = $setupService->handle($this->getUser());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    $options = new QROptions([
 | 
				
			||||||
 | 
					                                        'svgLogo' => public_path('pelican.svg'),
 | 
				
			||||||
 | 
					                                        'addLogoSpace' => true,
 | 
				
			||||||
 | 
					                                        'logoSpaceWidth' => 13,
 | 
				
			||||||
 | 
					                                        'logoSpaceHeight' => 13,
 | 
				
			||||||
 | 
					                                    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    // https://github.com/chillerlan/php-qrcode/blob/main/examples/svgWithLogo.php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    // SVG logo options (see extended class)
 | 
				
			||||||
 | 
					                                    $options->svgLogo = public_path('pelican.svg'); // logo from: https://github.com/simple-icons/simple-icons
 | 
				
			||||||
 | 
					                                    $options->svgLogoScale = 0.05;
 | 
				
			||||||
 | 
					                                    // $options->svgLogoCssClass     = 'dark';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    // QROptions
 | 
				
			||||||
 | 
					                                    $options->version = Version::AUTO;
 | 
				
			||||||
 | 
					                                    // $options->outputInterface     = QRSvgWithLogo::class;
 | 
				
			||||||
 | 
					                                    $options->outputBase64 = false;
 | 
				
			||||||
 | 
					                                    $options->eccLevel = EccLevel::H; // ECC level H is necessary when using logos
 | 
				
			||||||
 | 
					                                    $options->addQuietzone = true;
 | 
				
			||||||
 | 
					                                    // $options->drawLightModules    = true;
 | 
				
			||||||
 | 
					                                    $options->connectPaths = true;
 | 
				
			||||||
 | 
					                                    $options->drawCircularModules = true;
 | 
				
			||||||
 | 
					                                    // $options->circleRadius        = 0.45;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    $options->svgDefs = '<linearGradient id="gradient" x1="100%" y2="100%">
 | 
				
			||||||
 | 
					                                            <stop stop-color="#7dd4fc" offset="0"/>
 | 
				
			||||||
 | 
					                                            <stop stop-color="#38bdf8" offset="0.5"/>
 | 
				
			||||||
 | 
					                                            <stop stop-color="#0369a1" offset="1"/>
 | 
				
			||||||
 | 
					                                        </linearGradient>
 | 
				
			||||||
 | 
					                                        <style><![CDATA[
 | 
				
			||||||
 | 
					                                            .dark{fill: url(#gradient);}
 | 
				
			||||||
 | 
					                                            .light{fill: #000;}
 | 
				
			||||||
 | 
					                                        ]]></style>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    $image = (new QRCode($options))->render($url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    return [
 | 
				
			||||||
 | 
					                                        Placeholder::make('qr')
 | 
				
			||||||
 | 
					                                            ->label('Scan QR Code')
 | 
				
			||||||
 | 
					                                            ->content(fn () => new HtmlString("
 | 
				
			||||||
 | 
					                                                <div style='width: 300px'>$image</div>
 | 
				
			||||||
 | 
					                                            "))
 | 
				
			||||||
 | 
					                                            ->default('asdfasdf'),
 | 
				
			||||||
 | 
					                                    ];
 | 
				
			||||||
 | 
					                                }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Tab::make('API Keys')
 | 
				
			||||||
 | 
					                                ->icon('tabler-key')
 | 
				
			||||||
 | 
					                                ->schema([
 | 
				
			||||||
 | 
					                                    Placeholder::make('Coming soon!'),
 | 
				
			||||||
 | 
					                                    TagsInput::make('allowed_ips')
 | 
				
			||||||
 | 
					                                        ->placeholder('Example: 127.0.0.1 or 192.168.1.1')
 | 
				
			||||||
 | 
					                                        ->label('Whitelisted IPv4 Addresses')
 | 
				
			||||||
 | 
					                                        ->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
 | 
				
			||||||
 | 
					                                        ->columnSpanFull()
 | 
				
			||||||
 | 
					                                        ->hidden()
 | 
				
			||||||
 | 
					                                        ->default(null),
 | 
				
			||||||
 | 
					                                ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Tab::make('SSH Keys')
 | 
				
			||||||
 | 
					                                ->icon('tabler-lock-code')
 | 
				
			||||||
 | 
					                                ->schema([
 | 
				
			||||||
 | 
					                                    Placeholder::make('Coming soon!'),
 | 
				
			||||||
 | 
					                                ]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Tab::make('Activity')
 | 
				
			||||||
 | 
					                                ->icon('tabler-history')
 | 
				
			||||||
 | 
					                                ->schema([
 | 
				
			||||||
 | 
					                                    Repeater::make('activity')
 | 
				
			||||||
 | 
					                                        ->deletable(false)
 | 
				
			||||||
 | 
					                                        ->addable(false)
 | 
				
			||||||
 | 
					                                        ->relationship()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        ->schema([
 | 
				
			||||||
 | 
					                                            Placeholder::make('activity!')->label('')->content(fn (ActivityLog $log) => new HtmlString($log->htmlable())),
 | 
				
			||||||
 | 
					                                        ]),
 | 
				
			||||||
 | 
					                                ]),
 | 
				
			||||||
 | 
					                        ]),
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    ->operation('edit')
 | 
				
			||||||
 | 
					                    ->model($this->getUser())
 | 
				
			||||||
 | 
					                    ->statePath('data')
 | 
				
			||||||
 | 
					                    ->inlineLabel(!static::isSimple()),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/EditUser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/EditUser.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\UserResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\UserResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\EditRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EditUser extends EditRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = UserResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\DeleteAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/ListUsers.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Filament/Resources/UserResource/Pages/ListUsers.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\UserResource\Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\UserResource;
 | 
				
			||||||
 | 
					use Filament\Actions;
 | 
				
			||||||
 | 
					use Filament\Resources\Pages\ListRecords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ListUsers extends ListRecords
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $resource = UserResource::class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getHeaderActions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            Actions\CreateAction::make(),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Filament\Resources\UserResource\RelationManagers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Server;
 | 
				
			||||||
 | 
					use Filament\Tables;
 | 
				
			||||||
 | 
					use Filament\Tables\Table;
 | 
				
			||||||
 | 
					use Filament\Resources\RelationManagers\RelationManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ServersRelationManager extends RelationManager
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    protected static string $relationship = 'servers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function table(Table $table): Table
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $table
 | 
				
			||||||
 | 
					            ->searchable(false)
 | 
				
			||||||
 | 
					            ->columns([
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('uuid')
 | 
				
			||||||
 | 
					                    ->hidden()
 | 
				
			||||||
 | 
					                    ->label('UUID')
 | 
				
			||||||
 | 
					                    ->searchable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-brand-docker')
 | 
				
			||||||
 | 
					                    ->searchable()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('node.name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-server-2')
 | 
				
			||||||
 | 
					                    ->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('egg.name')
 | 
				
			||||||
 | 
					                    ->icon('tabler-egg')
 | 
				
			||||||
 | 
					                    ->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\SelectColumn::make('allocation.id')
 | 
				
			||||||
 | 
					                    ->label('Primary Allocation')
 | 
				
			||||||
 | 
					                    ->options(fn ($state, Server $server) => [$server->allocation->id => $server->allocation->address])
 | 
				
			||||||
 | 
					                    ->selectablePlaceholder(false)
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('image')->hidden(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('databases_count')
 | 
				
			||||||
 | 
					                    ->counts('databases')
 | 
				
			||||||
 | 
					                    ->label('Databases')
 | 
				
			||||||
 | 
					                    ->icon('tabler-database')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					                Tables\Columns\TextColumn::make('backups_count')
 | 
				
			||||||
 | 
					                    ->counts('backups')
 | 
				
			||||||
 | 
					                    ->label('Backups')
 | 
				
			||||||
 | 
					                    ->icon('tabler-file-download')
 | 
				
			||||||
 | 
					                    ->numeric()
 | 
				
			||||||
 | 
					                    ->sortable(),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,152 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace App\Http\Controllers\Admin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use App\Models\Egg;
 | 
					 | 
				
			||||||
use Ramsey\Uuid\Uuid;
 | 
					 | 
				
			||||||
use Illuminate\View\View;
 | 
					 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					 | 
				
			||||||
use Illuminate\Http\Response;
 | 
					 | 
				
			||||||
use App\Models\Mount;
 | 
					 | 
				
			||||||
use Illuminate\Http\RedirectResponse;
 | 
					 | 
				
			||||||
use Prologue\Alerts\AlertsMessageBag;
 | 
					 | 
				
			||||||
use Illuminate\View\Factory as ViewFactory;
 | 
					 | 
				
			||||||
use App\Http\Controllers\Controller;
 | 
					 | 
				
			||||||
use App\Http\Requests\Admin\MountFormRequest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MountController extends Controller
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * MountController constructor.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function __construct(
 | 
					 | 
				
			||||||
        protected AlertsMessageBag $alert,
 | 
					 | 
				
			||||||
        protected ViewFactory $view
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Return the mount overview page.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function index(): View
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return view('admin.mounts.index', [
 | 
					 | 
				
			||||||
            'mounts' => Mount::query()->withCount(['eggs', 'nodes'])->get(),
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Return the mount view page.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function view(string $id): View
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return view('admin.mounts.view', [
 | 
					 | 
				
			||||||
            'mount' => Mount::with(['eggs', 'nodes'])->findOrFail($id),
 | 
					 | 
				
			||||||
            'eggs' => Egg::all(),
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handle request to create new mount.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws \Throwable
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function create(MountFormRequest $request): RedirectResponse
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $model = (new Mount())->fill($request->validated());
 | 
					 | 
				
			||||||
        $model->forceFill(['uuid' => Uuid::uuid4()->toString()]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $model->saveOrFail();
 | 
					 | 
				
			||||||
        $mount = $model->fresh();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->alert->success('Mount was created successfully.')->flash();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return redirect()->route('admin.mounts.view', $mount->id);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handle request to update or delete location.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws \Throwable
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function update(MountFormRequest $request, Mount $mount): RedirectResponse
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if ($request->input('action') === 'delete') {
 | 
					 | 
				
			||||||
            return $this->delete($mount);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $mount->forceFill($request->validated())->save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->alert->success('Mount was updated successfully.')->flash();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return redirect()->route('admin.mounts.view', $mount->id);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Delete a location from the system.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws \Exception
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function delete(Mount $mount): RedirectResponse
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $mount->delete();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return redirect()->route('admin.mounts');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Adds eggs to the mount's many-to-many relation.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function addEggs(Request $request, Mount $mount): RedirectResponse
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $validatedData = $request->validate([
 | 
					 | 
				
			||||||
            'eggs' => 'required|exists:eggs,id',
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $eggs = $validatedData['eggs'] ?? [];
 | 
					 | 
				
			||||||
        if (count($eggs) > 0) {
 | 
					 | 
				
			||||||
            $mount->eggs()->attach($eggs);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->alert->success('Mount was updated successfully.')->flash();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return redirect()->route('admin.mounts.view', $mount->id);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Adds nodes to the mount's many-to-many relation.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function addNodes(Request $request, Mount $mount): RedirectResponse
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $data = $request->validate(['nodes' => 'required|exists:nodes,id']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $nodes = $data['nodes'] ?? [];
 | 
					 | 
				
			||||||
        if (count($nodes) > 0) {
 | 
					 | 
				
			||||||
            $mount->nodes()->attach($nodes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->alert->success('Mount was updated successfully.')->flash();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return redirect()->route('admin.mounts.view', $mount->id);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Deletes an egg from the mount's many-to-many relation.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function deleteEgg(Mount $mount, int $egg_id): Response
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $mount->eggs()->detach($egg_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return response('', 204);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Deletes a node from the mount's many-to-many relation.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function deleteNode(Mount $mount, int $node_id): Response
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $mount->nodes()->detach($node_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return response('', 204);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -39,29 +39,8 @@ class NodeViewController extends Controller
 | 
				
			|||||||
            ->where('node_id', '=', $node->id)
 | 
					            ->where('node_id', '=', $node->id)
 | 
				
			||||||
            ->first();
 | 
					            ->first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $usageStats = Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])
 | 
					 | 
				
			||||||
            ->mapWithKeys(function ($value, $key) use ($node) {
 | 
					 | 
				
			||||||
                $maxUsage = $node->{$key};
 | 
					 | 
				
			||||||
                if ($node->{$key . '_overallocate'} > 0) {
 | 
					 | 
				
			||||||
                    $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                $percent = ($value / $maxUsage) * 100;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return [
 | 
					 | 
				
			||||||
                    $key => [
 | 
					 | 
				
			||||||
                        'value' => number_format($value),
 | 
					 | 
				
			||||||
                        'max' => number_format($maxUsage),
 | 
					 | 
				
			||||||
                        'percent' => $percent,
 | 
					 | 
				
			||||||
                        'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                ];
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            ->toArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return view('admin.nodes.view.index', [
 | 
					        return view('admin.nodes.view.index', [
 | 
				
			||||||
            'node' => $node,
 | 
					            'node' => $node,
 | 
				
			||||||
            'stats' => $usageStats,
 | 
					 | 
				
			||||||
            'version' => $this->versionService,
 | 
					            'version' => $this->versionService,
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,12 +2,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Controllers\Admin\Servers;
 | 
					namespace App\Http\Controllers\Admin\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use App\Models\DatabaseHost;
 | 
					use App\Models\DatabaseHost;
 | 
				
			||||||
use App\Models\Egg;
 | 
					use App\Models\Egg;
 | 
				
			||||||
use App\Models\Mount;
 | 
					use App\Models\Mount;
 | 
				
			||||||
use App\Models\Node;
 | 
					use App\Models\Node;
 | 
				
			||||||
use Illuminate\View\View;
 | 
					use Illuminate\View\View;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					 | 
				
			||||||
use App\Models\Server;
 | 
					use App\Models\Server;
 | 
				
			||||||
use App\Exceptions\DisplayException;
 | 
					use App\Exceptions\DisplayException;
 | 
				
			||||||
use App\Http\Controllers\Controller;
 | 
					use App\Http\Controllers\Controller;
 | 
				
			||||||
@ -22,14 +22,14 @@ class ServerViewController extends Controller
 | 
				
			|||||||
     * ServerViewController constructor.
 | 
					     * ServerViewController constructor.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private EnvironmentService $environmentService,
 | 
					        private readonly EnvironmentService $environmentService,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the index view for a server.
 | 
					     * Returns the index view for a server.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function index(Request $request, Server $server): View
 | 
					    public function index(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return view('admin.servers.view.index', compact('server'));
 | 
					        return view('admin.servers.view.index', compact('server'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -37,7 +37,7 @@ class ServerViewController extends Controller
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the server details page.
 | 
					     * Returns the server details page.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function details(Request $request, Server $server): View
 | 
					    public function details(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return view('admin.servers.view.details', compact('server'));
 | 
					        return view('admin.servers.view.details', compact('server'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -45,7 +45,7 @@ class ServerViewController extends Controller
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns a view of server build settings.
 | 
					     * Returns a view of server build settings.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function build(Request $request, Server $server): View
 | 
					    public function build(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $allocations = $server->node->allocations->toBase();
 | 
					        $allocations = $server->node->allocations->toBase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -59,7 +59,7 @@ class ServerViewController extends Controller
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the server startup management page.
 | 
					     * Returns the server startup management page.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function startup(Request $request, Server $server): View
 | 
					    public function startup(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $variables = $this->environmentService->handle($server);
 | 
					        $variables = $this->environmentService->handle($server);
 | 
				
			||||||
        $eggs = Egg::all()->keyBy('id');
 | 
					        $eggs = Egg::all()->keyBy('id');
 | 
				
			||||||
@ -76,7 +76,7 @@ class ServerViewController extends Controller
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns all the databases that exist for the server.
 | 
					     * Returns all the databases that exist for the server.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function database(Request $request, Server $server): View
 | 
					    public function database(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return view('admin.servers.view.database', [
 | 
					        return view('admin.servers.view.database', [
 | 
				
			||||||
            'hosts' => DatabaseHost::all(),
 | 
					            'hosts' => DatabaseHost::all(),
 | 
				
			||||||
@ -87,7 +87,7 @@ class ServerViewController extends Controller
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns all the mounts that exist for the server.
 | 
					     * Returns all the mounts that exist for the server.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function mounts(Request $request, Server $server): View
 | 
					    public function mounts(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $server->load('mounts');
 | 
					        $server->load('mounts');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -108,9 +108,9 @@ class ServerViewController extends Controller
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @throws \App\Exceptions\DisplayException
 | 
					     * @throws \App\Exceptions\DisplayException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function manage(Request $request, Server $server): View
 | 
					    public function manage(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($server->status === Server::STATUS_INSTALL_FAILED) {
 | 
					        if ($server->status === ServerState::InstallFailed) {
 | 
				
			||||||
            throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.');
 | 
					            throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -135,7 +135,7 @@ class ServerViewController extends Controller
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the server deletion page.
 | 
					     * Returns the server deletion page.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function delete(Request $request, Server $server): View
 | 
					    public function delete(Server $server): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return view('admin.servers.view.delete', compact('server'));
 | 
					        return view('admin.servers.view.delete', compact('server'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Controllers\Admin;
 | 
					namespace App\Http\Controllers\Admin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use App\Models\User;
 | 
					use App\Models\User;
 | 
				
			||||||
use Illuminate\Http\Response;
 | 
					use Illuminate\Http\Response;
 | 
				
			||||||
@ -71,11 +72,11 @@ class ServersController extends Controller
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function toggleInstall(Server $server): RedirectResponse
 | 
					    public function toggleInstall(Server $server): RedirectResponse
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($server->status === Server::STATUS_INSTALL_FAILED) {
 | 
					        if ($server->status === ServerState::InstallFailed) {
 | 
				
			||||||
            throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
 | 
					            throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $server->status = $server->isInstalled() ? Server::STATUS_INSTALLING : null;
 | 
					        $server->status = $server->isInstalled() ? ServerState::Installing : null;
 | 
				
			||||||
        $server->save();
 | 
					        $server->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->alert->success(trans('admin/server.alerts.install_toggled'))->flash();
 | 
					        $this->alert->success(trans('admin/server.alerts.install_toggled'))->flash();
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Controllers\Api\Client\Servers;
 | 
					namespace App\Http\Controllers\Api\Client\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use App\Models\Backup;
 | 
					use App\Models\Backup;
 | 
				
			||||||
use App\Models\Server;
 | 
					use App\Models\Server;
 | 
				
			||||||
@ -212,7 +213,7 @@ class BackupController extends ClientApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // Update the status right away for the server so that we know not to allow certain
 | 
					            // Update the status right away for the server so that we know not to allow certain
 | 
				
			||||||
            // actions against it via the Panel API.
 | 
					            // actions against it via the Panel API.
 | 
				
			||||||
            $server->update(['status' => Server::STATUS_RESTORING_BACKUP]);
 | 
					            $server->update(['status' => ServerState::RestoringBackup]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate'));
 | 
					            $this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate'));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Controllers\Api\Remote\Servers;
 | 
					namespace App\Http\Controllers\Api\Remote\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use App\Models\Backup;
 | 
					use App\Models\Backup;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use App\Models\Server;
 | 
					use App\Models\Server;
 | 
				
			||||||
@ -81,7 +82,7 @@ class ServerDetailsController extends Controller
 | 
				
			|||||||
                    ->latest('timestamp'),
 | 
					                    ->latest('timestamp'),
 | 
				
			||||||
            ])
 | 
					            ])
 | 
				
			||||||
            ->where('node_id', $node->id)
 | 
					            ->where('node_id', $node->id)
 | 
				
			||||||
            ->where('status', Server::STATUS_RESTORING_BACKUP)
 | 
					            ->where('status', ServerState::RestoringBackup)
 | 
				
			||||||
            ->get();
 | 
					            ->get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->connection->transaction(function () use ($node, $servers) {
 | 
					        $this->connection->transaction(function () use ($node, $servers) {
 | 
				
			||||||
@ -108,7 +109,7 @@ class ServerDetailsController extends Controller
 | 
				
			|||||||
            // Update any server marked as installing or restoring as being in a normal state
 | 
					            // Update any server marked as installing or restoring as being in a normal state
 | 
				
			||||||
            // at this point in the process.
 | 
					            // at this point in the process.
 | 
				
			||||||
            Server::query()->where('node_id', $node->id)
 | 
					            Server::query()->where('node_id', $node->id)
 | 
				
			||||||
                ->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP])
 | 
					                ->whereIn('status', [ServerState::Installing, ServerState::RestoringBackup])
 | 
				
			||||||
                ->update(['status' => null]);
 | 
					                ->update(['status' => null]);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Controllers\Api\Remote\Servers;
 | 
					namespace App\Http\Controllers\Api\Remote\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use Illuminate\Http\Response;
 | 
					use Illuminate\Http\Response;
 | 
				
			||||||
use App\Models\Server;
 | 
					use App\Models\Server;
 | 
				
			||||||
use Illuminate\Http\JsonResponse;
 | 
					use Illuminate\Http\JsonResponse;
 | 
				
			||||||
@ -36,16 +37,16 @@ class ServerInstallController extends Controller
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Make sure the type of failure is accurate
 | 
					        // Make sure the type of failure is accurate
 | 
				
			||||||
        if (!$request->boolean('successful')) {
 | 
					        if (!$request->boolean('successful')) {
 | 
				
			||||||
            $status = Server::STATUS_INSTALL_FAILED;
 | 
					            $status = ServerState::InstallFailed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if ($request->boolean('reinstall')) {
 | 
					            if ($request->boolean('reinstall')) {
 | 
				
			||||||
                $status = Server::STATUS_REINSTALL_FAILED;
 | 
					                $status = ServerState::ReinstallFailed;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Keep the server suspended if it's already suspended
 | 
					        // Keep the server suspended if it's already suspended
 | 
				
			||||||
        if ($server->status === Server::STATUS_SUSPENDED) {
 | 
					        if ($server->status === ServerState::Suspended) {
 | 
				
			||||||
            $status = Server::STATUS_SUSPENDED;
 | 
					            $status = ServerState::Suspended;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $previouslyInstalledAt = $server->installed_at;
 | 
					        $previouslyInstalledAt = $server->installed_at;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace App\Http\Requests\Admin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use App\Models\Mount;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MountFormRequest extends AdminFormRequest
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Set up the validation rules to use for these requests.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function rules(): array
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if ($this->method() === 'PATCH') {
 | 
					 | 
				
			||||||
            /** @var Mount $mount */
 | 
					 | 
				
			||||||
            $mount = $this->route()->parameter('mount');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return Mount::getRulesForUpdate($mount->id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Mount::getRules();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -2,7 +2,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Http\Requests\Admin\Node;
 | 
					namespace App\Http\Requests\Admin\Node;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Rules\Fqdn;
 | 
					 | 
				
			||||||
use App\Models\Node;
 | 
					use App\Models\Node;
 | 
				
			||||||
use App\Http\Requests\Admin\AdminFormRequest;
 | 
					use App\Http\Requests\Admin\AdminFormRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -17,9 +16,6 @@ class NodeFormRequest extends AdminFormRequest
 | 
				
			|||||||
            return Node::getRulesForUpdate($this->route()->parameter('node'));
 | 
					            return Node::getRulesForUpdate($this->route()->parameter('node'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $data = Node::getRules();
 | 
					        return Node::getRules();
 | 
				
			||||||
        $data['fqdn'][] = Fqdn::make('scheme');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return $data;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										30
									
								
								app/Livewire/NodeSystemInformation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/Livewire/NodeSystemInformation.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Livewire;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Node;
 | 
				
			||||||
 | 
					use Livewire\Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NodeSystemInformation extends Component
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public Node $node;
 | 
				
			||||||
 | 
					    public string $sizeClasses;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function render()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return view('livewire.node-system-information');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function placeholder()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return <<<'HTML'
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <x-filament::icon
 | 
				
			||||||
 | 
					                :icon="'tabler-heart-question'"
 | 
				
			||||||
 | 
					                @class(['fi-ta-icon-item', $sizeClasses, 'fi-color-custom text-custom-500 dark:text-custom-400', 'fi-color-warning'])
 | 
				
			||||||
 | 
					                @style([\Filament\Support\get_color_css_variables('warning', shades: [400, 500], alias: 'tables::columns.icon-column.item')])
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        HTML;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -144,4 +144,29 @@ class ActivityLog extends Model
 | 
				
			|||||||
            Event::dispatch(new ActivityLogged($model));
 | 
					            Event::dispatch(new ActivityLogged($model));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function htmlable()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $user = $this->actor;
 | 
				
			||||||
 | 
					        if (!$user instanceof User) {
 | 
				
			||||||
 | 
					            $user = new User([
 | 
				
			||||||
 | 
					                'email' => 'system@pelican.dev',
 | 
				
			||||||
 | 
					                'username' => 'system',
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $event = __('activity.'.str($this->event)->replace(':', '.'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return "
 | 
				
			||||||
 | 
					            <div style='display: flex; align-items: center;'>
 | 
				
			||||||
 | 
					                <img width='50px' height='50px' src='{$user->getFilamentAvatarUrl()}' style='margin-right: 15px' />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <p>$user->username — $this->event</p>
 | 
				
			||||||
 | 
					                    <p>$event</p>
 | 
				
			||||||
 | 
					                    <p>$this->ip — <span title='{$this->timestamp->format('M j, Y g:ia')}'>{$this->timestamp->diffForHumans()}</span></p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        ";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@
 | 
				
			|||||||
namespace App\Models;
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Exceptions\Service\Allocation\ServerUsingAllocationException;
 | 
					use App\Exceptions\Service\Allocation\ServerUsingAllocationException;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Casts\Attribute;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
					use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -111,9 +112,16 @@ class Allocation extends Model
 | 
				
			|||||||
        return !is_null($this->ip_alias);
 | 
					        return !is_null($this->ip_alias);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function address(): Attribute
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Attribute::make(
 | 
				
			||||||
 | 
					            get: fn () => "$this->ip:$this->port",
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function toString(): string
 | 
					    public function toString(): string
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return sprintf('%s:%s', $this->ip, $this->port);
 | 
					        return $this->address;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -83,6 +83,8 @@ class ApiKey extends Model
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public const KEY_LENGTH = 32;
 | 
					    public const KEY_LENGTH = 32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public const RESOURCES = ['servers', 'nodes', 'allocations', 'users', 'eggs', 'database_hosts', 'server_databases'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The table associated with the model.
 | 
					     * The table associated with the model.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -92,12 +94,21 @@ class ApiKey extends Model
 | 
				
			|||||||
     * Fields that are mass assignable.
 | 
					     * Fields that are mass assignable.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $fillable = [
 | 
					    protected $fillable = [
 | 
				
			||||||
 | 
					        'user_id',
 | 
				
			||||||
 | 
					        'key_type',
 | 
				
			||||||
        'identifier',
 | 
					        'identifier',
 | 
				
			||||||
        'token',
 | 
					        'token',
 | 
				
			||||||
        'allowed_ips',
 | 
					        'allowed_ips',
 | 
				
			||||||
        'memo',
 | 
					        'memo',
 | 
				
			||||||
        'last_used_at',
 | 
					        'last_used_at',
 | 
				
			||||||
        'expires_at',
 | 
					        'expires_at',
 | 
				
			||||||
 | 
					        'r_' . AdminAcl::RESOURCE_USERS,
 | 
				
			||||||
 | 
					        'r_' . AdminAcl::RESOURCE_ALLOCATIONS,
 | 
				
			||||||
 | 
					        'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS,
 | 
				
			||||||
 | 
					        'r_' . AdminAcl::RESOURCE_SERVER_DATABASES,
 | 
				
			||||||
 | 
					        'r_' . AdminAcl::RESOURCE_EGGS,
 | 
				
			||||||
 | 
					        'r_' . AdminAcl::RESOURCE_NODES,
 | 
				
			||||||
 | 
					        'r_' . AdminAcl::RESOURCE_SERVERS,
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -187,7 +198,7 @@ class ApiKey extends Model
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]);
 | 
					        Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $type === self::TYPE_ACCOUNT ? 'ptlc_' : 'ptla_';
 | 
					        return $type === self::TYPE_ACCOUNT ? 'plcn_' : 'peli_';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -65,6 +65,11 @@ class DatabaseHost extends Model
 | 
				
			|||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRouteKeyName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'id';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Gets the node associated with a database host.
 | 
					     * Gets the node associated with a database host.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
 | 
				
			|||||||
@ -152,6 +152,11 @@ class Egg extends Model
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRouteKeyName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'id';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the install script for the egg; if egg is copying from another
 | 
					     * Returns the install script for the egg; if egg is copying from another
 | 
				
			||||||
     * it will return the copied script.
 | 
					     * it will return the copied script.
 | 
				
			||||||
 | 
				
			|||||||
@ -71,8 +71,8 @@ class Mount extends Model
 | 
				
			|||||||
     * Blacklisted source paths.
 | 
					     * Blacklisted source paths.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static $invalidSourcePaths = [
 | 
					    public static $invalidSourcePaths = [
 | 
				
			||||||
        '/etc/panel',
 | 
					        '/etc/pelican',
 | 
				
			||||||
        '/var/lib/panel/volumes',
 | 
					        '/var/lib/pelican/volumes',
 | 
				
			||||||
        '/srv/daemon-data',
 | 
					        '/srv/daemon-data',
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -115,4 +115,9 @@ class Mount extends Model
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->belongsToMany(Server::class);
 | 
					        return $this->belongsToMany(Server::class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRouteKeyName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'id';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Models;
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Exceptions\Service\HasActiveServersException;
 | 
				
			||||||
 | 
					use App\Repositories\Daemon\DaemonConfigurationRepository;
 | 
				
			||||||
 | 
					use Exception;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Http;
 | 
				
			||||||
use Illuminate\Support\Str;
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
use Symfony\Component\Yaml\Yaml;
 | 
					use Symfony\Component\Yaml\Yaml;
 | 
				
			||||||
use Illuminate\Notifications\Notifiable;
 | 
					use Illuminate\Notifications\Notifiable;
 | 
				
			||||||
@ -79,9 +83,9 @@ class Node extends Model
 | 
				
			|||||||
        'fqdn' => 'required|string',
 | 
					        'fqdn' => 'required|string',
 | 
				
			||||||
        'scheme' => 'required',
 | 
					        'scheme' => 'required',
 | 
				
			||||||
        'behind_proxy' => 'boolean',
 | 
					        'behind_proxy' => 'boolean',
 | 
				
			||||||
        'memory' => 'required|numeric|min:1',
 | 
					        'memory' => 'required|numeric|min:0',
 | 
				
			||||||
        'memory_overallocate' => 'required|numeric|min:-1',
 | 
					        'memory_overallocate' => 'required|numeric|min:-1',
 | 
				
			||||||
        'disk' => 'required|numeric|min:1',
 | 
					        'disk' => 'required|numeric|min:0',
 | 
				
			||||||
        'disk_overallocate' => 'required|numeric|min:-1',
 | 
					        'disk_overallocate' => 'required|numeric|min:-1',
 | 
				
			||||||
        'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/',
 | 
					        'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/',
 | 
				
			||||||
        'daemon_sftp' => 'required|numeric|between:1,65535',
 | 
					        'daemon_sftp' => 'required|numeric|between:1,65535',
 | 
				
			||||||
@ -96,7 +100,9 @@ class Node extends Model
 | 
				
			|||||||
    protected $attributes = [
 | 
					    protected $attributes = [
 | 
				
			||||||
        'public' => true,
 | 
					        'public' => true,
 | 
				
			||||||
        'behind_proxy' => false,
 | 
					        'behind_proxy' => false,
 | 
				
			||||||
 | 
					        'memory' => 0,
 | 
				
			||||||
        'memory_overallocate' => 0,
 | 
					        'memory_overallocate' => 0,
 | 
				
			||||||
 | 
					        'disk' => 0,
 | 
				
			||||||
        'disk_overallocate' => 0,
 | 
					        'disk_overallocate' => 0,
 | 
				
			||||||
        'daemon_base' => '/var/lib/pelican/volumes',
 | 
					        'daemon_base' => '/var/lib/pelican/volumes',
 | 
				
			||||||
        'daemon_sftp' => 2022,
 | 
					        'daemon_sftp' => 2022,
 | 
				
			||||||
@ -117,6 +123,26 @@ class Node extends Model
 | 
				
			|||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRouteKeyName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'id';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static function booted(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        static::creating(function (self $node) {
 | 
				
			||||||
 | 
					            $node->uuid = Str::uuid();
 | 
				
			||||||
 | 
					            $node->daemon_token = encrypt(Str::random(self::DAEMON_TOKEN_LENGTH));
 | 
				
			||||||
 | 
					            $node->daemon_token_id = Str::random(self::DAEMON_TOKEN_ID_LENGTH);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static::deleting(function (self $node) {
 | 
				
			||||||
 | 
					            throw_if($node->servers()->count(), new HasActiveServersException(trans('exceptions.egg.delete_has_servers')));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the connection address to use when making calls to this node.
 | 
					     * Get the connection address to use when making calls to this node.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -240,4 +266,40 @@ class Node extends Model
 | 
				
			|||||||
            ];
 | 
					            ];
 | 
				
			||||||
        })->values();
 | 
					        })->values();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function systemInformation(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return once(function () {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                return resolve(DaemonConfigurationRepository::class)
 | 
				
			||||||
 | 
					                    ->setNode($this)
 | 
				
			||||||
 | 
					                    ->getSystemInformation(connectTimeout: 3);
 | 
				
			||||||
 | 
					            } catch (Exception $exception) {
 | 
				
			||||||
 | 
					                $message = str($exception->getMessage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ($message->startsWith('cURL error 6: Could not resolve host')) {
 | 
				
			||||||
 | 
					                    $message = str('Could not resolve host');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ($message->startsWith('cURL error 28: Failed to connect to ')) {
 | 
				
			||||||
 | 
					                    $message = $message->after('cURL error 28: ')->before(' after ');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return ['exception' => $message->toString()];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function serverStatuses(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            /** @var \Illuminate\Http\Client\Response $response */
 | 
				
			||||||
 | 
					            $response = Http::daemon($this)->connectTimeout(1)->timeout(1)->get('/api/servers');
 | 
				
			||||||
 | 
					            $statuses = $response->json();
 | 
				
			||||||
 | 
					        } catch (Exception) {
 | 
				
			||||||
 | 
					            $statuses = [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cache()->remember("nodes.$this->id.servers", now()->addSeconds(2), fn () => $statuses);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Models;
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use App\Exceptions\Http\Connection\DaemonConnectionException;
 | 
					use App\Exceptions\Http\Connection\DaemonConnectionException;
 | 
				
			||||||
use GuzzleHttp\Exception\GuzzleException;
 | 
					use GuzzleHttp\Exception\GuzzleException;
 | 
				
			||||||
use Illuminate\Notifications\Notifiable;
 | 
					use Illuminate\Notifications\Notifiable;
 | 
				
			||||||
@ -112,12 +113,6 @@ class Server extends Model
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public const RESOURCE_NAME = 'server';
 | 
					    public const RESOURCE_NAME = 'server';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public const STATUS_INSTALLING = 'installing';
 | 
					 | 
				
			||||||
    public const STATUS_INSTALL_FAILED = 'install_failed';
 | 
					 | 
				
			||||||
    public const STATUS_REINSTALL_FAILED = 'reinstall_failed';
 | 
					 | 
				
			||||||
    public const STATUS_SUSPENDED = 'suspended';
 | 
					 | 
				
			||||||
    public const STATUS_RESTORING_BACKUP = 'restoring_backup';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The table associated with the model.
 | 
					     * The table associated with the model.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -128,7 +123,7 @@ class Server extends Model
 | 
				
			|||||||
     * on server instances unless the user specifies otherwise in the request.
 | 
					     * on server instances unless the user specifies otherwise in the request.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $attributes = [
 | 
					    protected $attributes = [
 | 
				
			||||||
        'status' => self::STATUS_INSTALLING,
 | 
					        'status' => ServerState::Installing,
 | 
				
			||||||
        'oom_disabled' => true,
 | 
					        'oom_disabled' => true,
 | 
				
			||||||
        'installed_at' => null,
 | 
					        'installed_at' => null,
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
@ -152,7 +147,7 @@ class Server extends Model
 | 
				
			|||||||
        'status' => 'nullable|string',
 | 
					        'status' => 'nullable|string',
 | 
				
			||||||
        'memory' => 'required|numeric|min:0',
 | 
					        'memory' => 'required|numeric|min:0',
 | 
				
			||||||
        'swap' => 'required|numeric|min:-1',
 | 
					        'swap' => 'required|numeric|min:-1',
 | 
				
			||||||
        'io' => 'required|numeric|between:10,1000',
 | 
					        'io' => 'required|numeric|between:0,1000',
 | 
				
			||||||
        'cpu' => 'required|numeric|min:0',
 | 
					        'cpu' => 'required|numeric|min:0',
 | 
				
			||||||
        'threads' => 'nullable|regex:/^[0-9-,]+$/',
 | 
					        'threads' => 'nullable|regex:/^[0-9-,]+$/',
 | 
				
			||||||
        'oom_disabled' => 'sometimes|boolean',
 | 
					        'oom_disabled' => 'sometimes|boolean',
 | 
				
			||||||
@ -171,6 +166,7 @@ class Server extends Model
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            'node_id' => 'integer',
 | 
					            'node_id' => 'integer',
 | 
				
			||||||
 | 
					            'status' => ServerState::class,
 | 
				
			||||||
            'skip_scripts' => 'boolean',
 | 
					            'skip_scripts' => 'boolean',
 | 
				
			||||||
            'owner_id' => 'integer',
 | 
					            'owner_id' => 'integer',
 | 
				
			||||||
            'memory' => 'integer',
 | 
					            'memory' => 'integer',
 | 
				
			||||||
@ -203,12 +199,12 @@ class Server extends Model
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public function isInstalled(): bool
 | 
					    public function isInstalled(): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->status !== self::STATUS_INSTALLING && $this->status !== self::STATUS_INSTALL_FAILED;
 | 
					        return $this->status !== ServerState::Installing && $this->status !== ServerState::InstallFailed;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function isSuspended(): bool
 | 
					    public function isSuspended(): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->status === self::STATUS_SUSPENDED;
 | 
					        return $this->status === ServerState::Suspended;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -251,6 +247,11 @@ class Server extends Model
 | 
				
			|||||||
        return $this->hasOne(Egg::class, 'id', 'egg_id');
 | 
					        return $this->hasOne(Egg::class, 'id', 'egg_id');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function eggVariables(): HasMany
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Gets information for the egg variables associated with this server.
 | 
					     * Gets information for the egg variables associated with this server.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -267,6 +268,11 @@ class Server extends Model
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function serverVariables(): HasMany
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->hasMany(ServerVariable::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Gets information for the node associated with this server.
 | 
					     * Gets information for the node associated with this server.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -349,7 +355,7 @@ class Server extends Model
 | 
				
			|||||||
            $this->isSuspended() ||
 | 
					            $this->isSuspended() ||
 | 
				
			||||||
            $this->node->isUnderMaintenance() ||
 | 
					            $this->node->isUnderMaintenance() ||
 | 
				
			||||||
            !$this->isInstalled() ||
 | 
					            !$this->isInstalled() ||
 | 
				
			||||||
            $this->status === self::STATUS_RESTORING_BACKUP ||
 | 
					            $this->status === ServerState::RestoringBackup ||
 | 
				
			||||||
            !is_null($this->transfer)
 | 
					            !is_null($this->transfer)
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            throw new ServerStateConflictException($this);
 | 
					            throw new ServerStateConflictException($this);
 | 
				
			||||||
@ -366,7 +372,7 @@ class Server extends Model
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            !$this->isInstalled() ||
 | 
					            !$this->isInstalled() ||
 | 
				
			||||||
            $this->status === self::STATUS_RESTORING_BACKUP ||
 | 
					            $this->status === ServerState::RestoringBackup ||
 | 
				
			||||||
            !is_null($this->transfer)
 | 
					            !is_null($this->transfer)
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            throw new ServerStateConflictException($this);
 | 
					            throw new ServerStateConflictException($this);
 | 
				
			||||||
@ -388,4 +394,9 @@ class Server extends Model
 | 
				
			|||||||
            throw new DaemonConnectionException($exception);
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function retrieveStatus()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->node->serverStatuses();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,11 @@ namespace App\Models;
 | 
				
			|||||||
use App\Exceptions\DisplayException;
 | 
					use App\Exceptions\DisplayException;
 | 
				
			||||||
use App\Rules\Username;
 | 
					use App\Rules\Username;
 | 
				
			||||||
use App\Facades\Activity;
 | 
					use App\Facades\Activity;
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					use Filament\Models\Contracts\FilamentUser;
 | 
				
			||||||
 | 
					use Filament\Models\Contracts\HasAvatar;
 | 
				
			||||||
 | 
					use Filament\Models\Contracts\HasName;
 | 
				
			||||||
 | 
					use Filament\Panel;
 | 
				
			||||||
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
use Illuminate\Validation\Rules\In;
 | 
					use Illuminate\Validation\Rules\In;
 | 
				
			||||||
use Illuminate\Auth\Authenticatable;
 | 
					use Illuminate\Auth\Authenticatable;
 | 
				
			||||||
use Illuminate\Notifications\Notifiable;
 | 
					use Illuminate\Notifications\Notifiable;
 | 
				
			||||||
@ -79,7 +83,7 @@ use App\Notifications\SendPasswordReset as ResetPasswordNotification;
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @mixin \Eloquent
 | 
					 * @mixin \Eloquent
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
 | 
					class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use Authenticatable;
 | 
					    use Authenticatable;
 | 
				
			||||||
    use Authorizable {can as protected canned; }
 | 
					    use Authorizable {can as protected canned; }
 | 
				
			||||||
@ -139,18 +143,20 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
 | 
				
			|||||||
        'language' => 'en',
 | 
					        'language' => 'en',
 | 
				
			||||||
        'use_totp' => false,
 | 
					        'use_totp' => false,
 | 
				
			||||||
        'totp_secret' => null,
 | 
					        'totp_secret' => null,
 | 
				
			||||||
 | 
					        'name_first' => '',
 | 
				
			||||||
 | 
					        'name_last' => '',
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Rules verifying that the data being stored matches the expectations of the database.
 | 
					     * Rules verifying that the data being stored matches the expectations of the database.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static array $validationRules = [
 | 
					    public static array $validationRules = [
 | 
				
			||||||
        'uuid' => 'required|string|size:36|unique:users,uuid',
 | 
					        'uuid' => 'nullable|string|size:36|unique:users,uuid',
 | 
				
			||||||
        'email' => 'required|email|between:1,191|unique:users,email',
 | 
					        'email' => 'required|email|between:1,191|unique:users,email',
 | 
				
			||||||
        'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id',
 | 
					        'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id',
 | 
				
			||||||
        'username' => 'required|between:1,191|unique:users,username',
 | 
					        'username' => 'required|between:1,191|unique:users,username',
 | 
				
			||||||
        'name_first' => 'required|string|between:1,191',
 | 
					        'name_first' => 'nullable|string|between:0,191',
 | 
				
			||||||
        'name_last' => 'required|string|between:1,191',
 | 
					        'name_last' => 'nullable|string|between:0,191',
 | 
				
			||||||
        'password' => 'sometimes|nullable|string',
 | 
					        'password' => 'sometimes|nullable|string',
 | 
				
			||||||
        'root_admin' => 'boolean',
 | 
					        'root_admin' => 'boolean',
 | 
				
			||||||
        'language' => 'string',
 | 
					        'language' => 'string',
 | 
				
			||||||
@ -170,6 +176,12 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    protected static function booted(): void
 | 
					    protected static function booted(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        static::creating(function (self $user) {
 | 
				
			||||||
 | 
					            $user->uuid = Str::uuid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static::deleting(function (self $user) {
 | 
					        static::deleting(function (self $user) {
 | 
				
			||||||
            throw_if($user->servers()->count() > 0, new DisplayException(__('admin/user.exceptions.user_has_servers')));
 | 
					            throw_if($user->servers()->count() > 0, new DisplayException(__('admin/user.exceptions.user_has_servers')));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -177,6 +189,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRouteKeyName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'id';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Implement language verification by overriding Eloquence's gather
 | 
					     * Implement language verification by overriding Eloquence's gather
 | 
				
			||||||
     * rules function.
 | 
					     * rules function.
 | 
				
			||||||
@ -196,7 +213,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function toVueObject(): array
 | 
					    public function toVueObject(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return Collection::make($this->toArray())->except(['id', 'external_id'])->toArray();
 | 
					        return collect($this->toArray())->except(['id', 'external_id'])->toArray();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -278,6 +295,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
 | 
				
			|||||||
            ->groupBy('servers.id');
 | 
					            ->groupBy('servers.id');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function subusers(): HasMany
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->hasMany(Subuser::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected function checkPermission(Server $server, string $permission = ''): bool
 | 
					    protected function checkPermission(Server $server, string $permission = ''): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($this->root_admin || $server->owner_id === $this->id) {
 | 
					        if ($this->root_admin || $server->owner_id === $this->id) {
 | 
				
			||||||
@ -313,4 +335,26 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return $this->canned($abilities, $arguments);
 | 
					        return $this->canned($abilities, $arguments);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function isLastRootAdmin(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $rootAdmins = User::query()->where('root_admin', true)->limit(2)->get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return once(fn () => $rootAdmins->count() === 1 && $rootAdmins->first()->is($this));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function canAccessPanel(Panel $panel): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->root_admin;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getFilamentName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->name_first ?: $this->username;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getFilamentAvatarUrl(): ?string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return 'https://gravatar.com/avatar/' . md5(strtolower($this->email));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								app/Policies/EggPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/Policies/EggPolicy.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Policies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EggPolicy
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function create(User $user): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								app/Providers/Filament/AdminPanelProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								app/Providers/Filament/AdminPanelProvider.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Providers\Filament;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Filament\Resources\UserResource\Pages\EditProfile;
 | 
				
			||||||
 | 
					use Filament\Http\Middleware\Authenticate;
 | 
				
			||||||
 | 
					use Filament\Http\Middleware\DisableBladeIconComponents;
 | 
				
			||||||
 | 
					use Filament\Http\Middleware\DispatchServingFilamentEvent;
 | 
				
			||||||
 | 
					use Filament\Panel;
 | 
				
			||||||
 | 
					use Filament\PanelProvider;
 | 
				
			||||||
 | 
					use Filament\Support\Colors\Color;
 | 
				
			||||||
 | 
					use Filament\Support\Facades\FilamentAsset;
 | 
				
			||||||
 | 
					use Filament\Widgets;
 | 
				
			||||||
 | 
					use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
 | 
				
			||||||
 | 
					use Illuminate\Cookie\Middleware\EncryptCookies;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
 | 
				
			||||||
 | 
					use Illuminate\Routing\Middleware\SubstituteBindings;
 | 
				
			||||||
 | 
					use Illuminate\Session\Middleware\AuthenticateSession;
 | 
				
			||||||
 | 
					use Illuminate\Session\Middleware\StartSession;
 | 
				
			||||||
 | 
					use Illuminate\View\Middleware\ShareErrorsFromSession;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AdminPanelProvider extends PanelProvider
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function boot()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        FilamentAsset::registerCssVariables([
 | 
				
			||||||
 | 
					            'sidebar-width' => '12rem !important',
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function panel(Panel $panel): Panel
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $panel
 | 
				
			||||||
 | 
					            ->default()
 | 
				
			||||||
 | 
					            ->id('admin')
 | 
				
			||||||
 | 
					            ->path('panel')
 | 
				
			||||||
 | 
					            ->login()
 | 
				
			||||||
 | 
					            ->brandName('Pelican')
 | 
				
			||||||
 | 
					            ->homeUrl('/')
 | 
				
			||||||
 | 
					            ->favicon('/pelican.ico')
 | 
				
			||||||
 | 
					            ->profile(EditProfile::class, false)
 | 
				
			||||||
 | 
					            ->colors([
 | 
				
			||||||
 | 
					                'danger' => Color::Red,
 | 
				
			||||||
 | 
					                'gray' => Color::Zinc,
 | 
				
			||||||
 | 
					                'info' => Color::Blue,
 | 
				
			||||||
 | 
					                'primary' => Color::Sky,
 | 
				
			||||||
 | 
					                'success' => Color::Green,
 | 
				
			||||||
 | 
					                'warning' => Color::Amber,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
 | 
				
			||||||
 | 
					            ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
 | 
				
			||||||
 | 
					            ->pages([
 | 
				
			||||||
 | 
					                // Pages\Dashboard::class,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
 | 
				
			||||||
 | 
					            ->widgets([
 | 
				
			||||||
 | 
					                Widgets\AccountWidget::class,
 | 
				
			||||||
 | 
					                Widgets\FilamentInfoWidget::class,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->middleware([
 | 
				
			||||||
 | 
					                EncryptCookies::class,
 | 
				
			||||||
 | 
					                AddQueuedCookiesToResponse::class,
 | 
				
			||||||
 | 
					                StartSession::class,
 | 
				
			||||||
 | 
					                AuthenticateSession::class,
 | 
				
			||||||
 | 
					                ShareErrorsFromSession::class,
 | 
				
			||||||
 | 
					                VerifyCsrfToken::class,
 | 
				
			||||||
 | 
					                SubstituteBindings::class,
 | 
				
			||||||
 | 
					                DisableBladeIconComponents::class,
 | 
				
			||||||
 | 
					                DispatchServingFilamentEvent::class,
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            ->authMiddleware([
 | 
				
			||||||
 | 
					                Authenticate::class,
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -35,11 +35,9 @@ class DaemonBackupRepository extends DaemonRepository
 | 
				
			|||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/backup', $this->server->uuid),
 | 
					                sprintf('/api/servers/%s/backup', $this->server->uuid),
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                    'adapter' => $this->adapter ?? config('backups.default'),
 | 
					                    'adapter' => $this->adapter ?? config('backups.default'),
 | 
				
			||||||
                    'uuid' => $backup->uuid,
 | 
					                    'uuid' => $backup->uuid,
 | 
				
			||||||
                    'ignore' => implode("\n", $backup->ignored_files),
 | 
					                    'ignore' => implode("\n", $backup->ignored_files),
 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
@ -60,11 +58,9 @@ class DaemonBackupRepository extends DaemonRepository
 | 
				
			|||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid),
 | 
					                sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid),
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                    'adapter' => $backup->disk,
 | 
					                    'adapter' => $backup->disk,
 | 
				
			||||||
                    'truncate_directory' => $truncate,
 | 
					                    'truncate_directory' => $truncate,
 | 
				
			||||||
                    'download_url' => $url ?? '',
 | 
					                    'download_url' => $url ?? '',
 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
 | 
				
			|||||||
@ -13,10 +13,13 @@ class DaemonConfigurationRepository extends DaemonRepository
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
 | 
					     * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getSystemInformation(?int $version = null): array
 | 
					    public function getSystemInformation(?int $version = null, $connectTimeout = 5): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            $response = $this->getHttpClient()->get('/api/system' . (!is_null($version) ? '?v=' . $version : ''));
 | 
					            $response = $this
 | 
				
			||||||
 | 
					                ->getHttpClient()
 | 
				
			||||||
 | 
					                ->connectTimeout($connectTimeout)
 | 
				
			||||||
 | 
					                ->get('/api/system' . (!is_null($version) ? '?v=' . $version : ''));
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
            throw new DaemonConnectionException($exception);
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -36,7 +39,7 @@ class DaemonConfigurationRepository extends DaemonRepository
 | 
				
			|||||||
        try {
 | 
					        try {
 | 
				
			||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                '/api/update',
 | 
					                '/api/update',
 | 
				
			||||||
                ['json' => $node->getConfiguration()]
 | 
					                $node->getConfiguration(),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
            throw new DaemonConnectionException($exception);
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
				
			|||||||
@ -103,10 +103,8 @@ class DaemonFileRepository extends DaemonRepository
 | 
				
			|||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/files/create-directory', $this->server->uuid),
 | 
					                sprintf('/api/servers/%s/files/create-directory', $this->server->uuid),
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                    'name' => $name,
 | 
					                    'name' => $name,
 | 
				
			||||||
                    'path' => $path,
 | 
					                    'path' => $path,
 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
@ -151,9 +149,7 @@ class DaemonFileRepository extends DaemonRepository
 | 
				
			|||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/files/copy', $this->server->uuid),
 | 
					                sprintf('/api/servers/%s/files/copy', $this->server->uuid),
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                    'location' => $location,
 | 
					                    'location' => $location,
 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
@ -174,10 +170,8 @@ class DaemonFileRepository extends DaemonRepository
 | 
				
			|||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/files/delete', $this->server->uuid),
 | 
					                sprintf('/api/servers/%s/files/delete', $this->server->uuid),
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                    'root' => $root ?? '/',
 | 
					                    'root' => $root ?? '/',
 | 
				
			||||||
                    'files' => $files,
 | 
					                    'files' => $files,
 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
@ -195,16 +189,15 @@ class DaemonFileRepository extends DaemonRepository
 | 
				
			|||||||
        Assert::isInstanceOf($this->server, Server::class);
 | 
					        Assert::isInstanceOf($this->server, Server::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            $response = $this->getHttpClient()->post(
 | 
					            $response = $this->getHttpClient()
 | 
				
			||||||
                sprintf('/api/servers/%s/files/compress', $this->server->uuid),
 | 
					 | 
				
			||||||
                [
 | 
					 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                        'root' => $root ?? '/',
 | 
					 | 
				
			||||||
                        'files' => $files,
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                // Wait for up to 15 minutes for the archive to be completed when calling this endpoint
 | 
					                // 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.
 | 
					                // since it will likely take quite awhile for large directories.
 | 
				
			||||||
                    'timeout' => 60 * 15,
 | 
					                ->timeout(60 * 15)
 | 
				
			||||||
 | 
					                ->post(
 | 
				
			||||||
 | 
					                    sprintf('/api/servers/%s/files/compress', $this->server->uuid),
 | 
				
			||||||
 | 
					                    [
 | 
				
			||||||
 | 
					                        'root' => $root ?? '/',
 | 
				
			||||||
 | 
					                        'files' => $files,
 | 
				
			||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
@ -224,16 +217,15 @@ class DaemonFileRepository extends DaemonRepository
 | 
				
			|||||||
        Assert::isInstanceOf($this->server, Server::class);
 | 
					        Assert::isInstanceOf($this->server, Server::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()
 | 
				
			||||||
                sprintf('/api/servers/%s/files/decompress', $this->server->uuid),
 | 
					 | 
				
			||||||
                [
 | 
					 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                        'root' => $root ?? '/',
 | 
					 | 
				
			||||||
                        'file' => $file,
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                // Wait for up to 15 minutes for the decompress to be completed when calling this endpoint
 | 
					                // 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.
 | 
					                // since it will likely take quite awhile for large directories.
 | 
				
			||||||
                    'timeout' => (int) CarbonInterval::minutes(15)->totalSeconds,
 | 
					                ->timeout((int) CarbonInterval::minutes(15)->totalSeconds)
 | 
				
			||||||
 | 
					                ->post(
 | 
				
			||||||
 | 
					                    sprintf('/api/servers/%s/files/decompress', $this->server->uuid),
 | 
				
			||||||
 | 
					                    [
 | 
				
			||||||
 | 
					                        'root' => $root ?? '/',
 | 
				
			||||||
 | 
					                        'file' => $file,
 | 
				
			||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
@ -254,10 +246,8 @@ class DaemonFileRepository extends DaemonRepository
 | 
				
			|||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/files/chmod', $this->server->uuid),
 | 
					                sprintf('/api/servers/%s/files/chmod', $this->server->uuid),
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    'json' => [
 | 
					 | 
				
			||||||
                    'root' => $root ?? '/',
 | 
					                    'root' => $root ?? '/',
 | 
				
			||||||
                    'files' => $files,
 | 
					                    'files' => $files,
 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
@ -286,7 +276,7 @@ class DaemonFileRepository extends DaemonRepository
 | 
				
			|||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/files/pull', $this->server->uuid),
 | 
					                sprintf('/api/servers/%s/files/pull', $this->server->uuid),
 | 
				
			||||||
                [
 | 
					                [
 | 
				
			||||||
                    'json' => array_filter($attributes, fn ($value) => !is_null($value)),
 | 
					                    array_filter($attributes, fn ($value) => !is_null($value)),
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ class DaemonPowerRepository extends DaemonRepository
 | 
				
			|||||||
        try {
 | 
					        try {
 | 
				
			||||||
            return $this->getHttpClient()->post(
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
                sprintf('/api/servers/%s/power', $this->server->uuid),
 | 
					                sprintf('/api/servers/%s/power', $this->server->uuid),
 | 
				
			||||||
                ['json' => ['action' => $action]]
 | 
					                ['action' => $action],
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } catch (TransferException $exception) {
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
            throw new DaemonConnectionException($exception);
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
				
			|||||||
@ -40,11 +40,9 @@ class DaemonServerRepository extends DaemonRepository
 | 
				
			|||||||
        Assert::isInstanceOf($this->server, Server::class);
 | 
					        Assert::isInstanceOf($this->server, Server::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            $this->getHttpClient()->post('/api/servers', [
 | 
					            $response = $this->getHttpClient()->post('/api/servers', [
 | 
				
			||||||
                'json' => [
 | 
					 | 
				
			||||||
                'uuid' => $this->server->uuid,
 | 
					                'uuid' => $this->server->uuid,
 | 
				
			||||||
                'start_on_completion' => $startOnCompletion,
 | 
					                'start_on_completion' => $startOnCompletion,
 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
            ]);
 | 
					            ]);
 | 
				
			||||||
        } catch (GuzzleException $exception) {
 | 
					        } catch (GuzzleException $exception) {
 | 
				
			||||||
            throw new DaemonConnectionException($exception);
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,80 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace App\Rules;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use Illuminate\Support\Arr;
 | 
					 | 
				
			||||||
use Illuminate\Contracts\Validation\Rule;
 | 
					 | 
				
			||||||
use Illuminate\Contracts\Validation\DataAwareRule;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Fqdn implements DataAwareRule, Rule
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    protected array $data = [];
 | 
					 | 
				
			||||||
    protected string $message = '';
 | 
					 | 
				
			||||||
    protected ?string $schemeField = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @param array $data
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function setData($data): self
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $this->data = $data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return $this;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Validates that the value provided resolves to an IP address. If a scheme is
 | 
					 | 
				
			||||||
     * specified when this rule is created additional checks will be applied.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param string $attribute
 | 
					 | 
				
			||||||
     * @param mixed $value
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function passes($attribute, $value): bool
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (filter_var($value, FILTER_VALIDATE_IP)) {
 | 
					 | 
				
			||||||
            // Check if the scheme is set to HTTPS.
 | 
					 | 
				
			||||||
            //
 | 
					 | 
				
			||||||
            // Unless someone owns their IP blocks and decides to pay who knows how much for a
 | 
					 | 
				
			||||||
            // custom SSL cert, IPs will not be able to use HTTPS.  This should prevent most
 | 
					 | 
				
			||||||
            // home users from making this mistake and wondering why their node is not working.
 | 
					 | 
				
			||||||
            if ($this->schemeField && Arr::get($this->data, $this->schemeField) === 'https') {
 | 
					 | 
				
			||||||
                $this->message = 'The :attribute must not be an IP address when HTTPS is enabled.';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Lookup A and AAAA DNS records for the FQDN. Note, this function will also resolve CNAMEs
 | 
					 | 
				
			||||||
        // for us automatically, there is no need to manually resolve them here.
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        // The error suppression is intentional, see https://bugs.php.net/bug.php?id=73149
 | 
					 | 
				
			||||||
        $records = @dns_get_record($value, DNS_A + DNS_AAAA);
 | 
					 | 
				
			||||||
        // If no records were returned fall back to trying to resolve the value using the hosts DNS
 | 
					 | 
				
			||||||
        // resolution. This will not work for IPv6 which is why we prefer to use `dns_get_record`
 | 
					 | 
				
			||||||
        // first.
 | 
					 | 
				
			||||||
        if (!empty($records) || filter_var(gethostbyname($value), FILTER_VALIDATE_IP)) {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->message = 'The :attribute could not be resolved to a valid IP address.';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function message(): string
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return $this->message;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns a new instance of the rule with a defined scheme set.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public static function make(string $schemeField = null): self
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return tap(new self(), function ($fqdn) use ($schemeField) {
 | 
					 | 
				
			||||||
            $fqdn->schemeField = $schemeField;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -37,7 +37,7 @@ class AssignmentService
 | 
				
			|||||||
     * @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException
 | 
					     * @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException
 | 
				
			||||||
     * @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException
 | 
					     * @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function handle(Node $node, array $data): void
 | 
					    public function handle(Node $node, array $data): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $explode = explode('/', $data['allocation_ip']);
 | 
					        $explode = explode('/', $data['allocation_ip']);
 | 
				
			||||||
        if (count($explode) !== 1) {
 | 
					        if (count($explode) !== 1) {
 | 
				
			||||||
@ -58,6 +58,8 @@ class AssignmentService
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->connection->beginTransaction();
 | 
					        $this->connection->beginTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $ids = [];
 | 
				
			||||||
        foreach ($parsed as $ip) {
 | 
					        foreach ($parsed as $ip) {
 | 
				
			||||||
            foreach ($data['allocation_ports'] as $port) {
 | 
					            foreach ($data['allocation_ports'] as $port) {
 | 
				
			||||||
                if (!is_digit($port) && !preg_match(self::PORT_RANGE_REGEX, $port)) {
 | 
					                if (!is_digit($port) && !preg_match(self::PORT_RANGE_REGEX, $port)) {
 | 
				
			||||||
@ -99,10 +101,12 @@ class AssignmentService
 | 
				
			|||||||
                    ];
 | 
					                    ];
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Allocation::query()->insertOrIgnore($insertData);
 | 
					                $ids[] = Allocation::query()->insertOrIgnore($insertData);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->connection->commit();
 | 
					        $this->connection->commit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $ids;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,17 +18,19 @@ class EggParserService
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function handle(UploadedFile $file): array
 | 
					    public function handle(UploadedFile $file): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) {
 | 
					        if ($file->getError() !== UPLOAD_ERR_OK) {
 | 
				
			||||||
            throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.');
 | 
					            throw new InvalidFileUploadException('The selected file was not uploaded successfully');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @var array $parsed */
 | 
					        $parsed = json_decode($file->getContent(), true, 512, JSON_THROW_ON_ERROR);
 | 
				
			||||||
        $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR);
 | 
					 | 
				
			||||||
        if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) {
 | 
					 | 
				
			||||||
            throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->convertToV2($parsed);
 | 
					        $version = $parsed['meta']['version'] ?? '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return match ($version) {
 | 
				
			||||||
 | 
					            'PTDL_v1' => $this->convertToV2($parsed),
 | 
				
			||||||
 | 
					            'PTDL_v2' => $parsed,
 | 
				
			||||||
 | 
					            default => throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.')
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -62,10 +64,6 @@ class EggParserService
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function convertToV2(array $parsed): array
 | 
					    protected function convertToV2(array $parsed): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) {
 | 
					 | 
				
			||||||
            return $parsed;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Maintain backwards compatability for eggs that are still using the old single image
 | 
					        // Maintain backwards compatability for eggs that are still using the old single image
 | 
				
			||||||
        // string format. New eggs can provide an array of Docker images that can be used.
 | 
					        // string format. New eggs can provide an array of Docker images that can be used.
 | 
				
			||||||
        if (!isset($parsed['images'])) {
 | 
					        if (!isset($parsed['images'])) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Services\Servers;
 | 
					namespace App\Services\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use App\Models\Server;
 | 
					use App\Models\Server;
 | 
				
			||||||
use Illuminate\Database\ConnectionInterface;
 | 
					use Illuminate\Database\ConnectionInterface;
 | 
				
			||||||
use App\Repositories\Daemon\DaemonServerRepository;
 | 
					use App\Repositories\Daemon\DaemonServerRepository;
 | 
				
			||||||
@ -25,7 +26,7 @@ class ReinstallServerService
 | 
				
			|||||||
    public function handle(Server $server): Server
 | 
					    public function handle(Server $server): Server
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->connection->transaction(function () use ($server) {
 | 
					        return $this->connection->transaction(function () use ($server) {
 | 
				
			||||||
            $server->fill(['status' => Server::STATUS_INSTALLING])->save();
 | 
					            $server->fill(['status' => ServerState::Installing])->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $this->daemonServerRepository->setServer($server)->reinstall();
 | 
					            $this->daemonServerRepository->setServer($server)->reinstall();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Services\Servers;
 | 
					namespace App\Services\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use App\Models\ServerVariable;
 | 
					use App\Models\ServerVariable;
 | 
				
			||||||
use Ramsey\Uuid\Uuid;
 | 
					use Ramsey\Uuid\Uuid;
 | 
				
			||||||
use Illuminate\Support\Arr;
 | 
					use Illuminate\Support\Arr;
 | 
				
			||||||
@ -132,7 +133,7 @@ class ServerCreationService
 | 
				
			|||||||
            'node_id' => Arr::get($data, 'node_id'),
 | 
					            'node_id' => Arr::get($data, 'node_id'),
 | 
				
			||||||
            'name' => Arr::get($data, 'name'),
 | 
					            'name' => Arr::get($data, 'name'),
 | 
				
			||||||
            'description' => Arr::get($data, 'description') ?? '',
 | 
					            'description' => Arr::get($data, 'description') ?? '',
 | 
				
			||||||
            'status' => Server::STATUS_INSTALLING,
 | 
					            'status' => ServerState::Installing,
 | 
				
			||||||
            'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
 | 
					            'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
 | 
				
			||||||
            'owner_id' => Arr::get($data, 'owner_id'),
 | 
					            'owner_id' => Arr::get($data, 'owner_id'),
 | 
				
			||||||
            'memory' => Arr::get($data, 'memory'),
 | 
					            'memory' => Arr::get($data, 'memory'),
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Services\Servers;
 | 
					namespace App\Services\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\ServerState;
 | 
				
			||||||
use Webmozart\Assert\Assert;
 | 
					use Webmozart\Assert\Assert;
 | 
				
			||||||
use App\Models\Server;
 | 
					use App\Models\Server;
 | 
				
			||||||
use App\Repositories\Daemon\DaemonServerRepository;
 | 
					use App\Repositories\Daemon\DaemonServerRepository;
 | 
				
			||||||
@ -44,7 +45,7 @@ class SuspensionService
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Update the server's suspension status.
 | 
					        // Update the server's suspension status.
 | 
				
			||||||
        $server->update([
 | 
					        $server->update([
 | 
				
			||||||
            'status' => $isSuspending ? Server::STATUS_SUSPENDED : null,
 | 
					            'status' => $isSuspending ? ServerState::Suspended : null,
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
@ -53,7 +54,7 @@ class SuspensionService
 | 
				
			|||||||
        } catch (\Exception $exception) {
 | 
					        } catch (\Exception $exception) {
 | 
				
			||||||
            // Rollback the server's suspension status if daemon fails to sync the server.
 | 
					            // Rollback the server's suspension status if daemon fails to sync the server.
 | 
				
			||||||
            $server->update([
 | 
					            $server->update([
 | 
				
			||||||
                'status' => $isSuspending ? null : Server::STATUS_SUSPENDED,
 | 
					                'status' => $isSuspending ? null : ServerState::Suspended,
 | 
				
			||||||
            ]);
 | 
					            ]);
 | 
				
			||||||
            throw $exception;
 | 
					            throw $exception;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,15 @@ trait AvailableLanguages
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private ?Filesystem $filesystem = null;
 | 
					    private ?Filesystem $filesystem = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public const TRANSLATED = [
 | 
				
			||||||
 | 
					        'cz',
 | 
				
			||||||
 | 
					        'da',
 | 
				
			||||||
 | 
					        'de',
 | 
				
			||||||
 | 
					        'en',
 | 
				
			||||||
 | 
					        'es',
 | 
				
			||||||
 | 
					        'tr',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return all the available languages on the Panel based on those
 | 
					     * Return all the available languages on the Panel based on those
 | 
				
			||||||
     * that are present in the language folder.
 | 
					     * that are present in the language folder.
 | 
				
			||||||
@ -18,12 +27,17 @@ trait AvailableLanguages
 | 
				
			|||||||
        return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) {
 | 
					        return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) {
 | 
				
			||||||
            $code = basename($path);
 | 
					            $code = basename($path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $value = Locale::getDisplayName($code, app()->currentLocale());
 | 
					            $value = Locale::getDisplayName($code, $code);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return [$code => title_case($value)];
 | 
					            return [$code => title_case($value)];
 | 
				
			||||||
        })->toArray();
 | 
					        })->toArray();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function isLanguageTranslated(string $countryCode = 'en'): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return in_array($countryCode, self::TRANSLATED, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return an instance of the filesystem for getting a folder listing.
 | 
					     * Return an instance of the filesystem for getting a folder listing.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,13 @@ if (!function_exists('is_digit')) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!function_exists('is_ip')) {
 | 
				
			||||||
 | 
					    function is_ip(?string $address): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $address !== null && filter_var($address, FILTER_VALIDATE_IP) !== false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (!function_exists('object_get_strict')) {
 | 
					if (!function_exists('object_get_strict')) {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get an object using dot notation. An object key with a value of null is still considered valid
 | 
					     * Get an object using dot notation. An object key with a value of null is still considered valid
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ return [
 | 
				
			|||||||
    App\Providers\BackupsServiceProvider::class,
 | 
					    App\Providers\BackupsServiceProvider::class,
 | 
				
			||||||
    App\Providers\BladeServiceProvider::class,
 | 
					    App\Providers\BladeServiceProvider::class,
 | 
				
			||||||
    App\Providers\EventServiceProvider::class,
 | 
					    App\Providers\EventServiceProvider::class,
 | 
				
			||||||
 | 
					    App\Providers\Filament\AdminPanelProvider::class,
 | 
				
			||||||
    App\Providers\HashidsServiceProvider::class,
 | 
					    App\Providers\HashidsServiceProvider::class,
 | 
				
			||||||
    App\Providers\RouteServiceProvider::class,
 | 
					    App\Providers\RouteServiceProvider::class,
 | 
				
			||||||
    App\Providers\ViewComposerServiceProvider::class,
 | 
					    App\Providers\ViewComposerServiceProvider::class,
 | 
				
			||||||
 | 
				
			|||||||
@ -9,8 +9,11 @@
 | 
				
			|||||||
        "ext-pdo": "*",
 | 
					        "ext-pdo": "*",
 | 
				
			||||||
        "ext-pdo_mysql": "*",
 | 
					        "ext-pdo_mysql": "*",
 | 
				
			||||||
        "ext-zip": "*",
 | 
					        "ext-zip": "*",
 | 
				
			||||||
 | 
					        "abdelhamiderrahmouni/filament-monaco-editor": "^0.2.0",
 | 
				
			||||||
        "aws/aws-sdk-php": "~3.288.1",
 | 
					        "aws/aws-sdk-php": "~3.288.1",
 | 
				
			||||||
 | 
					        "chillerlan/php-qrcode": "^5.0",
 | 
				
			||||||
        "doctrine/dbal": "~3.6.0",
 | 
					        "doctrine/dbal": "~3.6.0",
 | 
				
			||||||
 | 
					        "filament/filament": "^3.2",
 | 
				
			||||||
        "guzzlehttp/guzzle": "^7.5",
 | 
					        "guzzlehttp/guzzle": "^7.5",
 | 
				
			||||||
        "hashids/hashids": "~5.0.0",
 | 
					        "hashids/hashids": "~5.0.0",
 | 
				
			||||||
        "laracasts/utilities": "~3.2.2",
 | 
					        "laracasts/utilities": "~3.2.2",
 | 
				
			||||||
@ -26,12 +29,14 @@
 | 
				
			|||||||
        "pragmarx/google2fa": "~8.0.0",
 | 
					        "pragmarx/google2fa": "~8.0.0",
 | 
				
			||||||
        "predis/predis": "~2.1.1",
 | 
					        "predis/predis": "~2.1.1",
 | 
				
			||||||
        "prologue/alerts": "^1.2",
 | 
					        "prologue/alerts": "^1.2",
 | 
				
			||||||
 | 
					        "ryangjchandler/blade-tabler-icons": "^2.3",
 | 
				
			||||||
        "s1lentium/iptools": "~1.2.0",
 | 
					        "s1lentium/iptools": "~1.2.0",
 | 
				
			||||||
        "spatie/laravel-fractal": "^6.1",
 | 
					        "spatie/laravel-fractal": "^6.1",
 | 
				
			||||||
        "spatie/laravel-query-builder": "^5.8",
 | 
					        "spatie/laravel-query-builder": "^5.8",
 | 
				
			||||||
        "symfony/mailgun-mailer": "^7.0",
 | 
					        "symfony/mailgun-mailer": "^7.0",
 | 
				
			||||||
        "symfony/postmark-mailer": "^7.0",
 | 
					        "symfony/postmark-mailer": "^7.0",
 | 
				
			||||||
        "symfony/yaml": "^7.0",
 | 
					        "symfony/yaml": "^7.0",
 | 
				
			||||||
 | 
					        "webbingbrasil/filament-copyactions": "^3.0",
 | 
				
			||||||
        "webmozart/assert": "~1.11.0"
 | 
					        "webmozart/assert": "~1.11.0"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "require-dev": {
 | 
					    "require-dev": {
 | 
				
			||||||
@ -66,7 +71,8 @@
 | 
				
			|||||||
        "cs:check": "php-cs-fixer fix --dry-run --diff --verbose",
 | 
					        "cs:check": "php-cs-fixer fix --dry-run --diff --verbose",
 | 
				
			||||||
        "post-autoload-dump": [
 | 
					        "post-autoload-dump": [
 | 
				
			||||||
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
 | 
					            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
 | 
				
			||||||
            "@php artisan package:discover --ansi || true"
 | 
					            "@php artisan package:discover --ansi || true",
 | 
				
			||||||
 | 
					            "@php artisan filament:upgrade"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "post-root-package-install": [
 | 
					        "post-root-package-install": [
 | 
				
			||||||
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
 | 
					            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2352
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2352
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,16 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
return [
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
    |--------------------------------------------------------------------------
 | 
					 | 
				
			||||||
    | Egg Feature: EULA Popup
 | 
					 | 
				
			||||||
    |--------------------------------------------------------------------------
 | 
					 | 
				
			||||||
    |
 | 
					 | 
				
			||||||
    | This popup is enabled for Minecraft eggs and allows a custom frontend
 | 
					 | 
				
			||||||
    | hook to run that monitors the console output of the server and pops up
 | 
					 | 
				
			||||||
    | a modal asking the user to accept it if necessary.
 | 
					 | 
				
			||||||
    |
 | 
					 | 
				
			||||||
    | There is no additional configuration necessary.
 | 
					 | 
				
			||||||
    |
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
							
								
								
									
										270
									
								
								config/filament-monaco-editor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								config/filament-monaco-editor.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,270 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return [
 | 
				
			||||||
 | 
					    'general' => [
 | 
				
			||||||
 | 
					        'enable-preview' => true,
 | 
				
			||||||
 | 
					        'show-full-screen-toggle' => true,
 | 
				
			||||||
 | 
					        'show-placeholder' => true,
 | 
				
			||||||
 | 
					        'placeholder-text' => 'Your code here...',
 | 
				
			||||||
 | 
					        'show-loader' => true,
 | 
				
			||||||
 | 
					        'font-size' => '15px',
 | 
				
			||||||
 | 
					        'line-numbers-min-chars' => true,
 | 
				
			||||||
 | 
					        'automatic-layout' => true,
 | 
				
			||||||
 | 
					        'default-theme' => 'iPlastic',
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    'themes' => [
 | 
				
			||||||
 | 
					        'blackboard' => [
 | 
				
			||||||
 | 
					            'base' => 'vs-dark',
 | 
				
			||||||
 | 
					            'inherit' => true,
 | 
				
			||||||
 | 
					            'rules' => [
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'background' => '0C1021',
 | 
				
			||||||
 | 
					                    'token' => '',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'aeaeae',
 | 
				
			||||||
 | 
					                    'token' => 'comment',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'd8fa3c',
 | 
				
			||||||
 | 
					                    'token' => 'constant',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ff6400',
 | 
				
			||||||
 | 
					                    'token' => 'entity',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'fbde2d',
 | 
				
			||||||
 | 
					                    'token' => 'keyword',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'fbde2d',
 | 
				
			||||||
 | 
					                    'token' => 'storage',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '61ce3c',
 | 
				
			||||||
 | 
					                    'token' => 'string',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '61ce3c',
 | 
				
			||||||
 | 
					                    'token' => 'meta.verbatim',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '8da6ce',
 | 
				
			||||||
 | 
					                    'token' => 'support',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ab2a1d',
 | 
				
			||||||
 | 
					                    'fontStyle' => 'italic',
 | 
				
			||||||
 | 
					                    'token' => 'invalid.deprecated',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'f8f8f8',
 | 
				
			||||||
 | 
					                    'background' => '9d1e15',
 | 
				
			||||||
 | 
					                    'token' => 'invalid.illegal',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ff6400',
 | 
				
			||||||
 | 
					                    'fontStyle' => 'italic',
 | 
				
			||||||
 | 
					                    'token' => 'entity.other.inherited-class',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ff6400',
 | 
				
			||||||
 | 
					                    'token' => 'string constant.other.placeholder',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'becde6',
 | 
				
			||||||
 | 
					                    'token' => 'meta.function-call.py',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '7f90aa',
 | 
				
			||||||
 | 
					                    'token' => 'meta.tag',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '7f90aa',
 | 
				
			||||||
 | 
					                    'token' => 'meta.tag entity',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ffffff',
 | 
				
			||||||
 | 
					                    'token' => 'entity.name.section',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'd5e0f3',
 | 
				
			||||||
 | 
					                    'token' => 'keyword.type.variant',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'f8f8f8',
 | 
				
			||||||
 | 
					                    'token' => 'source.ocaml keyword.operator.symbol',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '8da6ce',
 | 
				
			||||||
 | 
					                    'token' => 'source.ocaml keyword.operator.symbol.infix',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '8da6ce',
 | 
				
			||||||
 | 
					                    'token' => 'source.ocaml keyword.operator.symbol.prefix',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'underline',
 | 
				
			||||||
 | 
					                    'token' => 'source.ocaml keyword.operator.symbol.infix.floating-point',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'underline',
 | 
				
			||||||
 | 
					                    'token' => 'source.ocaml keyword.operator.symbol.prefix.floating-point',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'underline',
 | 
				
			||||||
 | 
					                    'token' => 'source.ocaml constant.numeric.floating-point',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'background' => 'ffffff08',
 | 
				
			||||||
 | 
					                    'token' => 'text.tex.latex meta.function.environment',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'background' => '7a96fa08',
 | 
				
			||||||
 | 
					                    'token' => 'text.tex.latex meta.function.environment meta.function.environment',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'fbde2d',
 | 
				
			||||||
 | 
					                    'token' => 'text.tex.latex support.function',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ffffff',
 | 
				
			||||||
 | 
					                    'token' => 'source.plist string.unquoted',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ffffff',
 | 
				
			||||||
 | 
					                    'token' => 'source.plist keyword.operator',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            'colors' => [
 | 
				
			||||||
 | 
					                'editor.foreground' => '#F8F8F8',
 | 
				
			||||||
 | 
					                'editor.background' => '#0C1021',
 | 
				
			||||||
 | 
					                'editor.selectionBackground' => '#253B76',
 | 
				
			||||||
 | 
					                'editor.lineHighlightBackground' => '#FFFFFF0F',
 | 
				
			||||||
 | 
					                'editorCursor.foreground' => '#FFFFFFA6',
 | 
				
			||||||
 | 
					                'editorWhitespace.foreground' => '#FFFFFF40',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        'iPlastic' => [
 | 
				
			||||||
 | 
					            'base' => 'vs',
 | 
				
			||||||
 | 
					            'inherit' => true,
 | 
				
			||||||
 | 
					            'rules' => [
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'background' => 'EEEEEEEB',
 | 
				
			||||||
 | 
					                    'token' => '',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '009933',
 | 
				
			||||||
 | 
					                    'token' => 'string',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '0066ff',
 | 
				
			||||||
 | 
					                    'token' => 'constant.numeric',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ff0080',
 | 
				
			||||||
 | 
					                    'token' => 'string.regexp',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '0000ff',
 | 
				
			||||||
 | 
					                    'token' => 'keyword',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '9700cc',
 | 
				
			||||||
 | 
					                    'token' => 'constant.language',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '990000',
 | 
				
			||||||
 | 
					                    'token' => 'support.class.exception',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ff8000',
 | 
				
			||||||
 | 
					                    'token' => 'entity.name.function',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'bold underline',
 | 
				
			||||||
 | 
					                    'token' => 'entity.name.type',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'italic',
 | 
				
			||||||
 | 
					                    'token' => 'variable.parameter',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '0066ff',
 | 
				
			||||||
 | 
					                    'fontStyle' => 'italic',
 | 
				
			||||||
 | 
					                    'token' => 'comment',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => 'ff0000',
 | 
				
			||||||
 | 
					                    'background' => 'e71a114d',
 | 
				
			||||||
 | 
					                    'token' => 'invalid',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'background' => 'e71a1100',
 | 
				
			||||||
 | 
					                    'token' => 'invalid.deprecated.trailing-whitespace',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '000000',
 | 
				
			||||||
 | 
					                    'background' => 'fafafafc',
 | 
				
			||||||
 | 
					                    'token' => 'text source',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '0033cc',
 | 
				
			||||||
 | 
					                    'token' => 'meta.tag',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '0033cc',
 | 
				
			||||||
 | 
					                    'token' => 'declaration.tag',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '6782d3',
 | 
				
			||||||
 | 
					                    'token' => 'constant',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '6782d3',
 | 
				
			||||||
 | 
					                    'token' => 'support.constant',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '3333ff',
 | 
				
			||||||
 | 
					                    'fontStyle' => 'bold',
 | 
				
			||||||
 | 
					                    'token' => 'support',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'bold',
 | 
				
			||||||
 | 
					                    'token' => 'storage',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'bold underline',
 | 
				
			||||||
 | 
					                    'token' => 'entity.name.section',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '000000',
 | 
				
			||||||
 | 
					                    'fontStyle' => 'bold',
 | 
				
			||||||
 | 
					                    'token' => 'entity.name.function.frame',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '333333',
 | 
				
			||||||
 | 
					                    'token' => 'meta.tag.preprocessor.xml',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'foreground' => '3366cc',
 | 
				
			||||||
 | 
					                    'fontStyle' => 'italic',
 | 
				
			||||||
 | 
					                    'token' => 'entity.other.attribute-name',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'fontStyle' => 'bold',
 | 
				
			||||||
 | 
					                    'token' => 'entity.name.tag',
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            'colors' => [
 | 
				
			||||||
 | 
					                'editor.foreground' => '#000000',
 | 
				
			||||||
 | 
					                'editor.background' => '#EEEEEEEB',
 | 
				
			||||||
 | 
					                'editor.selectionBackground' => '#BAD6FD',
 | 
				
			||||||
 | 
					                'editor.lineHighlightBackground' => '#0000001A',
 | 
				
			||||||
 | 
					                'editorCursor.foreground' => '#000000',
 | 
				
			||||||
 | 
					                'editorWhitespace.foreground' => '#B3B3B3F4',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@ -24,7 +24,7 @@ return [
 | 
				
			|||||||
    */
 | 
					    */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    'service' => [
 | 
					    'service' => [
 | 
				
			||||||
        'author' => env('APP_SERVICE_AUTHOR', 'unknown@unknown.com'),
 | 
					        'author' => env('APP_SERVICE_AUTHOR', 'unknown@example.com'),
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
 | 
				
			|||||||
@ -1,28 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
return [
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
     * Set trusted proxy IP addresses.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * Both IPv4 and IPv6 addresses are
 | 
					 | 
				
			||||||
     * supported, along with CIDR notation.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * The "*" character is syntactic sugar
 | 
					 | 
				
			||||||
     * within TrustedProxy to trust any proxy
 | 
					 | 
				
			||||||
     * that connects directly to your server,
 | 
					 | 
				
			||||||
     * a requirement when you cannot know the address
 | 
					 | 
				
			||||||
     * of your proxy (e.g. if using Rackspace balancers).
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * The "**" character is syntactic sugar within
 | 
					 | 
				
			||||||
     * TrustedProxy to trust not just any proxy that
 | 
					 | 
				
			||||||
     * connects directly to your server, but also
 | 
					 | 
				
			||||||
     * proxies that connect to those proxies, and all
 | 
					 | 
				
			||||||
     * the way back until you reach the original source
 | 
					 | 
				
			||||||
     * IP. It will mean that $request->getClientIp()
 | 
					 | 
				
			||||||
     * always gets the originating client IP, no matter
 | 
					 | 
				
			||||||
     * how many proxies that client's request has
 | 
					 | 
				
			||||||
     * subsequently passed through.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ?
 | 
					 | 
				
			||||||
        env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES') ?? ''),
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
							
								
								
									
										96
									
								
								contributor_license_agreement.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								contributor_license_agreement.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					Thank you for your interest in Pelican ("Pelican Developers"). To clarify the intellectual property license
 | 
				
			||||||
 | 
					granted with Contributions from any person or entity, the Pelican Developers
 | 
				
			||||||
 | 
					must have on file a signed Contributor License Agreement ("CLA")
 | 
				
			||||||
 | 
					from each Contributor, indicating agreement with the license
 | 
				
			||||||
 | 
					terms below. This agreement is for your protection as a Contributor
 | 
				
			||||||
 | 
					as well as the protection of the Pelican Developers and its users. It does not
 | 
				
			||||||
 | 
					change your rights to use your own Contributions for any other purpose.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You accept and agree to the following terms and conditions for Your
 | 
				
			||||||
 | 
					Contributions (present and future) that you submit to the Pelican Developers. In
 | 
				
			||||||
 | 
					return, the Pelican Developers shall not use Your Contributions in a way that
 | 
				
			||||||
 | 
					is contrary to the public benefit or inconsistent with its nonprofit
 | 
				
			||||||
 | 
					status and bylaws in effect at the time of the Contribution. Except
 | 
				
			||||||
 | 
					for the license granted herein to the Pelican Developers and recipients of
 | 
				
			||||||
 | 
					software distributed by the Pelican Developers, You reserve all right, title,
 | 
				
			||||||
 | 
					and interest in and to Your Contributions.
 | 
				
			||||||
 | 
					1. Definitions.
 | 
				
			||||||
 | 
					   "You" (or "Your") shall mean the copyright owner or legal entity
 | 
				
			||||||
 | 
					   authorized by the copyright owner that is making this Agreement
 | 
				
			||||||
 | 
					   with the Pelican Developers. For legal entities, the entity making a
 | 
				
			||||||
 | 
					   Contribution and all other entities that control, are controlled
 | 
				
			||||||
 | 
					   by, or are under common control with that entity are considered to
 | 
				
			||||||
 | 
					   be a single Contributor. For the purposes of this definition,
 | 
				
			||||||
 | 
					   "control" means (i) the power, direct or indirect, to cause the
 | 
				
			||||||
 | 
					   direction or management of such entity, whether by contract or
 | 
				
			||||||
 | 
					   otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
				
			||||||
 | 
					   outstanding shares, or (iii) beneficial ownership of such entity.
 | 
				
			||||||
 | 
					   "Contribution" shall mean any original work of authorship,
 | 
				
			||||||
 | 
					   including any modifications or additions to an existing work, that
 | 
				
			||||||
 | 
					   is intentionally submitted by You to the Pelican Developers for inclusion
 | 
				
			||||||
 | 
					   in, or documentation of, any of the products owned or managed by
 | 
				
			||||||
 | 
					   the Pelican Developers (the "Work"). For the purposes of this definition,
 | 
				
			||||||
 | 
					   "submitted" means any form of electronic, verbal, or written
 | 
				
			||||||
 | 
					   communication sent to the Pelican Developers or its representatives,
 | 
				
			||||||
 | 
					   including but not limited to communication on electronic mailing
 | 
				
			||||||
 | 
					   lists, source code control systems, and issue tracking systems that
 | 
				
			||||||
 | 
					   are managed by, or on behalf of, the Pelican Developers for the purpose of
 | 
				
			||||||
 | 
					   discussing and improving the Work, but excluding communication that
 | 
				
			||||||
 | 
					   is conspicuously marked or otherwise designated in writing by You
 | 
				
			||||||
 | 
					   as "Not a Contribution."
 | 
				
			||||||
 | 
					2. Grant of Copyright License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					   this Agreement, You hereby grant to the Pelican Developers and to
 | 
				
			||||||
 | 
					   recipients of software distributed by the Pelican Developers a perpetual,
 | 
				
			||||||
 | 
					   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					   copyright license to reproduce, prepare derivative works of,
 | 
				
			||||||
 | 
					   publicly display, publicly perform, sublicense, and distribute Your
 | 
				
			||||||
 | 
					   Contributions and such derivative works.
 | 
				
			||||||
 | 
					3. Grant of Patent License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					   this Agreement, You hereby grant to the Pelican Developers and to
 | 
				
			||||||
 | 
					   recipients of software distributed by the Pelican Developers a perpetual,
 | 
				
			||||||
 | 
					   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					   (except as stated in this section) patent license to make, have
 | 
				
			||||||
 | 
					   made, use, offer to sell, sell, import, and otherwise transfer the
 | 
				
			||||||
 | 
					   Work, where such license applies only to those patent claims
 | 
				
			||||||
 | 
					   licensable by You that are necessarily infringed by Your
 | 
				
			||||||
 | 
					   Contribution(s) alone or by combination of Your Contribution(s)
 | 
				
			||||||
 | 
					   with the Work to which such Contribution(s) was submitted. If any
 | 
				
			||||||
 | 
					   entity institutes patent litigation against You or any other entity
 | 
				
			||||||
 | 
					   (including a cross-claim or counterclaim in a lawsuit) alleging
 | 
				
			||||||
 | 
					   that your Contribution, or the Work to which you have contributed,
 | 
				
			||||||
 | 
					   constitutes direct or contributory patent infringement, then any
 | 
				
			||||||
 | 
					   patent licenses granted to that entity under this Agreement for
 | 
				
			||||||
 | 
					   that Contribution or Work shall terminate as of the date such
 | 
				
			||||||
 | 
					   litigation is filed.
 | 
				
			||||||
 | 
					4. You represent that you are legally entitled to grant the above
 | 
				
			||||||
 | 
					   license. If your employer(s) has rights to intellectual property
 | 
				
			||||||
 | 
					   that you create that includes your Contributions, you represent
 | 
				
			||||||
 | 
					   that you have received permission to make Contributions on behalf
 | 
				
			||||||
 | 
					   of that employer, that your employer has waived such rights for
 | 
				
			||||||
 | 
					   your Contributions to the Pelican Developers, or that your employer has
 | 
				
			||||||
 | 
					   executed a separate Corporate CLA with the Pelican Developers.
 | 
				
			||||||
 | 
					5. You represent that each of Your Contributions is Your original
 | 
				
			||||||
 | 
					   creation (see section 7 for submissions on behalf of others). You
 | 
				
			||||||
 | 
					   represent that Your Contribution submissions include complete
 | 
				
			||||||
 | 
					   details of any third-party license or other restriction (including,
 | 
				
			||||||
 | 
					   but not limited to, related patents and trademarks) of which you
 | 
				
			||||||
 | 
					   are personally aware and which are associated with any part of Your
 | 
				
			||||||
 | 
					   Contributions.
 | 
				
			||||||
 | 
					6. You are not expected to provide support for Your Contributions,
 | 
				
			||||||
 | 
					   except to the extent You desire to provide support. You may provide
 | 
				
			||||||
 | 
					   support for free, for a fee, or not at all. Unless required by
 | 
				
			||||||
 | 
					   applicable law or agreed to in writing, You provide Your
 | 
				
			||||||
 | 
					   Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
 | 
				
			||||||
 | 
					   OF ANY KIND, either express or implied, including, without
 | 
				
			||||||
 | 
					   limitation, any warranties or conditions of TITLE, NON-
 | 
				
			||||||
 | 
					   INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
 | 
				
			||||||
 | 
					7. Should You wish to submit work that is not Your original creation,
 | 
				
			||||||
 | 
					   You may submit it to the Pelican Developers separately from any
 | 
				
			||||||
 | 
					   Contribution, identifying the complete details of its source and of
 | 
				
			||||||
 | 
					   any license or other restriction (including, but not limited to,
 | 
				
			||||||
 | 
					   related patents, trademarks, and license agreements) of which you
 | 
				
			||||||
 | 
					   are personally aware, and conspicuously marking the work as
 | 
				
			||||||
 | 
					   "Submitted on behalf of a third-party: [named here]".
 | 
				
			||||||
 | 
					8. You agree to notify the Pelican Developers of any facts or circumstances of
 | 
				
			||||||
 | 
					   which you become aware that would make these representations
 | 
				
			||||||
 | 
					   inaccurate in any respect.
 | 
				
			||||||
							
								
								
									
										11
									
								
								crowdin.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								crowdin.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					files:
 | 
				
			||||||
 | 
					  - source: /lang/en/*.php
 | 
				
			||||||
 | 
					    translation: /lang/%two_letters_code%/%original_file_name%
 | 
				
			||||||
 | 
					  - source: /lang/en/admin
 | 
				
			||||||
 | 
					    translation: /lang/%two_letters_code%/admin/%original_file_name%
 | 
				
			||||||
 | 
					  - source: /lang/en/command
 | 
				
			||||||
 | 
					    translation: /lang/%two_letters_code%/command/%original_file_name%
 | 
				
			||||||
 | 
					  - source: /lang/en/dashboard
 | 
				
			||||||
 | 
					    translation: /lang/%two_letters_code%/dashboard/%original_file_name%
 | 
				
			||||||
 | 
					  - source: /lang/en/server
 | 
				
			||||||
 | 
					    translation: /lang/%two_letters_code%/server/%original_file_name%
 | 
				
			||||||
@ -14,10 +14,10 @@
 | 
				
			|||||||
        "pid_limit"
 | 
					        "pid_limit"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "docker_images": {
 | 
					    "docker_images": {
 | 
				
			||||||
        "Java 17": "ghcr.io\/App\/yolks:java_17",
 | 
					        "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
 | 
				
			||||||
        "Java 16": "ghcr.io\/App\/yolks:java_16",
 | 
					        "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
 | 
				
			||||||
        "Java 11": "ghcr.io\/App\/yolks:java_11",
 | 
					        "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
 | 
				
			||||||
        "Java 8": "ghcr.io\/App\/yolks:java_8"
 | 
					        "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
 | 
					    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
 | 
				
			||||||
@ -30,7 +30,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/ash\r\n# Bungeecord Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${BUNGEE_VERSION}\" ] || [ \"${BUNGEE_VERSION}\" == \"latest\" ]; then\r\n    BUNGEE_VERSION=\"lastStableBuild\"\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/ci.md-5.net\/job\/BungeeCord\/${BUNGEE_VERSION}\/artifact\/bootstrap\/target\/BungeeCord.jar",
 | 
					            "script": "#!\/bin\/ash\r\n# Bungeecord Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${BUNGEE_VERSION}\" ] || [ \"${BUNGEE_VERSION}\" == \"latest\" ]; then\r\n    BUNGEE_VERSION=\"lastStableBuild\"\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/ci.md-5.net\/job\/BungeeCord\/${BUNGEE_VERSION}\/artifact\/bootstrap\/target\/BungeeCord.jar",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:alpine",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:alpine",
 | 
				
			||||||
            "entrypoint": "ash"
 | 
					            "entrypoint": "ash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -14,10 +14,10 @@
 | 
				
			|||||||
        "pid_limit"
 | 
					        "pid_limit"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "docker_images": {
 | 
					    "docker_images": {
 | 
				
			||||||
        "Java 17": "ghcr.io\/App\/yolks:java_17",
 | 
					        "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
 | 
				
			||||||
        "Java 16": "ghcr.io\/App\/yolks:java_16",
 | 
					        "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
 | 
				
			||||||
        "Java 11": "ghcr.io\/App\/yolks:java_11",
 | 
					        "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
 | 
				
			||||||
        "Java 8": "ghcr.io\/App\/yolks:java_8"
 | 
					        "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true $( [[  ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )",
 | 
					    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true $( [[  ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )",
 | 
				
			||||||
 | 
				
			|||||||
@ -14,10 +14,10 @@
 | 
				
			|||||||
        "pid_limit"
 | 
					        "pid_limit"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "docker_images": {
 | 
					    "docker_images": {
 | 
				
			||||||
        "Java 17": "ghcr.io\/App\/yolks:java_17",
 | 
					        "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
 | 
				
			||||||
        "Java 16": "ghcr.io\/App\/yolks:java_16",
 | 
					        "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
 | 
				
			||||||
        "Java 11": "ghcr.io\/App\/yolks:java_11",
 | 
					        "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
 | 
				
			||||||
        "Java 8": "ghcr.io\/App\/yolks:java_8"
 | 
					        "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",
 | 
					    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",
 | 
				
			||||||
@ -30,7 +30,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n    echo -e \"Downloading MC server.properties\"\r\n    curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi",
 | 
					            "script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n    echo -e \"Downloading MC server.properties\"\r\n    curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:alpine",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:alpine",
 | 
				
			||||||
            "entrypoint": "ash"
 | 
					            "entrypoint": "ash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -14,9 +14,9 @@
 | 
				
			|||||||
        "pid_limit"
 | 
					        "pid_limit"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "docker_images": {
 | 
					    "docker_images": {
 | 
				
			||||||
        "Java 16": "ghcr.io\/App\/yolks:java_16",
 | 
					        "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
 | 
				
			||||||
        "Java 11": "ghcr.io\/App\/yolks:java_11",
 | 
					        "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
 | 
				
			||||||
        "Java 8": "ghcr.io\/App\/yolks:java_8"
 | 
					        "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
 | 
					    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
 | 
				
			||||||
@ -29,7 +29,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/ash\r\n# Sponge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sSL \"https:\/\/repo.spongepowered.org\/maven\/org\/spongepowered\/spongevanilla\/${SPONGE_VERSION}\/spongevanilla-${SPONGE_VERSION}.jar\" -o ${SERVER_JARFILE}",
 | 
					            "script": "#!\/bin\/ash\r\n# Sponge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sSL \"https:\/\/repo.spongepowered.org\/maven\/org\/spongepowered\/spongevanilla\/${SPONGE_VERSION}\/spongevanilla-${SPONGE_VERSION}.jar\" -o ${SERVER_JARFILE}",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:alpine",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:alpine",
 | 
				
			||||||
            "entrypoint": "ash"
 | 
					            "entrypoint": "ash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -14,10 +14,10 @@
 | 
				
			|||||||
        "pid_limit"
 | 
					        "pid_limit"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "docker_images": {
 | 
					    "docker_images": {
 | 
				
			||||||
        "Java 17": "ghcr.io\/App\/yolks:java_17",
 | 
					        "Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
 | 
				
			||||||
        "Java 16": "ghcr.io\/App\/yolks:java_16",
 | 
					        "Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
 | 
				
			||||||
        "Java 11": "ghcr.io\/App\/yolks:java_11",
 | 
					        "Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
 | 
				
			||||||
        "Java 8": "ghcr.io\/App\/yolks:java_8"
 | 
					        "Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
 | 
					    "startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
 | 
				
			||||||
@ -30,7 +30,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\nLATEST_SNAPSHOT_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.snapshot'`\r\n\r\necho -e \"latest version is $LATEST_VERSION\"\r\necho -e \"latest snapshot is $LATEST_SNAPSHOT_VERSION\"\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelif [ \"$VANILLA_VERSION\" == \"snapshot\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_SNAPSHOT_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelse\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $VANILLA_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nfi\r\n\r\nDOWNLOAD_URL=$(curl ${MANIFEST_URL} | jq .downloads.server | jq -r '. | .url')\r\n\r\necho -e \"running: curl -o ${SERVER_JARFILE} $DOWNLOAD_URL\"\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL\r\n\r\necho -e \"Install Complete\"",
 | 
					            "script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\nLATEST_SNAPSHOT_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.snapshot'`\r\n\r\necho -e \"latest version is $LATEST_VERSION\"\r\necho -e \"latest snapshot is $LATEST_SNAPSHOT_VERSION\"\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelif [ \"$VANILLA_VERSION\" == \"snapshot\" ]; then\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_SNAPSHOT_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelse\r\n  MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $VANILLA_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nfi\r\n\r\nDOWNLOAD_URL=$(curl ${MANIFEST_URL} | jq .downloads.server | jq -r '. | .url')\r\n\r\necho -e \"running: curl -o ${SERVER_JARFILE} $DOWNLOAD_URL\"\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL\r\n\r\necho -e \"Install Complete\"",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:alpine",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:alpine",
 | 
				
			||||||
            "entrypoint": "ash"
 | 
					            "entrypoint": "ash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
        "steam_disk_space"
 | 
					        "steam_disk_space"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "docker_images": {
 | 
					    "docker_images": {
 | 
				
			||||||
        "ghcr.io\/App\/games:rust": "ghcr.io\/App\/games:rust"
 | 
					        "ghcr.io\/pterodactyl\/games:rust": "ghcr.io\/pterodactyl\/games:rust"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}}  $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
 | 
					    "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}}  $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
 | 
				
			||||||
@ -25,7 +25,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nSRCDS_APPID=258550\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
					            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nSRCDS_APPID=258550\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:debian",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:debian",
 | 
				
			||||||
            "entrypoint": "bash"
 | 
					            "entrypoint": "bash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
        "steam_disk_space"
 | 
					        "steam_disk_space"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "images": [
 | 
					    "images": [
 | 
				
			||||||
        "ghcr.io\/App\/games:source"
 | 
					        "ghcr.io\/pterodactyl\/games:source"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
 | 
					    "startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
 | 
				
			||||||
@ -26,7 +26,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
					            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:debian",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:debian",
 | 
				
			||||||
            "entrypoint": "bash"
 | 
					            "entrypoint": "bash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
        "steam_disk_space"
 | 
					        "steam_disk_space"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "images": [
 | 
					    "images": [
 | 
				
			||||||
        "ghcr.io\/App\/games:source"
 | 
					        "ghcr.io\/pterodactyl\/games:source"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
 | 
					    "startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
 | 
				
			||||||
@ -25,7 +25,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
					            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:debian",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:debian",
 | 
				
			||||||
            "entrypoint": "bash"
 | 
					            "entrypoint": "bash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
        "steam_disk_space"
 | 
					        "steam_disk_space"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "images": [
 | 
					    "images": [
 | 
				
			||||||
        "ghcr.io\/App\/games:source"
 | 
					        "ghcr.io\/pterodactyl\/games:source"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}}  -tickrate {{TICKRATE}}  $( [ \"$LUA_REFRESH\" == \"1\" ] || printf %s '-disableluarefresh' )",
 | 
					    "startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}}  -tickrate {{TICKRATE}}  $( [ \"$LUA_REFRESH\" == \"1\" ] || printf %s '-disableluarefresh' )",
 | 
				
			||||||
@ -26,7 +26,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl   \"\"\r\nsv_downloadurl  \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t    0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
 | 
					            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl   \"\"\r\nsv_downloadurl  \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t    0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:debian",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:debian",
 | 
				
			||||||
            "entrypoint": "bash"
 | 
					            "entrypoint": "bash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
        "steam_disk_space"
 | 
					        "steam_disk_space"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "images": [
 | 
					    "images": [
 | 
				
			||||||
        "ghcr.io\/App\/games:source"
 | 
					        "ghcr.io\/pterodactyl\/games:source"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": ".\/srcds_run -game insurgency -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
 | 
					    "startup": ".\/srcds_run -game insurgency -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
 | 
				
			||||||
@ -25,7 +25,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login anonymous +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
					            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login anonymous +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:debian",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:debian",
 | 
				
			||||||
            "entrypoint": "bash"
 | 
					            "entrypoint": "bash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
        "steam_disk_space"
 | 
					        "steam_disk_space"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "images": [
 | 
					    "images": [
 | 
				
			||||||
        "ghcr.io\/App\/games:source"
 | 
					        "ghcr.io\/pterodactyl\/games:source"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "file_denylist": [],
 | 
					    "file_denylist": [],
 | 
				
			||||||
    "startup": ".\/srcds_run -game tf -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
 | 
					    "startup": ".\/srcds_run -game tf -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
 | 
				
			||||||
@ -26,7 +26,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
					            "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n    echo -e \"steam user is not set.\\n\"\r\n    echo -e \"Using anonymous user.\\n\"\r\n    STEAM_USER=anonymous\r\n    STEAM_PASS=\"\"\r\n    STEAM_AUTH=\"\"\r\nelse\r\n    echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:debian",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:debian",
 | 
				
			||||||
            "entrypoint": "bash"
 | 
					            "entrypoint": "bash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@
 | 
				
			|||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "installation": {
 | 
					        "installation": {
 | 
				
			||||||
            "script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n    mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n    echo \"Config file already exists.\"\r\nelse \r\n    echo \"Downloading the config file.\"\r\n    apk add --no-cache murmur\r\n    cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n    apk del murmur\r\nfi\r\necho \"done\"",
 | 
					            "script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n    mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n    echo \"Config file already exists.\"\r\nelse \r\n    echo \"Downloading the config file.\"\r\n    apk add --no-cache murmur\r\n    cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n    apk del murmur\r\nfi\r\necho \"done\"",
 | 
				
			||||||
            "container": "ghcr.io\/App\/installers:alpine",
 | 
					            "container": "ghcr.io\/pterodactyl\/installers:alpine",
 | 
				
			||||||
            "entrypoint": "ash"
 | 
					            "entrypoint": "ash"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user