mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-20 19:14:45 +02:00
Merge branch 'master' into issue/fix-3
This commit is contained in:
commit
69b70bf649
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +1,2 @@
|
|||||||
|
github: pelican-dev
|
||||||
custom: [https://buy.stripe.com/14kdU99SI4UT7ni9AB, https://buy.stripe.com/14kaHXc0Q9b9372eUU]
|
custom: [https://buy.stripe.com/14kdU99SI4UT7ni9AB, https://buy.stripe.com/14kaHXc0Q9b9372eUU]
|
||||||
|
24
.github/docker/default.conf
vendored
24
.github/docker/default.conf
vendored
@ -4,6 +4,30 @@
|
|||||||
# If using CentOS this file should be placed in:
|
# If using CentOS this file should be placed in:
|
||||||
# /etc/nginx/conf.d/
|
# /etc/nginx/conf.d/
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Pterodactyl®
|
||||||
|
# Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
3
.github/workflows/build.yaml
vendored
3
.github/workflows/build.yaml
vendored
@ -1,9 +1,6 @@
|
|||||||
name: Build
|
name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- '**'
|
||||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: Tests
|
name: Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- '**'
|
||||||
|
|
||||||
|
30
.github/workflows/cla.yaml
vendored
Normal file
30
.github/workflows/cla.yaml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: "CLA Assistant"
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened,closed,synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
statuses: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CLAAssistant:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "CLA Assistant"
|
||||||
|
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||||
|
uses: contributor-assistant/github-action@v2.3.0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
with:
|
||||||
|
path-to-signatures: 'version1/cla.json'
|
||||||
|
path-to-document: 'https://github.com/pelican-dev/panel/blob/3.x/contributor_license_agreement.md'
|
||||||
|
branch: 'main'
|
||||||
|
allowlist: dependabot[bot]
|
||||||
|
remote-organization-name: pelican-dev
|
||||||
|
remote-repository-name: cla-signatures
|
3
.github/workflows/lint.yaml
vendored
3
.github/workflows/lint.yaml
vendored
@ -1,9 +1,6 @@
|
|||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- '**'
|
||||||
|
@ -44,7 +44,6 @@ class InfoCommand extends Command
|
|||||||
['Session Driver', config('session.driver')],
|
['Session Driver', config('session.driver')],
|
||||||
['Filesystem Driver', config('filesystems.default')],
|
['Filesystem Driver', config('filesystems.default')],
|
||||||
['Default Theme', config('themes.active')],
|
['Default Theme', config('themes.active')],
|
||||||
['Proxies', config('trustedproxies.proxies')],
|
|
||||||
], 'compact');
|
], 'compact');
|
||||||
|
|
||||||
$this->output->title('Database Configuration');
|
$this->output->title('Database Configuration');
|
||||||
|
43
app/Enums/ContainerStatus.php
Normal file
43
app/Enums/ContainerStatus.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum ContainerStatus: string
|
||||||
|
{
|
||||||
|
case Created = 'created';
|
||||||
|
case Running = 'running';
|
||||||
|
case Restarting = 'restarting';
|
||||||
|
case Exited = 'exited';
|
||||||
|
case Paused = 'paused';
|
||||||
|
case Dead = 'dead';
|
||||||
|
case Removing = 'removing';
|
||||||
|
case Missing = 'missing';
|
||||||
|
|
||||||
|
public function icon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Created => 'tabler-heart-plus',
|
||||||
|
self::Running => 'tabler-heartbeat',
|
||||||
|
self::Restarting => 'tabler-heart-bolt',
|
||||||
|
self::Exited => 'tabler-heart-exclamation',
|
||||||
|
self::Paused => 'tabler-heart-pause',
|
||||||
|
self::Dead => 'tabler-heart-x',
|
||||||
|
self::Removing => 'tabler-heart-down',
|
||||||
|
self::Missing => 'tabler-heart-question',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function color(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Created => 'primary',
|
||||||
|
self::Running => 'success',
|
||||||
|
self::Restarting => 'info',
|
||||||
|
self::Exited => 'danger',
|
||||||
|
self::Paused => 'warning',
|
||||||
|
self::Dead => 'danger',
|
||||||
|
self::Removing => 'warning',
|
||||||
|
self::Missing => 'gray',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
37
app/Enums/ServerState.php
Normal file
37
app/Enums/ServerState.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum ServerState: string
|
||||||
|
{
|
||||||
|
case None = 'none';
|
||||||
|
case Installing = 'installing';
|
||||||
|
case InstallFailed = 'install_failed';
|
||||||
|
case ReinstallFailed = 'reinstall_failed';
|
||||||
|
case Suspended = 'suspended';
|
||||||
|
case RestoringBackup = 'restoring_backup';
|
||||||
|
|
||||||
|
public function icon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::None => 'tabler-heart',
|
||||||
|
self::Installing => 'tabler-heart-bolt',
|
||||||
|
self::InstallFailed => 'tabler-heart-x',
|
||||||
|
self::ReinstallFailed => 'tabler-heart-x',
|
||||||
|
self::Suspended => 'tabler-heart-cancel',
|
||||||
|
self::RestoringBackup => 'tabler-heart-up',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function color(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::None => 'primary',
|
||||||
|
self::Installing => 'info',
|
||||||
|
self::InstallFailed => 'danger',
|
||||||
|
self::ReinstallFailed => 'danger',
|
||||||
|
self::Suspended => 'danger',
|
||||||
|
self::RestoringBackup => 'info',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Exceptions\Http\Server;
|
namespace App\Exceptions\Http\Server;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ class ServerStateConflictException extends ConflictHttpException
|
|||||||
$message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
|
$message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
|
||||||
} elseif (!$server->isInstalled()) {
|
} elseif (!$server->isInstalled()) {
|
||||||
$message = 'This server has not yet completed its installation process, please try again later.';
|
$message = 'This server has not yet completed its installation process, please try again later.';
|
||||||
} elseif ($server->status === Server::STATUS_RESTORING_BACKUP) {
|
} elseif ($server->status === ServerState::RestoringBackup) {
|
||||||
$message = 'This server is currently restoring from a backup, please try again later.';
|
$message = 'This server is currently restoring from a backup, please try again later.';
|
||||||
} elseif (!is_null($server->transfer)) {
|
} elseif (!is_null($server->transfer)) {
|
||||||
$message = 'This server is currently being transferred to a new machine, please try again later.';
|
$message = 'This server is currently being transferred to a new machine, please try again later.';
|
||||||
|
@ -1,5 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/* The MIT License (MIT)
|
||||||
|
|
||||||
|
Pterodactyl®
|
||||||
|
Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE. */
|
||||||
|
|
||||||
namespace App\Extensions\Filesystem;
|
namespace App\Extensions\Filesystem;
|
||||||
|
|
||||||
use Aws\S3\S3ClientInterface;
|
use Aws\S3\S3ClientInterface;
|
||||||
|
82
app/Filament/Pages/Dashboard.php
Normal file
82
app/Filament/Pages/Dashboard.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\NodeResource\Pages\ListNodes;
|
||||||
|
use App\Models\Egg;
|
||||||
|
use App\Models\Node;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\User;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
|
||||||
|
class Dashboard extends Page
|
||||||
|
{
|
||||||
|
protected static ?string $navigationIcon = 'tabler-layout-dashboard';
|
||||||
|
|
||||||
|
protected static string $view = 'filament.pages.dashboard';
|
||||||
|
|
||||||
|
protected ?string $heading = '';
|
||||||
|
|
||||||
|
protected static ?string $title = 'Dashboard';
|
||||||
|
|
||||||
|
protected static ?string $slug = '/';
|
||||||
|
|
||||||
|
public string $activeTab = 'nodes';
|
||||||
|
|
||||||
|
public function getViewData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'inDevelopment' => config('app.version') === 'canary',
|
||||||
|
'eggsCount' => Egg::query()->count(),
|
||||||
|
'nodesList' => ListNodes::getUrl(),
|
||||||
|
'nodesCount' => Node::query()->count(),
|
||||||
|
'serversCount' => Server::query()->count(),
|
||||||
|
'usersCount' => User::query()->count(),
|
||||||
|
|
||||||
|
'devActions' => [
|
||||||
|
CreateAction::make()
|
||||||
|
->label('Create Issue')
|
||||||
|
->icon('tabler-brand-github')
|
||||||
|
->url('https://github.com/pelican-dev/panel/issues/new/choose', true)
|
||||||
|
->color('warning'),
|
||||||
|
CreateAction::make()
|
||||||
|
->label('Discuss Features')
|
||||||
|
->icon('tabler-brand-github')
|
||||||
|
->url('https://github.com/pelican-dev/panel/discussions', true)
|
||||||
|
->color('primary'),
|
||||||
|
],
|
||||||
|
'nodeActions' => [
|
||||||
|
CreateAction::make()
|
||||||
|
->label('Create first Node in Pelican')
|
||||||
|
->icon('tabler-server-2')
|
||||||
|
->url(route('filament.admin.resources.nodes.create'))
|
||||||
|
->color('primary'),
|
||||||
|
],
|
||||||
|
'supportActions' => [
|
||||||
|
CreateAction::make()
|
||||||
|
->label('Help Translate')
|
||||||
|
->icon('tabler-language')
|
||||||
|
->url('https://crowdin.com/project/pelican-dev', true)
|
||||||
|
->color('info'),
|
||||||
|
CreateAction::make()
|
||||||
|
->label('Donate Directly')
|
||||||
|
->icon('tabler-cash')
|
||||||
|
->url('https://pelican.dev/donate', true)
|
||||||
|
->color('success'),
|
||||||
|
],
|
||||||
|
'helpActions' => [
|
||||||
|
CreateAction::make()
|
||||||
|
->label('Read Documentation')
|
||||||
|
->icon('tabler-speedboat')
|
||||||
|
->url('https://pelican.dev/docs', true)
|
||||||
|
->color('info'),
|
||||||
|
CreateAction::make()
|
||||||
|
->label('Get Help in Discord')
|
||||||
|
->icon('tabler-brand-discord')
|
||||||
|
->url('https://discord.gg/pelican-panel', true)
|
||||||
|
->color('primary'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
194
app/Filament/Resources/ApiKeyResource.php
Normal file
194
app/Filament/Resources/ApiKeyResource.php
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\ApiKeyResource\Pages;
|
||||||
|
use App\Models\ApiKey;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Components\Tab;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class ApiKeyResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = ApiKey::class;
|
||||||
|
protected static ?string $label = 'API Key';
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-key';
|
||||||
|
|
||||||
|
public static function canEdit($record): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTabs(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'all' => Tab::make('All Keys'),
|
||||||
|
'application' => Tab::make('Application Keys')
|
||||||
|
->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultActiveTab(): string|int|null
|
||||||
|
{
|
||||||
|
return 'application';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Hidden::make('identifier')->default(ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION)),
|
||||||
|
Forms\Components\Hidden::make('token')->default(encrypt(str_random(ApiKey::KEY_LENGTH))),
|
||||||
|
|
||||||
|
Forms\Components\Select::make('user_id')
|
||||||
|
->hidden()
|
||||||
|
->searchable()
|
||||||
|
->preload()
|
||||||
|
->relationship('user', 'username')
|
||||||
|
->default(auth()->user()->id)
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\Select::make('key_type')
|
||||||
|
->options(function (ApiKey $apiKey) {
|
||||||
|
$originalOptions = [
|
||||||
|
ApiKey::TYPE_NONE => 'None',
|
||||||
|
ApiKey::TYPE_ACCOUNT => 'Account',
|
||||||
|
ApiKey::TYPE_APPLICATION => 'Application',
|
||||||
|
ApiKey::TYPE_DAEMON_USER => 'Daemon User',
|
||||||
|
ApiKey::TYPE_DAEMON_APPLICATION => 'Daemon Application',
|
||||||
|
];
|
||||||
|
|
||||||
|
return collect($originalOptions)
|
||||||
|
->filter(fn ($value, $key) => $key <= ApiKey::TYPE_APPLICATION || $apiKey->key_type === $key)
|
||||||
|
->all();
|
||||||
|
})
|
||||||
|
->hidden()
|
||||||
|
->selectablePlaceholder(false)
|
||||||
|
->required()
|
||||||
|
->default(ApiKey::TYPE_APPLICATION),
|
||||||
|
|
||||||
|
Forms\Components\Fieldset::make('Permissions')
|
||||||
|
->columns([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 2,
|
||||||
|
])
|
||||||
|
->schema(
|
||||||
|
collect(ApiKey::RESOURCES)->map(fn ($resource) => Forms\Components\ToggleButtons::make("r_$resource")
|
||||||
|
->label(str($resource)->replace('_', ' ')->title())
|
||||||
|
->options([
|
||||||
|
0 => 'None',
|
||||||
|
1 => 'Read',
|
||||||
|
// 2 => 'Write',
|
||||||
|
3 => 'Read & Write',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
0 => 'tabler-book-off',
|
||||||
|
1 => 'tabler-book',
|
||||||
|
2 => 'tabler-writing',
|
||||||
|
3 => 'tabler-writing',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
0 => 'success',
|
||||||
|
1 => 'warning',
|
||||||
|
2 => 'danger',
|
||||||
|
3 => 'danger',
|
||||||
|
])
|
||||||
|
->inline()
|
||||||
|
->required()
|
||||||
|
->disabledOn('edit')
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 1,
|
||||||
|
])
|
||||||
|
->default(0),
|
||||||
|
)->all(),
|
||||||
|
),
|
||||||
|
|
||||||
|
Forms\Components\TagsInput::make('allowed_ips')
|
||||||
|
->placeholder('Example: 127.0.0.1 or 192.168.1.1')
|
||||||
|
->label('Whitelisted IPv4 Addresses')
|
||||||
|
->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
|
||||||
|
->columnSpanFull()
|
||||||
|
->hidden()
|
||||||
|
->default(null),
|
||||||
|
|
||||||
|
Forms\Components\Textarea::make('memo')
|
||||||
|
->required()
|
||||||
|
->label('Description')
|
||||||
|
->helperText('
|
||||||
|
Once you have assigned permissions and created this set of credentials you will be unable to come back and edit it.
|
||||||
|
If you need to make changes down the road you will need to create a new set of credentials.
|
||||||
|
')
|
||||||
|
->columnSpanFull(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->searchable(false)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('user.username')
|
||||||
|
->searchable()
|
||||||
|
->hidden()
|
||||||
|
->sortable(),
|
||||||
|
|
||||||
|
Tables\Columns\TextColumn::make('key')
|
||||||
|
->copyable()
|
||||||
|
->icon('tabler-clipboard-text')
|
||||||
|
->state(fn (ApiKey $key) => $key->identifier . decrypt($key->token)),
|
||||||
|
|
||||||
|
Tables\Columns\TextColumn::make('memo')
|
||||||
|
->label('Description')
|
||||||
|
->wrap()
|
||||||
|
->limit(50),
|
||||||
|
|
||||||
|
Tables\Columns\TextColumn::make('identifier')
|
||||||
|
->hidden()
|
||||||
|
->searchable(),
|
||||||
|
|
||||||
|
Tables\Columns\TextColumn::make('last_used_at')
|
||||||
|
->label('Last Used')
|
||||||
|
->dateTime()
|
||||||
|
->sortable(),
|
||||||
|
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->label('Created')
|
||||||
|
->dateTime()
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListApiKeys::route('/'),
|
||||||
|
'create' => Pages\CreateApiKey::route('/create'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
13
app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php
Normal file
13
app/Filament/Resources/ApiKeyResource/Pages/CreateApiKey.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\ApiKeyResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\ApiKeyResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateApiKey extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = ApiKeyResource::class;
|
||||||
|
|
||||||
|
protected ?string $heading = 'Create Application API Key';
|
||||||
|
}
|
40
app/Filament/Resources/ApiKeyResource/Pages/ListApiKeys.php
Normal file
40
app/Filament/Resources/ApiKeyResource/Pages/ListApiKeys.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\ApiKeyResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\ApiKeyResource;
|
||||||
|
use App\Models\ApiKey;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Components\Tab;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class ListApiKeys extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = ApiKeyResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTabs(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'all' => Tab::make('All Keys'),
|
||||||
|
'application' => Tab::make('Application Keys')
|
||||||
|
->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)
|
||||||
|
),
|
||||||
|
'account' => Tab::make('Account Keys')
|
||||||
|
->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_ACCOUNT)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultActiveTab(): string|int|null
|
||||||
|
{
|
||||||
|
return 'application';
|
||||||
|
}
|
||||||
|
}
|
116
app/Filament/Resources/DatabaseHostResource.php
Normal file
116
app/Filament/Resources/DatabaseHostResource.php
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseHostResource\Pages;
|
||||||
|
use App\Models\DatabaseHost;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Components\Section;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class DatabaseHostResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = DatabaseHost::class;
|
||||||
|
|
||||||
|
protected static ?string $label = 'Databases';
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-database';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Section::make()->schema([
|
||||||
|
Forms\Components\TextInput::make('host')
|
||||||
|
->helperText('The IP address or Domain name that should be used when attempting to connect to this MySQL host from this Panel to create new databases.')
|
||||||
|
->required()
|
||||||
|
->live()
|
||||||
|
->debounce(500)
|
||||||
|
->afterStateUpdated(fn ($state, Forms\Set $set) => $set('name', $state))
|
||||||
|
->maxLength(191),
|
||||||
|
Forms\Components\TextInput::make('port')
|
||||||
|
->helperText('The port that MySQL is running on for this host.')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(3306)
|
||||||
|
->minValue(0)
|
||||||
|
->maxValue(65535),
|
||||||
|
Forms\Components\TextInput::make('username')
|
||||||
|
->helperText('The username of an account that has enough permissions to create new users and databases on the system.')
|
||||||
|
->required()
|
||||||
|
->maxLength(191),
|
||||||
|
Forms\Components\TextInput::make('password')
|
||||||
|
->helperText('The password for the database user.')
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->maxLength(191)
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->helperText('A short identifier used to distinguish this location from others. Must be between 1 and 60 characters, for example, us.nyc.lvl3.')
|
||||||
|
->required()
|
||||||
|
->maxLength(60),
|
||||||
|
Forms\Components\Select::make('node_id')
|
||||||
|
->searchable()
|
||||||
|
->preload()
|
||||||
|
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
|
||||||
|
->label('Linked Node')
|
||||||
|
->relationship('node', 'name'),
|
||||||
|
])->columns([
|
||||||
|
'default' => 1,
|
||||||
|
'lg' => 2,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->searchable(false)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('host')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('port')
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('username')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('max_databases')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('node.name')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListDatabaseHosts::route('/'),
|
||||||
|
'create' => Pages\CreateDatabaseHost::route('/create'),
|
||||||
|
'edit' => Pages\EditDatabaseHost::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\DatabaseHostResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseHostResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateDatabaseHost extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = DatabaseHostResource::class;
|
||||||
|
|
||||||
|
protected ?string $heading = 'Database Hosts';
|
||||||
|
|
||||||
|
protected ?string $subheading = '(database servers that can have individual databases)';
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\DatabaseHostResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseHostResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditDatabaseHost extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = DatabaseHostResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\DatabaseHostResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseHostResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListDatabaseHosts extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = DatabaseHostResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
110
app/Filament/Resources/DatabaseResource.php
Normal file
110
app/Filament/Resources/DatabaseResource.php
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseResource\Pages;
|
||||||
|
use App\Models\Database;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class DatabaseResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Database::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-database';
|
||||||
|
|
||||||
|
protected static bool $shouldRegisterNavigation = false;
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Select::make('server_id')
|
||||||
|
->relationship('server', 'name')
|
||||||
|
->searchable()
|
||||||
|
->preload()
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('database_host_id')
|
||||||
|
->required()
|
||||||
|
->numeric(),
|
||||||
|
Forms\Components\TextInput::make('database')
|
||||||
|
->required()
|
||||||
|
->maxLength(191),
|
||||||
|
Forms\Components\TextInput::make('remote')
|
||||||
|
->required()
|
||||||
|
->maxLength(191)
|
||||||
|
->default('%'),
|
||||||
|
Forms\Components\TextInput::make('username')
|
||||||
|
->required()
|
||||||
|
->maxLength(191),
|
||||||
|
Forms\Components\TextInput::make('password')
|
||||||
|
->password()
|
||||||
|
->revealable()
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('max_connections')
|
||||||
|
->numeric()
|
||||||
|
->minValue(0)
|
||||||
|
->default(0),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('server.name')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('database_host_id')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('database')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('username')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('remote')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('max_connections')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListDatabases::route('/'),
|
||||||
|
'create' => Pages\CreateDatabase::route('/create'),
|
||||||
|
'edit' => Pages\EditDatabase::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateDatabase extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = DatabaseResource::class;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditDatabase extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = DatabaseResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\DatabaseResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListDatabases extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = DatabaseResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
195
app/Filament/Resources/EggResource.php
Normal file
195
app/Filament/Resources/EggResource.php
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EggResource\Pages;
|
||||||
|
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
||||||
|
use App\Models\Egg;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class EggResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Egg::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-eggs';
|
||||||
|
|
||||||
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
|
protected static ?string $recordRouteKeyName = 'id';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Tabs::make()->tabs([
|
||||||
|
Forms\Components\Tabs\Tab::make('Configuration')
|
||||||
|
->columns(2)
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('name')->required()->maxLength(191)
|
||||||
|
->helperText('A simple, human-readable name to use as an identifier for this Egg.'),
|
||||||
|
Forms\Components\Textarea::make('description')->rows(5)
|
||||||
|
->helperText('A description of this Egg that will be displayed throughout the Panel as needed.'),
|
||||||
|
Forms\Components\TextInput::make('uuid')->disabled()
|
||||||
|
->helperText('This is the globally unique identifier for this Egg which the Daemon uses as an identifier.'),
|
||||||
|
Forms\Components\TextInput::make('author')->required()->maxLength(191)->disabled()
|
||||||
|
->helperText('The author of this version of the Egg. Uploading a new Egg configuration from a different author will change this.'),
|
||||||
|
Forms\Components\Toggle::make('force_outgoing_ip')->required()
|
||||||
|
->helperText("Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP.
|
||||||
|
Required for certain games to work properly when the Node has multiple public IP addresses.
|
||||||
|
Enabling this option will disable internal networking for any servers using this egg, causing them to be unable to internally access other servers on the same node."),
|
||||||
|
Forms\Components\Textarea::make('startup')->rows(5)
|
||||||
|
->helperText('The default startup command that should be used for new servers using this Egg.'),
|
||||||
|
Forms\Components\KeyValue::make('docker_images')
|
||||||
|
->columnSpanFull()
|
||||||
|
->addActionLabel('Add Image')
|
||||||
|
->keyLabel('Name')
|
||||||
|
->valueLabel('Image URI')
|
||||||
|
->helperText('The docker images available to servers using this egg.'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Forms\Components\Tabs\Tab::make('Process Management')
|
||||||
|
->columns(2)
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Select::make('config_from')
|
||||||
|
->label('Copy Settings From')
|
||||||
|
->placeholder('None')
|
||||||
|
->relationship('configFrom', 'name', ignoreRecord: true)
|
||||||
|
->helperText('If you would like to default to settings from another Egg select it from the menu above.'),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('config_stop')
|
||||||
|
->maxLength(191)
|
||||||
|
->label('Stop Command')
|
||||||
|
->helperText('The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.'),
|
||||||
|
|
||||||
|
Forms\Components\Textarea::make('config_startup')->rows(10)->json()
|
||||||
|
->label('Start Configuration')
|
||||||
|
->helperText('List of values the daemon should be looking for when booting a server to determine completion.'),
|
||||||
|
|
||||||
|
Forms\Components\Textarea::make('config_files')->rows(10)->json()
|
||||||
|
->label('Configuration Files')
|
||||||
|
->helperText('This should be a JSON representation of configuration files to modify and what parts should be changed.'),
|
||||||
|
|
||||||
|
Forms\Components\Textarea::make('config_logs')->rows(10)->json()
|
||||||
|
->label('Log Configuration')
|
||||||
|
->helperText('This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.'),
|
||||||
|
]),
|
||||||
|
Forms\Components\Tabs\Tab::make('Variables')
|
||||||
|
->columnSpanFull()
|
||||||
|
// ->columns(2)
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Repeater::make('Blah')
|
||||||
|
->grid(3)
|
||||||
|
->relationship('variables')
|
||||||
|
->name('name')
|
||||||
|
->columns(1)
|
||||||
|
->columnSpan(1)
|
||||||
|
->itemLabel(fn (array $state) => $state['name'])
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('name')->live()->maxLength(191)->columnSpanFull(),
|
||||||
|
Forms\Components\Textarea::make('description')->columnSpanFull(),
|
||||||
|
Forms\Components\TextInput::make('env_variable')->maxLength(191),
|
||||||
|
Forms\Components\TextInput::make('default_value')->maxLength(191),
|
||||||
|
Forms\Components\Textarea::make('rules')->rows(3)->columnSpanFull()->required(),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Forms\Components\Tabs\Tab::make('Install Script')
|
||||||
|
->columns(3)
|
||||||
|
->schema([
|
||||||
|
|
||||||
|
Forms\Components\Select::make('copy_script_from')
|
||||||
|
->placeholder('None')
|
||||||
|
->relationship('scriptFrom', 'name', ignoreRecord: true),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('script_container')
|
||||||
|
->required()
|
||||||
|
->maxLength(191)
|
||||||
|
->default('alpine:3.4'),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('script_entry')
|
||||||
|
->required()
|
||||||
|
->maxLength(191)
|
||||||
|
->default('ash'),
|
||||||
|
|
||||||
|
MonacoEditor::make('script_install')
|
||||||
|
->columnSpanFull()
|
||||||
|
->fontSize('16px')
|
||||||
|
->language('shell')
|
||||||
|
->view('filament.plugins.monaco-editor'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
])->columnSpanFull()->persistTabInQueryString(),
|
||||||
|
|
||||||
|
// Forms\Components\TagsInput::make('features'),
|
||||||
|
// Forms\Components\TagsInput::make('file_denylist')->placeholder('new-file.txt'),
|
||||||
|
// Forms\Components\TextInput::make('update_url'),
|
||||||
|
// Forms\Components\Toggle::make('script_is_privileged')->required(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->defaultPaginationPageOption(25)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('id')
|
||||||
|
->label('Id')
|
||||||
|
->hidden()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->icon('tabler-egg')
|
||||||
|
->description(fn ($record): string => $record->description)
|
||||||
|
->wrap()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('author')
|
||||||
|
->hidden()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('servers_count')
|
||||||
|
->counts('servers')
|
||||||
|
->icon('tabler-server')
|
||||||
|
->label('Servers'),
|
||||||
|
Tables\Columns\TextColumn::make('script_container')
|
||||||
|
->searchable()
|
||||||
|
->hidden(),
|
||||||
|
Tables\Columns\TextColumn::make('copyFrom.name')
|
||||||
|
->hidden()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('script_entry')
|
||||||
|
->hidden()
|
||||||
|
->searchable(),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->headerActions([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListEggs::route('/'),
|
||||||
|
'create' => Pages\CreateEgg::route('/create'),
|
||||||
|
'edit' => Pages\EditEgg::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
11
app/Filament/Resources/EggResource/Pages/CreateEgg.php
Normal file
11
app/Filament/Resources/EggResource/Pages/CreateEgg.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EggResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EggResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateEgg extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = EggResource::class;
|
||||||
|
}
|
19
app/Filament/Resources/EggResource/Pages/EditEgg.php
Normal file
19
app/Filament/Resources/EggResource/Pages/EditEgg.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EggResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EggResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditEgg extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = EggResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
59
app/Filament/Resources/EggResource/Pages/ListEggs.php
Normal file
59
app/Filament/Resources/EggResource/Pages/ListEggs.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\EggResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\EggResource;
|
||||||
|
use App\Services\Eggs\Sharing\EggImporterService;
|
||||||
|
use Exception;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||||
|
|
||||||
|
class ListEggs extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = EggResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
|
||||||
|
Actions\Action::make('import')
|
||||||
|
->label('Import Egg')
|
||||||
|
->form([
|
||||||
|
Forms\Components\FileUpload::make('egg')
|
||||||
|
->acceptedFileTypes(['application/json'])
|
||||||
|
->storeFiles(false),
|
||||||
|
])
|
||||||
|
->action(function (array $data): void {
|
||||||
|
/** @var TemporaryUploadedFile $eggFile */
|
||||||
|
$eggFile = $data['egg'];
|
||||||
|
|
||||||
|
/** @var EggImporterService $eggImportService */
|
||||||
|
$eggImportService = resolve(EggImporterService::class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$newEgg = $eggImportService->handle($eggFile);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
Notification::make()
|
||||||
|
->title('Egg Import Failed')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
|
||||||
|
report($exception);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title("Egg Import Success: $newEgg->name")
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
|
||||||
|
redirect()->route('filament.admin.resources.eggs.edit', [$newEgg]);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
154
app/Filament/Resources/MountResource.php
Normal file
154
app/Filament/Resources/MountResource.php
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\MountResource\Pages;
|
||||||
|
use App\Models\Mount;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Components\Group;
|
||||||
|
use Filament\Forms\Components\Section;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class MountResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Mount::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-layers-linked';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Section::make()->schema([
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->required()
|
||||||
|
->helperText('Unique name used to separate this mount from another.')
|
||||||
|
->maxLength(64),
|
||||||
|
Forms\Components\ToggleButtons::make('read_only')
|
||||||
|
->label('Read only?')
|
||||||
|
->helperText('Is the mount read only inside the container?')
|
||||||
|
->options([
|
||||||
|
false => 'Writeable',
|
||||||
|
true => 'Read only',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
false => 'tabler-writing',
|
||||||
|
true => 'tabler-writing-off',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
false => 'warning',
|
||||||
|
true => 'success',
|
||||||
|
])
|
||||||
|
->inline()
|
||||||
|
->default(false)
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('source')
|
||||||
|
->required()
|
||||||
|
->helperText('File path on the host system to mount to a container.')
|
||||||
|
->maxLength(191),
|
||||||
|
Forms\Components\TextInput::make('target')
|
||||||
|
->required()
|
||||||
|
->helperText('Where the mount will be accessible inside a container.')
|
||||||
|
->maxLength(191),
|
||||||
|
Forms\Components\ToggleButtons::make('user_mountable')
|
||||||
|
->hidden()
|
||||||
|
->label('User mountable?')
|
||||||
|
->options([
|
||||||
|
false => 'No',
|
||||||
|
true => 'Yes',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
false => 'tabler-user-cancel',
|
||||||
|
true => 'tabler-user-bolt',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
false => 'success',
|
||||||
|
true => 'warning',
|
||||||
|
])
|
||||||
|
->default(false)
|
||||||
|
->inline()
|
||||||
|
->required(),
|
||||||
|
Forms\Components\Textarea::make('description')
|
||||||
|
->helperText('A longer description for this mount.')
|
||||||
|
->columnSpanFull(),
|
||||||
|
])->columnSpan(1)->columns([
|
||||||
|
'default' => 1,
|
||||||
|
'lg' => 2,
|
||||||
|
]),
|
||||||
|
Group::make()->schema([
|
||||||
|
Section::make()->schema([
|
||||||
|
Select::make('eggs')->multiple()
|
||||||
|
->relationship('eggs', 'name')
|
||||||
|
->preload(),
|
||||||
|
Select::make('nodes')->multiple()
|
||||||
|
->relationship('nodes', 'name')
|
||||||
|
->searchable(['name', 'fqdn'])
|
||||||
|
->preload(),
|
||||||
|
]),
|
||||||
|
])->columns([
|
||||||
|
'default' => 1,
|
||||||
|
'lg' => 2,
|
||||||
|
]),
|
||||||
|
])->columns([
|
||||||
|
'default' => 1,
|
||||||
|
'lg' => 2,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->searchable(false)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('id')
|
||||||
|
->label('')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('source')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('target')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\IconColumn::make('read_only')
|
||||||
|
->icon(fn (bool $state) => $state ? 'tabler-circle-check-filled' : 'tabler-circle-x-filled')
|
||||||
|
->color(fn (bool $state) => $state ? 'success' : 'danger')
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\IconColumn::make('user_mountable')
|
||||||
|
->hidden()
|
||||||
|
->icon(fn (bool $state) => $state ? 'tabler-circle-check-filled' : 'tabler-circle-x-filled')
|
||||||
|
->color(fn (bool $state) => $state ? 'success' : 'danger')
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListMounts::route('/'),
|
||||||
|
'create' => Pages\CreateMount::route('/create'),
|
||||||
|
'edit' => Pages\EditMount::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
11
app/Filament/Resources/MountResource/Pages/CreateMount.php
Normal file
11
app/Filament/Resources/MountResource/Pages/CreateMount.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\MountResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\MountResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateMount extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = MountResource::class;
|
||||||
|
}
|
19
app/Filament/Resources/MountResource/Pages/EditMount.php
Normal file
19
app/Filament/Resources/MountResource/Pages/EditMount.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\MountResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\MountResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditMount extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = MountResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
19
app/Filament/Resources/MountResource/Pages/ListMounts.php
Normal file
19
app/Filament/Resources/MountResource/Pages/ListMounts.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\MountResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\MountResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListMounts extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = MountResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
163
app/Filament/Resources/NodeResource.php
Normal file
163
app/Filament/Resources/NodeResource.php
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\NodeResource\Pages;
|
||||||
|
use App\Models\Node;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class NodeResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Node::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-server-2';
|
||||||
|
|
||||||
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Toggle::make('behind_proxy')
|
||||||
|
->helperText('If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.')
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('memory')
|
||||||
|
->required()
|
||||||
|
->numeric(),
|
||||||
|
Forms\Components\TextInput::make('memory_overallocate')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(0),
|
||||||
|
Forms\Components\TextInput::make('disk')
|
||||||
|
->required()
|
||||||
|
->numeric(),
|
||||||
|
Forms\Components\TextInput::make('disk_overallocate')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(0),
|
||||||
|
Forms\Components\TextInput::make('upload_size')
|
||||||
|
->required()
|
||||||
|
->integer()
|
||||||
|
->default(100),
|
||||||
|
Forms\Components\TextInput::make('daemonListen')
|
||||||
|
->required()
|
||||||
|
->integer()
|
||||||
|
->label('Daemon Port')
|
||||||
|
->default(8080),
|
||||||
|
Forms\Components\TextInput::make('daemonSFTP')
|
||||||
|
->required()
|
||||||
|
->integer()
|
||||||
|
->label('Daemon SFTP Port')
|
||||||
|
->default(2022),
|
||||||
|
Forms\Components\TextInput::make('daemonBase')
|
||||||
|
->required()
|
||||||
|
->maxLength(191)
|
||||||
|
->default('/home/daemon-files'),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('public')
|
||||||
|
->label('Node Visibility')
|
||||||
|
->inline()
|
||||||
|
->default(true)
|
||||||
|
->helperText('By setting a node to private you will be denying the ability to auto-deploy to this node.')
|
||||||
|
->options([
|
||||||
|
true => 'Public',
|
||||||
|
false => 'Private',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
true => 'warning',
|
||||||
|
false => 'danger',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
true => 'tabler-eye-check',
|
||||||
|
false => 'tabler-eye-cancel',
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->searchable(false)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('uuid')
|
||||||
|
->label('UUID')
|
||||||
|
->searchable()
|
||||||
|
->hidden(),
|
||||||
|
Tables\Columns\IconColumn::make('health')
|
||||||
|
->alignCenter()
|
||||||
|
->state(fn (Node $node) => $node)
|
||||||
|
->view('livewire.columns.version-column'),
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->icon('tabler-server-2')
|
||||||
|
->sortable()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('fqdn')
|
||||||
|
->visibleFrom('md')
|
||||||
|
->label('Address')
|
||||||
|
->icon('tabler-network')
|
||||||
|
->sortable()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('memory')
|
||||||
|
->visibleFrom('sm')
|
||||||
|
->icon('tabler-device-desktop-analytics')
|
||||||
|
->numeric()
|
||||||
|
->suffix(' GB')
|
||||||
|
->formatStateUsing(fn ($state) => number_format($state / 1000, 2))
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('disk')
|
||||||
|
->visibleFrom('sm')
|
||||||
|
->icon('tabler-file')
|
||||||
|
->numeric()
|
||||||
|
->suffix(' GB')
|
||||||
|
->formatStateUsing(fn ($state) => number_format($state / 1000, 2))
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\IconColumn::make('scheme')
|
||||||
|
->visibleFrom('xl')
|
||||||
|
->label('SSL')
|
||||||
|
->trueIcon('tabler-lock')
|
||||||
|
->falseIcon('tabler-lock-open-off')
|
||||||
|
->state(fn (Node $node) => $node->scheme === 'https'),
|
||||||
|
Tables\Columns\IconColumn::make('public')
|
||||||
|
->visibleFrom('lg')
|
||||||
|
->trueIcon('tabler-eye-check')
|
||||||
|
->falseIcon('tabler-eye-cancel'),
|
||||||
|
Tables\Columns\TextColumn::make('servers_count')
|
||||||
|
->visibleFrom('sm')
|
||||||
|
->counts('servers')
|
||||||
|
->label('Servers')
|
||||||
|
->sortable()
|
||||||
|
->icon('tabler-brand-docker'),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListNodes::route('/'),
|
||||||
|
'create' => Pages\CreateNode::route('/create'),
|
||||||
|
'edit' => Pages\EditNode::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
200
app/Filament/Resources/NodeResource/Pages/CreateNode.php
Normal file
200
app/Filament/Resources/NodeResource/Pages/CreateNode.php
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\NodeResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\NodeResource;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
|
||||||
|
class CreateNode extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = NodeResource::class;
|
||||||
|
|
||||||
|
protected static bool $canCreateAnother = false;
|
||||||
|
|
||||||
|
protected ?string $subheading = 'which is a machine that runs your Servers';
|
||||||
|
|
||||||
|
public function form(Forms\Form $form): Forms\Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->columns([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 3,
|
||||||
|
'md' => 3,
|
||||||
|
'lg' => 4,
|
||||||
|
])
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('fqdn')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->autofocus()
|
||||||
|
->live(debounce: 1500)
|
||||||
|
->rule('prohibited', fn ($state) => is_ip($state) && request()->isSecure())
|
||||||
|
->label(fn ($state) => is_ip($state) ? 'IP Address' : 'Domain Name')
|
||||||
|
->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
|
||||||
|
->helperText(function ($state) {
|
||||||
|
if (is_ip($state)) {
|
||||||
|
if (request()->isSecure()) {
|
||||||
|
return '
|
||||||
|
Your panel is currently secured via an SSL certificate and that means your nodes require one too.
|
||||||
|
You must use a domain name, because you cannot get SSL certificates for IP Addresses
|
||||||
|
';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return "
|
||||||
|
This is the domain name that points to your node's IP Address.
|
||||||
|
If you've already set up this, you can verify it by checking the next field!
|
||||||
|
";
|
||||||
|
})
|
||||||
|
->hintColor('danger')
|
||||||
|
->hint(function ($state) {
|
||||||
|
if (is_ip($state) && request()->isSecure()) {
|
||||||
|
return 'You cannot connect to an IP Address over SSL';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
->afterStateUpdated(function (Forms\Set $set, ?string $state) {
|
||||||
|
$set('dns', null);
|
||||||
|
$set('ip', null);
|
||||||
|
|
||||||
|
[$subdomain] = str($state)->explode('.', 2);
|
||||||
|
if (!is_numeric($subdomain)) {
|
||||||
|
$set('name', $subdomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$state || is_ip($state)) {
|
||||||
|
$set('dns', null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validRecords = gethostbynamel($state);
|
||||||
|
if ($validRecords) {
|
||||||
|
$set('dns', true);
|
||||||
|
|
||||||
|
$set('ip', collect($validRecords)->first());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$set('dns', false);
|
||||||
|
})
|
||||||
|
->maxLength(191),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('ip')
|
||||||
|
->disabled()
|
||||||
|
->hidden(),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('dns')
|
||||||
|
->label('DNS Record Check')
|
||||||
|
->helperText('This lets you know if your DNS record correctly points to an IP Address.')
|
||||||
|
->disabled()
|
||||||
|
->inline()
|
||||||
|
->default(null)
|
||||||
|
->hint(fn (Forms\Get $get) => $get('ip'))
|
||||||
|
->hintColor('success')
|
||||||
|
->options([
|
||||||
|
true => 'Valid',
|
||||||
|
false => 'Invalid',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
true => 'success',
|
||||||
|
false => 'danger',
|
||||||
|
])
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 1,
|
||||||
|
'lg' => 1,
|
||||||
|
]),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('daemonListen')
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 1,
|
||||||
|
'lg' => 1,
|
||||||
|
])
|
||||||
|
->label('Port')
|
||||||
|
->helperText('If you are running the daemon behind Cloudflare you should set the daemon port to 8443 to allow websocket proxying over SSL.')
|
||||||
|
->minValue(0)
|
||||||
|
->maxValue(65536)
|
||||||
|
->default(8080)
|
||||||
|
->required()
|
||||||
|
->integer(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->label('Display Name')
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 1,
|
||||||
|
'lg' => 2,
|
||||||
|
])
|
||||||
|
->required()
|
||||||
|
->regex('/[a-zA-Z0-9_\.\- ]+/')
|
||||||
|
->helperText('This name is for display only and can be changed later.')
|
||||||
|
->maxLength(100),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('scheme')
|
||||||
|
->label('Communicate over SSL')
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 1,
|
||||||
|
'lg' => 1,
|
||||||
|
])
|
||||||
|
->required()
|
||||||
|
->inline()
|
||||||
|
->helperText(function (Forms\Get $get) {
|
||||||
|
if (request()->isSecure()) {
|
||||||
|
return new HtmlString('Your Panel is using a secure SSL connection,<br>so your Daemon must too.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_ip($get('fqdn'))) {
|
||||||
|
return 'An IP address cannot use SSL.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
|
||||||
|
->options([
|
||||||
|
'http' => 'HTTP',
|
||||||
|
'https' => 'HTTPS (SSL)',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
'http' => 'warning',
|
||||||
|
'https' => 'success',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
'http' => 'tabler-lock-open-off',
|
||||||
|
'https' => 'tabler-lock',
|
||||||
|
])
|
||||||
|
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||||
|
|
||||||
|
Forms\Components\Textarea::make('description')
|
||||||
|
->hidden()
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 2,
|
||||||
|
'lg' => 4,
|
||||||
|
])
|
||||||
|
->rows(5),
|
||||||
|
|
||||||
|
Forms\Components\Hidden::make('skipValidation')->default(true),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRedirectUrlParameters(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'tab' => '-configuration-tab',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
130
app/Filament/Resources/NodeResource/Pages/EditNode.php
Normal file
130
app/Filament/Resources/NodeResource/Pages/EditNode.php
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\NodeResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\NodeResource;
|
||||||
|
use App\Models\Allocation;
|
||||||
|
use App\Models\Node;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Components\Tabs;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||||
|
|
||||||
|
class EditNode extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = NodeResource::class;
|
||||||
|
|
||||||
|
public function form(Forms\Form $form): Forms\Form
|
||||||
|
{
|
||||||
|
return $form->schema([
|
||||||
|
Tabs::make('Tabs')
|
||||||
|
->columns([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 3,
|
||||||
|
'md' => 3,
|
||||||
|
'lg' => 4,
|
||||||
|
])
|
||||||
|
->persistTabInQueryString()
|
||||||
|
->columnSpanFull()
|
||||||
|
->tabs([
|
||||||
|
Tabs\Tab::make('Basic Settings')
|
||||||
|
->icon('tabler-server')
|
||||||
|
->schema((new CreateNode())->form($form)->getComponents()),
|
||||||
|
Tabs\Tab::make('Advanced Settings')
|
||||||
|
->icon('tabler-server-cog'),
|
||||||
|
Tabs\Tab::make('Configuration')
|
||||||
|
->icon('tabler-code')
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Placeholder::make('instructions')
|
||||||
|
->columnSpanFull()
|
||||||
|
->content(new HtmlString('
|
||||||
|
Save this file to your <span title="usually /etc/pelican/">daemon\'s root directory</span>, named <code>config.yml</code>
|
||||||
|
')),
|
||||||
|
Forms\Components\Textarea::make('config')
|
||||||
|
->label('/etc/pelican/config.yml')
|
||||||
|
->disabled()
|
||||||
|
->rows(19)
|
||||||
|
->hintAction(CopyAction::make())
|
||||||
|
->columnSpanFull(),
|
||||||
|
]),
|
||||||
|
Tabs\Tab::make('Allocations')
|
||||||
|
->icon('tabler-plug-connected')
|
||||||
|
->columns(4)
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Repeater::make('allocations')
|
||||||
|
->orderColumn('server_id')
|
||||||
|
->columnSpan(1)
|
||||||
|
->columns(4)
|
||||||
|
->relationship()
|
||||||
|
->addActionLabel('Create New Allocation')
|
||||||
|
->addAction(fn ($action) => $action->color('info'))
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('ip')
|
||||||
|
->label('IP Address'),
|
||||||
|
Forms\Components\TextInput::make('ip_alias')
|
||||||
|
->label('Alias'),
|
||||||
|
Forms\Components\TextInput::make('port')
|
||||||
|
->minValue(0)
|
||||||
|
->maxValue(65535)
|
||||||
|
->numeric(),
|
||||||
|
Forms\Components\TextInput::make('server')
|
||||||
|
->formatStateUsing(fn (Allocation $allocation) => $allocation->server?->name)
|
||||||
|
->readOnly()
|
||||||
|
->placeholder('No Server'),
|
||||||
|
]),
|
||||||
|
Forms\Components\Section::make('Assign New Allocations')
|
||||||
|
->columnSpan(2)
|
||||||
|
->inlineLabel()
|
||||||
|
->headerActions([
|
||||||
|
Forms\Components\Actions\Action::make('submit')
|
||||||
|
->color('success')
|
||||||
|
->action(function () {
|
||||||
|
// ...
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('ip')
|
||||||
|
->label('IP Address')
|
||||||
|
->placeholder('0.0.0.0')
|
||||||
|
->helperText('IP address to assign ports to')
|
||||||
|
->columnSpanFull(),
|
||||||
|
Forms\Components\TextInput::make('ip_alias')
|
||||||
|
->label('Alias')
|
||||||
|
->placeholder('minecraft')
|
||||||
|
->helperText('Display name to help you remember')
|
||||||
|
->columnSpanFull(),
|
||||||
|
Forms\Components\TextInput::make('ports')
|
||||||
|
->label('Ports')
|
||||||
|
->placeholder('25565')
|
||||||
|
->helperText('Individual ports or port ranges here separated by commas or spaces')
|
||||||
|
->columnSpanFull(),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function mutateFormDataBeforeFill(array $data): array
|
||||||
|
{
|
||||||
|
$node = Node::findOrFail($data['id']);
|
||||||
|
|
||||||
|
$data['config'] = $node->getYamlConfiguration();
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSteps(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
19
app/Filament/Resources/NodeResource/Pages/ListNodes.php
Normal file
19
app/Filament/Resources/NodeResource/Pages/ListNodes.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\NodeResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\NodeResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListNodes extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = NodeResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
661
app/Filament/Resources/ServerResource.php
Normal file
661
app/Filament/Resources/ServerResource.php
Normal file
@ -0,0 +1,661 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Enums\ContainerStatus;
|
||||||
|
use App\Enums\ServerState;
|
||||||
|
use App\Filament\Resources\ServerResource\Pages;
|
||||||
|
use App\Models\Allocation;
|
||||||
|
use App\Models\Egg;
|
||||||
|
use App\Models\Node;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
|
use App\Services\Allocations\AssignmentService;
|
||||||
|
use Closure;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class ServerResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Server::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-brand-docker';
|
||||||
|
|
||||||
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->columns([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 2,
|
||||||
|
'md' => 4,
|
||||||
|
'lg' => 6,
|
||||||
|
])
|
||||||
|
->schema([
|
||||||
|
Forms\Components\ToggleButtons::make('docker')
|
||||||
|
->label('Container Status')
|
||||||
|
->hiddenOn('create')
|
||||||
|
->disableOptionWhen(fn ($state, $value) => $state !== $value)
|
||||||
|
->formatStateUsing(function ($state, Server $server) {
|
||||||
|
if ($server->node_id === null) {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var DaemonServerRepository $service */
|
||||||
|
$service = resolve(DaemonServerRepository::class);
|
||||||
|
$details = $service->setServer($server)->getDetails();
|
||||||
|
|
||||||
|
return $details['state'] ?? 'unknown';
|
||||||
|
})
|
||||||
|
->options(collect(ContainerStatus::cases())->mapWithKeys(
|
||||||
|
fn (ContainerStatus $status) => [$status->value => str($status->value)->ucwords()]
|
||||||
|
))
|
||||||
|
->colors(collect(ContainerStatus::cases())->mapWithKeys(
|
||||||
|
fn (ContainerStatus $status) => [$status->value => $status->color()]
|
||||||
|
))
|
||||||
|
->icons(collect(ContainerStatus::cases())->mapWithKeys(
|
||||||
|
fn (ContainerStatus $status) => [$status->value => $status->icon()]
|
||||||
|
))
|
||||||
|
->grouped()
|
||||||
|
->columnSpanFull()
|
||||||
|
->inline(),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('status')
|
||||||
|
->label('Server State')
|
||||||
|
->helperText('')
|
||||||
|
->hiddenOn('create')
|
||||||
|
->disableOptionWhen(fn ($state, $value) => $state !== $value)
|
||||||
|
->formatStateUsing(fn ($state) => $state ?? 'none')
|
||||||
|
->options(collect(ServerState::cases())->mapWithKeys(
|
||||||
|
fn (ServerState $state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()]
|
||||||
|
))
|
||||||
|
->colors(collect(ServerState::cases())->mapWithKeys(
|
||||||
|
fn (ServerState $state) => [$state->value => $state->color()]
|
||||||
|
))
|
||||||
|
->icons(collect(ServerState::cases())->mapWithKeys(
|
||||||
|
fn (ServerState $state) => [$state->value => $state->icon()]
|
||||||
|
))
|
||||||
|
->grouped()
|
||||||
|
->columnSpanFull()
|
||||||
|
->inline(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('external_id')
|
||||||
|
->maxLength(191)
|
||||||
|
->hidden(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->prefixIcon('tabler-server')
|
||||||
|
->label('Display Name')
|
||||||
|
->suffixAction(Forms\Components\Actions\Action::make('random')
|
||||||
|
->icon('tabler-dice-' . random_int(1, 6))
|
||||||
|
->color('primary')
|
||||||
|
->action(function (Forms\Set $set, Forms\Get $get) {
|
||||||
|
$egg = Egg::find($get('egg_id'));
|
||||||
|
$prefix = $egg ? str($egg->name)->lower()->kebab() . '-' : '';
|
||||||
|
|
||||||
|
$set('name', $prefix . fake()->domainWord);
|
||||||
|
}))
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 4,
|
||||||
|
'md' => 2,
|
||||||
|
'lg' => 3,
|
||||||
|
])
|
||||||
|
->required()
|
||||||
|
->maxLength(191),
|
||||||
|
|
||||||
|
Forms\Components\Select::make('owner_id')
|
||||||
|
->prefixIcon('tabler-user')
|
||||||
|
->default(auth()->user()->id)
|
||||||
|
->label('Owner')
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 4,
|
||||||
|
'md' => 2,
|
||||||
|
'lg' => 3,
|
||||||
|
])
|
||||||
|
->relationship('user', 'username')
|
||||||
|
->searchable()
|
||||||
|
->preload()
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\Select::make('node_id')
|
||||||
|
->disabledOn('edit')
|
||||||
|
->prefixIcon('tabler-server-2')
|
||||||
|
->default(fn () => Node::query()->latest()->first()?->id)
|
||||||
|
->columnSpan(2)
|
||||||
|
->live()
|
||||||
|
->relationship('node', 'name')
|
||||||
|
->searchable()
|
||||||
|
->preload()
|
||||||
|
->afterStateUpdated(fn (Forms\Set $set) => $set('allocation_id', null))
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\Select::make('allocation_id')
|
||||||
|
->preload()
|
||||||
|
->live()
|
||||||
|
->prefixIcon('tabler-network')
|
||||||
|
->label('Primary Allocation')
|
||||||
|
->columnSpan(2)
|
||||||
|
->disabled(fn (Forms\Get $get) => $get('node_id') === null)
|
||||||
|
->searchable(['ip', 'port', 'ip_alias'])
|
||||||
|
->afterStateUpdated(function (Forms\Set $set) {
|
||||||
|
$set('allocation_additional', null);
|
||||||
|
$set('allocation_additional.needstobeastringhere.extra_allocations', null);
|
||||||
|
})
|
||||||
|
->getOptionLabelFromRecordUsing(
|
||||||
|
fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
|
||||||
|
($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
|
||||||
|
)
|
||||||
|
->placeholder(function (Forms\Get $get) {
|
||||||
|
$node = Node::find($get('node_id'));
|
||||||
|
|
||||||
|
if ($node?->allocations) {
|
||||||
|
return 'Select an Allocation';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Create a New Allocation';
|
||||||
|
})
|
||||||
|
->relationship(
|
||||||
|
'allocation',
|
||||||
|
'ip',
|
||||||
|
fn (Builder $query, Forms\Get $get) => $query
|
||||||
|
->where('node_id', $get('node_id'))
|
||||||
|
->whereNull('server_id'),
|
||||||
|
)
|
||||||
|
->createOptionForm(fn (Forms\Get $get) => [
|
||||||
|
Forms\Components\TextInput::make('allocation_ip')
|
||||||
|
->ipv4()
|
||||||
|
->datalist(function () use ($get) {
|
||||||
|
$node = Node::find($get('node_id'));
|
||||||
|
if (is_ip($node->fqdn)) {
|
||||||
|
return [$node->fqdn];
|
||||||
|
}
|
||||||
|
|
||||||
|
$validRecords = gethostbynamel($node->fqdn);
|
||||||
|
if (!$validRecords) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $validRecords ?: [];
|
||||||
|
})
|
||||||
|
->label('IP Address')
|
||||||
|
->helperText("Usually your machine's public IP unless you are port forwarding.")
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('allocation_alias')
|
||||||
|
->label('Alias')
|
||||||
|
->default(null)
|
||||||
|
->datalist([
|
||||||
|
$get('name'),
|
||||||
|
Egg::find($get('egg_id'))?->name,
|
||||||
|
])
|
||||||
|
->helperText('This is just a display only name to help you recognize what this Allocation is used for.')
|
||||||
|
->required(false),
|
||||||
|
Forms\Components\TagsInput::make('allocation_ports')
|
||||||
|
->placeholder('Examples: 27015, 27017-27019')
|
||||||
|
->helperText('
|
||||||
|
These are the ports that users can connect to this Server through.
|
||||||
|
They usually consist of the port forwarded ones.
|
||||||
|
')
|
||||||
|
->label('Ports')
|
||||||
|
->required(),
|
||||||
|
])
|
||||||
|
->createOptionUsing(function (array $data, Forms\Get $get): int {
|
||||||
|
return collect(
|
||||||
|
resolve(AssignmentService::class)->handle(Node::find($get('node_id')), $data)
|
||||||
|
)->first();
|
||||||
|
})
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\Repeater::make('allocation_additional')
|
||||||
|
->label('Additional Allocations')
|
||||||
|
->columnSpan(2)
|
||||||
|
->addActionLabel('Add Allocation')
|
||||||
|
->disabled(fn (Forms\Get $get) => $get('allocation_id') === null)
|
||||||
|
// ->addable() TODO disable when all allocations are taken
|
||||||
|
// ->addable() TODO disable until first additional allocation is selected
|
||||||
|
->simple(
|
||||||
|
Forms\Components\Select::make('extra_allocations')
|
||||||
|
->live()
|
||||||
|
->preload()
|
||||||
|
->disableOptionsWhenSelectedInSiblingRepeaterItems()
|
||||||
|
->prefixIcon('tabler-network')
|
||||||
|
->label('Additional Allocations')
|
||||||
|
->columnSpan(2)
|
||||||
|
->disabled(fn (Forms\Get $get) => $get('../../node_id') === null)
|
||||||
|
->searchable(['ip', 'port', 'ip_alias'])
|
||||||
|
->getOptionLabelFromRecordUsing(
|
||||||
|
fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
|
||||||
|
($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
|
||||||
|
)
|
||||||
|
->placeholder('Select additional Allocations')
|
||||||
|
->relationship(
|
||||||
|
'allocations',
|
||||||
|
'ip',
|
||||||
|
fn (Builder $query, Forms\Get $get, Forms\Components\Select $component, $state) => $query
|
||||||
|
->where('node_id', $get('../../node_id'))
|
||||||
|
->whereNotIn(
|
||||||
|
'id',
|
||||||
|
collect(($repeater = $component->getParentRepeater())->getState())
|
||||||
|
->pluck(
|
||||||
|
(string) str($component->getStatePath())
|
||||||
|
->after("{$repeater->getStatePath()}.")
|
||||||
|
->after('.'),
|
||||||
|
)
|
||||||
|
->flatten()
|
||||||
|
->diff(Arr::wrap($state))
|
||||||
|
->filter(fn (mixed $siblingItemState): bool => filled($siblingItemState))
|
||||||
|
->add($get('../../allocation_id'))
|
||||||
|
)
|
||||||
|
->whereNull('server_id'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Forms\Components\Textarea::make('description')
|
||||||
|
->hidden()
|
||||||
|
->default('')
|
||||||
|
->required()
|
||||||
|
->columnSpanFull(),
|
||||||
|
|
||||||
|
Forms\Components\Select::make('egg_id')
|
||||||
|
->disabledOn('edit')
|
||||||
|
->prefixIcon('tabler-egg')
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 2,
|
||||||
|
'md' => 2,
|
||||||
|
'lg' => 6,
|
||||||
|
])
|
||||||
|
->relationship('egg', 'name')
|
||||||
|
->searchable()
|
||||||
|
->preload()
|
||||||
|
->live()
|
||||||
|
->afterStateUpdated(function ($state, Forms\Set $set) {
|
||||||
|
$egg = Egg::find($state);
|
||||||
|
$set('startup', $egg->startup);
|
||||||
|
|
||||||
|
$variables = $egg->variables ?? [];
|
||||||
|
$serverVariables = collect();
|
||||||
|
foreach ($variables as $variable) {
|
||||||
|
$serverVariables->add($variable->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
$variables = [];
|
||||||
|
$set($path = 'server_variables', $serverVariables->all());
|
||||||
|
for ($i = 0; $i < $serverVariables->count(); $i++) {
|
||||||
|
$set("$path.$i.variable_value", $serverVariables[$i]['default_value']);
|
||||||
|
$set("$path.$i.variable_id", $serverVariables[$i]['id']);
|
||||||
|
$variables[$serverVariables[$i]['env_variable']] = $serverVariables[$i]['default_value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$set('environment', $variables);
|
||||||
|
})
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('skip_scripts')
|
||||||
|
->label('Run Egg Install Script?')
|
||||||
|
->default(false)
|
||||||
|
->options([
|
||||||
|
false => 'Yes',
|
||||||
|
true => 'Skip',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
false => 'primary',
|
||||||
|
true => 'danger',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
false => 'tabler-code',
|
||||||
|
true => 'tabler-code-off',
|
||||||
|
])
|
||||||
|
->inline()
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('custom_image')
|
||||||
|
->live()
|
||||||
|
->label('Custom Image?')
|
||||||
|
->default(false)
|
||||||
|
->formatStateUsing(function ($state, Forms\Get $get) {
|
||||||
|
if ($state !== null) {
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
$images = Egg::find($get('egg_id'))->docker_images ?? [];
|
||||||
|
|
||||||
|
return !in_array($get('image'), $images);
|
||||||
|
})
|
||||||
|
->options([
|
||||||
|
false => 'No',
|
||||||
|
true => 'Yes',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
false => 'primary',
|
||||||
|
true => 'danger',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
false => 'tabler-settings-cancel',
|
||||||
|
true => 'tabler-settings-check',
|
||||||
|
])
|
||||||
|
->inline(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('image')
|
||||||
|
->hidden(fn (Forms\Get $get) => !$get('custom_image'))
|
||||||
|
->disabled(fn (Forms\Get $get) => !$get('custom_image'))
|
||||||
|
->label('Docker Image')
|
||||||
|
->placeholder('Enter a custom Image')
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 2,
|
||||||
|
'md' => 2,
|
||||||
|
'lg' => 4,
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\Select::make('image')
|
||||||
|
->hidden(fn (Forms\Get $get) => $get('custom_image'))
|
||||||
|
->disabled(fn (Forms\Get $get) => $get('custom_image'))
|
||||||
|
->label('Docker Image')
|
||||||
|
->prefixIcon('tabler-brand-docker')
|
||||||
|
->options(function (Forms\Get $get, Forms\Set $set) {
|
||||||
|
$images = Egg::find($get('egg_id'))->docker_images ?? [];
|
||||||
|
|
||||||
|
$set('image', collect($images)->first());
|
||||||
|
|
||||||
|
return $images;
|
||||||
|
})
|
||||||
|
->disabled(fn (Forms\Components\Select $component) => empty($component->getOptions()))
|
||||||
|
->selectablePlaceholder(false)
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 2,
|
||||||
|
'md' => 2,
|
||||||
|
'lg' => 4,
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Forms\Components\Fieldset::make('Application Feature Limits')
|
||||||
|
->inlineLabel()
|
||||||
|
->hiddenOn('create')
|
||||||
|
->columns(3)
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('allocation_limit')
|
||||||
|
->suffixIcon('tabler-network')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(0),
|
||||||
|
Forms\Components\TextInput::make('database_limit')
|
||||||
|
->suffixIcon('tabler-database')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(0),
|
||||||
|
Forms\Components\TextInput::make('backup_limit')
|
||||||
|
->suffixIcon('tabler-copy-check')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->default(0),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Forms\Components\Textarea::make('startup')
|
||||||
|
->hintIcon('tabler-code')
|
||||||
|
->label('Startup Command')
|
||||||
|
->required()
|
||||||
|
->live()
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 4,
|
||||||
|
'md' => 4,
|
||||||
|
'lg' => 6,
|
||||||
|
])
|
||||||
|
->rows(function ($state) {
|
||||||
|
return str($state)->explode("\n")->reduce(
|
||||||
|
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
Forms\Components\Hidden::make('environment')->default([]),
|
||||||
|
|
||||||
|
Forms\Components\Hidden::make('start_on_completion')->default(true),
|
||||||
|
|
||||||
|
Forms\Components\Section::make('Egg Variables')
|
||||||
|
->icon('tabler-eggs')
|
||||||
|
->iconColor('primary')
|
||||||
|
->collapsible()
|
||||||
|
->collapsed()
|
||||||
|
->columnSpan(([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 4,
|
||||||
|
'md' => 4,
|
||||||
|
'lg' => 6,
|
||||||
|
]))
|
||||||
|
->schema([
|
||||||
|
Forms\Components\Placeholder::make('Select an egg first to show its variables!')
|
||||||
|
->hidden(fn (Forms\Get $get) => !empty($get('server_variables'))),
|
||||||
|
|
||||||
|
Forms\Components\Repeater::make('server_variables')
|
||||||
|
->relationship('serverVariables')
|
||||||
|
->grid(2)
|
||||||
|
->reorderable(false)
|
||||||
|
->addable(false)
|
||||||
|
->deletable(false)
|
||||||
|
->default([])
|
||||||
|
->hidden(fn ($state) => empty($state))
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('variable_value')
|
||||||
|
->rules([
|
||||||
|
fn (Forms\Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) {
|
||||||
|
$validator = Validator::make(['validatorkey' => $value], [
|
||||||
|
'validatorkey' => $get('rules'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
$message = str($validator->errors()->first())->replace('validatorkey', $get('name'));
|
||||||
|
|
||||||
|
$fail($message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
])
|
||||||
|
->label(fn (Forms\Get $get) => $get('name'))
|
||||||
|
//->hint('Rule')
|
||||||
|
->hintIcon('tabler-code')
|
||||||
|
->hintIconTooltip(fn (Forms\Get $get) => $get('rules'))
|
||||||
|
->prefix(fn (Forms\Get $get) => '{{' . $get('env_variable') . '}}')
|
||||||
|
->helperText(fn (Forms\Get $get) => empty($get('description')) ? '—' : $get('description'))
|
||||||
|
->maxLength(191),
|
||||||
|
|
||||||
|
Forms\Components\Hidden::make('variable_id')->default(0),
|
||||||
|
])
|
||||||
|
->columnSpan(2),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Forms\Components\Section::make('Resource Management')
|
||||||
|
// ->hiddenOn('create')
|
||||||
|
->collapsed()
|
||||||
|
->icon('tabler-server-cog')
|
||||||
|
->iconColor('primary')
|
||||||
|
->columns(2)
|
||||||
|
->columnSpan(([
|
||||||
|
'default' => 2,
|
||||||
|
'sm' => 4,
|
||||||
|
'md' => 4,
|
||||||
|
'lg' => 6,
|
||||||
|
]))
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('memory')
|
||||||
|
->default(0)
|
||||||
|
->label('Allocated Memory')
|
||||||
|
->suffix('MB')
|
||||||
|
->required()
|
||||||
|
->numeric(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('swap')
|
||||||
|
->default(0)
|
||||||
|
->label('Swap Memory')
|
||||||
|
->suffix('MB')
|
||||||
|
->helperText('0 disables swap and -1 allows unlimited swap')
|
||||||
|
->minValue(-1)
|
||||||
|
->required()
|
||||||
|
->numeric(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('disk')
|
||||||
|
->default(0)
|
||||||
|
->label('Disk Space Limit')
|
||||||
|
->suffix('MB')
|
||||||
|
->required()
|
||||||
|
->numeric(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('cpu')
|
||||||
|
->default(0)
|
||||||
|
->label('CPU Limit')
|
||||||
|
->suffix('%')
|
||||||
|
->required()
|
||||||
|
->numeric(),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('threads')
|
||||||
|
->hint('Advanced')
|
||||||
|
->hintColor('danger')
|
||||||
|
->helperText('Examples: 0, 0-1,3, or 0,1,3,4')
|
||||||
|
->label('CPU Pinning')
|
||||||
|
->suffixIcon('tabler-cpu')
|
||||||
|
->maxLength(191),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('io')
|
||||||
|
->helperText('The IO performance relative to other running containers')
|
||||||
|
->label('Block IO Proportion')
|
||||||
|
->required()
|
||||||
|
->minValue(0)
|
||||||
|
->maxValue(1000)
|
||||||
|
->step(10)
|
||||||
|
->default(0)
|
||||||
|
->numeric(),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('oom_disabled')
|
||||||
|
->label('OOM Killer')
|
||||||
|
->inline()
|
||||||
|
->default(false)
|
||||||
|
->options([
|
||||||
|
false => 'Disabled',
|
||||||
|
true => 'Enabled',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
false => 'success',
|
||||||
|
true => 'danger',
|
||||||
|
])
|
||||||
|
->icons([
|
||||||
|
false => 'tabler-sword-off',
|
||||||
|
true => 'tabler-sword',
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->searchable(false)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('status')
|
||||||
|
->default('unknown')
|
||||||
|
->badge()
|
||||||
|
->default(function (Server $server) {
|
||||||
|
if ($server->status !== null) {
|
||||||
|
return $server->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
$statuses = collect($server->retrieveStatus())
|
||||||
|
->mapWithKeys(function ($status) {
|
||||||
|
return [$status['configuration']['uuid'] => $status['state']];
|
||||||
|
})->all();
|
||||||
|
|
||||||
|
return $statuses[$server->uuid] ?? 'node_fail';
|
||||||
|
})
|
||||||
|
->icon(fn ($state) => match ($state) {
|
||||||
|
'node_fail' => 'tabler-server-off',
|
||||||
|
'running' => 'tabler-heartbeat',
|
||||||
|
'removing' => 'tabler-heart-x',
|
||||||
|
'offline' => 'tabler-heart-off',
|
||||||
|
'paused' => 'tabler-heart-pause',
|
||||||
|
'installing' => 'tabler-heart-bolt',
|
||||||
|
'suspended' => 'tabler-heart-cancel',
|
||||||
|
default => 'tabler-heart-question',
|
||||||
|
})
|
||||||
|
->color(fn (string $state): string => match ($state) {
|
||||||
|
'running' => 'success',
|
||||||
|
'installing', 'restarting' => 'primary',
|
||||||
|
'paused', 'removing' => 'warning',
|
||||||
|
'node_fail', 'install_failed', 'suspended' => 'danger',
|
||||||
|
default => 'gray',
|
||||||
|
}),
|
||||||
|
|
||||||
|
Tables\Columns\TextColumn::make('uuid')
|
||||||
|
->hidden()
|
||||||
|
->label('UUID')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->icon('tabler-brand-docker')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('node.name')
|
||||||
|
->icon('tabler-server-2')
|
||||||
|
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('egg.name')
|
||||||
|
->icon('tabler-egg')
|
||||||
|
->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('user.username')
|
||||||
|
->icon('tabler-user')
|
||||||
|
->label('Owner')
|
||||||
|
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\SelectColumn::make('allocation_id')
|
||||||
|
->label('Primary Allocation')
|
||||||
|
->options(fn ($state, Server $server) => $server->allocations->mapWithKeys(
|
||||||
|
fn ($allocation) => [$allocation->id => $allocation->address])
|
||||||
|
)
|
||||||
|
->selectablePlaceholder(false)
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('image')->hidden(),
|
||||||
|
Tables\Columns\TextColumn::make('backups_count')
|
||||||
|
->counts('backups')
|
||||||
|
->label('Backups')
|
||||||
|
->icon('tabler-file-download')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
// Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListServers::route('/'),
|
||||||
|
'create' => Pages\CreateServer::route('/create'),
|
||||||
|
'edit' => Pages\EditServer::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
31
app/Filament/Resources/ServerResource/Pages/CreateServer.php
Normal file
31
app/Filament/Resources/ServerResource/Pages/CreateServer.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\ServerResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\ServerResource;
|
||||||
|
use App\Services\Servers\ServerCreationService;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CreateServer extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = ServerResource::class;
|
||||||
|
protected static bool $canCreateAnother = false;
|
||||||
|
|
||||||
|
protected function handleRecordCreation(array $data): Model
|
||||||
|
{
|
||||||
|
$data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all();
|
||||||
|
|
||||||
|
/** @var ServerCreationService $service */
|
||||||
|
$service = resolve(ServerCreationService::class);
|
||||||
|
|
||||||
|
$server = $service->handle($data);
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// protected function getRedirectUrl(): string
|
||||||
|
// {
|
||||||
|
// return $this->getResource()::getUrl('edit');
|
||||||
|
// }
|
||||||
|
}
|
25
app/Filament/Resources/ServerResource/Pages/EditServer.php
Normal file
25
app/Filament/Resources/ServerResource/Pages/EditServer.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\ServerResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\ServerResource;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Services\Servers\ServerDeletionService;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditServer extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = ServerResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\Action::make('Delete')
|
||||||
|
->successRedirectUrl($this->getResource()::getUrl('index'))
|
||||||
|
->color('danger')
|
||||||
|
->action(fn (Server $server) => resolve(ServerDeletionService::class)->handle($server))
|
||||||
|
->requiresConfirmation(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
19
app/Filament/Resources/ServerResource/Pages/ListServers.php
Normal file
19
app/Filament/Resources/ServerResource/Pages/ListServers.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\ServerResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\ServerResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListServers extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = ServerResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
153
app/Filament/Resources/UserResource.php
Normal file
153
app/Filament/Resources/UserResource.php
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Resources\UserResource\Pages;
|
||||||
|
use App\Filament\Resources\UserResource\RelationManagers;
|
||||||
|
use App\Models\User;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Components\Section;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class UserResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = User::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'tabler-users';
|
||||||
|
|
||||||
|
protected static ?string $recordTitleAttribute = 'username';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Section::make()->schema([
|
||||||
|
Forms\Components\TextInput::make('username')->required()->maxLength(191),
|
||||||
|
Forms\Components\TextInput::make('email')->email()->required()->maxLength(191),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('name_first')
|
||||||
|
->maxLength(191)
|
||||||
|
->hidden(fn (string $operation): bool => $operation === 'create')
|
||||||
|
->label('First Name'),
|
||||||
|
Forms\Components\TextInput::make('name_last')
|
||||||
|
->maxLength(191)
|
||||||
|
->hidden(fn (string $operation): bool => $operation === 'create')
|
||||||
|
->label('Last Name'),
|
||||||
|
|
||||||
|
Forms\Components\TextInput::make('password')
|
||||||
|
->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
|
||||||
|
->dehydrated(fn (?string $state): bool => filled($state))
|
||||||
|
->required(fn (string $operation): bool => $operation === 'create')
|
||||||
|
->password(),
|
||||||
|
|
||||||
|
Forms\Components\ToggleButtons::make('root_admin')
|
||||||
|
->label('Administrator (Root)')
|
||||||
|
->options([
|
||||||
|
false => 'No',
|
||||||
|
true => 'Admin',
|
||||||
|
])
|
||||||
|
->colors([
|
||||||
|
false => 'primary',
|
||||||
|
true => 'danger',
|
||||||
|
])
|
||||||
|
->disableOptionWhen(function (string $operation, $value, User $user) {
|
||||||
|
if ($operation !== 'edit' || $value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user->isLastRootAdmin();
|
||||||
|
})
|
||||||
|
->hint(fn (User $user) => $user->isLastRootAdmin() ? 'This is the last root administrator!' : '')
|
||||||
|
->helperText(fn (User $user) => $user->isLastRootAdmin() ? 'You must have at least one root administrator in your system.' : '')
|
||||||
|
->hintColor('warning')
|
||||||
|
->inline()
|
||||||
|
->required()
|
||||||
|
->default(false),
|
||||||
|
|
||||||
|
Forms\Components\Hidden::make('skipValidation')->default(true),
|
||||||
|
Forms\Components\Select::make('language')
|
||||||
|
->required()
|
||||||
|
->hidden()
|
||||||
|
->default('en')
|
||||||
|
->options(fn (User $user) => $user->getAvailableLanguages()),
|
||||||
|
])->columns(2),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->searchable(false)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\ImageColumn::make('picture')
|
||||||
|
->visibleFrom('lg')
|
||||||
|
->label('')
|
||||||
|
->extraImgAttributes(['class' => 'rounded-full'])
|
||||||
|
->defaultImageUrl(fn (User $user) => 'https://gravatar.com/avatar/' . md5(strtolower($user->email))),
|
||||||
|
Tables\Columns\TextColumn::make('external_id')
|
||||||
|
->searchable()
|
||||||
|
->hidden(),
|
||||||
|
Tables\Columns\TextColumn::make('uuid')
|
||||||
|
->label('UUID')
|
||||||
|
->hidden()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('username')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('email')
|
||||||
|
->searchable()
|
||||||
|
->icon('tabler-mail'),
|
||||||
|
Tables\Columns\IconColumn::make('root_admin')
|
||||||
|
->visibleFrom('md')
|
||||||
|
->label('Admin')
|
||||||
|
->boolean()
|
||||||
|
->trueIcon('tabler-star')
|
||||||
|
->falseIcon('tabler-star-off')
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\IconColumn::make('use_totp')->label('2FA')
|
||||||
|
->visibleFrom('lg')
|
||||||
|
->icon(fn (User $user) => $user->use_totp ? 'tabler-lock' : 'tabler-lock-open-off')
|
||||||
|
->boolean()->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('servers_count')
|
||||||
|
->counts('servers')
|
||||||
|
->icon('tabler-server')
|
||||||
|
->label('Servers'),
|
||||||
|
Tables\Columns\TextColumn::make('subusers_count')
|
||||||
|
->visibleFrom('sm')
|
||||||
|
->counts('subusers')
|
||||||
|
->icon('tabler-users')
|
||||||
|
// ->formatStateUsing(fn (string $state, $record): string => (string) ($record->servers_count + $record->subusers_count))
|
||||||
|
->label('Subuser Accounts'),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
RelationManagers\ServersRelationManager::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListUsers::route('/'),
|
||||||
|
'create' => Pages\CreateUser::route('/create'),
|
||||||
|
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
11
app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
11
app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\UserResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateUser extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
}
|
183
app/Filament/Resources/UserResource/Pages/EditProfile.php
Normal file
183
app/Filament/Resources/UserResource/Pages/EditProfile.php
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Models\ActivityLog;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\Users\TwoFactorSetupService;
|
||||||
|
use chillerlan\QRCode\Common\EccLevel;
|
||||||
|
use chillerlan\QRCode\Common\Version;
|
||||||
|
use chillerlan\QRCode\QRCode;
|
||||||
|
use chillerlan\QRCode\QROptions;
|
||||||
|
use Filament\Forms\Components\Placeholder;
|
||||||
|
use Filament\Forms\Components\Repeater;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\Tabs;
|
||||||
|
use Filament\Forms\Components\TagsInput;
|
||||||
|
use Filament\Forms\Components\Tabs\Tab;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Get;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\HtmlString;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||||
|
{
|
||||||
|
protected function getForms(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'form' => $this->form(
|
||||||
|
$this->makeForm()
|
||||||
|
->schema([
|
||||||
|
Tabs::make()->schema([
|
||||||
|
Tab::make('Account')
|
||||||
|
->icon('tabler-user')
|
||||||
|
->schema([
|
||||||
|
TextInput::make('username')
|
||||||
|
->disabled()
|
||||||
|
->readOnly()
|
||||||
|
->maxLength(191)
|
||||||
|
->unique(ignoreRecord: true)
|
||||||
|
->autofocus(),
|
||||||
|
|
||||||
|
TextInput::make('email')
|
||||||
|
->prefixIcon('tabler-mail')
|
||||||
|
->email()
|
||||||
|
->required()
|
||||||
|
->maxLength(191)
|
||||||
|
->unique(ignoreRecord: true),
|
||||||
|
|
||||||
|
TextInput::make('password')
|
||||||
|
->password()
|
||||||
|
->prefixIcon('tabler-password')
|
||||||
|
->revealable(filament()->arePasswordsRevealable())
|
||||||
|
->rule(Password::default())
|
||||||
|
->autocomplete('new-password')
|
||||||
|
->dehydrated(fn ($state): bool => filled($state))
|
||||||
|
->dehydrateStateUsing(fn ($state): string => Hash::make($state))
|
||||||
|
->live(debounce: 500)
|
||||||
|
->same('passwordConfirmation'),
|
||||||
|
|
||||||
|
TextInput::make('passwordConfirmation')
|
||||||
|
->password()
|
||||||
|
->prefixIcon('tabler-password-fingerprint')
|
||||||
|
->revealable(filament()->arePasswordsRevealable())
|
||||||
|
->required()
|
||||||
|
->visible(fn (Get $get): bool => filled($get('password')))
|
||||||
|
->dehydrated(false),
|
||||||
|
|
||||||
|
Select::make('language')
|
||||||
|
->required()
|
||||||
|
->prefixIcon('tabler-flag')
|
||||||
|
->live()
|
||||||
|
->default('en')
|
||||||
|
->helperText(fn (User $user, $state) => new HtmlString($user->isLanguageTranslated($state) ? '' : "
|
||||||
|
Your language ($state) has not been translated yet!
|
||||||
|
But never fear, you can help fix that by
|
||||||
|
<a style='color: rgb(56, 189, 248)' href='https://crowdin.com/project/pelican-dev'>contributing directly here</a>.
|
||||||
|
")
|
||||||
|
)
|
||||||
|
->options(fn (User $user) => $user->getAvailableLanguages()),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Tab::make('2FA')
|
||||||
|
->icon('tabler-shield-lock')
|
||||||
|
->schema(function () {
|
||||||
|
|
||||||
|
if ($this->getUser()->use_totp) {
|
||||||
|
return [
|
||||||
|
Placeholder::make('2FA already enabled!'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$setupService = app(TwoFactorSetupService::class);
|
||||||
|
|
||||||
|
['image_url_data' => $url] = $setupService->handle($this->getUser());
|
||||||
|
|
||||||
|
$options = new QROptions([
|
||||||
|
'svgLogo' => public_path('pelican.svg'),
|
||||||
|
'addLogoSpace' => true,
|
||||||
|
'logoSpaceWidth' => 13,
|
||||||
|
'logoSpaceHeight' => 13,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// https://github.com/chillerlan/php-qrcode/blob/main/examples/svgWithLogo.php
|
||||||
|
|
||||||
|
// SVG logo options (see extended class)
|
||||||
|
$options->svgLogo = public_path('pelican.svg'); // logo from: https://github.com/simple-icons/simple-icons
|
||||||
|
$options->svgLogoScale = 0.05;
|
||||||
|
// $options->svgLogoCssClass = 'dark';
|
||||||
|
|
||||||
|
// QROptions
|
||||||
|
$options->version = Version::AUTO;
|
||||||
|
// $options->outputInterface = QRSvgWithLogo::class;
|
||||||
|
$options->outputBase64 = false;
|
||||||
|
$options->eccLevel = EccLevel::H; // ECC level H is necessary when using logos
|
||||||
|
$options->addQuietzone = true;
|
||||||
|
// $options->drawLightModules = true;
|
||||||
|
$options->connectPaths = true;
|
||||||
|
$options->drawCircularModules = true;
|
||||||
|
// $options->circleRadius = 0.45;
|
||||||
|
|
||||||
|
$options->svgDefs = '<linearGradient id="gradient" x1="100%" y2="100%">
|
||||||
|
<stop stop-color="#7dd4fc" offset="0"/>
|
||||||
|
<stop stop-color="#38bdf8" offset="0.5"/>
|
||||||
|
<stop stop-color="#0369a1" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<style><![CDATA[
|
||||||
|
.dark{fill: url(#gradient);}
|
||||||
|
.light{fill: #000;}
|
||||||
|
]]></style>';
|
||||||
|
|
||||||
|
$image = (new QRCode($options))->render($url);
|
||||||
|
|
||||||
|
return [
|
||||||
|
Placeholder::make('qr')
|
||||||
|
->label('Scan QR Code')
|
||||||
|
->content(fn () => new HtmlString("
|
||||||
|
<div style='width: 300px'>$image</div>
|
||||||
|
"))
|
||||||
|
->default('asdfasdf'),
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
|
||||||
|
Tab::make('API Keys')
|
||||||
|
->icon('tabler-key')
|
||||||
|
->schema([
|
||||||
|
Placeholder::make('Coming soon!'),
|
||||||
|
TagsInput::make('allowed_ips')
|
||||||
|
->placeholder('Example: 127.0.0.1 or 192.168.1.1')
|
||||||
|
->label('Whitelisted IPv4 Addresses')
|
||||||
|
->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
|
||||||
|
->columnSpanFull()
|
||||||
|
->hidden()
|
||||||
|
->default(null),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Tab::make('SSH Keys')
|
||||||
|
->icon('tabler-lock-code')
|
||||||
|
->schema([
|
||||||
|
Placeholder::make('Coming soon!'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Tab::make('Activity')
|
||||||
|
->icon('tabler-history')
|
||||||
|
->schema([
|
||||||
|
Repeater::make('activity')
|
||||||
|
->deletable(false)
|
||||||
|
->addable(false)
|
||||||
|
->relationship()
|
||||||
|
|
||||||
|
->schema([
|
||||||
|
Placeholder::make('activity!')->label('')->content(fn (ActivityLog $log) => new HtmlString($log->htmlable())),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
->operation('edit')
|
||||||
|
->model($this->getUser())
|
||||||
|
->statePath('data')
|
||||||
|
->inlineLabel(!static::isSimple()),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
19
app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
19
app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\UserResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditUser extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
19
app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
19
app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\UserResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListUsers extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\UserResource\RelationManagers;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
|
|
||||||
|
class ServersRelationManager extends RelationManager
|
||||||
|
{
|
||||||
|
protected static string $relationship = 'servers';
|
||||||
|
|
||||||
|
public function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->searchable(false)
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('uuid')
|
||||||
|
->hidden()
|
||||||
|
->label('UUID')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->icon('tabler-brand-docker')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('node.name')
|
||||||
|
->icon('tabler-server-2')
|
||||||
|
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('egg.name')
|
||||||
|
->icon('tabler-egg')
|
||||||
|
->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\SelectColumn::make('allocation.id')
|
||||||
|
->label('Primary Allocation')
|
||||||
|
->options(fn ($state, Server $server) => [$server->allocation->id => $server->allocation->address])
|
||||||
|
->selectablePlaceholder(false)
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('image')->hidden(),
|
||||||
|
Tables\Columns\TextColumn::make('databases_count')
|
||||||
|
->counts('databases')
|
||||||
|
->label('Databases')
|
||||||
|
->icon('tabler-database')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('backups_count')
|
||||||
|
->counts('backups')
|
||||||
|
->label('Backups')
|
||||||
|
->icon('tabler-file-download')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,152 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Models\Egg;
|
|
||||||
use Ramsey\Uuid\Uuid;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use App\Models\Mount;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\MountFormRequest;
|
|
||||||
|
|
||||||
class MountController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* MountController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the mount overview page.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return view('admin.mounts.index', [
|
|
||||||
'mounts' => Mount::query()->withCount(['eggs', 'nodes'])->get(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the mount view page.
|
|
||||||
*/
|
|
||||||
public function view(string $id): View
|
|
||||||
{
|
|
||||||
return view('admin.mounts.view', [
|
|
||||||
'mount' => Mount::with(['eggs', 'nodes'])->findOrFail($id),
|
|
||||||
'eggs' => Egg::all(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to create new mount.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function create(MountFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$model = (new Mount())->fill($request->validated());
|
|
||||||
$model->forceFill(['uuid' => Uuid::uuid4()->toString()]);
|
|
||||||
|
|
||||||
$model->saveOrFail();
|
|
||||||
$mount = $model->fresh();
|
|
||||||
|
|
||||||
$this->alert->success('Mount was created successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to update or delete location.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function update(MountFormRequest $request, Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
if ($request->input('action') === 'delete') {
|
|
||||||
return $this->delete($mount);
|
|
||||||
}
|
|
||||||
|
|
||||||
$mount->forceFill($request->validated())->save();
|
|
||||||
|
|
||||||
$this->alert->success('Mount was updated successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a location from the system.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public function delete(Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
$mount->delete();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds eggs to the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function addEggs(Request $request, Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
$validatedData = $request->validate([
|
|
||||||
'eggs' => 'required|exists:eggs,id',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$eggs = $validatedData['eggs'] ?? [];
|
|
||||||
if (count($eggs) > 0) {
|
|
||||||
$mount->eggs()->attach($eggs);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->alert->success('Mount was updated successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds nodes to the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function addNodes(Request $request, Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
$data = $request->validate(['nodes' => 'required|exists:nodes,id']);
|
|
||||||
|
|
||||||
$nodes = $data['nodes'] ?? [];
|
|
||||||
if (count($nodes) > 0) {
|
|
||||||
$mount->nodes()->attach($nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->alert->success('Mount was updated successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an egg from the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function deleteEgg(Mount $mount, int $egg_id): Response
|
|
||||||
{
|
|
||||||
$mount->eggs()->detach($egg_id);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a node from the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function deleteNode(Mount $mount, int $node_id): Response
|
|
||||||
{
|
|
||||||
$mount->nodes()->detach($node_id);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,29 +39,8 @@ class NodeViewController extends Controller
|
|||||||
->where('node_id', '=', $node->id)
|
->where('node_id', '=', $node->id)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
$usageStats = Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])
|
|
||||||
->mapWithKeys(function ($value, $key) use ($node) {
|
|
||||||
$maxUsage = $node->{$key};
|
|
||||||
if ($node->{$key . '_overallocate'} > 0) {
|
|
||||||
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
$percent = ($value / $maxUsage) * 100;
|
|
||||||
|
|
||||||
return [
|
|
||||||
$key => [
|
|
||||||
'value' => number_format($value),
|
|
||||||
'max' => number_format($maxUsage),
|
|
||||||
'percent' => $percent,
|
|
||||||
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
})
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return view('admin.nodes.view.index', [
|
return view('admin.nodes.view.index', [
|
||||||
'node' => $node,
|
'node' => $node,
|
||||||
'stats' => $usageStats,
|
|
||||||
'version' => $this->versionService,
|
'version' => $this->versionService,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Servers;
|
namespace App\Http\Controllers\Admin\Servers;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use App\Models\DatabaseHost;
|
use App\Models\DatabaseHost;
|
||||||
use App\Models\Egg;
|
use App\Models\Egg;
|
||||||
use App\Models\Mount;
|
use App\Models\Mount;
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Exceptions\DisplayException;
|
use App\Exceptions\DisplayException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@ -22,14 +22,14 @@ class ServerViewController extends Controller
|
|||||||
* ServerViewController constructor.
|
* ServerViewController constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private EnvironmentService $environmentService,
|
private readonly EnvironmentService $environmentService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the index view for a server.
|
* Returns the index view for a server.
|
||||||
*/
|
*/
|
||||||
public function index(Request $request, Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
return view('admin.servers.view.index', compact('server'));
|
return view('admin.servers.view.index', compact('server'));
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ class ServerViewController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Returns the server details page.
|
* Returns the server details page.
|
||||||
*/
|
*/
|
||||||
public function details(Request $request, Server $server): View
|
public function details(Server $server): View
|
||||||
{
|
{
|
||||||
return view('admin.servers.view.details', compact('server'));
|
return view('admin.servers.view.details', compact('server'));
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ class ServerViewController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Returns a view of server build settings.
|
* Returns a view of server build settings.
|
||||||
*/
|
*/
|
||||||
public function build(Request $request, Server $server): View
|
public function build(Server $server): View
|
||||||
{
|
{
|
||||||
$allocations = $server->node->allocations->toBase();
|
$allocations = $server->node->allocations->toBase();
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ class ServerViewController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Returns the server startup management page.
|
* Returns the server startup management page.
|
||||||
*/
|
*/
|
||||||
public function startup(Request $request, Server $server): View
|
public function startup(Server $server): View
|
||||||
{
|
{
|
||||||
$variables = $this->environmentService->handle($server);
|
$variables = $this->environmentService->handle($server);
|
||||||
$eggs = Egg::all()->keyBy('id');
|
$eggs = Egg::all()->keyBy('id');
|
||||||
@ -76,7 +76,7 @@ class ServerViewController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Returns all the databases that exist for the server.
|
* Returns all the databases that exist for the server.
|
||||||
*/
|
*/
|
||||||
public function database(Request $request, Server $server): View
|
public function database(Server $server): View
|
||||||
{
|
{
|
||||||
return view('admin.servers.view.database', [
|
return view('admin.servers.view.database', [
|
||||||
'hosts' => DatabaseHost::all(),
|
'hosts' => DatabaseHost::all(),
|
||||||
@ -87,7 +87,7 @@ class ServerViewController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Returns all the mounts that exist for the server.
|
* Returns all the mounts that exist for the server.
|
||||||
*/
|
*/
|
||||||
public function mounts(Request $request, Server $server): View
|
public function mounts(Server $server): View
|
||||||
{
|
{
|
||||||
$server->load('mounts');
|
$server->load('mounts');
|
||||||
|
|
||||||
@ -108,9 +108,9 @@ class ServerViewController extends Controller
|
|||||||
*
|
*
|
||||||
* @throws \App\Exceptions\DisplayException
|
* @throws \App\Exceptions\DisplayException
|
||||||
*/
|
*/
|
||||||
public function manage(Request $request, Server $server): View
|
public function manage(Server $server): View
|
||||||
{
|
{
|
||||||
if ($server->status === Server::STATUS_INSTALL_FAILED) {
|
if ($server->status === ServerState::InstallFailed) {
|
||||||
throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.');
|
throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ class ServerViewController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Returns the server deletion page.
|
* Returns the server deletion page.
|
||||||
*/
|
*/
|
||||||
public function delete(Request $request, Server $server): View
|
public function delete(Server $server): View
|
||||||
{
|
{
|
||||||
return view('admin.servers.view.delete', compact('server'));
|
return view('admin.servers.view.delete', compact('server'));
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
@ -71,11 +72,11 @@ class ServersController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function toggleInstall(Server $server): RedirectResponse
|
public function toggleInstall(Server $server): RedirectResponse
|
||||||
{
|
{
|
||||||
if ($server->status === Server::STATUS_INSTALL_FAILED) {
|
if ($server->status === ServerState::InstallFailed) {
|
||||||
throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
|
throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$server->status = $server->isInstalled() ? Server::STATUS_INSTALLING : null;
|
$server->status = $server->isInstalled() ? ServerState::Installing : null;
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.install_toggled'))->flash();
|
$this->alert->success(trans('admin/server.alerts.install_toggled'))->flash();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api\Client\Servers;
|
namespace App\Http\Controllers\Api\Client\Servers;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\Backup;
|
use App\Models\Backup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -212,7 +213,7 @@ class BackupController extends ClientApiController
|
|||||||
|
|
||||||
// Update the status right away for the server so that we know not to allow certain
|
// Update the status right away for the server so that we know not to allow certain
|
||||||
// actions against it via the Panel API.
|
// actions against it via the Panel API.
|
||||||
$server->update(['status' => Server::STATUS_RESTORING_BACKUP]);
|
$server->update(['status' => ServerState::RestoringBackup]);
|
||||||
|
|
||||||
$this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate'));
|
$this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate'));
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api\Remote\Servers;
|
namespace App\Http\Controllers\Api\Remote\Servers;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use App\Models\Backup;
|
use App\Models\Backup;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@ -81,7 +82,7 @@ class ServerDetailsController extends Controller
|
|||||||
->latest('timestamp'),
|
->latest('timestamp'),
|
||||||
])
|
])
|
||||||
->where('node_id', $node->id)
|
->where('node_id', $node->id)
|
||||||
->where('status', Server::STATUS_RESTORING_BACKUP)
|
->where('status', ServerState::RestoringBackup)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$this->connection->transaction(function () use ($node, $servers) {
|
$this->connection->transaction(function () use ($node, $servers) {
|
||||||
@ -108,7 +109,7 @@ class ServerDetailsController extends Controller
|
|||||||
// Update any server marked as installing or restoring as being in a normal state
|
// Update any server marked as installing or restoring as being in a normal state
|
||||||
// at this point in the process.
|
// at this point in the process.
|
||||||
Server::query()->where('node_id', $node->id)
|
Server::query()->where('node_id', $node->id)
|
||||||
->whereIn('status', [Server::STATUS_INSTALLING, Server::STATUS_RESTORING_BACKUP])
|
->whereIn('status', [ServerState::Installing, ServerState::RestoringBackup])
|
||||||
->update(['status' => null]);
|
->update(['status' => null]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api\Remote\Servers;
|
namespace App\Http\Controllers\Api\Remote\Servers;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@ -36,16 +37,16 @@ class ServerInstallController extends Controller
|
|||||||
|
|
||||||
// Make sure the type of failure is accurate
|
// Make sure the type of failure is accurate
|
||||||
if (!$request->boolean('successful')) {
|
if (!$request->boolean('successful')) {
|
||||||
$status = Server::STATUS_INSTALL_FAILED;
|
$status = ServerState::InstallFailed;
|
||||||
|
|
||||||
if ($request->boolean('reinstall')) {
|
if ($request->boolean('reinstall')) {
|
||||||
$status = Server::STATUS_REINSTALL_FAILED;
|
$status = ServerState::ReinstallFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the server suspended if it's already suspended
|
// Keep the server suspended if it's already suspended
|
||||||
if ($server->status === Server::STATUS_SUSPENDED) {
|
if ($server->status === ServerState::Suspended) {
|
||||||
$status = Server::STATUS_SUSPENDED;
|
$status = ServerState::Suspended;
|
||||||
}
|
}
|
||||||
|
|
||||||
$previouslyInstalledAt = $server->installed_at;
|
$previouslyInstalledAt = $server->installed_at;
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
|
||||||
|
|
||||||
use App\Models\Mount;
|
|
||||||
|
|
||||||
class MountFormRequest extends AdminFormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Set up the validation rules to use for these requests.
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
if ($this->method() === 'PATCH') {
|
|
||||||
/** @var Mount $mount */
|
|
||||||
$mount = $this->route()->parameter('mount');
|
|
||||||
|
|
||||||
return Mount::getRulesForUpdate($mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Mount::getRules();
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin\Node;
|
namespace App\Http\Requests\Admin\Node;
|
||||||
|
|
||||||
use App\Rules\Fqdn;
|
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
use App\Http\Requests\Admin\AdminFormRequest;
|
use App\Http\Requests\Admin\AdminFormRequest;
|
||||||
|
|
||||||
@ -17,9 +16,6 @@ class NodeFormRequest extends AdminFormRequest
|
|||||||
return Node::getRulesForUpdate($this->route()->parameter('node'));
|
return Node::getRulesForUpdate($this->route()->parameter('node'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = Node::getRules();
|
return Node::getRules();
|
||||||
$data['fqdn'][] = Fqdn::make('scheme');
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
app/Livewire/NodeSystemInformation.php
Normal file
30
app/Livewire/NodeSystemInformation.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Node;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class NodeSystemInformation extends Component
|
||||||
|
{
|
||||||
|
public Node $node;
|
||||||
|
public string $sizeClasses;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.node-system-information');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function placeholder()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div>
|
||||||
|
<x-filament::icon
|
||||||
|
:icon="'tabler-heart-question'"
|
||||||
|
@class(['fi-ta-icon-item', $sizeClasses, 'fi-color-custom text-custom-500 dark:text-custom-400', 'fi-color-warning'])
|
||||||
|
@style([\Filament\Support\get_color_css_variables('warning', shades: [400, 500], alias: 'tables::columns.icon-column.item')])
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
@ -144,4 +144,29 @@ class ActivityLog extends Model
|
|||||||
Event::dispatch(new ActivityLogged($model));
|
Event::dispatch(new ActivityLogged($model));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function htmlable()
|
||||||
|
{
|
||||||
|
$user = $this->actor;
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
$user = new User([
|
||||||
|
'email' => 'system@pelican.dev',
|
||||||
|
'username' => 'system',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = __('activity.'.str($this->event)->replace(':', '.'));
|
||||||
|
|
||||||
|
return "
|
||||||
|
<div style='display: flex; align-items: center;'>
|
||||||
|
<img width='50px' height='50px' src='{$user->getFilamentAvatarUrl()}' style='margin-right: 15px' />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>$user->username — $this->event</p>
|
||||||
|
<p>$event</p>
|
||||||
|
<p>$this->ip — <span title='{$this->timestamp->format('M j, Y g:ia')}'>{$this->timestamp->diffForHumans()}</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Exceptions\Service\Allocation\ServerUsingAllocationException;
|
use App\Exceptions\Service\Allocation\ServerUsingAllocationException;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,9 +112,16 @@ class Allocation extends Model
|
|||||||
return !is_null($this->ip_alias);
|
return !is_null($this->ip_alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function address(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => "$this->ip:$this->port",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function toString(): string
|
public function toString(): string
|
||||||
{
|
{
|
||||||
return sprintf('%s:%s', $this->ip, $this->port);
|
return $this->address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,6 +83,8 @@ class ApiKey extends Model
|
|||||||
*/
|
*/
|
||||||
public const KEY_LENGTH = 32;
|
public const KEY_LENGTH = 32;
|
||||||
|
|
||||||
|
public const RESOURCES = ['servers', 'nodes', 'allocations', 'users', 'eggs', 'database_hosts', 'server_databases'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The table associated with the model.
|
* The table associated with the model.
|
||||||
*/
|
*/
|
||||||
@ -92,12 +94,21 @@ class ApiKey extends Model
|
|||||||
* Fields that are mass assignable.
|
* Fields that are mass assignable.
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'key_type',
|
||||||
'identifier',
|
'identifier',
|
||||||
'token',
|
'token',
|
||||||
'allowed_ips',
|
'allowed_ips',
|
||||||
'memo',
|
'memo',
|
||||||
'last_used_at',
|
'last_used_at',
|
||||||
'expires_at',
|
'expires_at',
|
||||||
|
'r_' . AdminAcl::RESOURCE_USERS,
|
||||||
|
'r_' . AdminAcl::RESOURCE_ALLOCATIONS,
|
||||||
|
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS,
|
||||||
|
'r_' . AdminAcl::RESOURCE_SERVER_DATABASES,
|
||||||
|
'r_' . AdminAcl::RESOURCE_EGGS,
|
||||||
|
'r_' . AdminAcl::RESOURCE_NODES,
|
||||||
|
'r_' . AdminAcl::RESOURCE_SERVERS,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,7 +198,7 @@ class ApiKey extends Model
|
|||||||
{
|
{
|
||||||
Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]);
|
Assert::oneOf($type, [self::TYPE_ACCOUNT, self::TYPE_APPLICATION]);
|
||||||
|
|
||||||
return $type === self::TYPE_ACCOUNT ? 'ptlc_' : 'ptla_';
|
return $type === self::TYPE_ACCOUNT ? 'plcn_' : 'peli_';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +65,11 @@ class DatabaseHost extends Model
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRouteKeyName(): string
|
||||||
|
{
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the node associated with a database host.
|
* Gets the node associated with a database host.
|
||||||
*/
|
*/
|
||||||
|
@ -152,6 +152,11 @@ class Egg extends Model
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRouteKeyName(): string
|
||||||
|
{
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the install script for the egg; if egg is copying from another
|
* Returns the install script for the egg; if egg is copying from another
|
||||||
* it will return the copied script.
|
* it will return the copied script.
|
||||||
|
@ -71,8 +71,8 @@ class Mount extends Model
|
|||||||
* Blacklisted source paths.
|
* Blacklisted source paths.
|
||||||
*/
|
*/
|
||||||
public static $invalidSourcePaths = [
|
public static $invalidSourcePaths = [
|
||||||
'/etc/panel',
|
'/etc/pelican',
|
||||||
'/var/lib/panel/volumes',
|
'/var/lib/pelican/volumes',
|
||||||
'/srv/daemon-data',
|
'/srv/daemon-data',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -115,4 +115,9 @@ class Mount extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsToMany(Server::class);
|
return $this->belongsToMany(Server::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRouteKeyName(): string
|
||||||
|
{
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Exceptions\Service\HasActiveServersException;
|
||||||
|
use App\Repositories\Daemon\DaemonConfigurationRepository;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
@ -79,9 +83,9 @@ class Node extends Model
|
|||||||
'fqdn' => 'required|string',
|
'fqdn' => 'required|string',
|
||||||
'scheme' => 'required',
|
'scheme' => 'required',
|
||||||
'behind_proxy' => 'boolean',
|
'behind_proxy' => 'boolean',
|
||||||
'memory' => 'required|numeric|min:1',
|
'memory' => 'required|numeric|min:0',
|
||||||
'memory_overallocate' => 'required|numeric|min:-1',
|
'memory_overallocate' => 'required|numeric|min:-1',
|
||||||
'disk' => 'required|numeric|min:1',
|
'disk' => 'required|numeric|min:0',
|
||||||
'disk_overallocate' => 'required|numeric|min:-1',
|
'disk_overallocate' => 'required|numeric|min:-1',
|
||||||
'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/',
|
'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/',
|
||||||
'daemon_sftp' => 'required|numeric|between:1,65535',
|
'daemon_sftp' => 'required|numeric|between:1,65535',
|
||||||
@ -96,7 +100,9 @@ class Node extends Model
|
|||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
'public' => true,
|
'public' => true,
|
||||||
'behind_proxy' => false,
|
'behind_proxy' => false,
|
||||||
|
'memory' => 0,
|
||||||
'memory_overallocate' => 0,
|
'memory_overallocate' => 0,
|
||||||
|
'disk' => 0,
|
||||||
'disk_overallocate' => 0,
|
'disk_overallocate' => 0,
|
||||||
'daemon_base' => '/var/lib/panel/volumes',
|
'daemon_base' => '/var/lib/panel/volumes',
|
||||||
'daemon_sftp' => 2022,
|
'daemon_sftp' => 2022,
|
||||||
@ -117,6 +123,26 @@ class Node extends Model
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRouteKeyName(): string
|
||||||
|
{
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
static::creating(function (self $node) {
|
||||||
|
$node->uuid = Str::uuid();
|
||||||
|
$node->daemon_token = encrypt(Str::random(self::DAEMON_TOKEN_LENGTH));
|
||||||
|
$node->daemon_token_id = Str::random(self::DAEMON_TOKEN_ID_LENGTH);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
static::deleting(function (self $node) {
|
||||||
|
throw_if($node->servers()->count(), new HasActiveServersException(trans('exceptions.egg.delete_has_servers')));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the connection address to use when making calls to this node.
|
* Get the connection address to use when making calls to this node.
|
||||||
*/
|
*/
|
||||||
@ -240,4 +266,40 @@ class Node extends Model
|
|||||||
];
|
];
|
||||||
})->values();
|
})->values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function systemInformation(): array
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
try {
|
||||||
|
return resolve(DaemonConfigurationRepository::class)
|
||||||
|
->setNode($this)
|
||||||
|
->getSystemInformation(connectTimeout: 3);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$message = str($exception->getMessage());
|
||||||
|
|
||||||
|
if ($message->startsWith('cURL error 6: Could not resolve host')) {
|
||||||
|
$message = str('Could not resolve host');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($message->startsWith('cURL error 28: Failed to connect to ')) {
|
||||||
|
$message = $message->after('cURL error 28: ')->before(' after ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['exception' => $message->toString()];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function serverStatuses(): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
/** @var \Illuminate\Http\Client\Response $response */
|
||||||
|
$response = Http::daemon($this)->connectTimeout(1)->timeout(1)->get('/api/servers');
|
||||||
|
$statuses = $response->json();
|
||||||
|
} catch (Exception) {
|
||||||
|
$statuses = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache()->remember("nodes.$this->id.servers", now()->addSeconds(2), fn () => $statuses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use App\Exceptions\Http\Connection\DaemonConnectionException;
|
use App\Exceptions\Http\Connection\DaemonConnectionException;
|
||||||
use GuzzleHttp\Exception\GuzzleException;
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
@ -112,12 +113,6 @@ class Server extends Model
|
|||||||
*/
|
*/
|
||||||
public const RESOURCE_NAME = 'server';
|
public const RESOURCE_NAME = 'server';
|
||||||
|
|
||||||
public const STATUS_INSTALLING = 'installing';
|
|
||||||
public const STATUS_INSTALL_FAILED = 'install_failed';
|
|
||||||
public const STATUS_REINSTALL_FAILED = 'reinstall_failed';
|
|
||||||
public const STATUS_SUSPENDED = 'suspended';
|
|
||||||
public const STATUS_RESTORING_BACKUP = 'restoring_backup';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The table associated with the model.
|
* The table associated with the model.
|
||||||
*/
|
*/
|
||||||
@ -128,7 +123,7 @@ class Server extends Model
|
|||||||
* on server instances unless the user specifies otherwise in the request.
|
* on server instances unless the user specifies otherwise in the request.
|
||||||
*/
|
*/
|
||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
'status' => self::STATUS_INSTALLING,
|
'status' => ServerState::Installing,
|
||||||
'oom_disabled' => true,
|
'oom_disabled' => true,
|
||||||
'installed_at' => null,
|
'installed_at' => null,
|
||||||
];
|
];
|
||||||
@ -152,7 +147,7 @@ class Server extends Model
|
|||||||
'status' => 'nullable|string',
|
'status' => 'nullable|string',
|
||||||
'memory' => 'required|numeric|min:0',
|
'memory' => 'required|numeric|min:0',
|
||||||
'swap' => 'required|numeric|min:-1',
|
'swap' => 'required|numeric|min:-1',
|
||||||
'io' => 'required|numeric|between:10,1000',
|
'io' => 'required|numeric|between:0,1000',
|
||||||
'cpu' => 'required|numeric|min:0',
|
'cpu' => 'required|numeric|min:0',
|
||||||
'threads' => 'nullable|regex:/^[0-9-,]+$/',
|
'threads' => 'nullable|regex:/^[0-9-,]+$/',
|
||||||
'oom_disabled' => 'sometimes|boolean',
|
'oom_disabled' => 'sometimes|boolean',
|
||||||
@ -171,6 +166,7 @@ class Server extends Model
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'node_id' => 'integer',
|
'node_id' => 'integer',
|
||||||
|
'status' => ServerState::class,
|
||||||
'skip_scripts' => 'boolean',
|
'skip_scripts' => 'boolean',
|
||||||
'owner_id' => 'integer',
|
'owner_id' => 'integer',
|
||||||
'memory' => 'integer',
|
'memory' => 'integer',
|
||||||
@ -203,12 +199,12 @@ class Server extends Model
|
|||||||
|
|
||||||
public function isInstalled(): bool
|
public function isInstalled(): bool
|
||||||
{
|
{
|
||||||
return $this->status !== self::STATUS_INSTALLING && $this->status !== self::STATUS_INSTALL_FAILED;
|
return $this->status !== ServerState::Installing && $this->status !== ServerState::InstallFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isSuspended(): bool
|
public function isSuspended(): bool
|
||||||
{
|
{
|
||||||
return $this->status === self::STATUS_SUSPENDED;
|
return $this->status === ServerState::Suspended;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -251,6 +247,11 @@ class Server extends Model
|
|||||||
return $this->hasOne(Egg::class, 'id', 'egg_id');
|
return $this->hasOne(Egg::class, 'id', 'egg_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function eggVariables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets information for the egg variables associated with this server.
|
* Gets information for the egg variables associated with this server.
|
||||||
*/
|
*/
|
||||||
@ -267,6 +268,11 @@ class Server extends Model
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function serverVariables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ServerVariable::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets information for the node associated with this server.
|
* Gets information for the node associated with this server.
|
||||||
*/
|
*/
|
||||||
@ -349,7 +355,7 @@ class Server extends Model
|
|||||||
$this->isSuspended() ||
|
$this->isSuspended() ||
|
||||||
$this->node->isUnderMaintenance() ||
|
$this->node->isUnderMaintenance() ||
|
||||||
!$this->isInstalled() ||
|
!$this->isInstalled() ||
|
||||||
$this->status === self::STATUS_RESTORING_BACKUP ||
|
$this->status === ServerState::RestoringBackup ||
|
||||||
!is_null($this->transfer)
|
!is_null($this->transfer)
|
||||||
) {
|
) {
|
||||||
throw new ServerStateConflictException($this);
|
throw new ServerStateConflictException($this);
|
||||||
@ -366,7 +372,7 @@ class Server extends Model
|
|||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
!$this->isInstalled() ||
|
!$this->isInstalled() ||
|
||||||
$this->status === self::STATUS_RESTORING_BACKUP ||
|
$this->status === ServerState::RestoringBackup ||
|
||||||
!is_null($this->transfer)
|
!is_null($this->transfer)
|
||||||
) {
|
) {
|
||||||
throw new ServerStateConflictException($this);
|
throw new ServerStateConflictException($this);
|
||||||
@ -388,4 +394,9 @@ class Server extends Model
|
|||||||
throw new DaemonConnectionException($exception);
|
throw new DaemonConnectionException($exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function retrieveStatus()
|
||||||
|
{
|
||||||
|
return $this->node->serverStatuses();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,11 @@ namespace App\Models;
|
|||||||
use App\Exceptions\DisplayException;
|
use App\Exceptions\DisplayException;
|
||||||
use App\Rules\Username;
|
use App\Rules\Username;
|
||||||
use App\Facades\Activity;
|
use App\Facades\Activity;
|
||||||
use Illuminate\Support\Collection;
|
use Filament\Models\Contracts\FilamentUser;
|
||||||
|
use Filament\Models\Contracts\HasAvatar;
|
||||||
|
use Filament\Models\Contracts\HasName;
|
||||||
|
use Filament\Panel;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Validation\Rules\In;
|
use Illuminate\Validation\Rules\In;
|
||||||
use Illuminate\Auth\Authenticatable;
|
use Illuminate\Auth\Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
@ -79,7 +83,7 @@ use App\Notifications\SendPasswordReset as ResetPasswordNotification;
|
|||||||
*
|
*
|
||||||
* @mixin \Eloquent
|
* @mixin \Eloquent
|
||||||
*/
|
*/
|
||||||
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
|
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName
|
||||||
{
|
{
|
||||||
use Authenticatable;
|
use Authenticatable;
|
||||||
use Authorizable {can as protected canned; }
|
use Authorizable {can as protected canned; }
|
||||||
@ -139,18 +143,20 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||||||
'language' => 'en',
|
'language' => 'en',
|
||||||
'use_totp' => false,
|
'use_totp' => false,
|
||||||
'totp_secret' => null,
|
'totp_secret' => null,
|
||||||
|
'name_first' => '',
|
||||||
|
'name_last' => '',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rules verifying that the data being stored matches the expectations of the database.
|
* Rules verifying that the data being stored matches the expectations of the database.
|
||||||
*/
|
*/
|
||||||
public static array $validationRules = [
|
public static array $validationRules = [
|
||||||
'uuid' => 'required|string|size:36|unique:users,uuid',
|
'uuid' => 'nullable|string|size:36|unique:users,uuid',
|
||||||
'email' => 'required|email|between:1,191|unique:users,email',
|
'email' => 'required|email|between:1,191|unique:users,email',
|
||||||
'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id',
|
'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id',
|
||||||
'username' => 'required|between:1,191|unique:users,username',
|
'username' => 'required|between:1,191|unique:users,username',
|
||||||
'name_first' => 'required|string|between:1,191',
|
'name_first' => 'nullable|string|between:0,191',
|
||||||
'name_last' => 'required|string|between:1,191',
|
'name_last' => 'nullable|string|between:0,191',
|
||||||
'password' => 'sometimes|nullable|string',
|
'password' => 'sometimes|nullable|string',
|
||||||
'root_admin' => 'boolean',
|
'root_admin' => 'boolean',
|
||||||
'language' => 'string',
|
'language' => 'string',
|
||||||
@ -170,6 +176,12 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
{
|
{
|
||||||
|
static::creating(function (self $user) {
|
||||||
|
$user->uuid = Str::uuid();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
static::deleting(function (self $user) {
|
static::deleting(function (self $user) {
|
||||||
throw_if($user->servers()->count() > 0, new DisplayException(__('admin/user.exceptions.user_has_servers')));
|
throw_if($user->servers()->count() > 0, new DisplayException(__('admin/user.exceptions.user_has_servers')));
|
||||||
|
|
||||||
@ -177,6 +189,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRouteKeyName(): string
|
||||||
|
{
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implement language verification by overriding Eloquence's gather
|
* Implement language verification by overriding Eloquence's gather
|
||||||
* rules function.
|
* rules function.
|
||||||
@ -196,7 +213,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||||||
*/
|
*/
|
||||||
public function toVueObject(): array
|
public function toVueObject(): array
|
||||||
{
|
{
|
||||||
return Collection::make($this->toArray())->except(['id', 'external_id'])->toArray();
|
return collect($this->toArray())->except(['id', 'external_id'])->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -278,6 +295,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||||||
->groupBy('servers.id');
|
->groupBy('servers.id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function subusers(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Subuser::class);
|
||||||
|
}
|
||||||
|
|
||||||
protected function checkPermission(Server $server, string $permission = ''): bool
|
protected function checkPermission(Server $server, string $permission = ''): bool
|
||||||
{
|
{
|
||||||
if ($this->root_admin || $server->owner_id === $this->id) {
|
if ($this->root_admin || $server->owner_id === $this->id) {
|
||||||
@ -313,4 +335,26 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||||||
|
|
||||||
return $this->canned($abilities, $arguments);
|
return $this->canned($abilities, $arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isLastRootAdmin(): bool
|
||||||
|
{
|
||||||
|
$rootAdmins = User::query()->where('root_admin', true)->limit(2)->get();
|
||||||
|
|
||||||
|
return once(fn () => $rootAdmins->count() === 1 && $rootAdmins->first()->is($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canAccessPanel(Panel $panel): bool
|
||||||
|
{
|
||||||
|
return $this->root_admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilamentName(): string
|
||||||
|
{
|
||||||
|
return $this->name_first ?: $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilamentAvatarUrl(): ?string
|
||||||
|
{
|
||||||
|
return 'https://gravatar.com/avatar/' . md5(strtolower($this->email));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
13
app/Policies/EggPolicy.php
Normal file
13
app/Policies/EggPolicy.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class EggPolicy
|
||||||
|
{
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
75
app/Providers/Filament/AdminPanelProvider.php
Normal file
75
app/Providers/Filament/AdminPanelProvider.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers\Filament;
|
||||||
|
|
||||||
|
use App\Filament\Resources\UserResource\Pages\EditProfile;
|
||||||
|
use Filament\Http\Middleware\Authenticate;
|
||||||
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||||
|
use Filament\Panel;
|
||||||
|
use Filament\PanelProvider;
|
||||||
|
use Filament\Support\Colors\Color;
|
||||||
|
use Filament\Support\Facades\FilamentAsset;
|
||||||
|
use Filament\Widgets;
|
||||||
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
|
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||||
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
|
||||||
|
class AdminPanelProvider extends PanelProvider
|
||||||
|
{
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
FilamentAsset::registerCssVariables([
|
||||||
|
'sidebar-width' => '12rem !important',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function panel(Panel $panel): Panel
|
||||||
|
{
|
||||||
|
return $panel
|
||||||
|
->default()
|
||||||
|
->id('admin')
|
||||||
|
->path('panel')
|
||||||
|
->login()
|
||||||
|
->brandName('Pelican')
|
||||||
|
->homeUrl('/')
|
||||||
|
->favicon('/pelican.ico')
|
||||||
|
->profile(EditProfile::class, false)
|
||||||
|
->colors([
|
||||||
|
'danger' => Color::Red,
|
||||||
|
'gray' => Color::Zinc,
|
||||||
|
'info' => Color::Blue,
|
||||||
|
'primary' => Color::Sky,
|
||||||
|
'success' => Color::Green,
|
||||||
|
'warning' => Color::Amber,
|
||||||
|
])
|
||||||
|
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||||
|
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||||
|
->pages([
|
||||||
|
// Pages\Dashboard::class,
|
||||||
|
])
|
||||||
|
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||||
|
->widgets([
|
||||||
|
Widgets\AccountWidget::class,
|
||||||
|
Widgets\FilamentInfoWidget::class,
|
||||||
|
])
|
||||||
|
->middleware([
|
||||||
|
EncryptCookies::class,
|
||||||
|
AddQueuedCookiesToResponse::class,
|
||||||
|
StartSession::class,
|
||||||
|
AuthenticateSession::class,
|
||||||
|
ShareErrorsFromSession::class,
|
||||||
|
VerifyCsrfToken::class,
|
||||||
|
SubstituteBindings::class,
|
||||||
|
DisableBladeIconComponents::class,
|
||||||
|
DispatchServingFilamentEvent::class,
|
||||||
|
])
|
||||||
|
->authMiddleware([
|
||||||
|
Authenticate::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -35,11 +35,9 @@ class DaemonBackupRepository extends DaemonRepository
|
|||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/backup', $this->server->uuid),
|
sprintf('/api/servers/%s/backup', $this->server->uuid),
|
||||||
[
|
[
|
||||||
'json' => [
|
'adapter' => $this->adapter ?? config('backups.default'),
|
||||||
'adapter' => $this->adapter ?? config('backups.default'),
|
'uuid' => $backup->uuid,
|
||||||
'uuid' => $backup->uuid,
|
'ignore' => implode("\n", $backup->ignored_files),
|
||||||
'ignore' => implode("\n", $backup->ignored_files),
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
@ -60,11 +58,9 @@ class DaemonBackupRepository extends DaemonRepository
|
|||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid),
|
sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid),
|
||||||
[
|
[
|
||||||
'json' => [
|
'adapter' => $backup->disk,
|
||||||
'adapter' => $backup->disk,
|
'truncate_directory' => $truncate,
|
||||||
'truncate_directory' => $truncate,
|
'download_url' => $url ?? '',
|
||||||
'download_url' => $url ?? '',
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
|
@ -13,10 +13,13 @@ class DaemonConfigurationRepository extends DaemonRepository
|
|||||||
*
|
*
|
||||||
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException
|
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException
|
||||||
*/
|
*/
|
||||||
public function getSystemInformation(?int $version = null): array
|
public function getSystemInformation(?int $version = null, $connectTimeout = 5): array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = $this->getHttpClient()->get('/api/system' . (!is_null($version) ? '?v=' . $version : ''));
|
$response = $this
|
||||||
|
->getHttpClient()
|
||||||
|
->connectTimeout($connectTimeout)
|
||||||
|
->get('/api/system' . (!is_null($version) ? '?v=' . $version : ''));
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
throw new DaemonConnectionException($exception);
|
throw new DaemonConnectionException($exception);
|
||||||
}
|
}
|
||||||
@ -36,7 +39,7 @@ class DaemonConfigurationRepository extends DaemonRepository
|
|||||||
try {
|
try {
|
||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
'/api/update',
|
'/api/update',
|
||||||
['json' => $node->getConfiguration()]
|
$node->getConfiguration(),
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
throw new DaemonConnectionException($exception);
|
throw new DaemonConnectionException($exception);
|
||||||
|
@ -103,10 +103,8 @@ class DaemonFileRepository extends DaemonRepository
|
|||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/files/create-directory', $this->server->uuid),
|
sprintf('/api/servers/%s/files/create-directory', $this->server->uuid),
|
||||||
[
|
[
|
||||||
'json' => [
|
'name' => $name,
|
||||||
'name' => $name,
|
'path' => $path,
|
||||||
'path' => $path,
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
@ -151,9 +149,7 @@ class DaemonFileRepository extends DaemonRepository
|
|||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/files/copy', $this->server->uuid),
|
sprintf('/api/servers/%s/files/copy', $this->server->uuid),
|
||||||
[
|
[
|
||||||
'json' => [
|
'location' => $location,
|
||||||
'location' => $location,
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
@ -174,10 +170,8 @@ class DaemonFileRepository extends DaemonRepository
|
|||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/files/delete', $this->server->uuid),
|
sprintf('/api/servers/%s/files/delete', $this->server->uuid),
|
||||||
[
|
[
|
||||||
'json' => [
|
'root' => $root ?? '/',
|
||||||
'root' => $root ?? '/',
|
'files' => $files,
|
||||||
'files' => $files,
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
@ -195,18 +189,17 @@ class DaemonFileRepository extends DaemonRepository
|
|||||||
Assert::isInstanceOf($this->server, Server::class);
|
Assert::isInstanceOf($this->server, Server::class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = $this->getHttpClient()->post(
|
$response = $this->getHttpClient()
|
||||||
sprintf('/api/servers/%s/files/compress', $this->server->uuid),
|
// Wait for up to 15 minutes for the archive to be completed when calling this endpoint
|
||||||
[
|
// since it will likely take quite awhile for large directories.
|
||||||
'json' => [
|
->timeout(60 * 15)
|
||||||
|
->post(
|
||||||
|
sprintf('/api/servers/%s/files/compress', $this->server->uuid),
|
||||||
|
[
|
||||||
'root' => $root ?? '/',
|
'root' => $root ?? '/',
|
||||||
'files' => $files,
|
'files' => $files,
|
||||||
],
|
]
|
||||||
// Wait for up to 15 minutes for the archive to be completed when calling this endpoint
|
);
|
||||||
// since it will likely take quite awhile for large directories.
|
|
||||||
'timeout' => 60 * 15,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
throw new DaemonConnectionException($exception);
|
throw new DaemonConnectionException($exception);
|
||||||
}
|
}
|
||||||
@ -224,18 +217,17 @@ class DaemonFileRepository extends DaemonRepository
|
|||||||
Assert::isInstanceOf($this->server, Server::class);
|
Assert::isInstanceOf($this->server, Server::class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()
|
||||||
sprintf('/api/servers/%s/files/decompress', $this->server->uuid),
|
// Wait for up to 15 minutes for the decompress to be completed when calling this endpoint
|
||||||
[
|
// since it will likely take quite awhile for large directories.
|
||||||
'json' => [
|
->timeout((int) CarbonInterval::minutes(15)->totalSeconds)
|
||||||
|
->post(
|
||||||
|
sprintf('/api/servers/%s/files/decompress', $this->server->uuid),
|
||||||
|
[
|
||||||
'root' => $root ?? '/',
|
'root' => $root ?? '/',
|
||||||
'file' => $file,
|
'file' => $file,
|
||||||
],
|
]
|
||||||
// Wait for up to 15 minutes for the decompress to be completed when calling this endpoint
|
);
|
||||||
// since it will likely take quite awhile for large directories.
|
|
||||||
'timeout' => (int) CarbonInterval::minutes(15)->totalSeconds,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
throw new DaemonConnectionException($exception);
|
throw new DaemonConnectionException($exception);
|
||||||
}
|
}
|
||||||
@ -254,10 +246,8 @@ class DaemonFileRepository extends DaemonRepository
|
|||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/files/chmod', $this->server->uuid),
|
sprintf('/api/servers/%s/files/chmod', $this->server->uuid),
|
||||||
[
|
[
|
||||||
'json' => [
|
'root' => $root ?? '/',
|
||||||
'root' => $root ?? '/',
|
'files' => $files,
|
||||||
'files' => $files,
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
@ -286,7 +276,7 @@ class DaemonFileRepository extends DaemonRepository
|
|||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/files/pull', $this->server->uuid),
|
sprintf('/api/servers/%s/files/pull', $this->server->uuid),
|
||||||
[
|
[
|
||||||
'json' => array_filter($attributes, fn ($value) => !is_null($value)),
|
array_filter($attributes, fn ($value) => !is_null($value)),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
|
@ -21,7 +21,7 @@ class DaemonPowerRepository extends DaemonRepository
|
|||||||
try {
|
try {
|
||||||
return $this->getHttpClient()->post(
|
return $this->getHttpClient()->post(
|
||||||
sprintf('/api/servers/%s/power', $this->server->uuid),
|
sprintf('/api/servers/%s/power', $this->server->uuid),
|
||||||
['json' => ['action' => $action]]
|
['action' => $action],
|
||||||
);
|
);
|
||||||
} catch (TransferException $exception) {
|
} catch (TransferException $exception) {
|
||||||
throw new DaemonConnectionException($exception);
|
throw new DaemonConnectionException($exception);
|
||||||
|
@ -40,11 +40,9 @@ class DaemonServerRepository extends DaemonRepository
|
|||||||
Assert::isInstanceOf($this->server, Server::class);
|
Assert::isInstanceOf($this->server, Server::class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->getHttpClient()->post('/api/servers', [
|
$response = $this->getHttpClient()->post('/api/servers', [
|
||||||
'json' => [
|
'uuid' => $this->server->uuid,
|
||||||
'uuid' => $this->server->uuid,
|
'start_on_completion' => $startOnCompletion,
|
||||||
'start_on_completion' => $startOnCompletion,
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
} catch (GuzzleException $exception) {
|
} catch (GuzzleException $exception) {
|
||||||
throw new DaemonConnectionException($exception);
|
throw new DaemonConnectionException($exception);
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Rules;
|
|
||||||
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
use Illuminate\Contracts\Validation\Rule;
|
|
||||||
use Illuminate\Contracts\Validation\DataAwareRule;
|
|
||||||
|
|
||||||
class Fqdn implements DataAwareRule, Rule
|
|
||||||
{
|
|
||||||
protected array $data = [];
|
|
||||||
protected string $message = '';
|
|
||||||
protected ?string $schemeField = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $data
|
|
||||||
*/
|
|
||||||
public function setData($data): self
|
|
||||||
{
|
|
||||||
$this->data = $data;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates that the value provided resolves to an IP address. If a scheme is
|
|
||||||
* specified when this rule is created additional checks will be applied.
|
|
||||||
*
|
|
||||||
* @param string $attribute
|
|
||||||
* @param mixed $value
|
|
||||||
*/
|
|
||||||
public function passes($attribute, $value): bool
|
|
||||||
{
|
|
||||||
if (filter_var($value, FILTER_VALIDATE_IP)) {
|
|
||||||
// Check if the scheme is set to HTTPS.
|
|
||||||
//
|
|
||||||
// Unless someone owns their IP blocks and decides to pay who knows how much for a
|
|
||||||
// custom SSL cert, IPs will not be able to use HTTPS. This should prevent most
|
|
||||||
// home users from making this mistake and wondering why their node is not working.
|
|
||||||
if ($this->schemeField && Arr::get($this->data, $this->schemeField) === 'https') {
|
|
||||||
$this->message = 'The :attribute must not be an IP address when HTTPS is enabled.';
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup A and AAAA DNS records for the FQDN. Note, this function will also resolve CNAMEs
|
|
||||||
// for us automatically, there is no need to manually resolve them here.
|
|
||||||
//
|
|
||||||
// The error suppression is intentional, see https://bugs.php.net/bug.php?id=73149
|
|
||||||
$records = @dns_get_record($value, DNS_A + DNS_AAAA);
|
|
||||||
// If no records were returned fall back to trying to resolve the value using the hosts DNS
|
|
||||||
// resolution. This will not work for IPv6 which is why we prefer to use `dns_get_record`
|
|
||||||
// first.
|
|
||||||
if (!empty($records) || filter_var(gethostbyname($value), FILTER_VALIDATE_IP)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->message = 'The :attribute could not be resolved to a valid IP address.';
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function message(): string
|
|
||||||
{
|
|
||||||
return $this->message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new instance of the rule with a defined scheme set.
|
|
||||||
*/
|
|
||||||
public static function make(string $schemeField = null): self
|
|
||||||
{
|
|
||||||
return tap(new self(), function ($fqdn) use ($schemeField) {
|
|
||||||
$fqdn->schemeField = $schemeField;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,7 +37,7 @@ class AssignmentService
|
|||||||
* @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException
|
* @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException
|
||||||
* @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
* @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
||||||
*/
|
*/
|
||||||
public function handle(Node $node, array $data): void
|
public function handle(Node $node, array $data): array
|
||||||
{
|
{
|
||||||
$explode = explode('/', $data['allocation_ip']);
|
$explode = explode('/', $data['allocation_ip']);
|
||||||
if (count($explode) !== 1) {
|
if (count($explode) !== 1) {
|
||||||
@ -58,6 +58,8 @@ class AssignmentService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->connection->beginTransaction();
|
$this->connection->beginTransaction();
|
||||||
|
|
||||||
|
$ids = [];
|
||||||
foreach ($parsed as $ip) {
|
foreach ($parsed as $ip) {
|
||||||
foreach ($data['allocation_ports'] as $port) {
|
foreach ($data['allocation_ports'] as $port) {
|
||||||
if (!is_digit($port) && !preg_match(self::PORT_RANGE_REGEX, $port)) {
|
if (!is_digit($port) && !preg_match(self::PORT_RANGE_REGEX, $port)) {
|
||||||
@ -99,10 +101,12 @@ class AssignmentService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Allocation::query()->insertOrIgnore($insertData);
|
$ids[] = Allocation::query()->insertOrIgnore($insertData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection->commit();
|
$this->connection->commit();
|
||||||
|
|
||||||
|
return $ids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,19 @@ class EggParserService
|
|||||||
*/
|
*/
|
||||||
public function handle(UploadedFile $file): array
|
public function handle(UploadedFile $file): array
|
||||||
{
|
{
|
||||||
if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) {
|
if ($file->getError() !== UPLOAD_ERR_OK) {
|
||||||
throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.');
|
throw new InvalidFileUploadException('The selected file was not uploaded successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var array $parsed */
|
$parsed = json_decode($file->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
$parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR);
|
|
||||||
if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) {
|
|
||||||
throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->convertToV2($parsed);
|
$version = $parsed['meta']['version'] ?? '';
|
||||||
|
|
||||||
|
return match ($version) {
|
||||||
|
'PTDL_v1' => $this->convertToV2($parsed),
|
||||||
|
'PTDL_v2' => $parsed,
|
||||||
|
default => throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,10 +64,6 @@ class EggParserService
|
|||||||
*/
|
*/
|
||||||
protected function convertToV2(array $parsed): array
|
protected function convertToV2(array $parsed): array
|
||||||
{
|
{
|
||||||
if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) {
|
|
||||||
return $parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maintain backwards compatability for eggs that are still using the old single image
|
// Maintain backwards compatability for eggs that are still using the old single image
|
||||||
// string format. New eggs can provide an array of Docker images that can be used.
|
// string format. New eggs can provide an array of Docker images that can be used.
|
||||||
if (!isset($parsed['images'])) {
|
if (!isset($parsed['images'])) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services\Servers;
|
namespace App\Services\Servers;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Database\ConnectionInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use App\Repositories\Daemon\DaemonServerRepository;
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
@ -25,7 +26,7 @@ class ReinstallServerService
|
|||||||
public function handle(Server $server): Server
|
public function handle(Server $server): Server
|
||||||
{
|
{
|
||||||
return $this->connection->transaction(function () use ($server) {
|
return $this->connection->transaction(function () use ($server) {
|
||||||
$server->fill(['status' => Server::STATUS_INSTALLING])->save();
|
$server->fill(['status' => ServerState::Installing])->save();
|
||||||
|
|
||||||
$this->daemonServerRepository->setServer($server)->reinstall();
|
$this->daemonServerRepository->setServer($server)->reinstall();
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services\Servers;
|
namespace App\Services\Servers;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use App\Models\ServerVariable;
|
use App\Models\ServerVariable;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
@ -132,7 +133,7 @@ class ServerCreationService
|
|||||||
'node_id' => Arr::get($data, 'node_id'),
|
'node_id' => Arr::get($data, 'node_id'),
|
||||||
'name' => Arr::get($data, 'name'),
|
'name' => Arr::get($data, 'name'),
|
||||||
'description' => Arr::get($data, 'description') ?? '',
|
'description' => Arr::get($data, 'description') ?? '',
|
||||||
'status' => Server::STATUS_INSTALLING,
|
'status' => ServerState::Installing,
|
||||||
'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
|
'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
|
||||||
'owner_id' => Arr::get($data, 'owner_id'),
|
'owner_id' => Arr::get($data, 'owner_id'),
|
||||||
'memory' => Arr::get($data, 'memory'),
|
'memory' => Arr::get($data, 'memory'),
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services\Servers;
|
namespace App\Services\Servers;
|
||||||
|
|
||||||
|
use App\Enums\ServerState;
|
||||||
use Webmozart\Assert\Assert;
|
use Webmozart\Assert\Assert;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Repositories\Daemon\DaemonServerRepository;
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
@ -44,7 +45,7 @@ class SuspensionService
|
|||||||
|
|
||||||
// Update the server's suspension status.
|
// Update the server's suspension status.
|
||||||
$server->update([
|
$server->update([
|
||||||
'status' => $isSuspending ? Server::STATUS_SUSPENDED : null,
|
'status' => $isSuspending ? ServerState::Suspended : null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -53,7 +54,7 @@ class SuspensionService
|
|||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
// Rollback the server's suspension status if daemon fails to sync the server.
|
// Rollback the server's suspension status if daemon fails to sync the server.
|
||||||
$server->update([
|
$server->update([
|
||||||
'status' => $isSuspending ? null : Server::STATUS_SUSPENDED,
|
'status' => $isSuspending ? null : ServerState::Suspended,
|
||||||
]);
|
]);
|
||||||
throw $exception;
|
throw $exception;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,15 @@ trait AvailableLanguages
|
|||||||
{
|
{
|
||||||
private ?Filesystem $filesystem = null;
|
private ?Filesystem $filesystem = null;
|
||||||
|
|
||||||
|
public const TRANSLATED = [
|
||||||
|
'cz',
|
||||||
|
'da',
|
||||||
|
'de',
|
||||||
|
'en',
|
||||||
|
'es',
|
||||||
|
'tr',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the available languages on the Panel based on those
|
* Return all the available languages on the Panel based on those
|
||||||
* that are present in the language folder.
|
* that are present in the language folder.
|
||||||
@ -18,12 +27,17 @@ trait AvailableLanguages
|
|||||||
return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) {
|
return collect($this->getFilesystemInstance()->directories(base_path('lang')))->mapWithKeys(function ($path) {
|
||||||
$code = basename($path);
|
$code = basename($path);
|
||||||
|
|
||||||
$value = Locale::getDisplayName($code, app()->currentLocale());
|
$value = Locale::getDisplayName($code, $code);
|
||||||
|
|
||||||
return [$code => title_case($value)];
|
return [$code => title_case($value)];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isLanguageTranslated(string $countryCode = 'en'): bool
|
||||||
|
{
|
||||||
|
return in_array($countryCode, self::TRANSLATED, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an instance of the filesystem for getting a folder listing.
|
* Return an instance of the filesystem for getting a folder listing.
|
||||||
*/
|
*/
|
||||||
|
@ -11,6 +11,13 @@ if (!function_exists('is_digit')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!function_exists('is_ip')) {
|
||||||
|
function is_ip(?string $address): bool
|
||||||
|
{
|
||||||
|
return $address !== null && filter_var($address, FILTER_VALIDATE_IP) !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!function_exists('object_get_strict')) {
|
if (!function_exists('object_get_strict')) {
|
||||||
/**
|
/**
|
||||||
* Get an object using dot notation. An object key with a value of null is still considered valid
|
* Get an object using dot notation. An object key with a value of null is still considered valid
|
||||||
|
@ -6,6 +6,7 @@ return [
|
|||||||
App\Providers\BackupsServiceProvider::class,
|
App\Providers\BackupsServiceProvider::class,
|
||||||
App\Providers\BladeServiceProvider::class,
|
App\Providers\BladeServiceProvider::class,
|
||||||
App\Providers\EventServiceProvider::class,
|
App\Providers\EventServiceProvider::class,
|
||||||
|
App\Providers\Filament\AdminPanelProvider::class,
|
||||||
App\Providers\HashidsServiceProvider::class,
|
App\Providers\HashidsServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
App\Providers\ViewComposerServiceProvider::class,
|
App\Providers\ViewComposerServiceProvider::class,
|
||||||
|
@ -9,8 +9,11 @@
|
|||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"ext-pdo_mysql": "*",
|
"ext-pdo_mysql": "*",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
|
"abdelhamiderrahmouni/filament-monaco-editor": "^0.2.0",
|
||||||
"aws/aws-sdk-php": "~3.288.1",
|
"aws/aws-sdk-php": "~3.288.1",
|
||||||
|
"chillerlan/php-qrcode": "^5.0",
|
||||||
"doctrine/dbal": "~3.6.0",
|
"doctrine/dbal": "~3.6.0",
|
||||||
|
"filament/filament": "^3.2",
|
||||||
"guzzlehttp/guzzle": "^7.5",
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
"hashids/hashids": "~5.0.0",
|
"hashids/hashids": "~5.0.0",
|
||||||
"laracasts/utilities": "~3.2.2",
|
"laracasts/utilities": "~3.2.2",
|
||||||
@ -26,12 +29,14 @@
|
|||||||
"pragmarx/google2fa": "~8.0.0",
|
"pragmarx/google2fa": "~8.0.0",
|
||||||
"predis/predis": "~2.1.1",
|
"predis/predis": "~2.1.1",
|
||||||
"prologue/alerts": "^1.2",
|
"prologue/alerts": "^1.2",
|
||||||
|
"ryangjchandler/blade-tabler-icons": "^2.3",
|
||||||
"s1lentium/iptools": "~1.2.0",
|
"s1lentium/iptools": "~1.2.0",
|
||||||
"spatie/laravel-fractal": "^6.1",
|
"spatie/laravel-fractal": "^6.1",
|
||||||
"spatie/laravel-query-builder": "^5.8",
|
"spatie/laravel-query-builder": "^5.8",
|
||||||
"symfony/mailgun-mailer": "^7.0",
|
"symfony/mailgun-mailer": "^7.0",
|
||||||
"symfony/postmark-mailer": "^7.0",
|
"symfony/postmark-mailer": "^7.0",
|
||||||
"symfony/yaml": "^7.0",
|
"symfony/yaml": "^7.0",
|
||||||
|
"webbingbrasil/filament-copyactions": "^3.0",
|
||||||
"webmozart/assert": "~1.11.0"
|
"webmozart/assert": "~1.11.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
@ -66,7 +71,8 @@
|
|||||||
"cs:check": "php-cs-fixer fix --dry-run --diff --verbose",
|
"cs:check": "php-cs-fixer fix --dry-run --diff --verbose",
|
||||||
"post-autoload-dump": [
|
"post-autoload-dump": [
|
||||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
"@php artisan package:discover --ansi || true"
|
"@php artisan package:discover --ansi || true",
|
||||||
|
"@php artisan filament:upgrade"
|
||||||
],
|
],
|
||||||
"post-root-package-install": [
|
"post-root-package-install": [
|
||||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
|
2352
composer.lock
generated
2352
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Egg Feature: EULA Popup
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This popup is enabled for Minecraft eggs and allows a custom frontend
|
|
||||||
| hook to run that monitors the console output of the server and pops up
|
|
||||||
| a modal asking the user to accept it if necessary.
|
|
||||||
|
|
|
||||||
| There is no additional configuration necessary.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
];
|
|
270
config/filament-monaco-editor.php
Normal file
270
config/filament-monaco-editor.php
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'general' => [
|
||||||
|
'enable-preview' => true,
|
||||||
|
'show-full-screen-toggle' => true,
|
||||||
|
'show-placeholder' => true,
|
||||||
|
'placeholder-text' => 'Your code here...',
|
||||||
|
'show-loader' => true,
|
||||||
|
'font-size' => '15px',
|
||||||
|
'line-numbers-min-chars' => true,
|
||||||
|
'automatic-layout' => true,
|
||||||
|
'default-theme' => 'iPlastic',
|
||||||
|
],
|
||||||
|
'themes' => [
|
||||||
|
'blackboard' => [
|
||||||
|
'base' => 'vs-dark',
|
||||||
|
'inherit' => true,
|
||||||
|
'rules' => [
|
||||||
|
[
|
||||||
|
'background' => '0C1021',
|
||||||
|
'token' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'aeaeae',
|
||||||
|
'token' => 'comment',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'd8fa3c',
|
||||||
|
'token' => 'constant',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ff6400',
|
||||||
|
'token' => 'entity',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'fbde2d',
|
||||||
|
'token' => 'keyword',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'fbde2d',
|
||||||
|
'token' => 'storage',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '61ce3c',
|
||||||
|
'token' => 'string',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '61ce3c',
|
||||||
|
'token' => 'meta.verbatim',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '8da6ce',
|
||||||
|
'token' => 'support',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ab2a1d',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'token' => 'invalid.deprecated',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'f8f8f8',
|
||||||
|
'background' => '9d1e15',
|
||||||
|
'token' => 'invalid.illegal',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ff6400',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'token' => 'entity.other.inherited-class',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ff6400',
|
||||||
|
'token' => 'string constant.other.placeholder',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'becde6',
|
||||||
|
'token' => 'meta.function-call.py',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '7f90aa',
|
||||||
|
'token' => 'meta.tag',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '7f90aa',
|
||||||
|
'token' => 'meta.tag entity',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ffffff',
|
||||||
|
'token' => 'entity.name.section',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'd5e0f3',
|
||||||
|
'token' => 'keyword.type.variant',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'f8f8f8',
|
||||||
|
'token' => 'source.ocaml keyword.operator.symbol',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '8da6ce',
|
||||||
|
'token' => 'source.ocaml keyword.operator.symbol.infix',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '8da6ce',
|
||||||
|
'token' => 'source.ocaml keyword.operator.symbol.prefix',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'underline',
|
||||||
|
'token' => 'source.ocaml keyword.operator.symbol.infix.floating-point',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'underline',
|
||||||
|
'token' => 'source.ocaml keyword.operator.symbol.prefix.floating-point',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'underline',
|
||||||
|
'token' => 'source.ocaml constant.numeric.floating-point',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'background' => 'ffffff08',
|
||||||
|
'token' => 'text.tex.latex meta.function.environment',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'background' => '7a96fa08',
|
||||||
|
'token' => 'text.tex.latex meta.function.environment meta.function.environment',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'fbde2d',
|
||||||
|
'token' => 'text.tex.latex support.function',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ffffff',
|
||||||
|
'token' => 'source.plist string.unquoted',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ffffff',
|
||||||
|
'token' => 'source.plist keyword.operator',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'colors' => [
|
||||||
|
'editor.foreground' => '#F8F8F8',
|
||||||
|
'editor.background' => '#0C1021',
|
||||||
|
'editor.selectionBackground' => '#253B76',
|
||||||
|
'editor.lineHighlightBackground' => '#FFFFFF0F',
|
||||||
|
'editorCursor.foreground' => '#FFFFFFA6',
|
||||||
|
'editorWhitespace.foreground' => '#FFFFFF40',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'iPlastic' => [
|
||||||
|
'base' => 'vs',
|
||||||
|
'inherit' => true,
|
||||||
|
'rules' => [
|
||||||
|
[
|
||||||
|
'background' => 'EEEEEEEB',
|
||||||
|
'token' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '009933',
|
||||||
|
'token' => 'string',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '0066ff',
|
||||||
|
'token' => 'constant.numeric',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ff0080',
|
||||||
|
'token' => 'string.regexp',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '0000ff',
|
||||||
|
'token' => 'keyword',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '9700cc',
|
||||||
|
'token' => 'constant.language',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '990000',
|
||||||
|
'token' => 'support.class.exception',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ff8000',
|
||||||
|
'token' => 'entity.name.function',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'bold underline',
|
||||||
|
'token' => 'entity.name.type',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'token' => 'variable.parameter',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '0066ff',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'token' => 'comment',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => 'ff0000',
|
||||||
|
'background' => 'e71a114d',
|
||||||
|
'token' => 'invalid',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'background' => 'e71a1100',
|
||||||
|
'token' => 'invalid.deprecated.trailing-whitespace',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '000000',
|
||||||
|
'background' => 'fafafafc',
|
||||||
|
'token' => 'text source',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '0033cc',
|
||||||
|
'token' => 'meta.tag',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '0033cc',
|
||||||
|
'token' => 'declaration.tag',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '6782d3',
|
||||||
|
'token' => 'constant',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '6782d3',
|
||||||
|
'token' => 'support.constant',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '3333ff',
|
||||||
|
'fontStyle' => 'bold',
|
||||||
|
'token' => 'support',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'bold',
|
||||||
|
'token' => 'storage',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'bold underline',
|
||||||
|
'token' => 'entity.name.section',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '000000',
|
||||||
|
'fontStyle' => 'bold',
|
||||||
|
'token' => 'entity.name.function.frame',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '333333',
|
||||||
|
'token' => 'meta.tag.preprocessor.xml',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'foreground' => '3366cc',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'token' => 'entity.other.attribute-name',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fontStyle' => 'bold',
|
||||||
|
'token' => 'entity.name.tag',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'colors' => [
|
||||||
|
'editor.foreground' => '#000000',
|
||||||
|
'editor.background' => '#EEEEEEEB',
|
||||||
|
'editor.selectionBackground' => '#BAD6FD',
|
||||||
|
'editor.lineHighlightBackground' => '#0000001A',
|
||||||
|
'editorCursor.foreground' => '#000000',
|
||||||
|
'editorWhitespace.foreground' => '#B3B3B3F4',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
@ -24,7 +24,7 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'service' => [
|
'service' => [
|
||||||
'author' => env('APP_SERVICE_AUTHOR', 'unknown@unknown.com'),
|
'author' => env('APP_SERVICE_AUTHOR', 'unknown@example.com'),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
/*
|
|
||||||
* Set trusted proxy IP addresses.
|
|
||||||
*
|
|
||||||
* Both IPv4 and IPv6 addresses are
|
|
||||||
* supported, along with CIDR notation.
|
|
||||||
*
|
|
||||||
* The "*" character is syntactic sugar
|
|
||||||
* within TrustedProxy to trust any proxy
|
|
||||||
* that connects directly to your server,
|
|
||||||
* a requirement when you cannot know the address
|
|
||||||
* of your proxy (e.g. if using Rackspace balancers).
|
|
||||||
*
|
|
||||||
* The "**" character is syntactic sugar within
|
|
||||||
* TrustedProxy to trust not just any proxy that
|
|
||||||
* connects directly to your server, but also
|
|
||||||
* proxies that connect to those proxies, and all
|
|
||||||
* the way back until you reach the original source
|
|
||||||
* IP. It will mean that $request->getClientIp()
|
|
||||||
* always gets the originating client IP, no matter
|
|
||||||
* how many proxies that client's request has
|
|
||||||
* subsequently passed through.
|
|
||||||
*/
|
|
||||||
'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ?
|
|
||||||
env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES') ?? ''),
|
|
||||||
];
|
|
96
contributor_license_agreement.md
Normal file
96
contributor_license_agreement.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
Thank you for your interest in Pelican ("Pelican Developers"). To clarify the intellectual property license
|
||||||
|
granted with Contributions from any person or entity, the Pelican Developers
|
||||||
|
must have on file a signed Contributor License Agreement ("CLA")
|
||||||
|
from each Contributor, indicating agreement with the license
|
||||||
|
terms below. This agreement is for your protection as a Contributor
|
||||||
|
as well as the protection of the Pelican Developers and its users. It does not
|
||||||
|
change your rights to use your own Contributions for any other purpose.
|
||||||
|
|
||||||
|
You accept and agree to the following terms and conditions for Your
|
||||||
|
Contributions (present and future) that you submit to the Pelican Developers. In
|
||||||
|
return, the Pelican Developers shall not use Your Contributions in a way that
|
||||||
|
is contrary to the public benefit or inconsistent with its nonprofit
|
||||||
|
status and bylaws in effect at the time of the Contribution. Except
|
||||||
|
for the license granted herein to the Pelican Developers and recipients of
|
||||||
|
software distributed by the Pelican Developers, You reserve all right, title,
|
||||||
|
and interest in and to Your Contributions.
|
||||||
|
1. Definitions.
|
||||||
|
"You" (or "Your") shall mean the copyright owner or legal entity
|
||||||
|
authorized by the copyright owner that is making this Agreement
|
||||||
|
with the Pelican Developers. For legal entities, the entity making a
|
||||||
|
Contribution and all other entities that control, are controlled
|
||||||
|
by, or are under common control with that entity are considered to
|
||||||
|
be a single Contributor. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
"Contribution" shall mean any original work of authorship,
|
||||||
|
including any modifications or additions to an existing work, that
|
||||||
|
is intentionally submitted by You to the Pelican Developers for inclusion
|
||||||
|
in, or documentation of, any of the products owned or managed by
|
||||||
|
the Pelican Developers (the "Work"). For the purposes of this definition,
|
||||||
|
"submitted" means any form of electronic, verbal, or written
|
||||||
|
communication sent to the Pelican Developers or its representatives,
|
||||||
|
including but not limited to communication on electronic mailing
|
||||||
|
lists, source code control systems, and issue tracking systems that
|
||||||
|
are managed by, or on behalf of, the Pelican Developers for the purpose of
|
||||||
|
discussing and improving the Work, but excluding communication that
|
||||||
|
is conspicuously marked or otherwise designated in writing by You
|
||||||
|
as "Not a Contribution."
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this Agreement, You hereby grant to the Pelican Developers and to
|
||||||
|
recipients of software distributed by the Pelican Developers a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare derivative works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute Your
|
||||||
|
Contributions and such derivative works.
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this Agreement, You hereby grant to the Pelican Developers and to
|
||||||
|
recipients of software distributed by the Pelican Developers a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have
|
||||||
|
made, use, offer to sell, sell, import, and otherwise transfer the
|
||||||
|
Work, where such license applies only to those patent claims
|
||||||
|
licensable by You that are necessarily infringed by Your
|
||||||
|
Contribution(s) alone or by combination of Your Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If any
|
||||||
|
entity institutes patent litigation against You or any other entity
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that your Contribution, or the Work to which you have contributed,
|
||||||
|
constitutes direct or contributory patent infringement, then any
|
||||||
|
patent licenses granted to that entity under this Agreement for
|
||||||
|
that Contribution or Work shall terminate as of the date such
|
||||||
|
litigation is filed.
|
||||||
|
4. You represent that you are legally entitled to grant the above
|
||||||
|
license. If your employer(s) has rights to intellectual property
|
||||||
|
that you create that includes your Contributions, you represent
|
||||||
|
that you have received permission to make Contributions on behalf
|
||||||
|
of that employer, that your employer has waived such rights for
|
||||||
|
your Contributions to the Pelican Developers, or that your employer has
|
||||||
|
executed a separate Corporate CLA with the Pelican Developers.
|
||||||
|
5. You represent that each of Your Contributions is Your original
|
||||||
|
creation (see section 7 for submissions on behalf of others). You
|
||||||
|
represent that Your Contribution submissions include complete
|
||||||
|
details of any third-party license or other restriction (including,
|
||||||
|
but not limited to, related patents and trademarks) of which you
|
||||||
|
are personally aware and which are associated with any part of Your
|
||||||
|
Contributions.
|
||||||
|
6. You are not expected to provide support for Your Contributions,
|
||||||
|
except to the extent You desire to provide support. You may provide
|
||||||
|
support for free, for a fee, or not at all. Unless required by
|
||||||
|
applicable law or agreed to in writing, You provide Your
|
||||||
|
Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
||||||
|
OF ANY KIND, either express or implied, including, without
|
||||||
|
limitation, any warranties or conditions of TITLE, NON-
|
||||||
|
INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
7. Should You wish to submit work that is not Your original creation,
|
||||||
|
You may submit it to the Pelican Developers separately from any
|
||||||
|
Contribution, identifying the complete details of its source and of
|
||||||
|
any license or other restriction (including, but not limited to,
|
||||||
|
related patents, trademarks, and license agreements) of which you
|
||||||
|
are personally aware, and conspicuously marking the work as
|
||||||
|
"Submitted on behalf of a third-party: [named here]".
|
||||||
|
8. You agree to notify the Pelican Developers of any facts or circumstances of
|
||||||
|
which you become aware that would make these representations
|
||||||
|
inaccurate in any respect.
|
11
crowdin.yml
Normal file
11
crowdin.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
files:
|
||||||
|
- source: /lang/en/*.php
|
||||||
|
translation: /lang/%two_letters_code%/%original_file_name%
|
||||||
|
- source: /lang/en/admin
|
||||||
|
translation: /lang/%two_letters_code%/admin/%original_file_name%
|
||||||
|
- source: /lang/en/command
|
||||||
|
translation: /lang/%two_letters_code%/command/%original_file_name%
|
||||||
|
- source: /lang/en/dashboard
|
||||||
|
translation: /lang/%two_letters_code%/dashboard/%original_file_name%
|
||||||
|
- source: /lang/en/server
|
||||||
|
translation: /lang/%two_letters_code%/server/%original_file_name%
|
@ -14,10 +14,10 @@
|
|||||||
"pid_limit"
|
"pid_limit"
|
||||||
],
|
],
|
||||||
"docker_images": {
|
"docker_images": {
|
||||||
"Java 17": "ghcr.io\/App\/yolks:java_17",
|
"Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
|
||||||
"Java 16": "ghcr.io\/App\/yolks:java_16",
|
"Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
|
||||||
"Java 11": "ghcr.io\/App\/yolks:java_11",
|
"Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
|
||||||
"Java 8": "ghcr.io\/App\/yolks:java_8"
|
"Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
|
||||||
},
|
},
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/ash\r\n# Bungeecord Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${BUNGEE_VERSION}\" ] || [ \"${BUNGEE_VERSION}\" == \"latest\" ]; then\r\n BUNGEE_VERSION=\"lastStableBuild\"\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/ci.md-5.net\/job\/BungeeCord\/${BUNGEE_VERSION}\/artifact\/bootstrap\/target\/BungeeCord.jar",
|
"script": "#!\/bin\/ash\r\n# Bungeecord Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\nif [ -z \"${BUNGEE_VERSION}\" ] || [ \"${BUNGEE_VERSION}\" == \"latest\" ]; then\r\n BUNGEE_VERSION=\"lastStableBuild\"\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} https:\/\/ci.md-5.net\/job\/BungeeCord\/${BUNGEE_VERSION}\/artifact\/bootstrap\/target\/BungeeCord.jar",
|
||||||
"container": "ghcr.io\/App\/installers:alpine",
|
"container": "ghcr.io\/pterodactyl\/installers:alpine",
|
||||||
"entrypoint": "ash"
|
"entrypoint": "ash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
"pid_limit"
|
"pid_limit"
|
||||||
],
|
],
|
||||||
"docker_images": {
|
"docker_images": {
|
||||||
"Java 17": "ghcr.io\/App\/yolks:java_17",
|
"Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
|
||||||
"Java 16": "ghcr.io\/App\/yolks:java_16",
|
"Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
|
||||||
"Java 11": "ghcr.io\/App\/yolks:java_11",
|
"Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
|
||||||
"Java 8": "ghcr.io\/App\/yolks:java_8"
|
"Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
|
||||||
},
|
},
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true $( [[ ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )",
|
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true $( [[ ! -f unix_args.txt ]] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )",
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
"pid_limit"
|
"pid_limit"
|
||||||
],
|
],
|
||||||
"docker_images": {
|
"docker_images": {
|
||||||
"Java 17": "ghcr.io\/App\/yolks:java_17",
|
"Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
|
||||||
"Java 16": "ghcr.io\/App\/yolks:java_16",
|
"Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
|
||||||
"Java 11": "ghcr.io\/App\/yolks:java_11",
|
"Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
|
||||||
"Java 8": "ghcr.io\/App\/yolks:java_8"
|
"Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
|
||||||
},
|
},
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",
|
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n echo -e \"Downloading MC server.properties\"\r\n curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi",
|
"script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nPROJECT=paper\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n\techo -e \"Using supplied download url: ${DL_PATH}\"\r\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n\tVER_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\r\n\tLATEST_VERSION=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n\telse\r\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\r\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\r\n\tfi\r\n\r\n\tBUILD_EXISTS=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\r\n\tLATEST_BUILD=`curl -s https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\r\n\r\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\r\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\r\n\telse\r\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\r\n\t\tBUILD_NUMBER=${LATEST_BUILD}\r\n\tfi\r\n\r\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\r\n\r\n\techo \"Version being downloaded\"\r\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n\techo -e \"Build: ${BUILD_NUMBER}\"\r\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\r\n\tDOWNLOAD_URL=https:\/\/api.papermc.io\/v2\/projects\/${PROJECT}\/versions\/${MINECRAFT_VERSION}\/builds\/${BUILD_NUMBER}\/downloads\/${JAR_NAME}\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n echo -e \"Downloading MC server.properties\"\r\n curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft\/java\/server.properties\r\nfi",
|
||||||
"container": "ghcr.io\/App\/installers:alpine",
|
"container": "ghcr.io\/pterodactyl\/installers:alpine",
|
||||||
"entrypoint": "ash"
|
"entrypoint": "ash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
"pid_limit"
|
"pid_limit"
|
||||||
],
|
],
|
||||||
"docker_images": {
|
"docker_images": {
|
||||||
"Java 16": "ghcr.io\/App\/yolks:java_16",
|
"Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
|
||||||
"Java 11": "ghcr.io\/App\/yolks:java_11",
|
"Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
|
||||||
"Java 8": "ghcr.io\/App\/yolks:java_8"
|
"Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
|
||||||
},
|
},
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/ash\r\n# Sponge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sSL \"https:\/\/repo.spongepowered.org\/maven\/org\/spongepowered\/spongevanilla\/${SPONGE_VERSION}\/spongevanilla-${SPONGE_VERSION}.jar\" -o ${SERVER_JARFILE}",
|
"script": "#!\/bin\/ash\r\n# Sponge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\ncd \/mnt\/server\r\n\r\ncurl -sSL \"https:\/\/repo.spongepowered.org\/maven\/org\/spongepowered\/spongevanilla\/${SPONGE_VERSION}\/spongevanilla-${SPONGE_VERSION}.jar\" -o ${SERVER_JARFILE}",
|
||||||
"container": "ghcr.io\/App\/installers:alpine",
|
"container": "ghcr.io\/pterodactyl\/installers:alpine",
|
||||||
"entrypoint": "ash"
|
"entrypoint": "ash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
"pid_limit"
|
"pid_limit"
|
||||||
],
|
],
|
||||||
"docker_images": {
|
"docker_images": {
|
||||||
"Java 17": "ghcr.io\/App\/yolks:java_17",
|
"Java 17": "ghcr.io\/pterodactyl\/yolks:java_17",
|
||||||
"Java 16": "ghcr.io\/App\/yolks:java_16",
|
"Java 16": "ghcr.io\/pterodactyl\/yolks:java_16",
|
||||||
"Java 11": "ghcr.io\/App\/yolks:java_11",
|
"Java 11": "ghcr.io\/pterodactyl\/yolks:java_11",
|
||||||
"Java 8": "ghcr.io\/App\/yolks:java_8"
|
"Java 8": "ghcr.io\/pterodactyl\/yolks:java_8"
|
||||||
},
|
},
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
"startup": "java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\nLATEST_SNAPSHOT_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.snapshot'`\r\n\r\necho -e \"latest version is $LATEST_VERSION\"\r\necho -e \"latest snapshot is $LATEST_SNAPSHOT_VERSION\"\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelif [ \"$VANILLA_VERSION\" == \"snapshot\" ]; then\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_SNAPSHOT_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelse\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $VANILLA_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nfi\r\n\r\nDOWNLOAD_URL=$(curl ${MANIFEST_URL} | jq .downloads.server | jq -r '. | .url')\r\n\r\necho -e \"running: curl -o ${SERVER_JARFILE} $DOWNLOAD_URL\"\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL\r\n\r\necho -e \"Install Complete\"",
|
"script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\nLATEST_SNAPSHOT_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.snapshot'`\r\n\r\necho -e \"latest version is $LATEST_VERSION\"\r\necho -e \"latest snapshot is $LATEST_SNAPSHOT_VERSION\"\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelif [ \"$VANILLA_VERSION\" == \"snapshot\" ]; then\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_SNAPSHOT_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelse\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $VANILLA_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nfi\r\n\r\nDOWNLOAD_URL=$(curl ${MANIFEST_URL} | jq .downloads.server | jq -r '. | .url')\r\n\r\necho -e \"running: curl -o ${SERVER_JARFILE} $DOWNLOAD_URL\"\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL\r\n\r\necho -e \"Install Complete\"",
|
||||||
"container": "ghcr.io\/App\/installers:alpine",
|
"container": "ghcr.io\/pterodactyl\/installers:alpine",
|
||||||
"entrypoint": "ash"
|
"entrypoint": "ash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"steam_disk_space"
|
"steam_disk_space"
|
||||||
],
|
],
|
||||||
"docker_images": {
|
"docker_images": {
|
||||||
"ghcr.io\/App\/games:rust": "ghcr.io\/App\/games:rust"
|
"ghcr.io\/pterodactyl\/games:rust": "ghcr.io\/pterodactyl\/games:rust"
|
||||||
},
|
},
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
|
"startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nSRCDS_APPID=258550\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nSRCDS_APPID=258550\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||||
"container": "ghcr.io\/App\/installers:debian",
|
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||||
"entrypoint": "bash"
|
"entrypoint": "bash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"steam_disk_space"
|
"steam_disk_space"
|
||||||
],
|
],
|
||||||
"images": [
|
"images": [
|
||||||
"ghcr.io\/App\/games:source"
|
"ghcr.io\/pterodactyl\/games:source"
|
||||||
],
|
],
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
|
"startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||||
"container": "ghcr.io\/App\/installers:debian",
|
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||||
"entrypoint": "bash"
|
"entrypoint": "bash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"steam_disk_space"
|
"steam_disk_space"
|
||||||
],
|
],
|
||||||
"images": [
|
"images": [
|
||||||
"ghcr.io\/App\/games:source"
|
"ghcr.io\/pterodactyl\/games:source"
|
||||||
],
|
],
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
|
"startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||||
"container": "ghcr.io\/App\/installers:debian",
|
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||||
"entrypoint": "bash"
|
"entrypoint": "bash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"steam_disk_space"
|
"steam_disk_space"
|
||||||
],
|
],
|
||||||
"images": [
|
"images": [
|
||||||
"ghcr.io\/App\/games:source"
|
"ghcr.io\/pterodactyl\/games:source"
|
||||||
],
|
],
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}} -tickrate {{TICKRATE}} $( [ \"$LUA_REFRESH\" == \"1\" ] || printf %s '-disableluarefresh' )",
|
"startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}} -tickrate {{TICKRATE}} $( [ \"$LUA_REFRESH\" == \"1\" ] || printf %s '-disableluarefresh' )",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\nsv_downloadurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
|
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\nsv_downloadurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
|
||||||
"container": "ghcr.io\/App\/installers:debian",
|
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||||
"entrypoint": "bash"
|
"entrypoint": "bash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"steam_disk_space"
|
"steam_disk_space"
|
||||||
],
|
],
|
||||||
"images": [
|
"images": [
|
||||||
"ghcr.io\/App\/games:source"
|
"ghcr.io\/pterodactyl\/games:source"
|
||||||
],
|
],
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": ".\/srcds_run -game insurgency -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
|
"startup": ".\/srcds_run -game insurgency -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login anonymous +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login anonymous +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||||
"container": "ghcr.io\/App\/installers:debian",
|
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||||
"entrypoint": "bash"
|
"entrypoint": "bash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"steam_disk_space"
|
"steam_disk_space"
|
||||||
],
|
],
|
||||||
"images": [
|
"images": [
|
||||||
"ghcr.io\/App\/games:source"
|
"ghcr.io\/pterodactyl\/games:source"
|
||||||
],
|
],
|
||||||
"file_denylist": [],
|
"file_denylist": [],
|
||||||
"startup": ".\/srcds_run -game tf -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
|
"startup": ".\/srcds_run -game tf -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
|
||||||
"container": "ghcr.io\/App\/installers:debian",
|
"container": "ghcr.io\/pterodactyl\/installers:debian",
|
||||||
"entrypoint": "bash"
|
"entrypoint": "bash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"installation": {
|
"installation": {
|
||||||
"script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n echo \"Config file already exists.\"\r\nelse \r\n echo \"Downloading the config file.\"\r\n apk add --no-cache murmur\r\n cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n apk del murmur\r\nfi\r\necho \"done\"",
|
"script": "#!\/bin\/ash\r\n\r\nif [ ! -d \/mnt\/server\/ ]; then\r\n mkdir \/mnt\/server\/\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nFILE=\/mnt\/server\/murmur.ini\r\nif [ -f \"$FILE\" ]; then\r\n echo \"Config file already exists.\"\r\nelse \r\n echo \"Downloading the config file.\"\r\n apk add --no-cache murmur\r\n cp \/etc\/murmur.ini \/mnt\/server\/murmur.ini\r\n apk del murmur\r\nfi\r\necho \"done\"",
|
||||||
"container": "ghcr.io\/App\/installers:alpine",
|
"container": "ghcr.io\/pterodactyl\/installers:alpine",
|
||||||
"entrypoint": "ash"
|
"entrypoint": "ash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user