Compare commits

...

58 Commits

Author SHA1 Message Date
Charles
dd7a01aa04 Merge pull request #345 from Boy132/show-git-version
Show update info on dashboard & show git commit (when using git)
2024-06-07 20:00:04 -04:00
Boy132
55badb5644 update colors 2024-06-08 00:43:25 +02:00
Boy132
93f059025c show update info on dashboard, show git commit (when using git) 2024-06-08 00:38:46 +02:00
Charles
7be0cd6928 Merge pull request #323 from Boy132/feature/node-sftp-alias
Add alias for node sftp address
2024-06-07 18:04:44 -04:00
Boy132
0156456919 Merge branch 'pelican-dev:main' into feature/node-sftp-alias 2024-06-07 23:49:38 +02:00
Charles
b9d1ce4438 Merge pull request #334 from pelican-dev/issue/297
Better exception handling
2024-06-07 17:46:33 -04:00
Charles
9ce262bf56 Merge pull request #316 from pelican-dev/issue/node-update
Fix Node Updating
2024-06-07 17:44:10 -04:00
notCharles
7ee52affb2 Update token rotation 2024-06-07 17:38:58 -04:00
Charles
93bfe925b9 Merge pull request #333 from pelican-dev/issue/2
Remove unused parameters
2024-06-07 17:32:40 -04:00
Boy132
cc1ac1eba1 Allow importing eggs via url (#344)
* allow importing eggs via url

* refactor

* run pint

* turn back into one button

* fix empty check

* small cleanup

* removed container for tabs

* Update URL function

* Use sys temp

---------

Co-authored-by: notCharles <charles@pelican.dev>
2024-06-07 17:31:34 -04:00
Charles
02d24b8a36 Fix the egg variable disaster... (#331)
* Migrations to update existing eggs in db

* Update stock eggs

* Update Eggs on import

* Also update updated versions of eggs that are uploaded

* Redo this..

Tests passed locally.

* Pint & Update replace

* Squash Migrations, simplify logic

* Maybe this way...

* Swap them over to single call

---------

Co-authored-by: Lance Pioch <git@lance.sh>
2024-06-07 16:23:25 -04:00
Charles
16fac3b5c6 Merge pull request #337 from Boy132/fix/schedules-run-every-minute
Fix schedules running every minute
2024-06-07 05:43:56 -04:00
Lance Pioch
eb99f53d87 Reset this for now 2024-06-07 00:08:41 -04:00
Lance Pioch
643e4168b9 Add required rule separately 2024-06-06 19:39:46 -04:00
Lance Pioch
51cd7a8e81 Remove unused route files 2024-06-06 16:15:35 -04:00
Boy132
91bf38b63d fix schedules running every minute 2024-06-06 15:53:29 +02:00
Charles
e3699f34d8 Merge pull request #336 from Boy132/fix/default-database-path
Use env value instead of config value for database path
2024-06-06 06:09:51 -04:00
Charles
dc3da2dc98 Merge pull request #335 from Boy132/add/mounts-helper-text
Add helper text to mounts on EditServer page
2024-06-06 06:05:27 -04:00
Boy132
d245751c97 use env value instead of config value 2024-06-06 11:59:24 +02:00
Boy132
e0d7a094ab add helper text to mounts 2024-06-06 10:18:05 +02:00
Lance Pioch
3010e3d61e Better default 2024-06-05 23:37:12 -04:00
Lance Pioch
d68e7218a8 Reformat as table 2024-06-05 23:37:09 -04:00
Lance Pioch
a4435a7454 Pint fix 2024-06-05 22:12:53 -04:00
Lance Pioch
df26c4f9f5 Better exception handling 2024-06-05 21:49:09 -04:00
Lance Pioch
6f1de67523 Remove extraneous parameters 2024-06-05 16:03:04 -04:00
Charles
6f009ee126 Remove cli from php
Every workflow run hangs at attempting to add the cli package and adds ~1 min to the workflow.
2024-06-05 14:15:33 -04:00
Boy132
328e159c6b Merge branch 'pelican-dev:main' into feature/node-sftp-alias 2024-06-05 08:47:20 +02:00
Boy132
f9fd426aca change column type to string
Co-authored-by: Lance Pioch <lancepioch@gmail.com>
2024-06-05 08:47:11 +02:00
Lance Pioch
6166fac929 Merge pull request #322 from Boy132/fix/make-user-db-test
Replace DB check in MakeUserCommand
2024-06-04 17:33:47 -04:00
Lance Pioch
4bd1070025 Merge pull request #324 from Boy132/patch-1
Remove maxLength from `variable_value` input
2024-06-04 17:30:16 -04:00
Lance Pioch
2d6e30b646 Merge pull request #326 from Boy132/fix/artisan-queue-again
Another call fix in AppSettingsCommand
2024-06-04 17:29:48 -04:00
Lance Pioch
f61c6b9dc2 Merge pull request #327 from Boy132/patch-2
Fix default sqlite database path in setup command
2024-06-04 17:28:45 -04:00
Lance Pioch
5e29737dc5 Merge pull request #328 from Boy132/fix/pelicanignore
Replace `panelignore` with `pelicanignore`
2024-06-04 15:43:09 -04:00
Boy132
d996019204 fix eslint 2024-06-04 17:49:04 +02:00
Boy132
91d8dbd084 replace panelignore with pelicanignore 2024-06-04 17:48:02 +02:00
Boy132
bb03ddda50 listen on all queues 2024-06-04 17:26:19 +02:00
Boy132
1c66681c0e make default sqlite database path relative 2024-06-04 13:26:05 +02:00
Boy132
0728266826 restart queue service if service already exists 2024-06-04 13:14:54 +02:00
Boy132
d81c9faac6 improve prompts 2024-06-04 13:01:52 +02:00
Boy132
cff54f1969 show output when running p:environment:queue-service 2024-06-04 13:01:24 +02:00
Boy132
201563a13b remove maxLength from variable_value input 2024-06-04 11:20:40 +02:00
Boy132
8f2261f6cd add alias for node sftp address 2024-06-04 09:17:36 +02:00
Boy132
29cc92f0dc replace db check in MakeUserCommand 2024-06-04 08:33:54 +02:00
Lance Pioch
33f10cbcb9 Merge pull request #312 from RMartinOscar/patch-1
Update EditUser.php
2024-06-03 10:35:31 -04:00
Lance Pioch
b538532e34 Merge pull request #314 from RMartinOscar/patch-2
Update EditDatabaseHost.php
2024-06-03 10:35:07 -04:00
Lance Pioch
a892821b4f Merge pull request #319 from RMartinOscar/patch-3
Update AllocationsRelationManager to allow big endian
2024-06-03 10:34:27 -04:00
Lance Pioch
5a3b50b31f Apply suggestions from code review 2024-06-03 10:34:08 -04:00
Lance Pioch
51b217571b Merge pull request #320 from Boy132/fix/artisan-call
Fix artisan call in AppSettingsCommand
2024-06-03 10:33:00 -04:00
Boy132
6e75c76c60 cleanup 2024-06-03 13:46:48 +02:00
Boy132
e22c5c3e0a fix artisan call in app settings command 2024-06-03 13:43:11 +02:00
MartinOscar
f3171939a4 Update AllocationsRelationManager.php
Remove useless range order
2024-06-03 07:11:09 +02:00
MartinOscar
189d564f87 Update AllocationsRelationManager.php 2024-06-03 06:30:05 +02:00
MartinOscar
7926f97c8e Update EditDatabaseHost.php 2024-06-03 04:09:36 +02:00
MartinOscar
f4d39c1c68 Update EditDatabaseHost.php 2024-06-03 04:02:31 +02:00
Lance Pioch
6c2d0a2d50 Remove shenanigans 2024-06-02 21:59:12 -04:00
MartinOscar
f6899301fd Update EditDatabaseHost.php 2024-06-03 03:54:33 +02:00
MartinOscar
cbb4ef1da2 Update EditUser.php 2024-06-03 03:52:39 +02:00
notCharles
f6ef76d98e Disable delete for own user. 2024-06-02 21:00:11 -04:00
55 changed files with 521 additions and 803 deletions

View File

@@ -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

View File

@@ -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
{

View File

@@ -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')
);
}

View File

@@ -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,13 +61,24 @@ WantedBy=multi-user.target
return;
}
$result = Process::run("systemctl enable --now $serviceName.service");
if ($result->failed()) {
$this->error('Error enabling service: ' . $result->errorOutput());
if ($fileExists) {
$result = Process::run("systemctl restart $serviceName.service");
if ($result->failed()) {
$this->error('Error restarting service: ' . $result->errorOutput());
return;
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());
return;
}
$this->line('Queue worker service file created successfully.');
}
$this->line('Queue worker service file created successfully.');
}
}

View File

@@ -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');

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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.');

View File

@@ -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' => [

View File

@@ -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'),
];
}

View File

@@ -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([
Forms\Components\FileUpload::make('egg')
->acceptedFileTypes(['application/json'])
->storeFiles(false)
->multiple(),
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);
foreach ($eggFile as $file) {
if (!empty($data['egg'])) {
/** @var TemporaryUploadedFile[] $eggFile */
$eggFile = $data['egg'];
foreach ($eggFile as $file) {
try {
$eggImportService->fromFile($file);
} catch (Exception $exception) {
Notification::make()
->title('Import Failed')
->danger()
->send();
report($exception);
return;
}
}
}
if (!empty($data['url'])) {
try {
$eggImportService->handle($file);
$eggImportService->fromUrl($data['url']);
} catch (Exception $exception) {
Notification::make()
->title('Import Failed')

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -371,19 +371,20 @@ class CreateServer extends CreateRecord
$text = Forms\Components\TextInput::make('variable_value')
->hidden($this->shouldHideComponent(...))
->maxLength(191)
->rules([
->required(fn (Forms\Get $get) => in_array('required', explode('|', $get('rules'))))
->rules(
fn (Forms\Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) {
$validator = Validator::make(['validatorkey' => $value], [
'validatorkey' => $get('rules'),
]);
if ($validator->fails()) {
$message = str($validator->errors()->first())->replace('validatorkey', $get('name'));
$message = str($validator->errors()->first())->replace('validatorkey', $get('name'))->toString();
$fail($message);
}
},
]);
);
$select = Forms\Components\Select::make('variable_value')
->hidden($this->shouldHideComponent(...))

View File

@@ -532,7 +532,6 @@ class EditServer extends EditRecord
$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], [
@@ -577,6 +576,7 @@ class EditServer extends EditRecord
->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')

View File

@@ -1,588 +0,0 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource;
use App\Services\Servers\RandomWordService;
use Filament\Actions;
use Filament\Forms;
use App\Enums\ContainerStatus;
use App\Enums\ServerState;
use App\Models\Egg;
use App\Models\Server;
use App\Models\ServerVariable;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Servers\ServerDeletionService;
use Filament\Forms\Form;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Facades\Validator;
use Closure;
class EditServerOrg extends EditRecord
{
protected static string $resource = ServerResource::class;
public function form(Form $form): Form
{
return $form
->columns([
'default' => 2,
'sm' => 2,
'md' => 4,
'lg' => 6,
])
->schema([
Forms\Components\ToggleButtons::make('docker')
->label('Container Status')->inline()->inlineLabel()
->formatStateUsing(function ($state, Server $server) {
if ($server->node_id === null) {
return 'unknown';
}
/** @var DaemonServerRepository $service */
$service = resolve(DaemonServerRepository::class);
$details = $service->setServer($server)->getDetails();
return $details['state'] ?? 'unknown';
})
->options(fn ($state) => collect(ContainerStatus::cases())->filter(fn ($containerStatus) => $containerStatus->value === $state)->mapWithKeys(
fn (ContainerStatus $state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()]
))
->colors(collect(ContainerStatus::cases())->mapWithKeys(
fn (ContainerStatus $status) => [$status->value => $status->color()]
))
->icons(collect(ContainerStatus::cases())->mapWithKeys(
fn (ContainerStatus $status) => [$status->value => $status->icon()]
))
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 3,
]),
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()]
))
->colors(collect(ServerState::cases())->mapWithKeys(
fn (ServerState $state) => [$state->value => $state->color()]
))
->icons(collect(ServerState::cases())->mapWithKeys(
fn (ServerState $state) => [$state->value => $state->icon()]
))
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 3,
]),
Forms\Components\TextInput::make('external_id')
->maxLength(191)
->hidden(),
Forms\Components\TextInput::make('name')
->prefixIcon('tabler-server')
->label('Display Name')
->suffixAction(Forms\Components\Actions\Action::make('random')
->icon('tabler-dice-' . random_int(1, 6))
->action(function (Forms\Set $set, Forms\Get $get) {
$egg = Egg::find($get('egg_id'));
$prefix = $egg ? str($egg->name)->lower()->kebab() . '-' : '';
$word = (new RandomWordService())->word();
$set('name', $prefix . $word);
}))
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 2,
'lg' => 3,
])
->required()
->maxLength(191),
Forms\Components\Select::make('owner_id')
->prefixIcon('tabler-user')
->label('Owner')
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 2,
'lg' => 3,
])
->relationship('user', 'username')
->searchable()
->preload()
->required(),
Forms\Components\Textarea::make('description')
->hidden()
->required()
->columnSpanFull(),
Forms\Components\Select::make('egg_id')
->disabledOn('edit')
->prefixIcon('tabler-egg')
->columnSpan([
'default' => 2,
'sm' => 2,
'md' => 2,
'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')
->hintIcon('tabler-code')
->label('Startup Command')
->required()
->live()
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->rows(function ($state) {
return str($state)->explode("\n")->reduce(
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
0
);
}),
Forms\Components\Hidden::make('start_on_completion'),
Forms\Components\Section::make('Egg Variables')
->icon('tabler-eggs')
->iconColor('primary')
->collapsible()
->collapsed()
->columnSpan(([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
]))
->schema([
Forms\Components\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),
]),
Forms\Components\Section::make('Environment Management')
->collapsed()
->icon('tabler-server-cog')
->iconColor('primary')
->columns([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 4,
])
->columnSpanFull()
->schema([
Forms\Components\Fieldset::make('Resource Limits')
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema([
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('unlimited_mem')
->label('Memory')->inlineLabel()->inline()
->afterStateUpdated(fn (Forms\Set $set) => $set('memory', 0))
->formatStateUsing(fn (Forms\Get $get) => $get('memory') == 0)
->live()
->options([
true => 'Unlimited',
false => 'Limited',
])
->colors([
true => 'primary',
false => 'warning',
])
->columnSpan(2),
Forms\Components\TextInput::make('memory')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => $get('unlimited_mem'))
->label('Memory Limit')->inlineLabel()
->suffix('MiB')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('unlimited_disk')
->label('Disk Space')->inlineLabel()->inline()
->live()
->afterStateUpdated(fn (Forms\Set $set) => $set('disk', 0))
->formatStateUsing(fn (Forms\Get $get) => $get('disk') == 0)
->options([
true => 'Unlimited',
false => 'Limited',
])
->colors([
true => 'primary',
false => 'warning',
])
->columnSpan(2),
Forms\Components\TextInput::make('disk')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => $get('unlimited_disk'))
->label('Disk Space Limit')->inlineLabel()
->suffix('MiB')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('unlimited_cpu')
->label('CPU')->inlineLabel()->inline()
->afterStateUpdated(fn (Forms\Set $set) => $set('cpu', 0))
->formatStateUsing(fn (Forms\Get $get) => $get('cpu') == 0)
->live()
->options([
true => 'Unlimited',
false => 'Limited',
])
->colors([
true => 'primary',
false => 'warning',
])
->columnSpan(2),
Forms\Components\TextInput::make('cpu')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => $get('unlimited_cpu'))
->label('CPU Limit')->inlineLabel()
->suffix('%')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('swap_support')
->live()
->label('Enable Swap Memory')->inlineLabel()->inline()
->columnSpan(2)
->afterStateUpdated(function ($state, Forms\Set $set) {
$value = match ($state) {
'unlimited' => -1,
'disabled' => 0,
'limited' => 128,
};
$set('swap', $value);
})
->formatStateUsing(function (Forms\Get $get) {
return match (true) {
$get('swap') > 0 => 'limited',
$get('swap') == 0 => 'disabled',
$get('swap') < 0 => 'unlimited',
};
})
->options([
'unlimited' => 'Unlimited',
'limited' => 'Limited',
'disabled' => 'Disabled',
])
->colors([
'unlimited' => 'primary',
'limited' => 'warning',
'disabled' => 'danger',
]),
Forms\Components\TextInput::make('swap')
->dehydratedWhenHidden()
->hidden(fn (Forms\Get $get) => match ($get('swap_support')) {
'disabled', 'unlimited', true => true,
'limited', false => false,
})
->label('Swap Memory')->inlineLabel()
->suffix('MiB')
->minValue(-1)
->columnSpan(2)
->required()
->integer(),
]),
Forms\Components\Hidden::make('io')
->helperText('The IO performance relative to other running containers')
->label('Block IO Proportion'),
Forms\Components\Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Forms\Components\ToggleButtons::make('oom_killer')
->label('OOM Killer')->inlineLabel()->inline()
->columnSpan(2)
->options([
false => 'Disabled',
true => 'Enabled',
])
->colors([
false => 'success',
true => 'danger',
]),
Forms\Components\TextInput::make('oom_disabled_hidden')
->hidden(),
]),
]),
Forms\Components\Fieldset::make('Feature Limits')
->inlineLabel()
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema([
Forms\Components\TextInput::make('allocation_limit')
->suffixIcon('tabler-network')
->required()
->numeric(),
Forms\Components\TextInput::make('database_limit')
->suffixIcon('tabler-database')
->required()
->numeric(),
Forms\Components\TextInput::make('backup_limit')
->suffixIcon('tabler-copy-check')
->required()
->numeric(),
]),
Forms\Components\Fieldset::make('Docker Settings')
->columnSpan([
'default' => 2,
'sm' => 4,
'md' => 4,
'lg' => 6,
])
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema([
Forms\Components\Select::make('select_image')
->label('Image Name')
->afterStateUpdated(fn (Forms\Set $set, $state) => $set('image', $state))
->options(function ($state, Forms\Get $get, Forms\Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];
$currentImage = $get('image');
if (!$currentImage && $images) {
$defaultImage = collect($images)->first();
$set('image', $defaultImage);
$set('select_image', $defaultImage);
}
return array_flip($images) + ['ghcr.io/custom-image' => 'Custom Image'];
})
->selectablePlaceholder(false)
->columnSpan(1),
Forms\Components\TextInput::make('image')
->label('Image')
->debounce(500)
->afterStateUpdated(function ($state, Forms\Get $get, Forms\Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];
if (in_array($state, $images)) {
$set('select_image', $state);
} else {
$set('select_image', 'ghcr.io/custom-image');
}
})
->placeholder('Enter a custom Image')
->columnSpan(1),
Forms\Components\KeyValue::make('docker_labels')
->label('Container Labels')
->keyLabel('Label Name')
->valueLabel('Label Description')
->columnSpanFull(),
]),
]),
]);
}
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make('Delete')
->successRedirectUrl(route('filament.admin.resources.servers.index'))
->color('danger')
->after(fn (Server $server) => resolve(ServerDeletionService::class)->handle($server))
->requiresConfirmation(),
Actions\Action::make('console')
->label('Console')
->icon('tabler-terminal')
->url(fn (Server $server) => "/server/$server->uuid_short"),
$this->getSaveFormAction()->formId('form'),
];
}
protected function getFormActions(): array
{
return [];
}
protected function mutateFormDataBeforeSave(array $data): array
{
unset($data['docker'], $data['status']);
return $data;
}
public function getRelationManagers(): array
{
return [
ServerResource\RelationManagers\AllocationsRelationManager::class,
];
}
private function shouldHideComponent(Forms\Get $get, Forms\Components\Component $component): bool
{
$containsRuleIn = str($get('rules'))->explode('|')->reduce(
fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true
);
if ($component instanceof Forms\Components\Select) {
return $containsRuleIn;
}
if ($component instanceof Forms\Components\TextInput) {
return !$containsRuleIn;
}
throw new \Exception('Component type not supported: ' . $component::class);
}
private function getSelectOptionsFromRules(Forms\Get $get): array
{
$inRule = str($get('rules'))->explode('|')->reduce(
fn ($result, $value) => str($value)->startsWith('in:') ? $value : $result, ''
);
return str($inRule)
->after('in:')
->explode(',')
->each(fn ($value) => str($value)->trim())
->mapWithKeys(fn ($value) => [$value => $value])
->all();
}
}

View File

@@ -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);
}
}

View File

@@ -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]);

View File

@@ -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')

View File

@@ -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']))

View File

@@ -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([

View File

@@ -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.*')

View File

@@ -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;
}
}

View File

@@ -33,6 +33,7 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough;
* @property string $daemon_token
* @property int $daemon_listen
* @property int $daemon_sftp
* @property string|null $daemon_sftp_alias
* @property string $daemon_base
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
@@ -72,7 +73,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',
];
@@ -91,6 +92,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',

View File

@@ -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);

View File

@@ -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),
''
);

View File

@@ -10,6 +10,16 @@ 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.build.environment',
];
/**
* Takes an uploaded file and parses out the egg configuration from within.
*
@@ -26,11 +36,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;
}
/**

View File

@@ -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'));
}
}

View File

@@ -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'));
}
}

View 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();
}
}
}

View File

@@ -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,
];
});
}
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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",

65
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8feeafbeb16044bd6716510a73393fc0",
"content-hash": "bf44faee3aae2b1d4c1b57893c1aba98",
"packages": [
{
"name": "abdelhamiderrahmouni/filament-monaco-editor",
@@ -6990,6 +6990,67 @@
],
"time": "2024-05-10T08:19:35+00:00"
},
{
"name": "spatie/temporary-directory",
"version": "2.2.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/temporary-directory.git",
"reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a",
"reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\TemporaryDirectory\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alex Vanderbist",
"email": "alex@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Easily create, use and destroy temporary directories",
"homepage": "https://github.com/spatie/temporary-directory",
"keywords": [
"php",
"spatie",
"temporary-directory"
],
"support": {
"issues": "https://github.com/spatie/temporary-directory/issues",
"source": "https://github.com/spatie/temporary-directory/tree/2.2.1"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
},
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2023-12-25T11:46:58+00:00"
},
{
"name": "symfony/clock",
"version": "v7.0.7",
@@ -13096,5 +13157,5 @@
"ext-zip": "*"
},
"platform-dev": [],
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}

View File

@@ -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']);
}
}

View File

@@ -4,7 +4,7 @@
"version": "PTDL_v2",
"update_url": null
},
"exported_at": "2024-06-02T20:42:01+00:00",
"exported_at": "2024-06-04T22:51:49+00:00",
"name": "Bungeecord",
"author": "panel@example.com",
"uuid": "9e6b409e-4028-4947-aea8-50a2c404c271",
@@ -24,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"

View File

@@ -4,7 +4,7 @@
"version": "PTDL_v2",
"update_url": null
},
"exported_at": "2024-06-02T20:42:02+00:00",
"exported_at": "2024-06-04T22:51:58+00:00",
"name": "Forge Minecraft",
"author": "panel@example.com",
"uuid": "ed072427-f209-4603-875c-f540c6dd5a65",
@@ -24,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"

View File

@@ -4,7 +4,7 @@
"version": "PTDL_v2",
"update_url": null
},
"exported_at": "2024-06-02T20:42:02+00:00",
"exported_at": "2024-06-04T22:51:57+00:00",
"name": "Paper",
"author": "parker@example.com",
"uuid": "5da37ef6-58da-4169-90a6-e683e1721247",
@@ -24,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"

View File

@@ -4,7 +4,7 @@
"version": "PTDL_v2",
"update_url": null
},
"exported_at": "2024-06-02T20:42:03+00:00",
"exported_at": "2024-06-04T22:50:55+00:00",
"name": "Sponge (SpongeVanilla)",
"author": "panel@example.com",
"uuid": "f0d2f88f-1ff3-42a0-b03f-ac44c5571e6d",
@@ -24,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"
@@ -60,4 +60,4 @@
"field_type": "text"
}
]
}
}

View File

@@ -4,7 +4,7 @@
"version": "PTDL_v2",
"update_url": null
},
"exported_at": "2024-06-02T20:42:03+00:00",
"exported_at": "2024-06-04T22:51:16+00:00",
"name": "Vanilla Minecraft",
"author": "panel@example.com",
"uuid": "9ac39f3d-0c34-4d93-8174-c52ab9e6c57b",
@@ -24,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"

View File

@@ -4,7 +4,7 @@
"version": "PTDL_v2",
"update_url": null
},
"exported_at": "2024-06-02T20:42:08+00:00",
"exported_at": "2024-06-04T22:53:03+00:00",
"name": "Mumble Server",
"author": "panel@example.com",
"uuid": "727ee758-7fb2-4979-972b-d3eba4e1e9f0",
@@ -16,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"

View File

@@ -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');
});
}
};

View File

@@ -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]);
}
}
};

View File

@@ -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: ',

View File

@@ -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!",

View File

@@ -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*

View File

@@ -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,

View File

@@ -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.
`}

View File

@@ -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&apos;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&apos;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>

View File

@@ -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} />

View File

@@ -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>

View File

@@ -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