mirror of
https://github.com/pelican-dev/panel.git
synced 2025-07-23 14:12:16 +02:00
Merge branch 'main' into issue/68
# Conflicts: # app/Filament/Resources/ServerResource/Pages/CreateServer.php # app/Models/Node.php
This commit is contained in:
commit
4319f24f51
@ -30,3 +30,8 @@ MAIL_FROM_NAME="Pelican Admin"
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
# Set this to true, and set start & end ports to auto create allocations.
|
||||
PANEL_CLIENT_ALLOCATIONS_ENABLED=false
|
||||
PANEL_CLIENT_ALLOCATIONS_RANGE_START=
|
||||
PANEL_CLIENT_ALLOCATIONS_RANGE_END=
|
||||
|
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@ -59,7 +59,7 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
@ -119,7 +119,7 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/clockwork/*
|
||||
/vendor
|
||||
*.DS_Store*
|
||||
.env
|
||||
|
@ -118,7 +118,9 @@ class AppSettingsCommand extends Command
|
||||
}
|
||||
|
||||
if ($this->variables['QUEUE_CONNECTION'] !== 'sync') {
|
||||
Artisan::call('p:environment:queue-service', $redisUsed ? ['--use-redis'] : []);
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--use-redis' => $redisUsed,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->info($this->console->output());
|
||||
@ -127,7 +129,7 @@ class AppSettingsCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Request connection details and verify them.
|
||||
* Request redis connection details and verify them.
|
||||
*/
|
||||
private function requestRedisSettings(): void
|
||||
{
|
||||
|
@ -98,7 +98,7 @@ class DatabaseSettingsCommand extends Command
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Path',
|
||||
config('database.connections.sqlite.database', database_path('database.sqlite'))
|
||||
env('DB_DATABASE', 'database.sqlite')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -19,17 +19,18 @@ class QueueWorkerServiceCommand extends Command
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$serviceName = $this->option('service-name') ?? $this->ask('Service name', 'pelican-queue');
|
||||
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
|
||||
$path = '/etc/systemd/system/' . $serviceName . '.service';
|
||||
|
||||
if (file_exists($path) && !$this->option('overwrite') && !$this->confirm('The service file already exists. Do you want to overwrite it?')) {
|
||||
$this->line('Creation of queue worker service file aborted.');
|
||||
$fileExists = file_exists($path);
|
||||
if ($fileExists && !$this->option('overwrite') && !$this->confirm('The service file already exists. Do you want to overwrite it?')) {
|
||||
$this->line('Creation of queue worker service file aborted because serive file already exists.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->option('user') ?? $this->ask('User', 'www-data');
|
||||
$group = $this->option('group') ?? $this->ask('Group', 'www-data');
|
||||
$user = $this->option('user') ?? $this->ask('Webserver User', 'www-data');
|
||||
$group = $this->option('group') ?? $this->ask('Webserver Group', 'www-data');
|
||||
|
||||
$afterRedis = $this->option('use-redis') ? '\nAfter=redis-server.service' : '';
|
||||
|
||||
@ -45,7 +46,7 @@ Description=Pelican Queue Service$afterRedis
|
||||
User=$user
|
||||
Group=$group
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/php $basePath/artisan queue:work --queue=high,standard,low --tries=3
|
||||
ExecStart=/usr/bin/php $basePath/artisan queue:work --tries=3
|
||||
StartLimitInterval=180
|
||||
StartLimitBurst=30
|
||||
RestartSec=5s
|
||||
@ -60,6 +61,16 @@ WantedBy=multi-user.target
|
||||
return;
|
||||
}
|
||||
|
||||
if ($fileExists) {
|
||||
$result = Process::run("systemctl restart $serviceName.service");
|
||||
if ($result->failed()) {
|
||||
$this->error('Error restarting service: ' . $result->errorOutput());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line('Queue worker service file updated successfully.');
|
||||
} else {
|
||||
$result = Process::run("systemctl enable --now $serviceName.service");
|
||||
if ($result->failed()) {
|
||||
$this->error('Error enabling service: ' . $result->errorOutput());
|
||||
@ -70,3 +81,4 @@ WantedBy=multi-user.target
|
||||
$this->line('Queue worker service file created successfully.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class InfoCommand extends Command
|
||||
{
|
||||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', config('app.version')],
|
||||
['Panel Version', $this->versionService->versionData()['version']],
|
||||
['Latest Version', $this->versionService->getPanel()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
], 'compact');
|
||||
|
@ -25,6 +25,7 @@ class MakeNodeCommand extends Command
|
||||
{--uploadSize= : Enter the maximum upload filesize.}
|
||||
{--daemonListeningPort= : Enter the daemon listening port.}
|
||||
{--daemonSFTPPort= : Enter the daemon SFTP listening port.}
|
||||
{--daemonSFTPAlias= : Enter the daemon SFTP alias.}
|
||||
{--daemonBase= : Enter the base folder.}';
|
||||
|
||||
protected $description = 'Creates a new node on the system via the CLI.';
|
||||
@ -65,6 +66,7 @@ class MakeNodeCommand extends Command
|
||||
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(__('commands.make_node.upload_size'), '100');
|
||||
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(__('commands.make_node.daemonListen'), '8080');
|
||||
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(__('commands.make_node.daemonSFTP'), '2022');
|
||||
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(__('commands.make_node.daemonSFTPAlias'), '');
|
||||
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(__('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
|
||||
|
||||
$node = $this->creationService->handle($data);
|
||||
|
@ -24,7 +24,7 @@ class ProcessRunnableCommand extends Command
|
||||
->whereRelation('server', fn (Builder $builder) => $builder->whereNull('status'))
|
||||
->where('is_active', true)
|
||||
->where('is_processing', false)
|
||||
->whereDate('next_run_at', '<=', Carbon::now()->toDateString())
|
||||
->whereDate('next_run_at', '<=', Carbon::now()->toDateTimeString())
|
||||
->get();
|
||||
|
||||
if ($schedules->count() < 1) {
|
||||
|
@ -30,7 +30,7 @@ class MakeUserCommand extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
DB::select('select 1 where 1');
|
||||
DB::connection()->getPdo();
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
|
@ -6,9 +6,9 @@ use App\Exceptions\DisplayException;
|
||||
|
||||
class TwoFactorAuthenticationTokenInvalid extends DisplayException
|
||||
{
|
||||
/**
|
||||
* TwoFactorAuthenticationTokenInvalid constructor.
|
||||
*/
|
||||
public string $title = 'Invalid 2FA Code';
|
||||
public string $icon = 'tabler-2fa';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('The provided two-factor authentication token was not valid.');
|
||||
|
@ -7,6 +7,7 @@ use App\Models\Egg;
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Pages\Page;
|
||||
|
||||
@ -29,8 +30,14 @@ class Dashboard extends Page
|
||||
|
||||
public function getViewData(): array
|
||||
{
|
||||
/** @var SoftwareVersionService $softwareVersionService */
|
||||
$softwareVersionService = app(SoftwareVersionService::class);
|
||||
|
||||
return [
|
||||
'inDevelopment' => config('app.version') === 'canary',
|
||||
'version' => $softwareVersionService->versionData()['version'],
|
||||
'latestVersion' => $softwareVersionService->getPanel(),
|
||||
'isLatest' => $softwareVersionService->isLatestPanel(),
|
||||
'eggsCount' => Egg::query()->count(),
|
||||
'nodesList' => ListNodes::getUrl(),
|
||||
'nodesCount' => Node::query()->count(),
|
||||
@ -43,6 +50,13 @@ class Dashboard extends Page
|
||||
->icon('tabler-brand-github')
|
||||
->url('https://github.com/pelican-dev/panel/discussions', true),
|
||||
],
|
||||
'updateActions' => [
|
||||
CreateAction::make()
|
||||
->label('Read Documentation')
|
||||
->icon('tabler-clipboard-text')
|
||||
->url('https://pelican.dev/docs/panel/update', true)
|
||||
->color('warning'),
|
||||
],
|
||||
'nodeActions' => [
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-first-node.button_label'))
|
||||
@ -53,7 +67,7 @@ class Dashboard extends Page
|
||||
CreateAction::make()
|
||||
->label(trans('dashboard/index.sections.intro-support.button_donate'))
|
||||
->icon('tabler-cash')
|
||||
->url('https://pelican.dev/donate', true)
|
||||
->url($softwareVersionService->getDonations(), true)
|
||||
->color('success'),
|
||||
],
|
||||
'helpActions' => [
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DatabaseHostResource;
|
||||
use App\Models\DatabaseHost;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Forms;
|
||||
@ -71,7 +72,9 @@ class EditDatabaseHost extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->label(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0 ? 'Database Host Has Databases' : 'Delete')
|
||||
->disabled(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Table;
|
||||
@ -62,21 +63,58 @@ class ListEggs extends ListRecords
|
||||
Actions\Action::make('import')
|
||||
->label('Import')
|
||||
->form([
|
||||
Tabs::make('Tabs')
|
||||
->tabs([
|
||||
Tabs\Tab::make('From File')
|
||||
->icon('tabler-file-upload')
|
||||
->schema([
|
||||
Forms\Components\FileUpload::make('egg')
|
||||
->label('Egg')
|
||||
->hint('This should be the json file ( egg-minecraft.json )')
|
||||
->acceptedFileTypes(['application/json'])
|
||||
->storeFiles(false)
|
||||
->multiple(),
|
||||
]),
|
||||
Tabs\Tab::make('From URL')
|
||||
->icon('tabler-world-upload')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('url')
|
||||
->label('URL')
|
||||
->hint('This URL should point to a single json file')
|
||||
->url(),
|
||||
]),
|
||||
])
|
||||
->contained(false),
|
||||
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
/** @var TemporaryUploadedFile $eggFile */
|
||||
$eggFile = $data['egg'];
|
||||
|
||||
/** @var EggImporterService $eggImportService */
|
||||
$eggImportService = resolve(EggImporterService::class);
|
||||
|
||||
if (!empty($data['egg'])) {
|
||||
/** @var TemporaryUploadedFile[] $eggFile */
|
||||
$eggFile = $data['egg'];
|
||||
|
||||
foreach ($eggFile as $file) {
|
||||
try {
|
||||
$eggImportService->handle($file);
|
||||
$eggImportService->fromFile($file);
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['url'])) {
|
||||
try {
|
||||
$eggImportService->fromUrl($data['url']);
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
|
@ -4,8 +4,10 @@ namespace App\Filament\Resources\NodeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\NodeResource;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Wizard;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class CreateNode extends CreateRecord
|
||||
@ -18,19 +20,19 @@ class CreateNode extends CreateRecord
|
||||
|
||||
public function form(Forms\Form $form): Forms\Form
|
||||
{
|
||||
return $form->schema([
|
||||
Tabs::make('Tabs')
|
||||
return $form
|
||||
->schema([
|
||||
Wizard::make([
|
||||
Wizard\Step::make('basic')
|
||||
->label('Basic Settings')
|
||||
->icon('tabler-server')
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
])
|
||||
->persistTabInQueryString()
|
||||
->columnSpanFull()
|
||||
->tabs([
|
||||
Tabs\Tab::make('Basic Settings')
|
||||
->icon('tabler-server')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('fqdn')
|
||||
->columnSpan(2)
|
||||
@ -184,30 +186,17 @@ class CreateNode extends CreateRecord
|
||||
])
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
]),
|
||||
Tabs\Tab::make('Advanced Settings')
|
||||
Wizard\Step::make('advanced')
|
||||
->label('Advanced Settings')
|
||||
->icon('tabler-server-cog')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('upload_size')
|
||||
->label('Upload Limit')
|
||||
->helperText('Enter the maximum size of files that can be uploaded through the web-based file manager.')
|
||||
->columnSpan(1)
|
||||
->numeric()->required()
|
||||
->default(256)
|
||||
->minValue(1)
|
||||
->maxValue(1024)
|
||||
->suffix('MiB'),
|
||||
Forms\Components\ToggleButtons::make('public')
|
||||
->label('Automatic Allocation')->inline()
|
||||
->default(true)
|
||||
->columnSpan(1)
|
||||
->options([
|
||||
true => 'Yes',
|
||||
false => 'No',
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
]),
|
||||
->schema([
|
||||
Forms\Components\ToggleButtons::make('maintenance_mode')
|
||||
->label('Maintenance Mode')->inline()
|
||||
->columnSpan(1)
|
||||
@ -222,13 +211,46 @@ class CreateNode extends CreateRecord
|
||||
true => 'danger',
|
||||
false => 'success',
|
||||
]),
|
||||
Forms\Components\ToggleButtons::make('public')
|
||||
->columnSpan(1)
|
||||
->label('Automatic Allocation')->inline()
|
||||
->options([
|
||||
true => 'Yes',
|
||||
false => 'No',
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
]),
|
||||
Forms\Components\TagsInput::make('tags')
|
||||
->label('Tags')
|
||||
->disabled()
|
||||
->placeholder('Not Implemented')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('Not Implemented')
|
||||
->columnSpan(1),
|
||||
->columnSpan(2),
|
||||
|
||||
Forms\Components\TextInput::make('upload_size')
|
||||
->label('Upload Limit')
|
||||
->helperText('Enter the maximum size of files that can be uploaded through the web-based file manager.')
|
||||
->columnSpan(1)
|
||||
->numeric()->required()
|
||||
->default(256)
|
||||
->minValue(1)
|
||||
->maxValue(1024)
|
||||
->suffix('MiB'),
|
||||
Forms\Components\TextInput::make('daemon_sftp')
|
||||
->columnSpan(1)
|
||||
->label('SFTP Port')
|
||||
->minValue(0)
|
||||
->maxValue(65536)
|
||||
->default(2022)
|
||||
->required()
|
||||
->integer(),
|
||||
Forms\Components\TextInput::make('daemon_sftp_alias')
|
||||
->columnSpan(2)
|
||||
->label('SFTP Alias')
|
||||
->helperText('Display alias for the SFTP address. Leave empty to use the Node FQDN.'),
|
||||
Forms\Components\Grid::make()
|
||||
->columns(6)
|
||||
->columnSpanFull()
|
||||
@ -353,7 +375,16 @@ class CreateNode extends CreateRecord
|
||||
->suffix('%'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])->columnSpanFull()
|
||||
->nextAction(fn (Action $action) => $action->label('Next Step'))
|
||||
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
|
||||
<x-filament::button
|
||||
type="submit"
|
||||
size="sm"
|
||||
>
|
||||
Create Node
|
||||
</x-filament::button>
|
||||
BLADE))),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -363,4 +394,9 @@ class CreateNode extends CreateRecord
|
||||
'tab' => '-configuration-tab',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -215,6 +215,18 @@ class EditNode extends EditRecord
|
||||
->minValue(1)
|
||||
->maxValue(1024)
|
||||
->suffix('MiB'),
|
||||
Forms\Components\TextInput::make('daemon_sftp')
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 3])
|
||||
->label('SFTP Port')
|
||||
->minValue(0)
|
||||
->maxValue(65536)
|
||||
->default(2022)
|
||||
->required()
|
||||
->integer(),
|
||||
Forms\Components\TextInput::make('daemon_sftp_alias')
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 3])
|
||||
->label('SFTP Alias')
|
||||
->helperText('Display alias for the SFTP address. Leave empty to use the Node FQDN.'),
|
||||
Forms\Components\ToggleButtons::make('public')
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 3])
|
||||
->label('Automatic Allocation')->inline()
|
||||
|
@ -113,7 +113,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
|
||||
$start = max((int) $start, 0);
|
||||
$end = min((int) $end, 2 ** 16 - 1);
|
||||
for ($i = $start; $i <= $end; $i++) {
|
||||
foreach (range($start, $end) as $i) {
|
||||
$ports->push($i);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,10 @@
|
||||
namespace App\Filament\Resources\ServerResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ServerResource;
|
||||
use App\Http\Controllers\Admin\ServersController;
|
||||
use App\Services\Servers\RandomWordService;
|
||||
use App\Services\Servers\SuspensionService;
|
||||
use App\Services\Servers\TransferServerService;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use App\Enums\ContainerStatus;
|
||||
@ -13,10 +16,13 @@ use App\Models\Server;
|
||||
use App\Models\ServerVariable;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use App\Services\Servers\ServerDeletionService;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Closure;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
class EditServer extends EditRecord
|
||||
{
|
||||
@ -26,10 +32,10 @@ class EditServer extends EditRecord
|
||||
{
|
||||
return $form
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 4,
|
||||
'lg' => 6,
|
||||
'md' => 2,
|
||||
'lg' => 4,
|
||||
])
|
||||
->schema([
|
||||
Forms\Components\ToggleButtons::make('docker')
|
||||
@ -58,13 +64,12 @@ class EditServer extends EditRecord
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
'lg' => 2,
|
||||
]),
|
||||
|
||||
Forms\Components\ToggleButtons::make('status')
|
||||
->label('Server State')->inline()->inlineLabel()
|
||||
->helperText('')
|
||||
|
||||
->formatStateUsing(fn ($state) => $state ?? ServerState::Normal)
|
||||
->options(fn ($state) => collect(ServerState::cases())->filter(fn ($serverState) => $serverState->value === $state)->mapWithKeys(
|
||||
fn (ServerState $state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()]
|
||||
@ -79,13 +84,22 @@ class EditServer extends EditRecord
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
'lg' => 2,
|
||||
]),
|
||||
|
||||
Forms\Components\TextInput::make('external_id')
|
||||
->maxLength(191)
|
||||
->hidden(),
|
||||
|
||||
Tabs::make('Tabs')
|
||||
->persistTabInQueryString()
|
||||
->columnSpan(6)
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 2,
|
||||
'md' => 4,
|
||||
'lg' => 6,
|
||||
])
|
||||
->tabs([
|
||||
Tabs\Tab::make('Information')
|
||||
->icon('tabler-info-circle')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->prefixIcon('tabler-server')
|
||||
->label('Display Name')
|
||||
@ -101,7 +115,7 @@ class EditServer extends EditRecord
|
||||
}))
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 4,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
@ -113,7 +127,7 @@ class EditServer extends EditRecord
|
||||
->label('Owner')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 4,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
@ -123,138 +137,50 @@ class EditServer extends EditRecord
|
||||
->required(),
|
||||
|
||||
Forms\Components\Textarea::make('description')
|
||||
->hidden()
|
||||
->required()
|
||||
->label('Description')
|
||||
->columnSpanFull(),
|
||||
|
||||
Forms\Components\Select::make('egg_id')
|
||||
->disabledOn('edit')
|
||||
->prefixIcon('tabler-egg')
|
||||
Forms\Components\TextInput::make('uuid')
|
||||
->hintAction(CopyAction::make())
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 5,
|
||||
'lg' => 3,
|
||||
])
|
||||
->relationship('egg', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
|
||||
Forms\Components\ToggleButtons::make('skip_scripts')
|
||||
->label('Run Egg Install Script?')->inline()
|
||||
->options([
|
||||
false => 'Yes',
|
||||
true => 'Skip',
|
||||
])
|
||||
->colors([
|
||||
false => 'primary',
|
||||
true => 'danger',
|
||||
])
|
||||
->icons([
|
||||
false => 'tabler-code',
|
||||
true => 'tabler-code-off',
|
||||
])
|
||||
->required(),
|
||||
|
||||
Forms\Components\Textarea::make('startup')
|
||||
->hintIcon('tabler-code')
|
||||
->label('Startup Command')
|
||||
->required()
|
||||
->live()
|
||||
->readOnly(),
|
||||
Forms\Components\TextInput::make('uuid_short')
|
||||
->label('Short UUID')
|
||||
->hintAction(CopyAction::make())
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 4,
|
||||
'md' => 4,
|
||||
'lg' => 6,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
->rows(function ($state) {
|
||||
return str($state)->explode("\n")->reduce(
|
||||
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
|
||||
0
|
||||
);
|
||||
}),
|
||||
|
||||
Forms\Components\Hidden::make('start_on_completion'),
|
||||
|
||||
Forms\Components\Section::make('Egg Variables')
|
||||
->icon('tabler-eggs')
|
||||
->iconColor('primary')
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->columnSpan(([
|
||||
->readOnly(),
|
||||
Forms\Components\TextInput::make('external_id')
|
||||
->label('External ID')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 4,
|
||||
'md' => 4,
|
||||
'lg' => 6,
|
||||
]))
|
||||
->schema([
|
||||
Forms\Components\Repeater::make('server_variables')
|
||||
->relationship('serverVariables')
|
||||
->grid()
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array &$data): array {
|
||||
foreach ($data as $key => $value) {
|
||||
if (!isset($data['variable_value'])) {
|
||||
$data['variable_value'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
})
|
||||
->reorderable(false)->addable(false)->deletable(false)
|
||||
->schema(function () {
|
||||
|
||||
$text = Forms\Components\TextInput::make('variable_value')
|
||||
->hidden($this->shouldHideComponent(...))
|
||||
->maxLength(191)
|
||||
->rules([
|
||||
fn (ServerVariable $serverVariable): Closure => function (string $attribute, $value, Closure $fail) use ($serverVariable) {
|
||||
$validator = Validator::make(['validatorkey' => $value], [
|
||||
'validatorkey' => $serverVariable->variable->rules,
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$message = str($validator->errors()->first())->replace('validatorkey', $serverVariable->variable->name);
|
||||
|
||||
$fail($message);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$select = Forms\Components\Select::make('variable_value')
|
||||
->hidden($this->shouldHideComponent(...))
|
||||
->options($this->getSelectOptionsFromRules(...))
|
||||
->selectablePlaceholder(false);
|
||||
|
||||
$components = [$text, $select];
|
||||
|
||||
/** @var Forms\Components\Component $component */
|
||||
foreach ($components as &$component) {
|
||||
$component = $component
|
||||
->live(onBlur: true)
|
||||
->hintIcon('tabler-code')
|
||||
->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name)
|
||||
->hintIconTooltip(fn (ServerVariable $serverVariable) => $serverVariable->variable->rules)
|
||||
->prefix(fn (ServerVariable $serverVariable) => '{{' . $serverVariable->variable->env_variable . '}}')
|
||||
->helperText(fn (ServerVariable $serverVariable) => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description);
|
||||
}
|
||||
|
||||
return $components;
|
||||
})
|
||||
->columnSpan(2),
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
->maxLength(191),
|
||||
Forms\Components\Select::make('node_id')
|
||||
->label('Node')
|
||||
->relationship('node', 'name')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
->disabled(),
|
||||
]),
|
||||
|
||||
Forms\Components\Section::make('Environment Management')
|
||||
->collapsed()
|
||||
->icon('tabler-server-cog')
|
||||
->iconColor('primary')
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 4,
|
||||
'md' => 4,
|
||||
'lg' => 4,
|
||||
])
|
||||
->columnSpanFull()
|
||||
Tabs\Tab::make('Environment')
|
||||
->icon('tabler-brand-docker')
|
||||
->schema([
|
||||
Forms\Components\Fieldset::make('Resource Limits')
|
||||
->columnSpan([
|
||||
@ -509,7 +435,7 @@ class EditServer extends EditRecord
|
||||
}
|
||||
})
|
||||
->placeholder('Enter a custom Image')
|
||||
->columnSpan(1),
|
||||
->columnSpan(2),
|
||||
|
||||
Forms\Components\KeyValue::make('docker_labels')
|
||||
->label('Container Labels')
|
||||
@ -518,7 +444,274 @@ class EditServer extends EditRecord
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]),
|
||||
Tabs\Tab::make('Egg')
|
||||
->icon('tabler-egg')
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 5,
|
||||
])
|
||||
->schema([
|
||||
Forms\Components\Select::make('egg_id')
|
||||
->disabledOn('edit')
|
||||
->prefixIcon('tabler-egg')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 5,
|
||||
])
|
||||
->relationship('egg', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
|
||||
Forms\Components\ToggleButtons::make('skip_scripts')
|
||||
->label('Run Egg Install Script?')->inline()
|
||||
->options([
|
||||
false => 'Yes',
|
||||
true => 'Skip',
|
||||
])
|
||||
->colors([
|
||||
false => 'primary',
|
||||
true => 'danger',
|
||||
])
|
||||
->icons([
|
||||
false => 'tabler-code',
|
||||
true => 'tabler-code-off',
|
||||
])
|
||||
->required(),
|
||||
|
||||
Forms\Components\Textarea::make('startup')
|
||||
->label('Startup Command')
|
||||
->required()
|
||||
->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\Textarea::make('defaultStartup')
|
||||
->hintAction(CopyAction::make())
|
||||
->label('Default Startup Command')
|
||||
->disabled()
|
||||
->formatStateUsing(function ($state, Forms\Get $get, Forms\Set $set) {
|
||||
$egg = Egg::query()->find($get('egg_id'));
|
||||
|
||||
return $egg->startup;
|
||||
})
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 4,
|
||||
'md' => 4,
|
||||
'lg' => 6,
|
||||
]),
|
||||
|
||||
Forms\Components\Repeater::make('server_variables')
|
||||
->relationship('serverVariables')
|
||||
->grid()
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array &$data): array {
|
||||
foreach ($data as $key => $value) {
|
||||
if (!isset($data['variable_value'])) {
|
||||
$data['variable_value'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
})
|
||||
->reorderable(false)->addable(false)->deletable(false)
|
||||
->schema(function () {
|
||||
|
||||
$text = Forms\Components\TextInput::make('variable_value')
|
||||
->hidden($this->shouldHideComponent(...))
|
||||
->rules([
|
||||
fn (ServerVariable $serverVariable): Closure => function (string $attribute, $value, Closure $fail) use ($serverVariable) {
|
||||
$validator = Validator::make(['validatorkey' => $value], [
|
||||
'validatorkey' => $serverVariable->variable->rules,
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$message = str($validator->errors()->first())->replace('validatorkey', $serverVariable->variable->name);
|
||||
|
||||
$fail($message);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
$select = Forms\Components\Select::make('variable_value')
|
||||
->hidden($this->shouldHideComponent(...))
|
||||
->options($this->getSelectOptionsFromRules(...))
|
||||
->selectablePlaceholder(false);
|
||||
|
||||
$components = [$text, $select];
|
||||
|
||||
/** @var Forms\Components\Component $component */
|
||||
foreach ($components as &$component) {
|
||||
$component = $component
|
||||
->live(onBlur: true)
|
||||
->hintIcon('tabler-code')
|
||||
->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name)
|
||||
->hintIconTooltip(fn (ServerVariable $serverVariable) => $serverVariable->variable->rules)
|
||||
->prefix(fn (ServerVariable $serverVariable) => '{{' . $serverVariable->variable->env_variable . '}}')
|
||||
->helperText(fn (ServerVariable $serverVariable) => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description);
|
||||
}
|
||||
|
||||
return $components;
|
||||
})
|
||||
->columnSpan(6),
|
||||
]),
|
||||
Tabs\Tab::make('Mounts')
|
||||
->icon('tabler-layers-linked')
|
||||
->schema([
|
||||
Forms\Components\CheckboxList::make('mounts')
|
||||
->relationship('mounts')
|
||||
->options(fn (Server $server) => $server->node->mounts->mapWithKeys(fn ($mount) => [$mount->id => $mount->name]))
|
||||
->descriptions(fn (Server $server) => $server->node->mounts->mapWithKeys(fn ($mount) => [$mount->id => "$mount->source -> $mount->target"]))
|
||||
->label('Mounts')
|
||||
->helperText(fn (Server $server) => $server->node->mounts->isNotEmpty() ? '' : 'No Mounts exist for this Node')
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Tabs\Tab::make('Databases')
|
||||
->icon('tabler-database')
|
||||
->schema([
|
||||
Forms\Components\Placeholder::make('soon')
|
||||
->label('Soon™'),
|
||||
]),
|
||||
Tabs\Tab::make('Actions')
|
||||
->icon('tabler-settings')
|
||||
->schema([
|
||||
Forms\Components\Fieldset::make('Server Actions')
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'lg' => 6,
|
||||
])
|
||||
->schema([
|
||||
Forms\Components\Grid::make()
|
||||
->columnSpan(3)
|
||||
->schema([
|
||||
Forms\Components\Actions::make([
|
||||
Forms\Components\Actions\Action::make('toggleInstall')
|
||||
->label('Toggle Install Status')
|
||||
->disabled(fn (Server $server) => $server->isSuspended())
|
||||
->action(function (ServersController $serversController, Server $server) {
|
||||
$serversController->toggleInstall($server);
|
||||
|
||||
return $this->refreshFormData(['status', 'docker']);
|
||||
}),
|
||||
])->fullWidth(),
|
||||
Forms\Components\ToggleButtons::make('')
|
||||
->hint('If you need to change the install status from uninstalled to installed, or vice versa, you may do so with this button.'),
|
||||
]),
|
||||
Forms\Components\Grid::make()
|
||||
->columnSpan(3)
|
||||
->schema([
|
||||
Forms\Components\Actions::make([
|
||||
Forms\Components\Actions\Action::make('toggleSuspend')
|
||||
->label('Suspend')
|
||||
->color('warning')
|
||||
->hidden(fn (Server $server) => $server->isSuspended())
|
||||
->action(function (SuspensionService $suspensionService, Server $server) {
|
||||
$suspensionService->toggle($server, 'suspend');
|
||||
Notification::make()->success()->title('Server Suspended!')->send();
|
||||
|
||||
return $this->refreshFormData(['status', 'docker']);
|
||||
}),
|
||||
Forms\Components\Actions\Action::make('toggleUnsuspend')
|
||||
->label('Unsuspend')
|
||||
->color('success')
|
||||
->hidden(fn (Server $server) => !$server->isSuspended())
|
||||
->action(function (SuspensionService $suspensionService, Server $server) {
|
||||
$suspensionService->toggle($server, 'unsuspend');
|
||||
Notification::make()->success()->title('Server Unsuspended!')->send();
|
||||
|
||||
return $this->refreshFormData(['status', 'docker']);
|
||||
}),
|
||||
])->fullWidth(),
|
||||
Forms\Components\ToggleButtons::make('')
|
||||
->hidden(fn (Server $server) => $server->isSuspended())
|
||||
->hint('This will suspend the server, stop any running processes, and immediately block the user from being able to access their files or otherwise manage the server through the panel or API.'),
|
||||
Forms\Components\ToggleButtons::make('')
|
||||
->hidden(fn (Server $server) => !$server->isSuspended())
|
||||
->hint('This will unsuspend the server and restore normal user access.'),
|
||||
]),
|
||||
Forms\Components\Grid::make()
|
||||
->columnSpan(3)
|
||||
->schema([
|
||||
Forms\Components\Actions::make([
|
||||
Forms\Components\Actions\Action::make('transfer')
|
||||
->label('Transfer Soon™')
|
||||
->action(fn (TransferServerService $transfer, Server $server) => $transfer->handle($server, $data))
|
||||
->disabled() //TODO!
|
||||
->form([ //TODO!
|
||||
Forms\Components\Select::make('newNode')
|
||||
->label('New Node')
|
||||
->required()
|
||||
->options([
|
||||
true => 'on',
|
||||
false => 'off',
|
||||
]),
|
||||
Forms\Components\Select::make('newMainAllocation')
|
||||
->label('New Main Allocation')
|
||||
->required()
|
||||
->options([
|
||||
true => 'on',
|
||||
false => 'off',
|
||||
]),
|
||||
Forms\Components\Select::make('newAdditionalAllocation')
|
||||
->label('New Additional Allocations')
|
||||
->options([
|
||||
true => 'on',
|
||||
false => 'off',
|
||||
]),
|
||||
])
|
||||
->modalHeading('Transfer'),
|
||||
])->fullWidth(),
|
||||
Forms\Components\ToggleButtons::make('')
|
||||
->hint('Transfer this server to another node connected to this panel. Warning! This feature has not been fully tested and may have bugs.'),
|
||||
]),
|
||||
Forms\Components\Grid::make()
|
||||
->columnSpan(3)
|
||||
->schema([
|
||||
Forms\Components\Actions::make([
|
||||
Forms\Components\Actions\Action::make('reinstall')
|
||||
->label('Reinstall')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Are you sure you want to reinstall this server?')
|
||||
->modalDescription('!! This can result in unrecoverable data loss !!')
|
||||
->disabled(fn (Server $server) => $server->isSuspended())
|
||||
->action(fn (ServersController $serversController, Server $server) => $serversController->reinstallServer($server)),
|
||||
])->fullWidth(),
|
||||
Forms\Components\ToggleButtons::make('')
|
||||
->hint('This will reinstall the server with the assigned egg install script.'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function transferServer(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('toNode')
|
||||
->label('New Node'),
|
||||
Forms\Components\TextInput::make('newAllocation')
|
||||
->label('Allocation'),
|
||||
]);
|
||||
|
||||
}
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
@ -543,6 +736,10 @@ class EditServer extends EditRecord
|
||||
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
if (!isset($data['description'])) {
|
||||
$data['description'] = '';
|
||||
}
|
||||
|
||||
unset($data['docker'], $data['status']);
|
||||
|
||||
return $data;
|
||||
|
@ -31,6 +31,9 @@ use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
/**
|
||||
* @method User getUser()
|
||||
*/
|
||||
class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
{
|
||||
protected function getForms(): array
|
||||
@ -117,6 +120,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
->helperText('Enter your current 2FA code to disable Two Factor Authentication'),
|
||||
];
|
||||
}
|
||||
/** @var TwoFactorSetupService */
|
||||
$setupService = app(TwoFactorSetupService::class);
|
||||
|
||||
['image_url_data' => $url, 'secret' => $secret] = cache()->remember(
|
||||
@ -126,6 +130,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
|
||||
$options = new QROptions([
|
||||
'svgLogo' => public_path('pelican.svg'),
|
||||
'svgLogoScale' => 0.05,
|
||||
'addLogoSpace' => true,
|
||||
'logoSpaceWidth' => 13,
|
||||
'logoSpaceHeight' => 13,
|
||||
@ -133,22 +138,24 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
|
||||
// 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
|
||||
// @phpstan-ignore property.protected
|
||||
$options->version = Version::AUTO;
|
||||
// $options->outputInterface = QRSvgWithLogo::class;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->outputBase64 = false;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->eccLevel = EccLevel::H; // ECC level H is necessary when using logos
|
||||
// @phpstan-ignore property.protected
|
||||
$options->addQuietzone = true;
|
||||
// $options->drawLightModules = true;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->connectPaths = true;
|
||||
// @phpstan-ignore property.protected
|
||||
$options->drawCircularModules = true;
|
||||
// $options->circleRadius = 0.45;
|
||||
|
||||
// @phpstan-ignore property.protected
|
||||
$options->svgDefs = '<linearGradient id="gradient" x1="100%" y2="100%">
|
||||
<stop stop-color="#7dd4fc" offset="0"/>
|
||||
<stop stop-color="#38bdf8" offset="0.5"/>
|
||||
@ -196,8 +203,8 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
])->headerActions([
|
||||
Action::make('Create')
|
||||
->successRedirectUrl(route('filament.admin.auth.profile', ['tab' => '-api-keys-tab']))
|
||||
->action(function (Get $get, Action $action) {
|
||||
$token = auth()->user()->createToken(
|
||||
->action(function (Get $get, Action $action, $user) {
|
||||
$token = $user->createToken(
|
||||
$get('description'),
|
||||
$get('allowed_ips'),
|
||||
);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use App\Services\Exceptions\FilamentExceptionHandler;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use App\Models\User;
|
||||
@ -66,7 +67,9 @@ class EditUser extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->label(fn (User $user) => auth()->user()->id === $user->id ? 'Can\'t Delete Yourself' : ($user->servers()->count() > 0 ? 'User Has Servers' : 'Delete'))
|
||||
->disabled(fn (User $user) => auth()->user()->id === $user->id || $user->servers()->count() > 0),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
@ -75,4 +78,9 @@ class EditUser extends EditRecord
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function exception($exception, $stopPropagation): void
|
||||
{
|
||||
(new FilamentExceptionHandler())->handle($exception, $stopPropagation);
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class EggShareController extends Controller
|
||||
*/
|
||||
public function import(EggImportFormRequest $request): RedirectResponse
|
||||
{
|
||||
$egg = $this->importerService->handle($request->file('import_file'));
|
||||
$egg = $this->importerService->fromFile($request->file('import_file'));
|
||||
$this->alert->success(trans('admin/eggs.notices.imported'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', ['egg' => $egg->id]);
|
||||
@ -61,7 +61,7 @@ class EggShareController extends Controller
|
||||
*/
|
||||
public function update(EggImportFormRequest $request, Egg $egg): RedirectResponse
|
||||
{
|
||||
$this->updateImporterService->handle($egg, $request->file('import_file'));
|
||||
$this->updateImporterService->fromFile($egg, $request->file('import_file'));
|
||||
$this->alert->success(trans('admin/eggs.notices.updated_via_import'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', ['egg' => $egg]);
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Admin\Nodes;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Node;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -13,7 +12,7 @@ class NodeController extends Controller
|
||||
/**
|
||||
* Returns a listing of nodes on the system.
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
public function index(): View
|
||||
{
|
||||
$nodes = QueryBuilder::for(
|
||||
Node::query()->withCount('servers')
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Admin\Nodes;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Models\Allocation;
|
||||
@ -29,16 +28,10 @@ class NodeViewController extends Controller
|
||||
/**
|
||||
* Returns index view for a specific node on the system.
|
||||
*/
|
||||
public function index(Request $request, Node $node): View
|
||||
public function index(Node $node): View
|
||||
{
|
||||
$node->loadCount('servers');
|
||||
|
||||
$stats = Node::query()
|
||||
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
|
||||
->join('servers', 'servers.node_id', '=', 'nodes.id')
|
||||
->where('node_id', '=', $node->id)
|
||||
->first();
|
||||
|
||||
return view('admin.nodes.view.index', [
|
||||
'node' => $node,
|
||||
'version' => $this->versionService,
|
||||
@ -48,7 +41,7 @@ class NodeViewController extends Controller
|
||||
/**
|
||||
* Returns the settings page for a specific node.
|
||||
*/
|
||||
public function settings(Request $request, Node $node): View
|
||||
public function settings(Node $node): View
|
||||
{
|
||||
return view('admin.nodes.view.settings', [
|
||||
'node' => $node,
|
||||
@ -58,7 +51,7 @@ class NodeViewController extends Controller
|
||||
/**
|
||||
* Return the node configuration page for a specific node.
|
||||
*/
|
||||
public function configuration(Request $request, Node $node): View
|
||||
public function configuration(Node $node): View
|
||||
{
|
||||
return view('admin.nodes.view.configuration', compact('node'));
|
||||
}
|
||||
@ -66,7 +59,7 @@ class NodeViewController extends Controller
|
||||
/**
|
||||
* Return the node allocation management page.
|
||||
*/
|
||||
public function allocations(Request $request, Node $node): View
|
||||
public function allocations(Node $node): View
|
||||
{
|
||||
$node->setRelation(
|
||||
'allocations',
|
||||
@ -92,7 +85,7 @@ class NodeViewController extends Controller
|
||||
/**
|
||||
* Return a listing of servers that exist for this specific node.
|
||||
*/
|
||||
public function servers(Request $request, Node $node): View
|
||||
public function servers(Node $node): View
|
||||
{
|
||||
$this->plainInject([
|
||||
'node' => Collection::wrap($node->makeVisible(['daemon_token_id', 'daemon_token']))
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers\Admin\Servers;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Server;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Spatie\QueryBuilder\AllowedFilter;
|
||||
@ -16,7 +15,7 @@ class ServerController extends Controller
|
||||
* Returns all the servers that exist on the system using a paginated result set. If
|
||||
* a query is passed along in the request it is also passed to the repository function.
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
public function index(): View
|
||||
{
|
||||
$servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation'))
|
||||
->allowedFilters([
|
||||
|
@ -3,13 +3,13 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Server;
|
||||
use App\Models\Database;
|
||||
use App\Models\MountServer;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use App\Exceptions\DisplayException;
|
||||
@ -70,7 +70,7 @@ class ServersController extends Controller
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function toggleInstall(Server $server): RedirectResponse
|
||||
public function toggleInstall(Server $server)
|
||||
{
|
||||
if ($server->status === ServerState::InstallFailed) {
|
||||
throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
|
||||
@ -79,9 +79,13 @@ class ServersController extends Controller
|
||||
$server->status = $server->isInstalled() ? ServerState::Installing : null;
|
||||
$server->save();
|
||||
|
||||
$this->alert->success(trans('admin/server.alerts.install_toggled'))->flash();
|
||||
Notification::make()
|
||||
->title('Success!')
|
||||
->body(trans('admin/server.alerts.install_toggled'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,12 +94,15 @@ class ServersController extends Controller
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function reinstallServer(Server $server): RedirectResponse
|
||||
public function reinstallServer(Server $server)
|
||||
{
|
||||
$this->reinstallService->handle($server);
|
||||
$this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
||||
Notification::make()
|
||||
->title('Success!')
|
||||
->body(trans('admin/server.alerts.server_reinstalled'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -228,12 +235,7 @@ class ServersController extends Controller
|
||||
*/
|
||||
public function addMount(Request $request, Server $server): RedirectResponse
|
||||
{
|
||||
$mountServer = (new MountServer())->forceFill([
|
||||
'mount_id' => $request->input('mount_id'),
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
|
||||
$mountServer->saveOrFail();
|
||||
$server->mounts()->attach($request->input('mount_id'));
|
||||
|
||||
$this->alert->success('Mount was added successfully.')->flash();
|
||||
|
||||
@ -245,7 +247,7 @@ class ServersController extends Controller
|
||||
*/
|
||||
public function deleteMount(Server $server, Mount $mount): RedirectResponse
|
||||
{
|
||||
MountServer::where('mount_id', $mount->id)->where('server_id', $server->id)->delete();
|
||||
$server->mounts()->detach($mount);
|
||||
|
||||
$this->alert->success('Mount was removed successfully.')->flash();
|
||||
|
||||
|
@ -37,7 +37,7 @@ class UserController extends Controller
|
||||
/**
|
||||
* Display user index page.
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
public function index(): View
|
||||
{
|
||||
$users = QueryBuilder::for(
|
||||
User::query()->select('users.*')
|
||||
|
@ -33,10 +33,9 @@ class StoreNodeRequest extends ApplicationApiRequest
|
||||
'upload_size',
|
||||
'daemon_listen',
|
||||
'daemon_sftp',
|
||||
'daemon_sftp_alias',
|
||||
'daemon_base',
|
||||
])->mapWithKeys(function ($value, $key) {
|
||||
$key = ($key === 'daemon_sftp') ? 'daemon_sftp' : $key;
|
||||
|
||||
return [snake_case($key) => $value];
|
||||
})->toArray();
|
||||
}
|
||||
@ -60,12 +59,8 @@ class StoreNodeRequest extends ApplicationApiRequest
|
||||
public function validated($key = null, $default = null): array
|
||||
{
|
||||
$response = parent::validated();
|
||||
$response['daemon_listen'] = $response['daemon_listen'];
|
||||
$response['daemon_sftp'] = $response['daemon_sftp'];
|
||||
$response['daemon_base'] = $response['daemon_base'] ?? (new Node())->getAttribute('daemon_base');
|
||||
|
||||
unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MountServer extends Model
|
||||
{
|
||||
protected $table = 'mount_server';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $primaryKey = null;
|
||||
|
||||
public $incrementing = false;
|
||||
}
|
@ -44,7 +44,7 @@ class Node extends Model
|
||||
'memory', 'memory_overallocate', 'disk',
|
||||
'disk_overallocate', 'cpu', 'cpu_overallocate',
|
||||
'upload_size', 'daemon_base',
|
||||
'daemon_sftp', 'daemon_listen',
|
||||
'daemon_sftp', 'daemon_sftp_alias', 'daemon_listen',
|
||||
'description', 'maintenance_mode',
|
||||
];
|
||||
|
||||
@ -63,6 +63,7 @@ class Node extends Model
|
||||
'cpu_overallocate' => 'required|numeric|min:-1',
|
||||
'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/',
|
||||
'daemon_sftp' => 'required|numeric|between:1,65535',
|
||||
'daemon_sftp_alias' => 'nullable|string',
|
||||
'daemon_listen' => 'required|numeric|between:1,65535',
|
||||
'maintenance_mode' => 'boolean',
|
||||
'upload_size' => 'int|between:1,1024',
|
||||
@ -103,6 +104,10 @@ class Node extends Model
|
||||
];
|
||||
}
|
||||
|
||||
public int $servers_sum_memory = 0;
|
||||
public int $servers_sum_disk = 0;
|
||||
public int $servers_sum_cpu = 0;
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'id';
|
||||
|
@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Enums\ServerState;
|
||||
use App\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
@ -13,7 +14,6 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use App\Exceptions\Http\Server\ServerStateConflictException;
|
||||
|
||||
class Server extends Model
|
||||
@ -224,12 +224,9 @@ class Server extends Model
|
||||
return $this->hasMany(Backup::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all mounts that have this server has mounted.
|
||||
*/
|
||||
public function mounts(): HasManyThrough
|
||||
public function mounts(): BelongsToMany
|
||||
{
|
||||
return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id');
|
||||
return $this->belongsToMany(Mount::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@ use App\Extensions\Themes\Theme;
|
||||
use App\Models;
|
||||
use App\Models\ApiKey;
|
||||
use App\Models\Node;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Dedoc\Scramble\Scramble;
|
||||
use Dedoc\Scramble\Support\Generator\OpenApi;
|
||||
use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
||||
@ -30,8 +31,9 @@ class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
View::share('appVersion', $this->versionData()['version'] ?? 'undefined');
|
||||
View::share('appIsGit', $this->versionData()['is_git'] ?? false);
|
||||
$versionData = app(SoftwareVersionService::class)->versionData();
|
||||
View::share('appVersion', $versionData['version'] ?? 'undefined');
|
||||
View::share('appIsGit', $versionData['is_git'] ?? false);
|
||||
|
||||
Paginator::useBootstrap();
|
||||
|
||||
@ -96,34 +98,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
Scramble::ignoreDefaultRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return version information for the footer.
|
||||
*/
|
||||
protected function versionData(): array
|
||||
{
|
||||
return cache()->remember('git-version', 5, function () {
|
||||
if (file_exists(base_path('.git/HEAD'))) {
|
||||
$head = explode(' ', file_get_contents(base_path('.git/HEAD')));
|
||||
|
||||
if (array_key_exists(1, $head)) {
|
||||
$path = base_path('.git/' . trim($head[1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($path) && file_exists($path)) {
|
||||
return [
|
||||
'version' => substr(file_get_contents($path), 0, 8),
|
||||
'is_git' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'version' => config('app.version'),
|
||||
'is_git' => false,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function bootAuth(): void
|
||||
{
|
||||
Sanctum::usePersonalAccessTokenModel(ApiKey::class);
|
||||
|
@ -124,38 +124,6 @@ class EggConfigurationService
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the legacy modifies from eggs with their new counterpart. The legacy Daemon would
|
||||
* set SERVER_MEMORY, SERVER_IP, and SERVER_PORT with their respective values on the Daemon
|
||||
* side. Ensure that anything referencing those properly replaces them with the matching config
|
||||
* value.
|
||||
*/
|
||||
protected function replaceLegacyModifiers(string $key, string $value): string
|
||||
{
|
||||
switch ($key) {
|
||||
case 'config.docker.interface':
|
||||
$replace = 'config.docker.network.interface';
|
||||
break;
|
||||
case 'server.build.env.SERVER_MEMORY':
|
||||
case 'env.SERVER_MEMORY':
|
||||
$replace = 'server.build.memory';
|
||||
break;
|
||||
case 'server.build.env.SERVER_IP':
|
||||
case 'env.SERVER_IP':
|
||||
$replace = 'server.build.default.ip';
|
||||
break;
|
||||
case 'server.build.env.SERVER_PORT':
|
||||
case 'env.SERVER_PORT':
|
||||
$replace = 'server.build.default.port';
|
||||
break;
|
||||
default:
|
||||
// By default, we don't need to change anything, only if we ended up matching a specific legacy item.
|
||||
$replace = $key;
|
||||
}
|
||||
|
||||
return str_replace("{{{$key}}}", "{{{$replace}}}", $value);
|
||||
}
|
||||
|
||||
protected function matchAndReplaceKeys(mixed $value, array $structure): mixed
|
||||
{
|
||||
preg_match_all('/{{(?<key>[\w.-]*)}}/', $value, $matches);
|
||||
@ -175,8 +143,6 @@ class EggConfigurationService
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->replaceLegacyModifiers($key, $value);
|
||||
|
||||
// We don't want to do anything with config keys since the Daemon will need to handle
|
||||
// that. For example, the Spigot egg uses "config.docker.interface" to identify the Docker
|
||||
// interface to proxy through, but the Panel would be unaware of that.
|
||||
@ -198,7 +164,7 @@ class EggConfigurationService
|
||||
// variable from the server configuration.
|
||||
$plucked = Arr::get(
|
||||
$structure,
|
||||
preg_replace('/^env\./', 'build.env.', $key),
|
||||
preg_replace('/^env\./', 'build.environment.', $key),
|
||||
''
|
||||
);
|
||||
|
||||
|
@ -10,6 +10,17 @@ use App\Exceptions\Service\InvalidFileUploadException;
|
||||
|
||||
class EggParserService
|
||||
{
|
||||
public const UPGRADE_VARIABLES = [
|
||||
'server.build.env.SERVER_IP' => 'server.allocations.default.ip',
|
||||
'server.build.default.ip' => 'server.allocations.default.ip',
|
||||
'server.build.env.SERVER_PORT' => 'server.allocations.default.port',
|
||||
'server.build.default.port' => 'server.allocations.default.port',
|
||||
'server.build.env.SERVER_MEMORY' => 'server.build.memory_limit',
|
||||
'server.build.memory' => 'server.build.memory_limit',
|
||||
'server.build.env.' => 'server.environment.',
|
||||
'server.build.environment.' => 'server.environment.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Takes an uploaded file and parses out the egg configuration from within.
|
||||
*
|
||||
@ -26,11 +37,20 @@ class EggParserService
|
||||
|
||||
$version = $parsed['meta']['version'] ?? '';
|
||||
|
||||
return match ($version) {
|
||||
$parsed = 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.')
|
||||
};
|
||||
|
||||
// Make sure we only use recent variable format from now on
|
||||
$parsed['config']['files'] = str_replace(
|
||||
array_keys(self::UPGRADE_VARIABLES),
|
||||
array_values(self::UPGRADE_VARIABLES),
|
||||
$parsed['config']['files'] ?? '',
|
||||
);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,6 +9,7 @@ use Illuminate\Http\UploadedFile;
|
||||
use App\Models\EggVariable;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use App\Services\Eggs\EggParserService;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
|
||||
class EggImporterService
|
||||
{
|
||||
@ -21,7 +22,7 @@ class EggImporterService
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function handle(UploadedFile $file): Egg
|
||||
public function fromFile(UploadedFile $file): Egg
|
||||
{
|
||||
$parsed = $this->parser->handle($file);
|
||||
|
||||
@ -45,4 +46,20 @@ class EggImporterService
|
||||
return $egg;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an url and parse it into a new egg.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function fromUrl(string $url): Egg
|
||||
{
|
||||
$info = pathinfo($url);
|
||||
$tmpDir = TemporaryDirectory::make()->deleteWhenDestroyed();
|
||||
$tmpPath = $tmpDir->path($info['basename']);
|
||||
|
||||
file_put_contents($tmpPath, file_get_contents($url));
|
||||
|
||||
return $this->fromFile(new UploadedFile($tmpPath, $info['basename'], 'application/json'));
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use Illuminate\Support\Collection;
|
||||
use App\Models\EggVariable;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use App\Services\Eggs\EggParserService;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
|
||||
class EggUpdateImporterService
|
||||
{
|
||||
@ -23,7 +24,7 @@ class EggUpdateImporterService
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function handle(Egg $egg, UploadedFile $file): Egg
|
||||
public function fromFile(Egg $egg, UploadedFile $file): Egg
|
||||
{
|
||||
$parsed = $this->parser->handle($file);
|
||||
|
||||
@ -47,4 +48,20 @@ class EggUpdateImporterService
|
||||
return $egg->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing Egg using an url.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function fromUrl(Egg $egg, string $url): Egg
|
||||
{
|
||||
$info = pathinfo($url);
|
||||
$tmpDir = TemporaryDirectory::make()->deleteWhenDestroyed();
|
||||
$tmpPath = $tmpDir->path($info['basename']);
|
||||
|
||||
file_put_contents($tmpPath, file_get_contents($url));
|
||||
|
||||
return $this->fromFile($egg, new UploadedFile($tmpPath, $info['basename'], 'application/json'));
|
||||
}
|
||||
}
|
||||
|
24
app/Services/Exceptions/FilamentExceptionHandler.php
Normal file
24
app/Services/Exceptions/FilamentExceptionHandler.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class FilamentExceptionHandler
|
||||
{
|
||||
public function handle(Exception $exception, callable $stopPropagation): void
|
||||
{
|
||||
Notification::make()
|
||||
->title($exception->title ?? null)
|
||||
->body($exception->body ?? $exception->getMessage())
|
||||
->color($exception->color ?? 'danger')
|
||||
->icon($exception->icon ?? 'tabler-x')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
if ($this->stopPropagation ?? true) {
|
||||
$stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,6 +49,14 @@ class SoftwareVersionService
|
||||
return Arr::get(self::$result, 'discord') ?? 'https://pelican.dev/discord';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the donation URL.
|
||||
*/
|
||||
public function getDonations(): string
|
||||
{
|
||||
return Arr::get(self::$result, 'donate') ?? 'https://pelican.dev/donate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current version of the panel is the latest.
|
||||
*/
|
||||
@ -93,8 +101,28 @@ class SoftwareVersionService
|
||||
});
|
||||
}
|
||||
|
||||
public function getDonations(): string
|
||||
public function versionData(): array
|
||||
{
|
||||
return 'https://github.com';
|
||||
return cache()->remember('git-version', 5, function () {
|
||||
if (file_exists(base_path('.git/HEAD'))) {
|
||||
$head = explode(' ', file_get_contents(base_path('.git/HEAD')));
|
||||
|
||||
if (array_key_exists(1, $head)) {
|
||||
$path = base_path('.git/' . trim($head[1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($path) && file_exists($path)) {
|
||||
return [
|
||||
'version' => 'canary (' . substr(file_get_contents($path), 0, 8) . ')',
|
||||
'is_git' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'version' => config('app.version'),
|
||||
'is_git' => false,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ class NodeUpdateService
|
||||
*/
|
||||
public function handle(Node $node, array $data, bool $resetToken = false): Node
|
||||
{
|
||||
$data['id'] = $node->id;
|
||||
|
||||
if ($resetToken) {
|
||||
$data['daemon_token'] = Str::random(Node::DAEMON_TOKEN_LENGTH);
|
||||
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
|
||||
@ -35,15 +37,9 @@ class NodeUpdateService
|
||||
[$updated, $exception] = $this->connection->transaction(function () use ($data, $node) {
|
||||
/** @var \App\Models\Node $updated */
|
||||
$updated = $node->replicate();
|
||||
$updated->exists = true;
|
||||
$updated->forceFill($data)->save();
|
||||
try {
|
||||
// If we're changing the FQDN for the node, use the newly provided FQDN for the connection
|
||||
// address. This should alleviate issues where the node gets pointed to a "valid" FQDN that
|
||||
// isn't actually running the daemon software, and therefore you can't actually change it
|
||||
// back.
|
||||
//
|
||||
// This makes more sense anyways, because only the Panel uses the FQDN for connecting, the
|
||||
// node doesn't actually care about this.
|
||||
$node->fqdn = $updated->fqdn;
|
||||
|
||||
$this->configurationRepository->setNode($node)->update($updated);
|
||||
|
@ -40,7 +40,7 @@ class ServerConfigurationStructureService
|
||||
*/
|
||||
protected function returnFormat(Server $server): array
|
||||
{
|
||||
return [
|
||||
$response = [
|
||||
'uuid' => $server->uuid,
|
||||
'meta' => [
|
||||
'name' => $server->name,
|
||||
@ -50,7 +50,6 @@ class ServerConfigurationStructureService
|
||||
'environment' => $this->environment->handle($server),
|
||||
'invocation' => $server->startup,
|
||||
'skip_egg_scripts' => $server->skip_scripts,
|
||||
'labels' => $server->docker_labels,
|
||||
'build' => [
|
||||
'memory_limit' => $server->memory,
|
||||
'swap' => $server->swap,
|
||||
@ -72,17 +71,27 @@ class ServerConfigurationStructureService
|
||||
],
|
||||
'mappings' => $server->getAllocationMappings(),
|
||||
],
|
||||
'mounts' => $server->mounts->map(function (Mount $mount) {
|
||||
return [
|
||||
'source' => $mount->source,
|
||||
'target' => $mount->target,
|
||||
'read_only' => $mount->read_only,
|
||||
];
|
||||
}),
|
||||
'egg' => [
|
||||
'id' => $server->egg->uuid,
|
||||
'file_denylist' => $server->egg->inherit_file_denylist,
|
||||
],
|
||||
];
|
||||
|
||||
if (!empty($server->docker_labels)) {
|
||||
$response['labels'] = $server->docker_labels;
|
||||
}
|
||||
|
||||
if ($server->mounts->isNotEmpty()) {
|
||||
$response['mounts'] = $server->mounts->map(function (Mount $mount) {
|
||||
return [
|
||||
'source' => $mount->source,
|
||||
'target' => $mount->target,
|
||||
'read_only' => $mount->read_only,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Services\Servers;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use Filament\Notifications\Notification;
|
||||
use Webmozart\Assert\Assert;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
@ -26,7 +27,7 @@ class SuspensionService
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function toggle(Server $server, string $action = self::ACTION_SUSPEND): void
|
||||
public function toggle(Server $server, string $action = self::ACTION_SUSPEND)
|
||||
{
|
||||
Assert::oneOf($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND]);
|
||||
|
||||
@ -35,11 +36,12 @@ class SuspensionService
|
||||
// suspended in the database. Additionally, nothing needs to happen if the server
|
||||
// is not suspended, and we try to un-suspend the instance.
|
||||
if ($isSuspending === $server->isSuspended()) {
|
||||
return;
|
||||
return Notification::make()->danger()->title('Failed!')->body('Server is already suspended!')->send();
|
||||
}
|
||||
|
||||
// Check if the server is currently being transferred.
|
||||
if (!is_null($server->transfer)) {
|
||||
Notification::make()->danger()->title('Failed!')->body('Server is currently being transferred.')->send();
|
||||
throw new ConflictHttpException('Cannot toggle suspension status on a server that is currently being transferred.');
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ class TransferServerService
|
||||
{
|
||||
$node_id = $data['node_id'];
|
||||
$allocation_id = intval($data['allocation_id']);
|
||||
$additional_allocations = array_map('intval', $data['allocation_additional'] ?? []);
|
||||
$additional_allocations = array_map(intval(...), $data['allocation_additional'] ?? []);
|
||||
|
||||
// Check if the node is viable for the transfer.
|
||||
$node = Node::query()
|
||||
|
@ -46,6 +46,7 @@ class ServerTransformer extends BaseClientTransformer
|
||||
'is_node_under_maintenance' => $server->node->isUnderMaintenance(),
|
||||
'sftp_details' => [
|
||||
'ip' => $server->node->fqdn,
|
||||
'alias' => $server->node->daemon_sftp_alias,
|
||||
'port' => $server->node->daemon_sftp,
|
||||
],
|
||||
'description' => $server->description,
|
||||
|
@ -9,10 +9,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
\Prologue\Alerts\AlertsServiceProvider::class,
|
||||
])
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
// api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
// channels: __DIR__.'/../routes/channels.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
|
@ -33,6 +33,7 @@
|
||||
"s1lentium/iptools": "~1.2.0",
|
||||
"spatie/laravel-fractal": "^6.2",
|
||||
"spatie/laravel-query-builder": "^5.8.1",
|
||||
"spatie/temporary-directory": "^2.2",
|
||||
"symfony/http-client": "^7.1",
|
||||
"symfony/mailgun-mailer": "^7.1",
|
||||
"symfony/postmark-mailer": "^7.0.7",
|
||||
|
739
composer.lock
generated
739
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -75,10 +75,10 @@ class EggSeeder extends Seeder
|
||||
->first();
|
||||
|
||||
if ($egg instanceof Egg) {
|
||||
$this->updateImporterService->handle($egg, $file);
|
||||
$this->updateImporterService->fromFile($egg, $file);
|
||||
$this->command->info('Updated ' . $decoded['name']);
|
||||
} else {
|
||||
$this->importerService->handle($file);
|
||||
$this->importerService->fromFile($file);
|
||||
$this->command->comment('Created ' . $decoded['name']);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:47:32+02:00",
|
||||
"exported_at": "2024-06-04T22:51:49+00:00",
|
||||
"name": "Bungeecord",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "9e6b409e-4028-4947-aea8-50a2c404c271",
|
||||
"description": "For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community's full potential.",
|
||||
"features": [
|
||||
"eula",
|
||||
@ -23,7 +24,7 @@
|
||||
"file_denylist": [],
|
||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
||||
"config": {
|
||||
"files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"regex:^(127\\\\.0\\\\.0\\\\.1|localhost)(:\\\\d{1,5})?$\": \"{{config.docker.interface}}$2\"\r\n }\r\n }\r\n }\r\n}",
|
||||
"files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_port\": \"{{server.allocations.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.allocations.default.port}}\",\r\n \"servers.*.address\": {\r\n \"regex:^(127\\\\.0\\\\.0\\\\.1|localhost)(:\\\\d{1,5})?$\": \"{{config.docker.interface}}$2\"\r\n }\r\n }\r\n }\r\n}",
|
||||
"startup": "{\r\n \"done\": \"Listening on \"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "end"
|
||||
@ -44,6 +45,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|alpha_num|between:1,6",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -54,6 +56,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:47:58+02:00",
|
||||
"exported_at": "2024-06-04T22:51:58+00:00",
|
||||
"name": "Forge Minecraft",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "ed072427-f209-4603-875c-f540c6dd5a65",
|
||||
"description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.",
|
||||
"features": [
|
||||
"eula",
|
||||
@ -23,7 +24,7 @@
|
||||
"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\" )",
|
||||
"config": {
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.allocations.default.port}}\",\r\n \"query.port\": \"{{server.allocations.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"startup": "{\r\n \"done\": \")! For help, type \"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "stop"
|
||||
@ -44,6 +45,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -54,6 +56,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:9",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -64,6 +67,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|in:recommended,latest",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -74,6 +78,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|regex:\/^[0-9\\.\\-]+$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:48:34+02:00",
|
||||
"exported_at": "2024-06-04T22:51:57+00:00",
|
||||
"name": "Paper",
|
||||
"author": "parker@example.com",
|
||||
"uuid": "5da37ef6-58da-4169-90a6-e683e1721247",
|
||||
"description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.",
|
||||
"features": [
|
||||
"eula",
|
||||
@ -23,7 +24,7 @@
|
||||
"file_denylist": [],
|
||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",
|
||||
"config": {
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.allocations.default.port}}\",\r\n \"query.port\": \"{{server.allocations.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"startup": "{\r\n \"done\": \")! For help, type \"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "stop"
|
||||
@ -44,6 +45,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string|max:20",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -54,6 +56,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -64,6 +67,7 @@
|
||||
"user_viewable": false,
|
||||
"user_editable": false,
|
||||
"rules": "nullable|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -74,6 +78,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:20",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:49:03+02:00",
|
||||
"exported_at": "2024-06-04T22:50:55+00:00",
|
||||
"name": "Sponge (SpongeVanilla)",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "f0d2f88f-1ff3-42a0-b03f-ac44c5571e6d",
|
||||
"description": "SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.",
|
||||
"features": [
|
||||
"eula",
|
||||
@ -23,7 +24,7 @@
|
||||
"file_denylist": [],
|
||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
||||
"config": {
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.allocations.default.port}}\",\r\n \"query.port\": \"{{server.allocations.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"startup": "{\r\n \"done\": \")! For help, type \"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "stop"
|
||||
@ -44,6 +45,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -54,6 +56,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:49:32+02:00",
|
||||
"exported_at": "2024-06-04T22:51:16+00:00",
|
||||
"name": "Vanilla Minecraft",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "9ac39f3d-0c34-4d93-8174-c52ab9e6c57b",
|
||||
"description": "Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.",
|
||||
"features": [
|
||||
"eula",
|
||||
@ -23,7 +24,7 @@
|
||||
"file_denylist": [],
|
||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
||||
"config": {
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.allocations.default.port}}\",\r\n \"query.port\": \"{{server.allocations.default.port}}\"\r\n }\r\n }\r\n}",
|
||||
"startup": "{\r\n \"done\": \")! For help, type \"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "stop"
|
||||
@ -44,6 +45,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -54,6 +56,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|between:3,15",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:57:56+02:00",
|
||||
"exported_at": "2024-06-02T20:42:09+00:00",
|
||||
"name": "Rust",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "bace2dfb-209c-452a-9459-7d6f340b07ae",
|
||||
"description": "The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.",
|
||||
"features": [
|
||||
"steam_disk_space"
|
||||
@ -38,6 +39,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:60",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -48,6 +50,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|in:vanilla,oxide,carbon",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -58,6 +61,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:20",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -68,6 +72,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -78,6 +83,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -88,6 +94,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -98,6 +105,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -108,6 +116,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -118,6 +127,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -128,6 +138,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -138,6 +149,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -148,6 +160,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^[\\w.-]*$\/|max:64",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -158,6 +171,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -168,6 +182,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -178,6 +193,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -188,6 +204,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -198,6 +215,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -208,6 +226,7 @@
|
||||
"user_viewable": false,
|
||||
"user_editable": false,
|
||||
"rules": "required|string|in:258550",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T12:03:19+02:00",
|
||||
"exported_at": "2024-06-02T20:42:04+00:00",
|
||||
"name": "Counter-Strike: Global Offensive",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "437c367d-06be-498f-a604-fdad135504d7",
|
||||
"description": "Counter-Strike: Global Offensive is a multiplayer first-person shooter video game developed by Hidden Path Entertainment and Valve Corporation.",
|
||||
"features": [
|
||||
"gsl_token",
|
||||
@ -39,6 +40,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|alpha_dash",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -49,6 +51,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|alpha_num|size:32",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -59,6 +62,7 @@
|
||||
"user_viewable": false,
|
||||
"user_editable": false,
|
||||
"rules": "required|string|max:20",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T12:02:53+02:00",
|
||||
"exported_at": "2024-06-02T20:42:04+00:00",
|
||||
"name": "Custom Source Engine Game",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "2a42d0c2-c0ba-4067-9a0a-9b95d77a3490",
|
||||
"description": "This option allows modifying the startup arguments and other details to run a custom SRCDS based game on the panel.",
|
||||
"features": [
|
||||
"steam_disk_space"
|
||||
@ -38,6 +39,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|numeric|digits_between:1,6",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -48,6 +50,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|alpha_dash|between:1,100",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -58,6 +61,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|alpha_dash",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -68,6 +72,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -78,6 +83,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -88,6 +94,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T12:02:26+02:00",
|
||||
"exported_at": "2024-06-02T20:42:05+00:00",
|
||||
"name": "Garrys Mod",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "60ef81d4-30a2-4d98-ab64-f59c69e2f915",
|
||||
"description": "Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.",
|
||||
"features": [
|
||||
"gsl_token",
|
||||
@ -39,6 +40,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|alpha_dash",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -49,6 +51,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string|alpha_num|size:32",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -59,6 +62,7 @@
|
||||
"user_viewable": false,
|
||||
"user_editable": false,
|
||||
"rules": "required|string|max:20",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -69,6 +73,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|integer",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -79,6 +84,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -89,6 +95,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer|max:128",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -99,6 +106,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer|max:100",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -109,6 +117,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|boolean",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T12:01:57+02:00",
|
||||
"exported_at": "2024-06-02T20:42:06+00:00",
|
||||
"name": "Insurgency",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "a5702286-655b-4069-bf1e-925c7300b61a",
|
||||
"description": "Take to the streets for intense close quarters combat, where a team's survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.",
|
||||
"features": [
|
||||
"steam_disk_space"
|
||||
@ -38,6 +39,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|regex:\/^(237410)$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -48,6 +50,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^(\\w{1,20})$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:59:56+02:00",
|
||||
"exported_at": "2024-06-02T20:42:07+00:00",
|
||||
"name": "Team Fortress 2",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "7f8eb681-b2c8-4bf8-b9f4-d79ff70b6e5d",
|
||||
"description": "Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.",
|
||||
"features": [
|
||||
"gsl_token",
|
||||
@ -39,6 +40,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|in:232250",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -49,6 +51,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^(\\w{1,20})$\/",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -59,6 +62,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|alpha_num|size:32",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-21T21:40:24-04:00",
|
||||
"exported_at": "2024-06-04T22:53:03+00:00",
|
||||
"name": "Mumble Server",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "727ee758-7fb2-4979-972b-d3eba4e1e9f0",
|
||||
"description": "Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.",
|
||||
"features": null,
|
||||
"docker_images": {
|
||||
@ -15,7 +16,7 @@
|
||||
"file_denylist": [],
|
||||
"startup": "mumble-server -fg -ini murmur.ini",
|
||||
"config": {
|
||||
"files": "{\r\n \"murmur.ini\": {\r\n \"parser\": \"ini\",\r\n \"find\": {\r\n \"database\": \"\/home\/container\/murmur.sqlite\",\r\n \"logfile\": \"\/home\/container\/murmur.log\",\r\n \"port\": \"{{server.build.default.port}}\",\r\n \"host\": \"\",\r\n \"users\": \"{{server.build.env.MAX_USERS}}\"\r\n }\r\n }\r\n}",
|
||||
"files": "{\r\n \"murmur.ini\": {\r\n \"parser\": \"ini\",\r\n \"find\": {\r\n \"database\": \"\/home\/container\/murmur.sqlite\",\r\n \"logfile\": \"\/home\/container\/murmur.log\",\r\n \"port\": \"{{server.allocations.default.port}}\",\r\n \"host\": \"\",\r\n \"users\": \"{{server.environment.MAX_USERS}}\"\r\n }\r\n }\r\n}",
|
||||
"startup": "{\r\n \"done\": \"Server listening on\"\r\n}",
|
||||
"logs": "{}",
|
||||
"stop": "^C"
|
||||
@ -36,6 +37,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|numeric|digits_between:1,5",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -4,9 +4,10 @@
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2024-05-08T11:53:32+02:00",
|
||||
"exported_at": "2024-06-02T20:42:08+00:00",
|
||||
"name": "Teamspeak3 Server",
|
||||
"author": "panel@example.com",
|
||||
"uuid": "983b1fac-d322-4d5f-a636-436127326b37",
|
||||
"description": "VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.",
|
||||
"features": null,
|
||||
"docker_images": {
|
||||
@ -36,6 +37,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:6",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -46,6 +48,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer|between:1025,65535",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -56,6 +59,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer|between:1025,65535",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -66,6 +70,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:12",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -76,6 +81,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer|between:1025,65535",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
@ -86,6 +92,7 @@
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer|between:1025,65535",
|
||||
"sort": null,
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$nameToUuidMapping = [
|
||||
'Bungeecord' => '9e6b409e-4028-4947-aea8-50a2c404c271',
|
||||
'Forge Minecraft' => 'ed072427-f209-4603-875c-f540c6dd5a65',
|
||||
'Paper' => '5da37ef6-58da-4169-90a6-e683e1721247',
|
||||
'Sponge (SpongeVanilla)' => 'f0d2f88f-1ff3-42a0-b03f-ac44c5571e6d',
|
||||
'Vanilla Minecraft' => '9ac39f3d-0c34-4d93-8174-c52ab9e6c57b',
|
||||
'Counter-Strike: Global Offensive' => '437c367d-06be-498f-a604-fdad135504d7',
|
||||
'Custom Source Engine Game' => '2a42d0c2-c0ba-4067-9a0a-9b95d77a3490',
|
||||
'Garrys Mod' => '60ef81d4-30a2-4d98-ab64-f59c69e2f915',
|
||||
'Insurgency' => 'a5702286-655b-4069-bf1e-925c7300b61a',
|
||||
'Team Fortress 2' => '7f8eb681-b2c8-4bf8-b9f4-d79ff70b6e5d',
|
||||
'Mumble Server' => '727ee758-7fb2-4979-972b-d3eba4e1e9f0',
|
||||
'Teamspeak3 Server' => '983b1fac-d322-4d5f-a636-436127326b37',
|
||||
'Rust' => 'bace2dfb-209c-452a-9459-7d6f340b07ae',
|
||||
];
|
||||
|
||||
foreach ($nameToUuidMapping as $name => $uuid) {
|
||||
DB::table('eggs')
|
||||
->where('name', $name)
|
||||
->update(['uuid' => $uuid]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('nodes', function (Blueprint $table) {
|
||||
$table->string('daemon_sftp_alias')->nullable()->after('daemon_sftp');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('nodes', function (Blueprint $table) {
|
||||
$table->dropColumn('daemon_sftp_alias');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$eggs = DB::table('eggs')->get();
|
||||
|
||||
foreach ($eggs as $egg) {
|
||||
$updatedPort = str_replace(
|
||||
'server.build.default.port',
|
||||
'server.allocations.default.port',
|
||||
$egg->config_files
|
||||
);
|
||||
|
||||
if ($updatedPort !== $egg->config_files) {
|
||||
$egg->config_files = $updatedPort;
|
||||
echo "Processed Port update with ID: {$egg->name}\n";
|
||||
}
|
||||
|
||||
$updatedIp = str_replace(
|
||||
'server.build.default.ip',
|
||||
'server.allocations.default.ip',
|
||||
$egg->config_files
|
||||
);
|
||||
|
||||
if ($updatedIp !== $egg->config_files) {
|
||||
$egg->config_files = $updatedIp;
|
||||
echo "Processed IP update with ID: {$egg->name}\n";
|
||||
}
|
||||
|
||||
$updatedEnv = str_replace(
|
||||
'server.build.env.',
|
||||
'server.environment.',
|
||||
$egg->config_files
|
||||
);
|
||||
|
||||
if ($updatedEnv !== $egg->config_files) {
|
||||
$egg->config_files = $updatedEnv;
|
||||
echo "Processed ENV update with ID: {$egg->name}\n";
|
||||
}
|
||||
|
||||
DB::table('eggs')
|
||||
->where('id', $egg->id)
|
||||
->update(['config_files' => $egg->config_files]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$eggs = DB::table('eggs')->get();
|
||||
|
||||
foreach ($eggs as $egg) {
|
||||
$revertedEnv = str_replace(
|
||||
'server.environment.',
|
||||
'server.build.env.',
|
||||
$egg->config_files
|
||||
);
|
||||
|
||||
if ($revertedEnv !== $egg->config_files) {
|
||||
$egg->config_files = $revertedEnv;
|
||||
}
|
||||
|
||||
$revertedIp = str_replace(
|
||||
'server.allocations.default.ip',
|
||||
'server.build.default.ip',
|
||||
$egg->config_files
|
||||
);
|
||||
|
||||
if ($revertedIp !== $egg->config_files) {
|
||||
$egg->config_files = $revertedIp;
|
||||
}
|
||||
|
||||
$revertedPort = str_replace(
|
||||
'server.allocations.default.port',
|
||||
'server.build.default.port',
|
||||
$egg->config_files
|
||||
);
|
||||
|
||||
if ($revertedPort !== $egg->config_files) {
|
||||
$egg->config_files = $revertedPort;
|
||||
}
|
||||
|
||||
DB::table('eggs')
|
||||
->where('id', $egg->id)
|
||||
->update(['config_files' => $egg->config_files]);
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$eggs = DB::table('eggs')->get();
|
||||
|
||||
foreach ($eggs as $egg) {
|
||||
$updatedEnv = str_replace(
|
||||
'server.build.environment.',
|
||||
'server.environment.',
|
||||
$egg->config_files
|
||||
);
|
||||
|
||||
if ($updatedEnv !== $egg->config_files) {
|
||||
$egg->config_files = $updatedEnv;
|
||||
echo "Processed ENV update with ID: {$egg->name}\n";
|
||||
}
|
||||
|
||||
DB::table('eggs')
|
||||
->where('id', $egg->id)
|
||||
->update(['config_files' => $egg->config_files]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// We shouldn't revert this...
|
||||
}
|
||||
};
|
@ -37,6 +37,7 @@ return [
|
||||
'upload_size' => "'Enter the maximum filesize upload",
|
||||
'daemonListen' => 'Enter the daemon listening port',
|
||||
'daemonSFTP' => 'Enter the daemon SFTP listening port',
|
||||
'daemonSFTPAlias' => 'Enter the daemon SFTP alias (can be empty)',
|
||||
'daemonBase' => 'Enter the base folder',
|
||||
'succes1' => 'Successfully created a new node with the name: ',
|
||||
'succes2' => 'and has an id of: ',
|
||||
|
@ -19,6 +19,10 @@ return [
|
||||
'button_issues' => 'Create Issue',
|
||||
'button_features' => 'Discuss Features',
|
||||
],
|
||||
'intro-update' => [
|
||||
'heading' => 'Update available',
|
||||
'content' => ':latestVersion is available! Read our documentation to update your Panel.',
|
||||
],
|
||||
'intro-first-node' => [
|
||||
'heading' => 'No Nodes Detected',
|
||||
'content' => "It looks like you don't have any Nodes set up yet, but don't worry because you click the action button to create your first one!",
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
function r({state:i}){return{state:i,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=o=>o===null?0:Array.isArray(o)?o.length:typeof o!="object"?0:Object.keys(o).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};
|
||||
function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};
|
||||
|
File diff suppressed because one or more lines are too long
58
readme.md
58
readme.md
@ -26,46 +26,20 @@ This gives you the power to run game servers without bloating machines with a ho
|
||||
|
||||
Some of our popular eggs include but are not limited to:
|
||||
|
||||
* [Minecraft](https://github.com/pelican-eggs/minecraft)
|
||||
* Paper
|
||||
* Sponge
|
||||
* Bungeecord
|
||||
* Waterfall
|
||||
* [SteamCMD](https://github.com/pelican-eggs/steamcmd)
|
||||
* 7 Days to Die
|
||||
* ARK: Survival
|
||||
* ARMA
|
||||
* Counter Strike
|
||||
* DayZ
|
||||
* Enshrouded
|
||||
* Left 4 Dead
|
||||
* Palworld
|
||||
* Project Zomboid
|
||||
* Sons of the Forest
|
||||
* [Other Games](https://github.com/pelican-eggs/games)
|
||||
* Among Us
|
||||
* Factorio
|
||||
* GTA
|
||||
* Rimworld
|
||||
* Terraria
|
||||
* [Discord Bots](https://github.com/pelican-eggs/chatbots)
|
||||
* Redbot
|
||||
* JMusicBot
|
||||
* SinusBot
|
||||
* Dynamica
|
||||
* [Software](https://github.com/pelican-eggs/software)
|
||||
* [Programming Languages](https://github.com/pelican-eggs/generic)
|
||||
* C#
|
||||
* Java
|
||||
* Lua
|
||||
* Node.js
|
||||
* Python
|
||||
* [Database](https://github.com/pelican-eggs/database)
|
||||
* Redis
|
||||
* MariaDB
|
||||
* PostgreSQL
|
||||
* [Voice Servers](https://github.com/pelican-eggs/voice)
|
||||
* [Storage](https://github.com/pelican-eggs/storage)
|
||||
* [Monitoring](https://github.com/pelican-eggs/monitoring)
|
||||
| Category | Eggs | | | |
|
||||
|----------------------------------------------------------------------|-----------------|---------------|--------------------|----------------|
|
||||
| [Minecraft](https://github.com/pelican-eggs/minecraft) | Paper | Sponge | Bungeecord | Waterfall |
|
||||
| [SteamCMD](https://github.com/pelican-eggs/steamcmd) | 7 Days to Die | ARK: Survival | Arma 3 | Counter Strike |
|
||||
| | DayZ | Enshrouded | Left 4 Dead | Palworld |
|
||||
| | Project Zomboid | Satisfactory | Sons of the Forest | Starbound |
|
||||
| [Standalone Games](https://github.com/pelican-eggs/games-standalone) | Among Us | Factorio | FTL | GTA |
|
||||
| | Kerbal Space | Mindustry | Rimworld | Terraria |
|
||||
| [Discord Bots](https://github.com/pelican-eggs/chatbots) | Redbot | JMusicBot | JMusicBot | Dynamica |
|
||||
| [Voice Servers](https://github.com/pelican-eggs/voice) | Mumble | Teamspeak | Lavalink | |
|
||||
| [Software](https://github.com/pelican-eggs/software) | Elasticsearch | Gitea | Grafana | RabbitMQ |
|
||||
| [Programming](https://github.com/pelican-eggs/generic) | Node.js | Python | Java | C# |
|
||||
| [Databases](https://github.com/pelican-eggs/database) | Redis | MariaDB | PostgreSQL | MongoDB |
|
||||
| [Storage](https://github.com/pelican-eggs/storage) | S3 | SFTP Share | | |
|
||||
| [Monitoring](https://github.com/pelican-eggs/monitoring) | Prometheus | Loki | | |
|
||||
|
||||
Copyright Pelican® 2024
|
||||
*Copyright Pelican® 2024*
|
||||
|
@ -21,6 +21,7 @@ export interface Server {
|
||||
status: ServerStatus;
|
||||
sftpDetails: {
|
||||
ip: string;
|
||||
alias: string;
|
||||
port: number;
|
||||
};
|
||||
invocation: string;
|
||||
@ -57,6 +58,7 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData)
|
||||
dockerImage: data.docker_image,
|
||||
sftpDetails: {
|
||||
ip: data.sftp_details.ip,
|
||||
alias: data.sftp_details.alias,
|
||||
port: data.sftp_details.port,
|
||||
},
|
||||
description: data.description ? (data.description.length > 0 ? data.description : null) : null,
|
||||
|
@ -40,7 +40,7 @@ const ModalContent = ({ ...props }: RequiredModalProps) => {
|
||||
label={'Ignored Files & Directories'}
|
||||
description={`
|
||||
Enter the files or folders to ignore while generating this backup. Leave blank to use
|
||||
the contents of the .panelignore file in the root of the server directory if present.
|
||||
the contents of the .pelicanignore file in the root of the server directory if present.
|
||||
Wildcard matching of files and folders is supported in addition to negating a rule by
|
||||
prefixing the path with an exclamation point.
|
||||
`}
|
||||
|
@ -91,13 +91,14 @@ export default () => {
|
||||
<FileManagerBreadcrumbs withinFileEditor isNewFile={action !== 'edit'} />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
{hash.replace(/^#/, '').endsWith('.panelignore') && (
|
||||
{hash.replace(/^#/, '').endsWith('.pelicanignore') && (
|
||||
<div css={tw`mb-4 p-4 border-l-4 bg-neutral-900 rounded border-cyan-400`}>
|
||||
<p css={tw`text-neutral-300 text-sm`}>
|
||||
You're editing a <code css={tw`font-mono bg-black rounded py-px px-1`}>.panelignore</code>{' '}
|
||||
file. Any files or directories listed in here will be excluded from backups. Wildcards are
|
||||
supported by using an asterisk (<code css={tw`font-mono bg-black rounded py-px px-1`}>*</code>).
|
||||
You can negate a prior rule by prepending an exclamation point (
|
||||
You're editing a{' '}
|
||||
<code css={tw`font-mono bg-black rounded py-px px-1`}>.pelicanignore</code> directories listed
|
||||
in here will be excluded from backups. Wildcards are supported by using an supported by using an
|
||||
asterisk (<code css={tw`font-mono bg-black rounded py-px px-1`}>*</code>). You can negate a
|
||||
prior rule by prepending an exclamation point (
|
||||
<code css={tw`font-mono bg-black rounded py-px px-1`}>!</code>).
|
||||
</p>
|
||||
</div>
|
||||
|
@ -168,7 +168,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
|
||||
<FormikFieldWrapper
|
||||
name={'payload'}
|
||||
description={
|
||||
'Optional. Include the files and folders to be excluded in this backup. By default, the contents of your .panelignore file will be used. If you have reached your backup limit, the oldest backup will be rotated.'
|
||||
'Optional. Include the files and folders to be excluded in this backup. By default, the contents of your .pelicanignore file will be used. If you have reached your backup limit, the oldest backup will be rotated.'
|
||||
}
|
||||
>
|
||||
<FormikField as={Textarea} name={'payload'} rows={6} />
|
||||
|
@ -31,8 +31,12 @@ export default () => {
|
||||
<TitledGreyBox title={'SFTP Details'} css={tw`mb-6 md:mb-10`}>
|
||||
<div>
|
||||
<Label>Server Address</Label>
|
||||
<CopyOnClick text={`sftp://${ip(sftp.ip)}:${sftp.port}`}>
|
||||
<Input type={'text'} value={`sftp://${ip(sftp.ip)}:${sftp.port}`} readOnly />
|
||||
<CopyOnClick text={`sftp://${sftp.alias ? sftp.alias : ip(sftp.ip)}:${sftp.port}`}>
|
||||
<Input
|
||||
type={'text'}
|
||||
value={`sftp://${sftp.alias ? sftp.alias : ip(sftp.ip)}:${sftp.port}`}
|
||||
readOnly
|
||||
/>
|
||||
</CopyOnClick>
|
||||
</div>
|
||||
<div css={tw`mt-6`}>
|
||||
@ -50,7 +54,10 @@ export default () => {
|
||||
</div>
|
||||
</div>
|
||||
<div css={tw`ml-4`}>
|
||||
<a href={`sftp://${username}.${id}@${ip(sftp.ip)}:${sftp.port}`}>
|
||||
<a
|
||||
href={`sftp://${username}.${id}@${sftp.alias ? sftp.alias : ip(sftp.ip)}:${sftp.port
|
||||
}`}
|
||||
>
|
||||
<Button.Text variant={Button.Variants.Secondary}>Launch SFTP</Button.Text>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
:actions="$this->getCachedHeaderActions()"
|
||||
:breadcrumbs="filament()->hasBreadcrumbs() ? $this->getBreadcrumbs() : []"
|
||||
:heading=" trans('dashboard/index.heading')"
|
||||
:subheading="trans('strings.version', ['version' => config('app.version')])"
|
||||
:subheading="trans('strings.version', ['version' => $version])"
|
||||
></x-filament-panels::header>
|
||||
|
||||
<p>{{ trans('dashboard/index.expand_sections') }}</p>
|
||||
@ -30,6 +30,22 @@
|
||||
</x-filament::section>
|
||||
@endif
|
||||
|
||||
@if (!$isLatest)
|
||||
<x-filament::section
|
||||
icon="tabler-info-circle"
|
||||
icon-color="warning"
|
||||
id="intro-update"
|
||||
collapsible
|
||||
persist-collapsed
|
||||
:header-actions="$updateActions"
|
||||
>
|
||||
<x-slot name="heading">{{ trans('dashboard/index.sections.intro-update.heading') }}</x-slot>
|
||||
|
||||
<p>{{ trans('dashboard/index.sections.intro-update.content', ['latestVersion' => $latestVersion]) }}</p>
|
||||
|
||||
</x-filament::section>
|
||||
@endif
|
||||
|
||||
{{-- No Nodes Created --}}
|
||||
@if ($nodesCount <= 0)
|
||||
<x-filament::section
|
||||
|
Loading…
x
Reference in New Issue
Block a user