mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Compare commits
26 Commits
charles/fi
...
boy132/com
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d02470cc2 | ||
|
|
bf13c6c05c | ||
|
|
9a0d95b1c0 | ||
|
|
725082020f | ||
|
|
dee4e361bf | ||
|
|
ef6e5b87d2 | ||
|
|
b006a40c8a | ||
|
|
2bbfb0eef9 | ||
|
|
2a64ea8536 | ||
|
|
4fdbbff74b | ||
|
|
2ed891633c | ||
|
|
58dcbeac0b | ||
|
|
91c5ddb2bd | ||
|
|
562be98b20 | ||
|
|
fd3b8a7ab3 | ||
|
|
1817383bf5 | ||
|
|
d39a0c4464 | ||
|
|
2e48095379 | ||
|
|
06c662988a | ||
|
|
98d7158dfc | ||
|
|
e01d9f2cf3 | ||
|
|
b693d0e728 | ||
|
|
612041e1f8 | ||
|
|
64bcdb514b | ||
|
|
c1105db702 | ||
|
|
761492d09f |
4
.github/workflows/build.yaml
vendored
4
.github/workflows/build.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
node-version: [22, 24]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
4
.github/workflows/lint.yaml
vendored
4
.github/workflows/lint.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.4"
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4, 8.5]
|
||||
php: [8.3, 8.4, 8.5]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install JS dependencies
|
||||
|
||||
@@ -13,7 +13,7 @@ class UpdateEggIndexCommand extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
$data = Http::timeout(5)->connectTimeout(1)->get('https://raw.githubusercontent.com/pelican-eggs/pelican-eggs.github.io/refs/heads/main/content/pelican.json')->throw()->json();
|
||||
$data = Http::timeout(5)->connectTimeout(1)->get(config('panel.cdn.egg_index_url'))->throw()->json();
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ enum SubuserPermission: string
|
||||
|
||||
case ActivityRead = 'activity.read';
|
||||
|
||||
case MountRead = 'mount.read';
|
||||
case MountUpdate = 'mount.update';
|
||||
|
||||
case StartupRead = 'startup.read';
|
||||
case StartupUpdate = 'startup.update';
|
||||
case StartupDockerImage = 'startup.docker-image';
|
||||
@@ -57,6 +60,7 @@ enum SubuserPermission: string
|
||||
case SettingsRename = 'settings.rename';
|
||||
case SettingsDescription = 'settings.description';
|
||||
case SettingsReinstall = 'settings.reinstall';
|
||||
case SettingsChangeIcon = 'settings.change-icon';
|
||||
|
||||
/** @return string[] */
|
||||
public function split(): array
|
||||
@@ -84,6 +88,7 @@ enum SubuserPermission: string
|
||||
'schedule' => TablerIcon::Clock,
|
||||
'settings' => TablerIcon::Settings,
|
||||
'activity' => TablerIcon::Stack,
|
||||
'mount' => TablerIcon::LayersLinked,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
17
app/Events/User/Deleting.php
Normal file
17
app/Events/User/Deleting.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user) {}
|
||||
}
|
||||
13
app/Events/User/PasswordChanged.php
Normal file
13
app/Events/User/PasswordChanged.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
final class PasswordChanged
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
public function __construct(public readonly User $user) {}
|
||||
}
|
||||
@@ -14,13 +14,14 @@ use Boquizo\FilamentLogViewer\Utils\Level;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ListLogs extends BaseListLogs
|
||||
{
|
||||
protected string $view = 'filament.components.list-logs';
|
||||
|
||||
public function getHeading(): string|null|\Illuminate\Contracts\Support\Htmlable
|
||||
public function getHeading(): string|null|Htmlable
|
||||
{
|
||||
return trans('admin/log.navigation.panel_logs');
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
use App\Enums\EditorLanguages;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Actions\DeleteIcon;
|
||||
use App\Filament\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Actions\UploadIcon;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Filament\Components\Forms\Fields\MonacoEditor;
|
||||
use App\Models\Egg;
|
||||
@@ -14,12 +16,10 @@ use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use App\Traits\Filament\CanCustomizeTabs;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
@@ -28,11 +28,8 @@ use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Flex;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Image;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
@@ -40,10 +37,7 @@ use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class EditEgg extends EditRecord
|
||||
{
|
||||
@@ -74,163 +68,17 @@ class EditEgg extends EditRecord
|
||||
->icon(TablerIcon::Egg)
|
||||
->schema([
|
||||
Grid::make(2)
|
||||
->columnSpan(1)
|
||||
->columnStart(1)
|
||||
->schema([
|
||||
Image::make('', '')
|
||||
->hidden(fn ($record) => !$record->image)
|
||||
->url(fn ($record) => $record->image)
|
||||
->alt('')
|
||||
->alignJustify()
|
||||
Image::make('', 'icon')
|
||||
->hidden(fn ($record) => !$record->icon)
|
||||
->url(fn ($record) => $record->icon)
|
||||
->imageSize(150)
|
||||
->columnSpanFull(),
|
||||
Flex::make([
|
||||
Action::make('uploadImage')
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/egg.import.import_image'))
|
||||
->iconSize(IconSize::Large)
|
||||
->icon(TablerIcon::PhotoUp)
|
||||
->modal()
|
||||
->modalHeading('')
|
||||
->modalSubmitActionLabel(trans('admin/egg.import.import_image'))
|
||||
->schema([
|
||||
Tabs::make()
|
||||
->contained(false)
|
||||
->tabs([
|
||||
Tab::make(trans('admin/egg.import.url'))
|
||||
->schema([
|
||||
Hidden::make('imageUrl'),
|
||||
Hidden::make('imageExtension'),
|
||||
TextInput::make('image_url')
|
||||
->label(trans('admin/egg.import.image_url'))
|
||||
->reactive()
|
||||
->autocomplete(false)
|
||||
->debounce(500)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
if (!$state) {
|
||||
$set('image_url_error', null);
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!filter_var($state, FILTER_VALIDATE_URL)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$extension = strtolower(pathinfo(parse_url($state, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
if (!array_key_exists($extension, Egg::IMAGE_FORMATS)) {
|
||||
throw new Exception(trans('admin/egg.import.unsupported_format', ['format' => implode(', ', array_keys(Egg::IMAGE_FORMATS))]));
|
||||
}
|
||||
|
||||
$host = parse_url($state, PHP_URL_HOST);
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
if (
|
||||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false
|
||||
) {
|
||||
throw new Exception(trans('admin/egg.import.no_local_ip'));
|
||||
}
|
||||
|
||||
$set('imageUrl', $state);
|
||||
$set('imageExtension', $extension);
|
||||
$set('image_url_error', null);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$set('image_url_error', $e->getMessage());
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', null);
|
||||
}
|
||||
}),
|
||||
TextEntry::make('image_url_error')
|
||||
->hiddenLabel()
|
||||
->visible(fn ($get) => $get('image_url_error') !== null)
|
||||
->afterStateHydrated(fn ($set, $get) => $get('image_url_error')),
|
||||
Image::make(fn (Get $get) => $get('image_url'), '')
|
||||
->imageSize(150)
|
||||
->visible(fn ($get) => $get('image_url') && !$get('image_url_error'))
|
||||
->alignCenter(),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.import.file'))
|
||||
->schema([
|
||||
FileUpload::make('image')
|
||||
->hiddenLabel()
|
||||
->previewable()
|
||||
->openable(false)
|
||||
->downloadable(false)
|
||||
->maxSize(256)
|
||||
->maxFiles(1)
|
||||
->columnSpanFull()
|
||||
->alignCenter()
|
||||
->imageEditor()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory(Egg::ICON_STORAGE_PATH)
|
||||
->acceptedFileTypes([
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/webp',
|
||||
'image/svg+xml',
|
||||
])
|
||||
->getUploadedFileNameForStorageUsing(function (TemporaryUploadedFile $file, $record) {
|
||||
return $record->uuid . '.' . $file->getClientOriginalExtension();
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->action(function (array $data, $record): void {
|
||||
if (!empty($data['imageUrl']) && !empty($data['imageExtension'])) {
|
||||
$this->saveImageFromUrl($data['imageUrl'], $data['imageExtension'], $record);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($data['imageUrl']) && empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.no_image'))
|
||||
->warning()
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
Action::make('delete_image')
|
||||
->visible(fn ($record) => $record->image)
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/egg.import.delete_image'))
|
||||
->icon(TablerIcon::Trash)
|
||||
->iconSize(IconSize::Large)
|
||||
->color('danger')
|
||||
->action(function ($record) {
|
||||
foreach (array_keys(Egg::IMAGE_FORMATS) as $ext) {
|
||||
$path = Egg::ICON_STORAGE_PATH . "/$record->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_deleted'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$record->refresh();
|
||||
}),
|
||||
]),
|
||||
->columnSpanFull()
|
||||
->alignJustify(),
|
||||
UploadIcon::make(),
|
||||
DeleteIcon::make()
|
||||
->iconStoragePath(Egg::getIconStoragePath()),
|
||||
]),
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
@@ -469,39 +317,6 @@ class EditEgg extends EditRecord
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an image from URL download to a file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveImageFromUrl(string $imageUrl, string $extension, Egg $egg): void
|
||||
{
|
||||
$context = stream_context_create([
|
||||
'http' => ['timeout' => 3],
|
||||
'https' => [
|
||||
'timeout' => 3,
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$normalizedExtension = match ($extension) {
|
||||
'svg+xml', 'svg' => 'svg',
|
||||
'jpeg', 'jpg' => 'jpg',
|
||||
'png' => 'png',
|
||||
'webp' => 'webp',
|
||||
default => throw new Exception(trans('admin/egg.import.unknown_extension')),
|
||||
};
|
||||
|
||||
$data = @file_get_contents($imageUrl, false, $context, 0, 1048576); // 1024KB
|
||||
|
||||
if (empty($data)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
Storage::disk('public')->put(Egg::ICON_STORAGE_PATH . "/$egg->uuid.$normalizedExtension", $data);
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
|
||||
@@ -38,6 +38,8 @@ class ListEggs extends ListRecords
|
||||
*/
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
$defaultEggIcon = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')));
|
||||
|
||||
return $table
|
||||
->searchable(true)
|
||||
->defaultPaginationPageOption(25)
|
||||
@@ -45,13 +47,11 @@ class ListEggs extends ListRecords
|
||||
TextColumn::make('id')
|
||||
->label('Id')
|
||||
->hidden(),
|
||||
ImageColumn::make('image')
|
||||
ImageColumn::make('icon')
|
||||
->label('')
|
||||
->alignCenter()
|
||||
->circular()
|
||||
->getStateUsing(fn ($record) => $record->image
|
||||
? $record->image
|
||||
: 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')))),
|
||||
->getStateUsing(fn (Egg $record) => $record->icon ?: $defaultEggIcon),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
|
||||
@@ -95,6 +95,12 @@ class MountResource extends Resource
|
||||
->icon(fn ($state) => $state ? TablerIcon::WritingOff : TablerIcon::Writing)
|
||||
->color(fn ($state) => $state ? 'success' : 'warning')
|
||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')),
|
||||
TextColumn::make('user_mountable')
|
||||
->label(trans('admin/mount.table.user_mountable'))
|
||||
->badge()
|
||||
->icon(fn ($state) => $state ? TablerIcon::User : TablerIcon::UserOff)
|
||||
->color(fn ($state) => $state ? 'success' : 'warning')
|
||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.user_mountable') : trans('admin/mount.toggles.not_user_mountable')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
@@ -124,7 +130,8 @@ class MountResource extends Resource
|
||||
->label(trans('admin/mount.name'))
|
||||
->required()
|
||||
->helperText(trans('admin/mount.name_help'))
|
||||
->maxLength(64),
|
||||
->maxLength(64)
|
||||
->columnSpanFull(),
|
||||
ToggleButtons::make('read_only')
|
||||
->label(trans('admin/mount.read_only'))
|
||||
->helperText(trans('admin/mount.read_only_help'))
|
||||
@@ -143,6 +150,24 @@ class MountResource extends Resource
|
||||
])
|
||||
->inline()
|
||||
->default(false),
|
||||
ToggleButtons::make('user_mountable')
|
||||
->label(trans('admin/mount.user_mountable'))
|
||||
->helperText(trans('admin/mount.user_mountable_help'))
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
false => trans('admin/mount.toggles.not_user_mountable'),
|
||||
true => trans('admin/mount.toggles.user_mountable'),
|
||||
])
|
||||
->icons([
|
||||
false => TablerIcon::UserOff,
|
||||
true => TablerIcon::User,
|
||||
])
|
||||
->colors([
|
||||
false => 'warning',
|
||||
true => 'success',
|
||||
])
|
||||
->inline()
|
||||
->default(true),
|
||||
TextInput::make('source')
|
||||
->label(trans('admin/mount.source'))
|
||||
->required()
|
||||
|
||||
@@ -42,7 +42,6 @@ class CreateMount extends CreateRecord
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
$data['uuid'] ??= Str::uuid()->toString();
|
||||
$data['user_mountable'] = 1;
|
||||
|
||||
return parent::handleRecordCreation($data);
|
||||
}
|
||||
|
||||
@@ -64,9 +64,8 @@ class CreateNode extends CreateRecord
|
||||
->icon(TablerIcon::Server)
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'default' => 1,
|
||||
'md' => 2,
|
||||
'lg' => 4,
|
||||
])
|
||||
->schema([
|
||||
@@ -76,81 +75,83 @@ class CreateNode extends CreateRecord
|
||||
->autofocus()
|
||||
->live(debounce: 1500)
|
||||
->rules(Node::getRulesForField('fqdn'))
|
||||
->prohibited(fn ($state) => is_ip($state) && request()->isSecure())
|
||||
->label(fn ($state) => is_ip($state) ? trans('admin/node.ip_address') : trans('admin/node.domain'))
|
||||
->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
|
||||
->helperText(function ($state) {
|
||||
->helperText(fn () => request()->isSecure() ? trans('admin/node.fqdn_ssl') : null)
|
||||
->validationMessages([
|
||||
'prohibited' => trans('admin/node.dns_error'),
|
||||
])
|
||||
->prohibited(function ($state, Get $get) {
|
||||
if (!$state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_ip($state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ip = $get('ip');
|
||||
|
||||
return !is_ip($ip);
|
||||
})
|
||||
->hintColor(function ($state, Get $get) {
|
||||
if (!$state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_ip($state)) {
|
||||
if (request()->isSecure()) {
|
||||
return trans('admin/node.fqdn_help');
|
||||
return 'warning';
|
||||
}
|
||||
} else {
|
||||
$ip = $get('ip');
|
||||
|
||||
return '';
|
||||
return is_ip($ip) ? 'success' : 'danger';
|
||||
}
|
||||
|
||||
return trans('admin/node.error');
|
||||
return null;
|
||||
})
|
||||
->hintColor('danger')
|
||||
->hint(function ($state) {
|
||||
if (is_ip($state) && request()->isSecure()) {
|
||||
return trans('admin/node.ssl_ip');
|
||||
->hint(function ($state, Get $get) {
|
||||
if (!$state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '';
|
||||
if (is_ip($state)) {
|
||||
if (request()->isSecure()) {
|
||||
return trans('admin/node.ssl_ip');
|
||||
}
|
||||
} else {
|
||||
$ip = $get('ip');
|
||||
|
||||
return is_ip($ip) ? trans('admin/node.valid') . ': ' . $ip : trans('admin/node.invalid');
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
->afterStateUpdated(function (Set $set, ?string $state) {
|
||||
$set('dns', null);
|
||||
$set('ip', null);
|
||||
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$subdomain] = str($state)->explode('.', 2);
|
||||
if (!is_numeric($subdomain)) {
|
||||
$set('name', $subdomain);
|
||||
}
|
||||
|
||||
if (!$state || is_ip($state)) {
|
||||
$set('dns', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ip = get_ip_from_hostname($state);
|
||||
if ($ip) {
|
||||
$set('dns', true);
|
||||
|
||||
$set('ip', $ip);
|
||||
} else {
|
||||
$set('dns', false);
|
||||
if (!is_ip($state)) {
|
||||
$ip = get_ip_from_hostname($state);
|
||||
if (is_ip($ip)) {
|
||||
$set('ip', $ip);
|
||||
} else {
|
||||
$set('ip', null);
|
||||
}
|
||||
}
|
||||
})
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('ip')
|
||||
->disabled()
|
||||
->hidden(),
|
||||
|
||||
ToggleButtons::make('dns')
|
||||
->label(trans('admin/node.dns'))
|
||||
->helperText(trans('admin/node.dns_help'))
|
||||
->disabled()
|
||||
->inline()
|
||||
->default(null)
|
||||
->hint(fn (Get $get) => $get('ip'))
|
||||
->hintColor('success')
|
||||
->options([
|
||||
true => trans('admin/node.valid'),
|
||||
false => trans('admin/node.invalid'),
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
|
||||
Hidden::make('ip')
|
||||
->saved(false),
|
||||
TextInput::make('daemon_connect')
|
||||
->columnSpan(1)
|
||||
->label(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port') : trans('admin/node.port'))
|
||||
@@ -160,7 +161,16 @@ class CreateNode extends CreateRecord
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer(),
|
||||
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/node.display_name'))
|
||||
->columnSpan([
|
||||
@@ -171,27 +181,16 @@ class CreateNode extends CreateRecord
|
||||
])
|
||||
->required()
|
||||
->maxLength(100),
|
||||
|
||||
Hidden::make('scheme')
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
|
||||
Hidden::make('behind_proxy')
|
||||
->default(false),
|
||||
|
||||
ToggleButtons::make('connection')
|
||||
->label(trans('admin/node.ssl'))
|
||||
->columnSpan(1)
|
||||
->columnSpan(2)
|
||||
->inline()
|
||||
->helperText(function (Get $get) {
|
||||
->helperText(function () {
|
||||
if (request()->isSecure()) {
|
||||
return new HtmlString(trans('admin/node.panel_on_ssl'));
|
||||
return trans('admin/node.panel_on_ssl');
|
||||
}
|
||||
|
||||
if (is_ip($get('fqdn'))) {
|
||||
return trans('admin/node.ssl_help');
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
})
|
||||
->disableOptionWhen(fn (string $value) => $value === 'http' && request()->isSecure())
|
||||
->options([
|
||||
@@ -219,17 +218,10 @@ class CreateNode extends CreateRecord
|
||||
$set('daemon_connect', $state === 'https_proxy' ? 443 : 8080);
|
||||
$set('daemon_listen', 8080);
|
||||
}),
|
||||
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
Hidden::make('scheme')
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
Hidden::make('behind_proxy')
|
||||
->default(false),
|
||||
]),
|
||||
Step::make('advanced')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
|
||||
@@ -137,74 +137,83 @@ class EditNode extends EditRecord
|
||||
->autofocus()
|
||||
->live(debounce: 1500)
|
||||
->rules(Node::getRulesForField('fqdn'))
|
||||
->prohibited(fn ($state) => is_ip($state) && request()->isSecure())
|
||||
->label(fn ($state) => is_ip($state) ? trans('admin/node.ip_address') : trans('admin/node.domain'))
|
||||
->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
|
||||
->helperText(function ($state) {
|
||||
->helperText(fn () => request()->isSecure() ? trans('admin/node.fqdn_ssl') : null)
|
||||
->validationMessages([
|
||||
'prohibited' => trans('admin/node.dns_error'),
|
||||
])
|
||||
->prohibited(function ($state, Get $get) {
|
||||
if (!$state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_ip($state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ip = $get('ip');
|
||||
|
||||
return !is_ip($ip);
|
||||
})
|
||||
->hintColor(function ($state, Get $get) {
|
||||
if (!$state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_ip($state)) {
|
||||
if (request()->isSecure()) {
|
||||
return trans('admin/node.fqdn_help');
|
||||
return 'warning';
|
||||
}
|
||||
} else {
|
||||
$ip = $get('ip');
|
||||
|
||||
return '';
|
||||
return is_ip($ip) ? 'success' : 'danger';
|
||||
}
|
||||
|
||||
return trans('admin/node.error');
|
||||
return null;
|
||||
})
|
||||
->hintColor('danger')
|
||||
->hint(function ($state) {
|
||||
if (is_ip($state) && request()->isSecure()) {
|
||||
return trans('admin/node.ssl_ip');
|
||||
->hint(function ($state, Get $get) {
|
||||
if (!$state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '';
|
||||
if (is_ip($state)) {
|
||||
if (request()->isSecure()) {
|
||||
return trans('admin/node.ssl_ip');
|
||||
}
|
||||
} else {
|
||||
$ip = $get('ip');
|
||||
|
||||
return is_ip($ip) ? trans('admin/node.valid') . ': ' . $ip : trans('admin/node.invalid');
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
->afterStateUpdated(function (Set $set, ?string $state) {
|
||||
$set('dns', null);
|
||||
$set('ip', null);
|
||||
|
||||
if (!$state) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$subdomain] = str($state)->explode('.', 2);
|
||||
if (!is_numeric($subdomain)) {
|
||||
$set('name', $subdomain);
|
||||
}
|
||||
|
||||
if (!$state || is_ip($state)) {
|
||||
$set('dns', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ip = get_ip_from_hostname($state);
|
||||
if ($ip) {
|
||||
$set('dns', true);
|
||||
|
||||
$set('ip', $ip);
|
||||
} else {
|
||||
$set('dns', false);
|
||||
if (!is_ip($state)) {
|
||||
$ip = get_ip_from_hostname($state);
|
||||
if (is_ip($ip)) {
|
||||
$set('ip', $ip);
|
||||
} else {
|
||||
$set('ip', null);
|
||||
}
|
||||
}
|
||||
})
|
||||
->maxLength(255),
|
||||
TextInput::make('ip')
|
||||
->disabled()
|
||||
->hidden(),
|
||||
ToggleButtons::make('dns')
|
||||
->label(trans('admin/node.dns'))
|
||||
->helperText(trans('admin/node.dns_help'))
|
||||
->disabled()
|
||||
->inline()
|
||||
->default(null)
|
||||
->hint(fn (Get $get) => $get('ip'))
|
||||
->hintColor('success')
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
1 => trans('admin/node.valid'),
|
||||
0 => trans('admin/node.invalid'),
|
||||
])
|
||||
->colors([
|
||||
1 => 'success',
|
||||
0 => 'danger',
|
||||
])
|
||||
->columnSpan(1),
|
||||
Hidden::make('ip')
|
||||
->saved(false),
|
||||
TextInput::make('daemon_connect')
|
||||
->columnSpan(1)
|
||||
->label(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port') : trans('admin/node.port'))
|
||||
@@ -214,6 +223,16 @@ class EditNode extends EditRecord
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer(),
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/node.display_name'))
|
||||
->columnSpan([
|
||||
@@ -224,22 +243,16 @@ class EditNode extends EditRecord
|
||||
])
|
||||
->required()
|
||||
->maxLength(100),
|
||||
Hidden::make('scheme'),
|
||||
Hidden::make('behind_proxy'),
|
||||
ToggleButtons::make('connection')
|
||||
->label(trans('admin/node.ssl'))
|
||||
->columnSpan(1)
|
||||
->columnSpan(2)
|
||||
->inline()
|
||||
->helperText(function (Get $get) {
|
||||
->helperText(function () {
|
||||
if (request()->isSecure()) {
|
||||
return new HtmlString(trans('admin/node.panel_on_ssl'));
|
||||
return trans('admin/node.panel_on_ssl');
|
||||
}
|
||||
|
||||
if (is_ip($get('fqdn'))) {
|
||||
return trans('admin/node.ssl_help');
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
})
|
||||
->disableOptionWhen(fn (string $value) => $value === 'http' && request()->isSecure())
|
||||
->options([
|
||||
@@ -267,16 +280,10 @@ class EditNode extends EditRecord
|
||||
$set('daemon_connect', $state === 'https_proxy' ? 443 : 8080);
|
||||
$set('daemon_listen', 8080);
|
||||
}),
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
Hidden::make('scheme')
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
Hidden::make('behind_proxy')
|
||||
->default(false),
|
||||
]),
|
||||
Tab::make('advanced_settings')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
@@ -735,7 +742,7 @@ class EditNode extends EditRecord
|
||||
$set('pulled', false);
|
||||
$set('uploaded', true);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->title(trans('admin/node.diagnostics.upload_failed'))
|
||||
->body($e->getMessage())
|
||||
|
||||
@@ -5,6 +5,9 @@ namespace App\Filament\Admin\Resources\Plugins;
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Plugins\Pages\ListPlugins;
|
||||
use App\Jobs\Plugin\InstallPlugin;
|
||||
use App\Jobs\Plugin\UninstallPlugin;
|
||||
use App\Jobs\Plugin\UpdatePlugin;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use BackedEnum;
|
||||
@@ -119,15 +122,14 @@ class PluginResource extends Resource
|
||||
->icon(TablerIcon::Terminal)
|
||||
->color('success')
|
||||
->hidden(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled)
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
->action(function (Plugin $plugin) {
|
||||
try {
|
||||
$pluginService->installPlugin($plugin, !$plugin->isTheme() || !$pluginService->hasThemePluginEnabled());
|
||||
|
||||
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||
InstallPlugin::dispatch(user(), $plugin);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.installed'))
|
||||
->title(trans('admin/plugin.notifications.install_started'))
|
||||
->body(trans('admin/plugin.notifications.background_info'))
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
@@ -143,15 +145,14 @@ class PluginResource extends Resource
|
||||
->icon(TablerIcon::Download)
|
||||
->color('success')
|
||||
->visible(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled && $plugin->isUpdateAvailable())
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
->action(function (Plugin $plugin) {
|
||||
try {
|
||||
$pluginService->updatePlugin($plugin);
|
||||
|
||||
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||
UpdatePlugin::dispatch(user(), $plugin);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.updated'))
|
||||
->title(trans('admin/plugin.notifications.update_started'))
|
||||
->body(trans('admin/plugin.notifications.background_info'))
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
@@ -220,15 +221,14 @@ class PluginResource extends Resource
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->hidden(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled || $plugin->status === PluginStatus::Errored)
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
->action(function (Plugin $plugin) {
|
||||
try {
|
||||
$pluginService->uninstallPlugin($plugin);
|
||||
|
||||
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||
UninstallPlugin::dispatch(user(), $plugin);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.uninstalled'))
|
||||
->title(trans('admin/plugin.notifications.uninstall_started'))
|
||||
->body(trans('admin/plugin.notifications.background_info'))
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
|
||||
@@ -5,8 +5,9 @@ namespace App\Filament\Admin\Resources\Servers\Pages;
|
||||
use App\Enums\SuspendAction;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Servers\ServerResource;
|
||||
use App\Filament\Components\Actions\DeleteServerIcon;
|
||||
use App\Filament\Components\Actions\DeleteIcon;
|
||||
use App\Filament\Components\Actions\PreviewStartupAction;
|
||||
use App\Filament\Components\Actions\UploadIcon;
|
||||
use App\Filament\Components\Forms\Fields\MonacoEditor;
|
||||
use App\Filament\Components\Forms\Fields\StartupVariable;
|
||||
use App\Filament\Components\StateCasts\ServerConditionStateCast;
|
||||
@@ -31,7 +32,6 @@ use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
@@ -41,7 +41,6 @@ use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
@@ -60,9 +59,7 @@ use Filament\Support\Enums\Alignment;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
use LogicException;
|
||||
use Random\RandomException;
|
||||
|
||||
@@ -111,141 +108,18 @@ class EditServer extends EditRecord
|
||||
->icon(TablerIcon::InfoCircle)
|
||||
->schema([
|
||||
Grid::make()
|
||||
->columns(2)
|
||||
->columnStart(1)
|
||||
->schema([
|
||||
Image::make('', 'icon')
|
||||
->hidden(fn ($record) => !$record->icon && !$record->egg->image)
|
||||
->url(fn ($record) => $record->icon ?: $record->egg->image)
|
||||
->hidden(fn ($record) => !$record->icon && !$record->egg->icon)
|
||||
->url(fn ($record) => $record->icon ?: $record->egg->icon)
|
||||
->tooltip(fn ($record) => $record->icon ? '' : trans('server/setting.server_info.icon.tooltip'))
|
||||
->columnSpan(2)
|
||||
->imageSize(150)
|
||||
->columnSpanFull()
|
||||
->alignJustify(),
|
||||
Action::make('uploadIcon')
|
||||
->hiddenLabel()
|
||||
->icon(TablerIcon::PhotoUp)
|
||||
->tooltip(trans('admin/server.import_image'))
|
||||
->modal()
|
||||
->modalSubmitActionLabel(trans('server/setting.server_info.icon.upload'))
|
||||
->schema([
|
||||
Tabs::make()
|
||||
->contained(false)
|
||||
->tabs([
|
||||
Tab::make(trans('admin/egg.import.url'))
|
||||
->schema([
|
||||
Hidden::make('imageUrl'),
|
||||
Hidden::make('imageExtension'),
|
||||
TextInput::make('image_url')
|
||||
->label(trans('admin/egg.import.image_url'))
|
||||
->reactive()
|
||||
->autocomplete(false)
|
||||
->debounce(500)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
if (!$state) {
|
||||
$set('image_url_error', null);
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!in_array(parse_url($state, PHP_URL_SCHEME), ['http', 'https'], true)) {
|
||||
throw new \Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
if (!filter_var($state, FILTER_VALIDATE_URL)) {
|
||||
throw new \Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$extension = strtolower(pathinfo(parse_url($state, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
if (!array_key_exists($extension, Server::IMAGE_FORMATS)) {
|
||||
throw new \Exception(trans('admin/egg.import.unsupported_format', ['format' => implode(', ', array_keys(Server::IMAGE_FORMATS))]));
|
||||
}
|
||||
|
||||
$host = parse_url($state, PHP_URL_HOST);
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
if (
|
||||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false
|
||||
) {
|
||||
throw new \Exception(trans('admin/egg.import.no_local_ip'));
|
||||
}
|
||||
|
||||
$set('imageUrl', $state);
|
||||
$set('imageExtension', $extension);
|
||||
$set('image_url_error', null);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$set('image_url_error', $e->getMessage());
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', null);
|
||||
}
|
||||
}),
|
||||
TextEntry::make('image_url_error')
|
||||
->hiddenLabel()
|
||||
->visible(fn (Get $get) => $get('image_url_error') !== null)
|
||||
->afterStateHydrated(fn (Get $get) => $get('image_url_error')),
|
||||
Image::make(fn (Get $get) => $get('image_url'), '')
|
||||
->imageSize(150)
|
||||
->visible(fn (Get $get) => $get('image_url') && !$get('image_url_error'))
|
||||
->alignCenter(),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.import.file'))
|
||||
->schema([
|
||||
FileUpload::make('image')
|
||||
->hiddenLabel()
|
||||
->previewable()
|
||||
->openable(false)
|
||||
->downloadable(false)
|
||||
->maxSize(256)
|
||||
->maxFiles(1)
|
||||
->columnSpanFull()
|
||||
->alignCenter()
|
||||
->imageEditor()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory(Server::ICON_STORAGE_PATH)
|
||||
->acceptedFileTypes([
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/webp',
|
||||
'image/svg+xml',
|
||||
])
|
||||
->getUploadedFileNameForStorageUsing(function (TemporaryUploadedFile $file, $record) {
|
||||
return $record->uuid . '.' . $file->getClientOriginalExtension();
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->action(function (array $data, $record): void {
|
||||
if (!empty($data['imageUrl']) && !empty($data['imageExtension'])) {
|
||||
$this->saveIconFromUrl($data['imageUrl'], $data['imageExtension'], $record);
|
||||
Notification::make()
|
||||
->title(trans('server/setting.server_info.icon.updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('server/setting.server_info.icon.updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($data['imageUrl']) && empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.no_image'))
|
||||
->warning()
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
DeleteServerIcon::make(),
|
||||
UploadIcon::make(),
|
||||
DeleteIcon::make()
|
||||
->iconStoragePath(Server::getIconStoragePath()),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(3)
|
||||
@@ -321,7 +195,7 @@ class EditServer extends EditRecord
|
||||
try {
|
||||
$logs = $serverRepository->setServer($server)->getInstallLogs();
|
||||
|
||||
return mb_convert_encoding($logs, 'UTF-8', ['UTF-8', 'UTF-16', 'ISO-8859-1', 'ASCII']);
|
||||
return convert_to_utf8($logs);
|
||||
} catch (ConnectionException) {
|
||||
Notification::make()
|
||||
->title(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
|
||||
@@ -1202,37 +1076,4 @@ class EditServer extends EditRecord
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an icon from URL download to a file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveIconFromUrl(string $imageUrl, string $extension, Server $server): void
|
||||
{
|
||||
$context = stream_context_create([
|
||||
'http' => ['timeout' => 3],
|
||||
'https' => [
|
||||
'timeout' => 3,
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$normalizedExtension = match ($extension) {
|
||||
'svg+xml', 'svg' => 'svg',
|
||||
'jpeg', 'jpg' => 'jpg',
|
||||
'png' => 'png',
|
||||
'webp' => 'webp',
|
||||
default => throw new Exception(trans('admin/egg.import.unknown_extension')),
|
||||
};
|
||||
|
||||
$data = @file_get_contents($imageUrl, false, $context, 0, 262144); //256KB
|
||||
|
||||
if (empty($data)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
Storage::disk('public')->put(Server::ICON_STORAGE_PATH . "/$server->uuid.$normalizedExtension", $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class ListServers extends ListRecords
|
||||
ImageColumn::make('icon')
|
||||
->label('')
|
||||
->imageSize(46)
|
||||
->state(fn (Server $server) => $server->icon ?: $server->egg->image),
|
||||
->state(fn (Server $server) => $server->icon ?: $server->egg->icon),
|
||||
TextColumn::make('condition')
|
||||
->label(trans('server/dashboard.status'))
|
||||
->badge()
|
||||
@@ -81,7 +81,8 @@ class ListServers extends ListRecords
|
||||
->label(trans('server/dashboard.title'))
|
||||
->description(fn (Server $server) => $server->description)
|
||||
->grow()
|
||||
->searchable(),
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('allocation.address')
|
||||
->label('')
|
||||
->badge()
|
||||
|
||||
79
app/Filament/Components/Actions/DeleteIcon.php
Normal file
79
app/Filament/Components/Actions/DeleteIcon.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Actions;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Models\Traits\HasIcon;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DeleteIcon extends Action
|
||||
{
|
||||
/** @var string[] */
|
||||
protected ?array $iconFormats = null;
|
||||
|
||||
protected ?string $iconStoragePath = null;
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'delete_icon';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->visible(fn ($record) => $record->icon);
|
||||
|
||||
$this->hiddenLabel();
|
||||
|
||||
$this->tooltip(trans('admin/egg.import.delete_icon'));
|
||||
|
||||
$this->icon(TablerIcon::Trash);
|
||||
|
||||
$this->color('danger');
|
||||
|
||||
$this->action(function ($record) {
|
||||
foreach ($this->getIconFormats() as $ext) {
|
||||
$path = $this->getIconStoragePath() . "/$record->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.icon_deleted'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$record->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/** @param string[] $iconFormats */
|
||||
public function iconFormats(?array $iconFormats): static
|
||||
{
|
||||
$this->iconFormats = $iconFormats;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function iconStoragePath(?string $iconStoragePath): static
|
||||
{
|
||||
$this->iconStoragePath = $iconStoragePath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getIconFormats(): array
|
||||
{
|
||||
return $this->iconFormats ?? array_keys(HasIcon::$iconFormats);
|
||||
}
|
||||
|
||||
public function getIconStoragePath(): ?string
|
||||
{
|
||||
return $this->iconStoragePath;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Actions;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Models\Server;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DeleteServerIcon extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'delete_icon';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->visible(fn ($record) => $record->icon);
|
||||
|
||||
$this->hiddenLabel();
|
||||
|
||||
$this->tooltip(trans('admin/server.import_image'));
|
||||
|
||||
$this->icon(TablerIcon::Trash);
|
||||
|
||||
$this->color('danger');
|
||||
|
||||
$this->action(function ($record) {
|
||||
foreach (array_keys(Server::IMAGE_FORMATS) as $ext) {
|
||||
$path = Server::ICON_STORAGE_PATH . "/$record->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/setting.server_info.icon.deleted'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$record->refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
164
app/Filament/Components/Actions/UploadIcon.php
Normal file
164
app/Filament/Components/Actions/UploadIcon.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Actions;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Models\Traits\HasIcon;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Schemas\Components\Image;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class UploadIcon extends Action
|
||||
{
|
||||
/** @var string[] */
|
||||
protected ?array $iconFormats = null;
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'upload_icon';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->hiddenLabel();
|
||||
|
||||
$this->tooltip(trans('admin/egg.import.import_icon'));
|
||||
|
||||
$this->icon(TablerIcon::PhotoUp);
|
||||
|
||||
$this->modal();
|
||||
|
||||
$this->modalHeading('');
|
||||
|
||||
$this->modalSubmitActionLabel(trans('admin/egg.import.import_icon'));
|
||||
|
||||
$this->schema([
|
||||
Tabs::make()
|
||||
->contained(false)
|
||||
->tabs([
|
||||
Tab::make(trans('admin/egg.import.url'))
|
||||
->schema([
|
||||
TextInput::make('icon_url')
|
||||
->label(trans('admin/egg.import.icon_url'))
|
||||
->reactive()
|
||||
->autocomplete(false)
|
||||
->debounce(500)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
if (!$state) {
|
||||
$set('icon_url_error', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validateIconUrl($state);
|
||||
|
||||
$set('icon_url_error', null);
|
||||
} catch (Exception $exception) {
|
||||
$set('icon_url_error', $exception->getMessage());
|
||||
}
|
||||
}),
|
||||
TextEntry::make('icon_url_error')
|
||||
->hiddenLabel()
|
||||
->visible(fn (Get $get) => $get('icon_url_error') !== null)
|
||||
->afterStateHydrated(fn (Get $get) => $get('icon_url_error')),
|
||||
Image::make(fn (Get $get) => $get('icon_url'), '')
|
||||
->imageSize(150)
|
||||
->visible(fn (Get $get) => $get('icon_url') && !$get('icon_url_error'))
|
||||
->alignCenter(),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.import.file'))
|
||||
->schema([
|
||||
FileUpload::make('icon')
|
||||
->hiddenLabel()
|
||||
->previewable()
|
||||
->openable(false)
|
||||
->downloadable(false)
|
||||
->maxSize(256)
|
||||
->maxFiles(1)
|
||||
->columnSpanFull()
|
||||
->alignCenter()
|
||||
->imageEditor()
|
||||
->image()
|
||||
->acceptedFileTypes(fn () => $this->getIconFormats())
|
||||
->saveUploadedFileUsing(fn (TemporaryUploadedFile $file, $record) => $record->writeIcon($file->getClientOriginalExtension(), $file->getContent())),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
|
||||
$this->action(function (array $data, $record) {
|
||||
if (!empty($data['icon_url'])) {
|
||||
$this->validateIconUrl($data['icon_url']);
|
||||
|
||||
$content = Http::timeout(5)->connectTimeout(1)->withoutRedirecting()->get($data['icon_url'])->body();
|
||||
|
||||
if (empty($content)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$extension = strtolower(pathinfo(parse_url($data['icon_url'], PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
$record->writeIcon($extension, $content);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.icon_updated'))
|
||||
->success()
|
||||
->send();
|
||||
} elseif (!empty($data['icon'])) {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.icon_updated'))
|
||||
->success()
|
||||
->send();
|
||||
} else {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.no_icon'))
|
||||
->warning()
|
||||
->send();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function validateIconUrl(string $url): void
|
||||
{
|
||||
if (!in_array(parse_url($url, PHP_URL_SCHEME), ['http', 'https'], true)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
throw new Exception(trans('admin/egg.import.no_local_ip'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @param string[] $iconFormats */
|
||||
public function iconFormats(?array $iconFormats): static
|
||||
{
|
||||
$this->iconFormats = $iconFormats;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getIconFormats(): array
|
||||
{
|
||||
return $this->iconFormats ?? array_values(HasIcon::$iconFormats);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class StartupVariable extends Field
|
||||
|
||||
$this->hintIcon(TablerIcon::Code, fn (StartupVariable $component) => implode('|', $component->getVariableRules()));
|
||||
|
||||
$this->helperText(fn (StartupVariable $component) => !$component->getVariableDesc() ? '—' : $component->getVariableDesc());
|
||||
$this->helperText(fn (StartupVariable $component) => $component->getVariableDesc());
|
||||
|
||||
$this->rules(fn (StartupVariable $component) => $component->getVariableRules());
|
||||
|
||||
@@ -70,7 +70,7 @@ class StartupVariable extends Field
|
||||
],
|
||||
StartupVariableType::Toggle => [
|
||||
...parent::getDefaultStateCasts(),
|
||||
new BooleanStateCast(false),
|
||||
new BooleanStateCast(false, true),
|
||||
],
|
||||
default => parent::getDefaultStateCasts()
|
||||
};
|
||||
|
||||
@@ -21,9 +21,9 @@ class TagsFilter extends BaseFilter
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->query(fn (Builder $query, array $data) => $query->when($data['tag'], fn (Builder $query, $tag) => $query->whereJsonContains('tags', $tag)));
|
||||
$this->query(fn (Builder $query, array $data) => $query->when($data['tag'] ?? null, fn (Builder $query, $tag) => $query->whereJsonContains('tags', $tag)));
|
||||
|
||||
$this->indicateUsing(fn (array $data) => $data['tag'] ? 'Tag: ' . $data['tag'] : null);
|
||||
$this->indicateUsing(fn (array $data) => ($data['tag'] ?? null) ? 'Tag: ' . $data['tag'] : null);
|
||||
|
||||
$this->resetState(['tag' => null]);
|
||||
|
||||
|
||||
114
app/Filament/Server/Pages/Mounts.php
Normal file
114
app/Filament/Server/Pages/Mounts.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Server\Pages;
|
||||
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Facades\Activity;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Server;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class Mounts extends ServerFormPage
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::LayersLinked;
|
||||
|
||||
protected static ?int $navigationSort = 9;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return parent::canAccess() && user()?->can(SubuserPermission::MountRead, Filament::getTenant());
|
||||
}
|
||||
|
||||
protected function authorizeAccess(): void
|
||||
{
|
||||
abort_unless(user()?->can(SubuserPermission::MountRead, Filament::getTenant()), 403);
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$this->form->fill([
|
||||
'mounts' => $this->getRecord()->mounts->pluck('id')->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
$server = $this->getRecord();
|
||||
|
||||
$allowedMounts = Mount::query()
|
||||
->where('user_mountable', true)
|
||||
->where(function ($query) use ($server) {
|
||||
$query->whereDoesntHave('nodes')
|
||||
->orWhereHas('nodes', fn ($q) => $q->where('nodes.id', $server->node_id));
|
||||
})
|
||||
->where(function ($query) use ($server) {
|
||||
$query->whereDoesntHave('eggs')
|
||||
->orWhereHas('eggs', fn ($q) => $q->where('eggs.id', $server->egg_id));
|
||||
})
|
||||
->get();
|
||||
|
||||
return parent::form($schema)
|
||||
->components([
|
||||
Section::make([
|
||||
CheckboxList::make('mounts')
|
||||
->label(trans('server/mount.description'))
|
||||
->relationship('mounts')
|
||||
->options(fn () => $allowedMounts->mapWithKeys(fn (Mount $mount) => [$mount->id => $mount->name]))
|
||||
->descriptions(fn () => $allowedMounts->mapWithKeys(fn (Mount $mount) => [$mount->id => new HtmlString(str("$mount->source -> $mount->target")->stripTags() . ($mount->description ? '<br>' . str($mount->description)->stripTags() : ''))]))
|
||||
->helperText(fn () => $allowedMounts->isEmpty() ? trans('server/mount.no_mounts') : null)
|
||||
->disabled(fn (Server $server) => !user()?->can(SubuserPermission::MountUpdate, $server))
|
||||
->bulkToggleable()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state) {
|
||||
$this->save();
|
||||
})
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
abort_unless(user()?->can(SubuserPermission::MountUpdate, $this->getRecord()), 403);
|
||||
|
||||
try {
|
||||
$this->form->getState();
|
||||
$this->form->saveRelationships();
|
||||
|
||||
Activity::event('server:mount.update')
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/mount.notification_updated'))
|
||||
->body(trans('server/mount.notification_updated_body'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/mount.notification_failed'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/mount.title');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/mount.title');
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,13 @@ namespace App\Filament\Server\Pages;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Components\Actions\DeleteServerIcon;
|
||||
use App\Filament\Components\Actions\DeleteIcon;
|
||||
use App\Filament\Components\Actions\UploadIcon;
|
||||
use App\Models\Server;
|
||||
use App\Services\Servers\ReinstallServerService;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
@@ -21,20 +20,14 @@ use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Image;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class Settings extends ServerFormPage
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Settings;
|
||||
|
||||
protected static ?int $navigationSort = 10;
|
||||
protected static ?int $navigationSort = 11;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
@@ -79,140 +72,20 @@ class Settings extends ServerFormPage
|
||||
->afterStateUpdated(fn ($state, Server $server) => $this->updateDescription($state ?? '', $server)),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(2)
|
||||
->columnStart(6)
|
||||
->schema([
|
||||
Image::make('', 'icon')
|
||||
->hidden(fn ($record) => !$record->icon && !$record->egg->image)
|
||||
->url(fn ($record) => $record->icon ?: $record->egg->image)
|
||||
->hidden(fn ($record) => !$record->icon && !$record->egg->icon)
|
||||
->url(fn ($record) => $record->icon ?: $record->egg->icon)
|
||||
->tooltip(fn ($record) => $record->icon ? '' : trans('server/setting.server_info.icon.tooltip'))
|
||||
->columnSpan(2)
|
||||
->imageSize(150)
|
||||
->columnSpanFull()
|
||||
->alignJustify(),
|
||||
Action::make('uploadIcon')
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/server.import_image'))
|
||||
->icon(TablerIcon::PhotoUp)
|
||||
->modal()
|
||||
->modalSubmitActionLabel(trans('server/setting.server_info.icon.upload'))
|
||||
->schema([
|
||||
Tabs::make()
|
||||
->contained(false)
|
||||
->tabs([
|
||||
Tab::make(trans('admin/egg.import.url'))
|
||||
->schema([
|
||||
Hidden::make('imageUrl'),
|
||||
Hidden::make('imageExtension'),
|
||||
TextInput::make('image_url')
|
||||
->label(trans('admin/egg.import.image_url'))
|
||||
->reactive()
|
||||
->autocomplete(false)
|
||||
->debounce(500)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
if (!$state) {
|
||||
$set('image_url_error', null);
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!in_array(parse_url($state, PHP_URL_SCHEME), ['http', 'https'], true)) {
|
||||
throw new \Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
if (!filter_var($state, FILTER_VALIDATE_URL)) {
|
||||
throw new \Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$extension = strtolower(pathinfo(parse_url($state, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
if (!array_key_exists($extension, Server::IMAGE_FORMATS)) {
|
||||
throw new \Exception(trans('admin/egg.import.unsupported_format', ['format' => implode(', ', array_keys(Server::IMAGE_FORMATS))]));
|
||||
}
|
||||
|
||||
$host = parse_url($state, PHP_URL_HOST);
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
if (
|
||||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false
|
||||
) {
|
||||
throw new \Exception(trans('admin/egg.import.no_local_ip'));
|
||||
}
|
||||
|
||||
$set('imageUrl', $state);
|
||||
$set('imageExtension', $extension);
|
||||
$set('image_url_error', null);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$set('image_url_error', $e->getMessage());
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', null);
|
||||
}
|
||||
}),
|
||||
TextEntry::make('image_url_error')
|
||||
->hiddenLabel()
|
||||
->visible(fn (Get $get) => $get('image_url_error') !== null)
|
||||
->afterStateHydrated(fn (Get $get) => $get('image_url_error')),
|
||||
Image::make(fn (Get $get) => $get('image_url'), '')
|
||||
->imageSize(150)
|
||||
->visible(fn (Get $get) => $get('image_url') && !$get('image_url_error'))
|
||||
->alignCenter(),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.import.file'))
|
||||
->schema([
|
||||
FileUpload::make('image')
|
||||
->hiddenLabel()
|
||||
->previewable()
|
||||
->openable(false)
|
||||
->downloadable(false)
|
||||
->maxSize(256)
|
||||
->maxFiles(1)
|
||||
->columnSpanFull()
|
||||
->alignCenter()
|
||||
->imageEditor()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory(Server::ICON_STORAGE_PATH)
|
||||
->acceptedFileTypes([
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/webp',
|
||||
'image/svg+xml',
|
||||
])
|
||||
->getUploadedFileNameForStorageUsing(function (TemporaryUploadedFile $file, $record) {
|
||||
return $record->uuid . '.' . $file->getClientOriginalExtension();
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->action(function (array $data, $record): void {
|
||||
|
||||
if (!empty($data['imageUrl']) && !empty($data['imageExtension'])) {
|
||||
$this->saveIconFromUrl($data['imageUrl'], $data['imageExtension'], $record);
|
||||
Notification::make()
|
||||
->title(trans('server/setting.server_info.icon.updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('server/setting.server_info.icon.updated'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
if (empty($data['imageUrl']) && empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.no_image'))
|
||||
->warning()
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
DeleteServerIcon::make(),
|
||||
UploadIcon::make()
|
||||
->authorize(fn (Server $server) => user()?->can(SubuserPermission::SettingsChangeIcon, $server)),
|
||||
DeleteIcon::make()
|
||||
->iconStoragePath(Server::getIconStoragePath())
|
||||
->authorize(fn (Server $server) => user()?->can(SubuserPermission::SettingsChangeIcon, $server)),
|
||||
]),
|
||||
TextInput::make('uuid')
|
||||
->label(trans('server/setting.server_info.uuid'))
|
||||
@@ -446,39 +319,6 @@ class Settings extends ServerFormPage
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an icon from URL download to a file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveIconFromUrl(string $imageUrl, string $extension, Server $server): void
|
||||
{
|
||||
$context = stream_context_create([
|
||||
'http' => ['timeout' => 3],
|
||||
'https' => [
|
||||
'timeout' => 3,
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$normalizedExtension = match ($extension) {
|
||||
'svg+xml', 'svg' => 'svg',
|
||||
'jpeg', 'jpg' => 'jpg',
|
||||
'png' => 'png',
|
||||
'webp' => 'webp',
|
||||
default => throw new Exception(trans('admin/egg.import.unknown_extension')),
|
||||
};
|
||||
|
||||
$data = @file_get_contents($imageUrl, false, $context, 0, 262144); //256KB
|
||||
|
||||
if (empty($data)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
Storage::disk('public')->put(Server::ICON_STORAGE_PATH . "/$server->uuid.$normalizedExtension", $data);
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/setting.title');
|
||||
|
||||
@@ -28,7 +28,7 @@ class Startup extends ServerFormPage
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::PlayerPlay;
|
||||
|
||||
protected static ?int $navigationSort = 9;
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
@@ -149,12 +149,16 @@ class Startup extends ServerFormPage
|
||||
return parent::canAccess() && user()?->can(SubuserPermission::StartupRead, Filament::getTenant());
|
||||
}
|
||||
|
||||
public function update(?string $state, ServerVariable $serverVariable): void
|
||||
public function update(null|string|bool $state, ServerVariable $serverVariable): void
|
||||
{
|
||||
if (!$serverVariable->variable->user_editable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_bool($state)) {
|
||||
$state = $state ? '1' : '0';
|
||||
}
|
||||
|
||||
$original = $serverVariable->variable_value;
|
||||
|
||||
try {
|
||||
|
||||
@@ -139,6 +139,7 @@ class ActivityResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return Builder<ActivityLog> */
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
/** @var Server $server */
|
||||
|
||||
@@ -149,7 +149,7 @@ class EditFiles extends Page
|
||||
try {
|
||||
$contents = $this->getDaemonFileRepository()->getContent($this->path, config('panel.files.max_edit_size'));
|
||||
|
||||
return mb_convert_encoding($contents, 'UTF-8', ['UTF-8', 'UTF-16', 'ISO-8859-1', 'ASCII']);
|
||||
return convert_to_utf8($contents);
|
||||
} catch (FileSizeTooLargeException) {
|
||||
AlertBanner::make('file_too_large')
|
||||
->title(trans('server/file.alerts.file_too_large.title', ['name' => basename($this->path)]))
|
||||
@@ -259,9 +259,9 @@ class EditFiles extends Page
|
||||
return $this->fileRepository;
|
||||
}
|
||||
|
||||
public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null, bool $shouldGuessMissingParameters = false): string
|
||||
public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null, bool $shouldGuessMissingParameters = false, ?string $configuration = null): string
|
||||
{
|
||||
return parent::getUrl($parameters, $isAbsolute, $panel, $tenant) . '/';
|
||||
return parent::getUrl($parameters, $isAbsolute, $panel, $tenant, $shouldGuessMissingParameters, $configuration) . '/';
|
||||
}
|
||||
|
||||
public static function route(string $path): PageRegistration
|
||||
|
||||
@@ -79,15 +79,15 @@ class SubuserResource extends Resource
|
||||
|
||||
foreach ($data['permissions'] as $permission) {
|
||||
$options[$permission] = str($permission)->headline();
|
||||
$descriptions[$permission] = trans('server/user.permissions.' . $data['name'] . '_' . str($permission)->replace('-', '_'));
|
||||
$descriptions[$permission] = trans($data['translation_prefix']. '.' . $data['name'] . '_' . str($permission)->replace('-', '_'));
|
||||
$permissionsArray[$data['name']][] = $permission;
|
||||
}
|
||||
|
||||
$tabs[] = Tab::make($data['name'])
|
||||
->label(str($data['name'])->headline())
|
||||
->label(trans($data['translation_prefix']. '.' . $data['name'] . '_title'))
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/user.permissions.' . $data['name'] . '_desc'))
|
||||
->description(trans($data['translation_prefix']. '.' . $data['name'] . '_desc'))
|
||||
->icon($data['icon'])
|
||||
->contained(false)
|
||||
->schema([
|
||||
|
||||
@@ -13,10 +13,17 @@ use Illuminate\Auth\SessionGuard;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||
use Throwable;
|
||||
|
||||
class AccountController extends ClientApiController
|
||||
{
|
||||
/**
|
||||
* The number of seconds that must elapse before the email change throttle resets.
|
||||
*/
|
||||
private const EMAIL_UPDATE_THROTTLE = 60 * 60 * 24;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
@@ -63,10 +70,22 @@ class AccountController extends ClientApiController
|
||||
*/
|
||||
public function updateEmail(UpdateEmailRequest $request): JsonResponse
|
||||
{
|
||||
$original = $request->user()->email;
|
||||
$this->updateService->handle($request->user(), $request->validated());
|
||||
$user = $request->user();
|
||||
|
||||
// Only allow a user to change their email three times in the span
|
||||
// of 24 hours. This prevents malicious users from trying to find
|
||||
// existing accounts in the system by constantly changing their email.
|
||||
if (RateLimiter::tooManyAttempts($key = "user:update-email:{$user->uuid}", 3)) {
|
||||
throw new TooManyRequestsHttpException(message: 'Your email address has been changed too many times today. Please try again later.');
|
||||
}
|
||||
|
||||
$original = $user->email;
|
||||
|
||||
if (mb_strtolower($original) !== mb_strtolower($request->validated('email'))) {
|
||||
RateLimiter::hit($key, self::EMAIL_UPDATE_THROTTLE);
|
||||
|
||||
$this->updateService->handle($user, $request->validated());
|
||||
|
||||
if ($original !== $request->input('email')) {
|
||||
Activity::event('user:account.email-changed')
|
||||
->property(['old' => $original, 'new' => $request->input('email')])
|
||||
->log();
|
||||
@@ -85,7 +104,9 @@ class AccountController extends ClientApiController
|
||||
*/
|
||||
public function updatePassword(UpdatePasswordRequest $request): JsonResponse
|
||||
{
|
||||
$user = $this->updateService->handle($request->user(), $request->validated());
|
||||
$user = Activity::event('user:account.password-changed')->transaction(function () use ($request) {
|
||||
return $this->updateService->handle($request->user(), $request->validated());
|
||||
});
|
||||
|
||||
$guard = $this->manager->guard();
|
||||
// If you do not update the user in the session you'll end up working with a
|
||||
@@ -98,8 +119,6 @@ class AccountController extends ClientApiController
|
||||
$guard->logoutOtherDevices($request->input('password'));
|
||||
}
|
||||
|
||||
Activity::event('user:account.password-changed')->log();
|
||||
|
||||
return new JsonResponse([], Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class BackupController extends ClientApiController
|
||||
}
|
||||
|
||||
$backup = Activity::event('server:backup.start')->transaction(function ($log) use ($action, $server, $request) {
|
||||
$server->backups()->lockForUpdate();
|
||||
$server->backups()->lockForUpdate()->count();
|
||||
|
||||
$backup = $action->handle($server, $request->input('name'));
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class DatabaseController extends ClientApiController
|
||||
public function store(StoreDatabaseRequest $request, Server $server): array
|
||||
{
|
||||
$database = Activity::event('server:database.create')->transaction(function ($log) use ($request, $server) {
|
||||
$server->databases()->lockForUpdate();
|
||||
$server->databases()->lockForUpdate()->count();
|
||||
|
||||
$database = $this->deployDatabaseService->handle($server, $request->validated());
|
||||
|
||||
@@ -87,15 +87,12 @@ class DatabaseController extends ClientApiController
|
||||
*/
|
||||
public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database): array
|
||||
{
|
||||
$this->managementService->rotatePassword($database);
|
||||
$database->refresh();
|
||||
|
||||
Activity::event('server:database.rotate-password')
|
||||
->subject($database)
|
||||
->property('name', $database->database)
|
||||
->log();
|
||||
->transaction(fn () => $this->managementService->rotatePassword($database));
|
||||
|
||||
return $this->fractal->item($database)
|
||||
return $this->fractal->item($database->refresh())
|
||||
->parseIncludes(['password'])
|
||||
->transformWith($this->getTransformer(DatabaseTransformer::class))
|
||||
->toArray();
|
||||
|
||||
@@ -77,7 +77,7 @@ class FileController extends ClientApiController
|
||||
->property('file', $request->get('file'))
|
||||
->log();
|
||||
|
||||
return new Response($response, Response::HTTP_OK, ['Content-Type' => 'text/plain']);
|
||||
return new Response(convert_to_utf8($response), Response::HTTP_OK, ['Content-Type' => 'text/plain; charset=utf-8']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,13 +87,13 @@ class StartupController extends ClientApiController
|
||||
|
||||
$startup = $this->startupCommandService->handle($server);
|
||||
|
||||
if ($variable->env_variable !== $request->input('value')) {
|
||||
if ($original !== $request->input('value')) {
|
||||
Activity::event('server:startup.edit')
|
||||
->subject($variable)
|
||||
->property([
|
||||
'variable' => $variable->env_variable,
|
||||
'old' => $original,
|
||||
'new' => $request->input('value'),
|
||||
'new' => $request->input('value') ?? '',
|
||||
])
|
||||
->log();
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class BackupRemoteUploadController extends Controller
|
||||
/** @var Server $server */
|
||||
$server = $model->server;
|
||||
if ($server->node_id !== $node->id) {
|
||||
throw new HttpForbiddenException('You do not have permission to access that backup.');
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
// Prevent backups that have already been completed from trying to
|
||||
|
||||
@@ -47,7 +47,7 @@ class BackupStatusController extends Controller
|
||||
/** @var Server $server */
|
||||
$server = $model->server;
|
||||
if ($server->node_id !== $node->id) {
|
||||
throw new HttpForbiddenException('You do not have permission to access that backup.');
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
if ($model->is_successful) {
|
||||
@@ -97,6 +97,11 @@ class BackupStatusController extends Controller
|
||||
/** @var Backup $model */
|
||||
$model = Backup::query()->where('uuid', $backup)->firstOrFail();
|
||||
|
||||
$node = $request->attributes->get('node');
|
||||
if (!$model->server->node->is($node)) {
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
$model->server->update(['status' => null]);
|
||||
|
||||
Activity::event($request->boolean('successful') ? 'server:backup.restore-complete' : 'server.backup.restore-failed')
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
namespace App\Http\Controllers\Api\Remote\Servers;
|
||||
|
||||
use App\Enums\ContainerStatus;
|
||||
use App\Exceptions\Http\HttpForbiddenException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\Remote\ServerRequest;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ServerContainersController extends Controller
|
||||
{
|
||||
/**
|
||||
* Updates the server container's status on the Panel
|
||||
*/
|
||||
public function status(ServerRequest $request, Server $server): JsonResponse
|
||||
public function status(Request $request, Server $server): JsonResponse
|
||||
{
|
||||
if (!$server->node->is($request->attributes->get('node'))) {
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
$status = ContainerStatus::tryFrom($request->json('data.new_state')) ?? ContainerStatus::Missing;
|
||||
|
||||
cache()->put("servers.$server->uuid.status", $status, now()->addHour());
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Http\Controllers\Api\Remote\Servers;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use App\Exceptions\Http\HttpForbiddenException;
|
||||
use App\Facades\Activity;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\Remote\ServerRequest;
|
||||
use App\Http\Resources\Daemon\ServerConfigurationCollection;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Backup;
|
||||
@@ -17,6 +17,7 @@ use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Throwable;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class ServerDetailsController extends Controller
|
||||
{
|
||||
@@ -33,8 +34,21 @@ class ServerDetailsController extends Controller
|
||||
* Returns details about the server that allows daemon to self-recover and ensure
|
||||
* that the state of the server matches the Panel at all times.
|
||||
*/
|
||||
public function __invoke(ServerRequest $request, Server $server): JsonResponse
|
||||
public function __invoke(Request $request, Server $server): JsonResponse
|
||||
{
|
||||
Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class);
|
||||
|
||||
$transfer = $server->transfer;
|
||||
|
||||
// If the server is being transferred allow either node to request information about
|
||||
// the server. If the server is not being transferred only the target node is allowed
|
||||
// to fetch these details.
|
||||
$valid = $transfer ? $node->id === $transfer->old_node || $node->id === $transfer->new_node : $node->id === $server->node_id;
|
||||
|
||||
if (!$valid) {
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'settings' => $this->configurationStructureService->handle($server),
|
||||
'process_configuration' => $this->eggConfigurationService->handle($server),
|
||||
|
||||
@@ -4,12 +4,13 @@ namespace App\Http\Controllers\Api\Remote\Servers;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use App\Events\Server\Installed as ServerInstalled;
|
||||
use App\Exceptions\Http\HttpForbiddenException;
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\Remote\InstallationDataRequest;
|
||||
use App\Http\Requests\Api\Remote\ServerRequest;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ServerInstallController extends Controller
|
||||
@@ -17,12 +18,18 @@ class ServerInstallController extends Controller
|
||||
/**
|
||||
* Returns installation information for a server.
|
||||
*/
|
||||
public function index(ServerRequest $request, Server $server): JsonResponse
|
||||
public function index(Request $request, Server $server): JsonResponse
|
||||
{
|
||||
if (!$server->node->is($request->attributes->get('node'))) {
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
$egg = $server->egg;
|
||||
|
||||
return new JsonResponse([
|
||||
'container_image' => $server->egg->copy_script_container,
|
||||
'entrypoint' => $server->egg->copy_script_entry,
|
||||
'script' => $server->egg->copy_script_install,
|
||||
'container_image' => $egg->copy_script_container,
|
||||
'entrypoint' => $egg->copy_script_entry,
|
||||
'script' => $egg->copy_script_install,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -35,6 +42,10 @@ class ServerInstallController extends Controller
|
||||
{
|
||||
$status = null;
|
||||
|
||||
if (!$server->node->is($request->attributes->get('node'))) {
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
$successful = $request->boolean('successful');
|
||||
|
||||
// Make sure the type of failure is accurate
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Remote\Servers;
|
||||
|
||||
use App\Exceptions\Http\HttpForbiddenException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\Remote\ServerRequest;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||
use Throwable;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class ServerTransferController extends Controller
|
||||
{
|
||||
@@ -29,13 +32,22 @@ class ServerTransferController extends Controller
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function failure(ServerRequest $request, Server $server): JsonResponse
|
||||
public function failure(Request $request, Server $server): JsonResponse
|
||||
{
|
||||
$transfer = $server->transfer;
|
||||
if (is_null($transfer)) {
|
||||
throw new ConflictHttpException('Server is not being transferred.');
|
||||
}
|
||||
|
||||
/* @var Node $node */
|
||||
Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class);
|
||||
|
||||
// Either node can tell the panel that the transfer has failed. Only the new node
|
||||
// can tell the panel that it was successful.
|
||||
if (!$node->is($transfer->newNode) && !$node->is($transfer->oldNode)) {
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
$this->connection->transaction(function () use ($transfer) {
|
||||
$transfer->forceFill(['successful' => false])->saveOrFail();
|
||||
|
||||
@@ -53,13 +65,22 @@ class ServerTransferController extends Controller
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function success(ServerRequest $request, Server $server): JsonResponse
|
||||
public function success(Request $request, Server $server): JsonResponse
|
||||
{
|
||||
$transfer = $server->transfer;
|
||||
if (is_null($transfer)) {
|
||||
throw new ConflictHttpException('Server is not being transferred.');
|
||||
}
|
||||
|
||||
/* @var Node $node */
|
||||
Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class);
|
||||
|
||||
// Only the new node communicates a successful state to the panel, so we should
|
||||
// not allow the old node to hit this endpoint.
|
||||
if (!$node->is($transfer->newNode)) {
|
||||
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
|
||||
}
|
||||
|
||||
/** @var Server $server */
|
||||
$server = $this->connection->transaction(function () use ($server, $transfer) {
|
||||
$data = [];
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as BaseMiddleware;
|
||||
|
||||
class VerifyCsrfToken extends BaseVerifier
|
||||
class PreventRequestForgery extends BaseMiddleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification. These are
|
||||
@@ -23,7 +23,7 @@ class RequireTwoFactorAuthentication
|
||||
* order to perform actions. If so, we check the level at which it is required (all users
|
||||
* or just admins) and then check if the user has enabled it for their account.
|
||||
*
|
||||
* @throws \App\Exceptions\Http\TwoFactorAuthRequiredException
|
||||
* @throws TwoFactorAuthRequiredException
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
|
||||
46
app/Http/Middleware/SetSecurityHeaders.php
Normal file
46
app/Http/Middleware/SetSecurityHeaders.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class SetSecurityHeaders
|
||||
{
|
||||
/**
|
||||
* Ideally we move away from X-Frame-Options/X-XSS-Protection and implement a
|
||||
* proper standard CSP, but I can guarantee that will break for a lot of folks
|
||||
* using custom plugins and who knows what image embeds.
|
||||
*
|
||||
* We'll circle back to that at a later date when it can be more fully controlled
|
||||
* by the admin to support those cases without too much trouble.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected static array $headers = [
|
||||
'X-Frame-Options' => 'DENY',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
'X-XSS-Protection' => '1; mode=block',
|
||||
'Referrer-Policy' => 'no-referrer-when-downgrade',
|
||||
];
|
||||
|
||||
/**
|
||||
* Enforces some basic security headers on all responses returned by the software.
|
||||
* If a header has already been set in another location within the code it will be
|
||||
* skipped over here.
|
||||
*
|
||||
* @param (\Closure(mixed): Response) $next
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
$response = $next($request);
|
||||
|
||||
foreach (static::$headers as $key => $value) {
|
||||
if (!$response->headers->has($key)) {
|
||||
$response->headers->set($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ abstract class ApplicationApiRequest extends FormRequest
|
||||
$value = $this->route()->parameter($key);
|
||||
|
||||
Assert::isInstanceOf($value, $expect);
|
||||
Assert::isInstanceOf($value, Model::class);
|
||||
Assert::isInstanceOf($value, Model::class); // @phpstan-ignore staticMethod.alreadyNarrowedType
|
||||
Assert::true($value->exists);
|
||||
|
||||
/* @var T $value */
|
||||
|
||||
@@ -2,8 +2,15 @@
|
||||
|
||||
namespace App\Http\Requests\Api\Remote;
|
||||
|
||||
class InstallationDataRequest extends ServerRequest
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class InstallationDataRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|string[]>
|
||||
*/
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Remote;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
/** @var Node $node */
|
||||
$node = $this->attributes->get('node');
|
||||
|
||||
/** @var ?Server $server */
|
||||
$server = $this->route()->parameter('server');
|
||||
|
||||
if ($server) {
|
||||
if ($server->transfer) {
|
||||
return $server->transfer->old_node === $node->id || $server->transfer->new_node === $node->id;
|
||||
}
|
||||
|
||||
return $server->node_id === $node->id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
|
||||
abstract class Job
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queueable Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This job base class provides a central location to place any logic that
|
||||
| is shared across all of your jobs. The trait included with the class
|
||||
| provides access to the "onQueue" and "delay" queue helper methods.
|
||||
|
|
||||
*/
|
||||
|
||||
use Queueable;
|
||||
}
|
||||
55
app/Jobs/Plugin/InstallPlugin.php
Normal file
55
app/Jobs/Plugin/InstallPlugin.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Plugin;
|
||||
|
||||
use App\Filament\Admin\Resources\Plugins\Pages\ListPlugins;
|
||||
use App\Models\Plugin;
|
||||
use App\Models\User;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InstallPlugin implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user, public Plugin $plugin) {}
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
try {
|
||||
$pluginService->installPlugin($this->plugin, !$this->plugin->isTheme() || !$pluginService->hasThemePluginEnabled());
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.installed'))
|
||||
->body($this->plugin->name)
|
||||
->actions([
|
||||
Action::make('goto_plugins')
|
||||
->label(trans('admin/plugin.notifications.goto_plugins'))
|
||||
->url(ListPlugins::getUrl(panel: 'admin')),
|
||||
])
|
||||
->sendToDatabase($this->user);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/plugin.notifications.install_error'))
|
||||
->body($exception->getMessage())
|
||||
->sendToDatabase($this->user);
|
||||
}
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'plugin:install:' . $this->plugin->id;
|
||||
}
|
||||
}
|
||||
55
app/Jobs/Plugin/UninstallPlugin.php
Normal file
55
app/Jobs/Plugin/UninstallPlugin.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Plugin;
|
||||
|
||||
use App\Filament\Admin\Resources\Plugins\Pages\ListPlugins;
|
||||
use App\Models\Plugin;
|
||||
use App\Models\User;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UninstallPlugin implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user, public Plugin $plugin) {}
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
try {
|
||||
$pluginService->uninstallPlugin($this->plugin);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.uninstalled'))
|
||||
->body($this->plugin->name)
|
||||
->actions([
|
||||
Action::make('goto_plugins')
|
||||
->label(trans('admin/plugin.notifications.goto_plugins'))
|
||||
->url(ListPlugins::getUrl(panel: 'admin')),
|
||||
])
|
||||
->sendToDatabase($this->user);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/plugin.notifications.uninstall_error'))
|
||||
->body($exception->getMessage())
|
||||
->sendToDatabase($this->user);
|
||||
}
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'plugin:uninstall:' . $this->plugin->id;
|
||||
}
|
||||
}
|
||||
55
app/Jobs/Plugin/UpdatePlugin.php
Normal file
55
app/Jobs/Plugin/UpdatePlugin.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Plugin;
|
||||
|
||||
use App\Filament\Admin\Resources\Plugins\Pages\ListPlugins;
|
||||
use App\Models\Plugin;
|
||||
use App\Models\User;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UpdatePlugin implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user, public Plugin $plugin) {}
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
try {
|
||||
$pluginService->updatePlugin($this->plugin);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.updated'))
|
||||
->body($this->plugin->name)
|
||||
->actions([
|
||||
Action::make('goto_plugins')
|
||||
->label(trans('admin/plugin.notifications.goto_plugins'))
|
||||
->url(ListPlugins::getUrl(panel: 'admin')),
|
||||
])
|
||||
->sendToDatabase($this->user);
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/plugin.notifications.update_error'))
|
||||
->body($exception->getMessage())
|
||||
->sendToDatabase($this->user);
|
||||
}
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'plugin:update:' . $this->plugin->id;
|
||||
}
|
||||
}
|
||||
55
app/Jobs/RevokeSftpAccessJob.php
Normal file
55
app/Jobs/RevokeSftpAccessJob.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Queue\Attributes\DeleteWhenMissingModels;
|
||||
use Illuminate\Queue\Attributes\WithoutRelations;
|
||||
|
||||
/**
|
||||
* Revokes all SFTP access for a user on a given node or for a specific server.
|
||||
*/
|
||||
#[DeleteWhenMissingModels]
|
||||
class RevokeSftpAccessJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public int $tries = 3;
|
||||
|
||||
public int $maxExceptions = 1;
|
||||
|
||||
public function __construct(
|
||||
public readonly string $user,
|
||||
#[WithoutRelations]
|
||||
public readonly Server|Node $target,
|
||||
) {}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
$target = $this->target instanceof Node ? "node:{$this->target->uuid}" : "server:{$this->target->uuid}";
|
||||
|
||||
return "revoke-sftp:{$this->user}:{$target}";
|
||||
}
|
||||
|
||||
public function handle(DaemonServerRepository $repository): void
|
||||
{
|
||||
try {
|
||||
if ($this->target instanceof Server) {
|
||||
$repository->setServer($this->target)->deauthorize($this->user);
|
||||
} else {
|
||||
$repository->setNode($this->target)->deauthorize($this->user);
|
||||
}
|
||||
} catch (ConnectionException) {
|
||||
// Keep retrying this job with a longer and longer backoff until we hit three
|
||||
// attempts at which point we stop and will assume the node is fully offline
|
||||
// and we are just wasting time.
|
||||
$this->release($this->attempts() * 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Jobs\Job;
|
||||
use App\Models\Task;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
@@ -14,9 +15,10 @@ use Illuminate\Queue\SerializesModels;
|
||||
use InvalidArgumentException;
|
||||
use Throwable;
|
||||
|
||||
class RunTaskJob extends Job implements ShouldQueue
|
||||
class RunTaskJob implements ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
|
||||
25
app/Listeners/RevocationListener.php
Normal file
25
app/Listeners/RevocationListener.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\User\Deleting;
|
||||
use App\Events\User\PasswordChanged;
|
||||
use App\Jobs\RevokeSftpAccessJob;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class RevocationListener
|
||||
{
|
||||
public function handle(Deleting|PasswordChanged $event): void
|
||||
{
|
||||
$user = $event->user;
|
||||
|
||||
// Look at all of the nodes that a user is associated with and trigger a job
|
||||
// that disconnects them from websockets and SFTP.
|
||||
Node::query()
|
||||
->whereIn('nodes.id', $user->directAccessibleServers()->select('servers.node_id')->distinct())
|
||||
->chunk(50, function (Collection $nodes) use ($user) {
|
||||
$nodes->each(fn (Node $node) => RevokeSftpAccessJob::dispatch($user->uuid, $node));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use Filament\Support\Exceptions\Halt;
|
||||
|
||||
class RequirementsStep
|
||||
{
|
||||
public const MIN_PHP_VERSION = '8.2';
|
||||
public const MIN_PHP_VERSION = '8.3';
|
||||
|
||||
public static function make(): Step
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Services\Nodes\NodeJWTService;
|
||||
use App\Services\Servers\GetUserPermissionsService;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\View\Components\Columns\IconColumnComponent\IconComponent;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
use Livewire\Attributes\Locked;
|
||||
@@ -30,7 +31,7 @@ class NodeClientConnectivity extends Component
|
||||
$this->nodeJWTService = $nodeJWTService;
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
public function render(): View
|
||||
{
|
||||
$httpUrl = $this->node->getConnectionAddress();
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Enums\TablerIcon;
|
||||
use App\Events\ActivityLogged;
|
||||
use App\Traits\HasValidation;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -17,6 +16,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -31,28 +31,28 @@ use LogicException;
|
||||
* @property string|null $description
|
||||
* @property string|null $actor_type
|
||||
* @property int|null $actor_id
|
||||
* @property Collection<array-key, mixed> $properties
|
||||
* @property Carbon $timestamp
|
||||
* @property int|null $api_key_id
|
||||
* @property Collection|null $properties
|
||||
* @property \Carbon\Carbon $timestamp
|
||||
* @property Model|\Eloquent $actor
|
||||
* @property \Illuminate\Database\Eloquent\Collection|ActivityLogSubject[] $subjects
|
||||
* @property int|null $subjects_count
|
||||
* @property ApiKey|null $apiKey
|
||||
* @property-read Model|\Eloquent|null $actor
|
||||
* @property-read ApiKey|null $apiKey
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, ActivityLogSubject> $subjects
|
||||
* @property-read int|null $subjects_count
|
||||
*
|
||||
* @method static Builder|ActivityLog forActor(Model $actor)
|
||||
* @method static Builder|ActivityLog forEvent(string $action)
|
||||
* @method static Builder|ActivityLog newModelQuery()
|
||||
* @method static Builder|ActivityLog newQuery()
|
||||
* @method static Builder|ActivityLog query()
|
||||
* @method static Builder|ActivityLog whereActorId($value)
|
||||
* @method static Builder|ActivityLog whereActorType($value)
|
||||
* @method static Builder|ActivityLog whereApiKeyId($value)
|
||||
* @method static Builder|ActivityLog whereDescription($value)
|
||||
* @method static Builder|ActivityLog whereEvent($value)
|
||||
* @method static Builder|ActivityLog whereId($value)
|
||||
* @method static Builder|ActivityLog whereIp($value)
|
||||
* @method static Builder|ActivityLog whereProperties($value)
|
||||
* @method static Builder|ActivityLog whereTimestamp($value)
|
||||
* @method static Builder<static>|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor)
|
||||
* @method static Builder<static>|ActivityLog forEvent(string $action)
|
||||
* @method static Builder<static>|ActivityLog newModelQuery()
|
||||
* @method static Builder<static>|ActivityLog newQuery()
|
||||
* @method static Builder<static>|ActivityLog query()
|
||||
* @method static Builder<static>|ActivityLog whereActorId($value)
|
||||
* @method static Builder<static>|ActivityLog whereActorType($value)
|
||||
* @method static Builder<static>|ActivityLog whereApiKeyId($value)
|
||||
* @method static Builder<static>|ActivityLog whereDescription($value)
|
||||
* @method static Builder<static>|ActivityLog whereEvent($value)
|
||||
* @method static Builder<static>|ActivityLog whereId($value)
|
||||
* @method static Builder<static>|ActivityLog whereIp($value)
|
||||
* @method static Builder<static>|ActivityLog whereProperties($value)
|
||||
* @method static Builder<static>|ActivityLog whereTimestamp($value)
|
||||
*/
|
||||
class ActivityLog extends Model implements HasIcon, HasLabel
|
||||
{
|
||||
|
||||
@@ -14,14 +14,18 @@ use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $activity_log_id
|
||||
* @property int $subject_id
|
||||
* @property string $subject_type
|
||||
* @property ActivityLog|null $activityLog
|
||||
* @property Model|\Eloquent $subject
|
||||
* @property int $subject_id
|
||||
* @property-read ActivityLog $activityLog
|
||||
* @property-read Model|\Eloquent $subject
|
||||
*
|
||||
* @method static Builder|ActivityLogSubject newModelQuery()
|
||||
* @method static Builder|ActivityLogSubject newQuery()
|
||||
* @method static Builder|ActivityLogSubject query()
|
||||
* @method static Builder<static>|ActivityLogSubject newModelQuery()
|
||||
* @method static Builder<static>|ActivityLogSubject newQuery()
|
||||
* @method static Builder<static>|ActivityLogSubject query()
|
||||
* @method static Builder<static>|ActivityLogSubject whereActivityLogId($value)
|
||||
* @method static Builder<static>|ActivityLogSubject whereId($value)
|
||||
* @method static Builder<static>|ActivityLogSubject whereSubjectId($value)
|
||||
* @method static Builder<static>|ActivityLogSubject whereSubjectType($value)
|
||||
*/
|
||||
class ActivityLogSubject extends Pivot
|
||||
{
|
||||
|
||||
@@ -4,13 +4,12 @@ namespace App\Models;
|
||||
|
||||
use App\Exceptions\Service\Allocation\ServerUsingAllocationException;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Database\Factories\AllocationFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* App\Models\Allocation.
|
||||
@@ -18,32 +17,33 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
* @property int $id
|
||||
* @property int $node_id
|
||||
* @property string $ip
|
||||
* @property string|null $ip_alias
|
||||
* @property int $port
|
||||
* @property int|null $server_id
|
||||
* @property string|null $notes
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property string $alias
|
||||
* @property bool $has_alias
|
||||
* @property string $address
|
||||
* @property Server|null $server
|
||||
* @property Node $node
|
||||
* @property string|null $ip_alias
|
||||
* @property string|null $notes
|
||||
* @property bool $is_locked
|
||||
* @property-read string $address
|
||||
* @property-read string $alias
|
||||
* @property-read bool $has_alias
|
||||
* @property-read Node $node
|
||||
* @property-read Server|null $server
|
||||
*
|
||||
* @method static AllocationFactory factory(...$parameters)
|
||||
* @method static Builder|Allocation newModelQuery()
|
||||
* @method static Builder|Allocation newQuery()
|
||||
* @method static Builder|Allocation query()
|
||||
* @method static Builder|Allocation whereCreatedAt($value)
|
||||
* @method static Builder|Allocation whereId($value)
|
||||
* @method static Builder|Allocation whereIp($value)
|
||||
* @method static Builder|Allocation whereIpAlias($value)
|
||||
* @method static Builder|Allocation whereNodeId($value)
|
||||
* @method static Builder|Allocation whereNotes($value)
|
||||
* @method static Builder|Allocation wherePort($value)
|
||||
* @method static Builder|Allocation whereServerId($value)
|
||||
* @method static Builder|Allocation whereUpdatedAt($value)
|
||||
* @method static \Database\Factories\AllocationFactory factory($count = null, $state = [])
|
||||
* @method static Builder<static>|Allocation newModelQuery()
|
||||
* @method static Builder<static>|Allocation newQuery()
|
||||
* @method static Builder<static>|Allocation query()
|
||||
* @method static Builder<static>|Allocation whereCreatedAt($value)
|
||||
* @method static Builder<static>|Allocation whereId($value)
|
||||
* @method static Builder<static>|Allocation whereIp($value)
|
||||
* @method static Builder<static>|Allocation whereIpAlias($value)
|
||||
* @method static Builder<static>|Allocation whereIsLocked($value)
|
||||
* @method static Builder<static>|Allocation whereNodeId($value)
|
||||
* @method static Builder<static>|Allocation whereNotes($value)
|
||||
* @method static Builder<static>|Allocation wherePort($value)
|
||||
* @method static Builder<static>|Allocation whereServerId($value)
|
||||
* @method static Builder<static>|Allocation whereUpdatedAt($value)
|
||||
*/
|
||||
class Allocation extends Model
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Models;
|
||||
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use App\Traits\HasValidation;
|
||||
use Database\Factories\ApiKeyFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -17,41 +16,35 @@ use Webmozart\Assert\Assert;
|
||||
* App\Models\ApiKey.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $key_type
|
||||
* @property string $identifier
|
||||
* @property string $token
|
||||
* @property string[]|null $permissions
|
||||
* @property string[]|null $allowed_ips
|
||||
* @property string|null $memo
|
||||
* @property Carbon|null $last_used_at
|
||||
* @property Carbon|null $expires_at
|
||||
* @property string[] $allowed_ips
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property User $tokenable
|
||||
* @property User $user
|
||||
* @property int|null $user_id
|
||||
* @property string|null $memo
|
||||
* @property string|null $identifier
|
||||
* @property int $key_type
|
||||
* @property Carbon|null $last_used_at
|
||||
* @property Carbon|null $expires_at
|
||||
* @property array<string, int> $permissions
|
||||
* @property-read User|null $user
|
||||
*
|
||||
* @method static ApiKeyFactory factory(...$parameters)
|
||||
* @method static Builder|ApiKey newModelQuery()
|
||||
* @method static Builder|ApiKey newQuery()
|
||||
* @method static Builder|ApiKey query()
|
||||
* @method static Builder|ApiKey whereAllowedIps($value)
|
||||
* @method static Builder|ApiKey whereCreatedAt($value)
|
||||
* @method static Builder|ApiKey whereId($value)
|
||||
* @method static Builder|ApiKey whereIdentifier($value)
|
||||
* @method static Builder|ApiKey whereKeyType($value)
|
||||
* @method static Builder|ApiKey whereLastUsedAt($value)
|
||||
* @method static Builder|ApiKey whereMemo($value)
|
||||
* @method static Builder|ApiKey whereRAllocations($value)
|
||||
* @method static Builder|ApiKey whereRDatabaseHosts($value)
|
||||
* @method static Builder|ApiKey whereREggs($value)
|
||||
* @method static Builder|ApiKey whereRNodes($value)
|
||||
* @method static Builder|ApiKey whereRServerDatabases($value)
|
||||
* @method static Builder|ApiKey whereRServers($value)
|
||||
* @method static Builder|ApiKey whereRUsers($value)
|
||||
* @method static Builder|ApiKey whereToken($value)
|
||||
* @method static Builder|ApiKey whereUpdatedAt($value)
|
||||
* @method static Builder|ApiKey whereUserId($value)
|
||||
* @method static \Database\Factories\ApiKeyFactory factory($count = null, $state = [])
|
||||
* @method static Builder<static>|ApiKey newModelQuery()
|
||||
* @method static Builder<static>|ApiKey newQuery()
|
||||
* @method static Builder<static>|ApiKey query()
|
||||
* @method static Builder<static>|ApiKey whereAllowedIps($value)
|
||||
* @method static Builder<static>|ApiKey whereCreatedAt($value)
|
||||
* @method static Builder<static>|ApiKey whereExpiresAt($value)
|
||||
* @method static Builder<static>|ApiKey whereId($value)
|
||||
* @method static Builder<static>|ApiKey whereIdentifier($value)
|
||||
* @method static Builder<static>|ApiKey whereKeyType($value)
|
||||
* @method static Builder<static>|ApiKey whereLastUsedAt($value)
|
||||
* @method static Builder<static>|ApiKey whereMemo($value)
|
||||
* @method static Builder<static>|ApiKey wherePermissions($value)
|
||||
* @method static Builder<static>|ApiKey whereToken($value)
|
||||
* @method static Builder<static>|ApiKey whereUpdatedAt($value)
|
||||
* @method static Builder<static>|ApiKey whereUserId($value)
|
||||
*/
|
||||
class ApiKey extends PersonalAccessToken
|
||||
{
|
||||
|
||||
@@ -18,20 +18,44 @@ use Illuminate\Database\Query\Builder;
|
||||
* @property int $id
|
||||
* @property int $server_id
|
||||
* @property string $uuid
|
||||
* @property bool $is_successful
|
||||
* @property bool $is_locked
|
||||
* @property string $name
|
||||
* @property string[] $ignored_files
|
||||
* @property string $disk
|
||||
* @property string|null $checksum
|
||||
* @property int $bytes
|
||||
* @property string|null $upload_id
|
||||
* @property CarbonImmutable|null $completed_at
|
||||
* @property BackupStatus $status
|
||||
* @property CarbonImmutable $created_at
|
||||
* @property CarbonImmutable $updated_at
|
||||
* @property CarbonImmutable|null $created_at
|
||||
* @property CarbonImmutable|null $updated_at
|
||||
* @property CarbonImmutable|null $deleted_at
|
||||
* @property Server $server
|
||||
* @property bool $is_successful
|
||||
* @property string|null $upload_id
|
||||
* @property bool $is_locked
|
||||
* @property-read Server $server
|
||||
* @property-read BackupStatus $status
|
||||
*
|
||||
* @method static \Database\Factories\BackupFactory factory($count = null, $state = [])
|
||||
* @method static BackupQueryBuilder<static>|Backup newModelQuery()
|
||||
* @method static BackupQueryBuilder<static>|Backup newQuery()
|
||||
* @method static BackupQueryBuilder<static>|Backup nonFailed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Backup onlyTrashed()
|
||||
* @method static BackupQueryBuilder<static>|Backup query()
|
||||
* @method static BackupQueryBuilder<static>|Backup whereBytes($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereChecksum($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereCompletedAt($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereCreatedAt($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereDeletedAt($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereDisk($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereId($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereIgnoredFiles($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereIsLocked($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereIsSuccessful($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereName($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereServerId($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereUpdatedAt($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereUploadId($value)
|
||||
* @method static BackupQueryBuilder<static>|Backup whereUuid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Backup withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Backup withoutTrashed()
|
||||
*/
|
||||
class Backup extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -4,11 +4,11 @@ namespace App\Models;
|
||||
|
||||
use App\Contracts\Validatable;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
@@ -19,12 +19,27 @@ use PDOException;
|
||||
* @property string $username
|
||||
* @property string $remote
|
||||
* @property string $password
|
||||
* @property ?int $max_connections
|
||||
* @property string $jdbc
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Server $server
|
||||
* @property DatabaseHost $host
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property int|null $max_connections
|
||||
* @property-read DatabaseHost $host
|
||||
* @property-read string $jdbc
|
||||
* @property-read Server $server
|
||||
*
|
||||
* @method static \Database\Factories\DatabaseFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereDatabase($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereDatabaseHostId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereMaxConnections($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database wherePassword($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereRemote($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereServerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Database whereUsername($value)
|
||||
*/
|
||||
class Database extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -21,13 +21,26 @@ use Illuminate\Support\Facades\DB;
|
||||
* @property string $username
|
||||
* @property string $password
|
||||
* @property int|null $max_databases
|
||||
* @property int|null $node_id
|
||||
* @property CarbonImmutable $created_at
|
||||
* @property CarbonImmutable $updated_at
|
||||
* @property Collection|Node[] $nodes
|
||||
* @property int|null $nodes_count
|
||||
* @property Collection|Database[] $databases
|
||||
* @property int|null $databases_count
|
||||
* @property CarbonImmutable|null $created_at
|
||||
* @property CarbonImmutable|null $updated_at
|
||||
* @property-read Collection<int, Database> $databases
|
||||
* @property-read int|null $databases_count
|
||||
* @property-read Collection<int, Node> $nodes
|
||||
* @property-read int|null $nodes_count
|
||||
*
|
||||
* @method static \Database\Factories\DatabaseHostFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost whereHost($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost whereMaxDatabases($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost wherePassword($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost wherePort($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DatabaseHost whereUsername($value)
|
||||
*/
|
||||
class DatabaseHost extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -5,62 +5,96 @@ namespace App\Models;
|
||||
use App\Contracts\Validatable;
|
||||
use App\Exceptions\Service\Egg\HasChildrenException;
|
||||
use App\Exceptions\Service\HasActiveServersException;
|
||||
use App\Models\Traits\HasIcon;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $uuid
|
||||
* @property string $author
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property string|null $image
|
||||
* @property string[]|null $features
|
||||
* @property array<string, string> $docker_images
|
||||
* @property string|null $update_url
|
||||
* @property bool $force_outgoing_ip
|
||||
* @property string[]|null $file_denylist
|
||||
* @property string|null $config_files
|
||||
* @property string|null $config_startup
|
||||
* @property string|null $config_logs
|
||||
* @property string|null $config_stop
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property int|null $config_from
|
||||
* @property array<string, string> $startup_commands
|
||||
* @property bool $script_is_privileged
|
||||
* @property string|null $config_stop
|
||||
* @property string|null $config_logs
|
||||
* @property string|null $config_startup
|
||||
* @property string|null $config_files
|
||||
* @property string|null $script_install
|
||||
* @property bool $script_is_privileged
|
||||
* @property string $script_entry
|
||||
* @property string $script_container
|
||||
* @property int|null $copy_script_from
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property string|null $copy_script_install
|
||||
* @property string $copy_script_entry
|
||||
* @property string $copy_script_container
|
||||
* @property string|null $inherit_config_files
|
||||
* @property string|null $inherit_config_startup
|
||||
* @property string|null $inherit_config_logs
|
||||
* @property string|null $inherit_config_stop
|
||||
* @property string $inherit_file_denylist
|
||||
* @property string[]|null $inherit_features
|
||||
* @property string|null $uuid
|
||||
* @property string $author
|
||||
* @property string[]|null $features
|
||||
* @property array<string, string> $docker_images
|
||||
* @property string|null $update_url
|
||||
* @property string[]|null $file_denylist
|
||||
* @property bool $force_outgoing_ip
|
||||
* @property string[] $tags
|
||||
* @property Collection|Server[] $servers
|
||||
* @property int|null $servers_count
|
||||
* @property Collection|EggVariable[] $variables
|
||||
* @property int|null $variables_count
|
||||
* @property \App\Models\Egg|null $scriptFrom
|
||||
* @property \App\Models\Egg|null $configFrom
|
||||
* @property array<string, string> $startup_commands
|
||||
* @property-read Collection<int, Egg> $children
|
||||
* @property-read int|null $children_count
|
||||
* @property-read Egg|null $configFrom
|
||||
* @property-read string $copy_script_container
|
||||
* @property-read string $copy_script_entry
|
||||
* @property-read string|null $copy_script_install
|
||||
* @property-read string|null $icon
|
||||
* @property-read string|null $inherit_config_files
|
||||
* @property-read string|null $inherit_config_logs
|
||||
* @property-read string|null $inherit_config_startup
|
||||
* @property-read string|null $inherit_config_stop
|
||||
* @property-read string[]|null $inherit_features
|
||||
* @property-read string[]|null $inherit_file_denylist
|
||||
* @property-read Collection<int, Mount> $mounts
|
||||
* @property-read int|null $mounts_count
|
||||
* @property-read Egg|null $scriptFrom
|
||||
* @property-read Collection<int, Server> $servers
|
||||
* @property-read int|null $servers_count
|
||||
* @property-read Collection<int, EggVariable> $variables
|
||||
* @property-read int|null $variables_count
|
||||
*
|
||||
* @method static \Database\Factories\EggFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereAuthor($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereConfigFiles($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereConfigFrom($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereConfigLogs($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereConfigStartup($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereConfigStop($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereCopyScriptFrom($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereDockerImages($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereFeatures($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereFileDenylist($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereForceOutgoingIp($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereScriptContainer($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereScriptEntry($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereScriptInstall($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereScriptIsPrivileged($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereStartupCommands($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereTags($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereUpdateUrl($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Egg whereUuid($value)
|
||||
*/
|
||||
class Egg extends Model implements Validatable
|
||||
{
|
||||
use HasFactory;
|
||||
use HasIcon;
|
||||
use HasValidation;
|
||||
|
||||
/**
|
||||
@@ -74,22 +108,6 @@ class Egg extends Model implements Validatable
|
||||
*/
|
||||
public const EXPORT_VERSION = 'PLCN_v3';
|
||||
|
||||
/**
|
||||
* Path to store egg icons relative to storage path.
|
||||
*/
|
||||
public const ICON_STORAGE_PATH = 'icons/egg';
|
||||
|
||||
/**
|
||||
* Supported image formats: file extension => MIME type
|
||||
*/
|
||||
public const IMAGE_FORMATS = [
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'webp' => 'image/webp',
|
||||
'svg' => 'image/svg+xml',
|
||||
];
|
||||
|
||||
/**
|
||||
* Fields that are not mass assignable.
|
||||
*/
|
||||
@@ -344,16 +362,4 @@ class Egg extends Model implements Validatable
|
||||
{
|
||||
return str($this->name)->kebab()->lower()->trim()->split('/[^\w\-]/')->join('');
|
||||
}
|
||||
|
||||
public function getImageAttribute(): ?string
|
||||
{
|
||||
foreach (array_keys(static::IMAGE_FORMATS) as $ext) {
|
||||
$path = static::ICON_STORAGE_PATH . "/$this->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
return Storage::disk('public')->url($path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Contracts\Validatable;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@@ -13,7 +14,6 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $egg_id
|
||||
* @property null $sort
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string $env_variable
|
||||
@@ -21,15 +21,32 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
* @property bool $user_viewable
|
||||
* @property bool $user_editable
|
||||
* @property string[] $rules
|
||||
* @property CarbonImmutable $created_at
|
||||
* @property CarbonImmutable $updated_at
|
||||
* @property bool $required
|
||||
* @property Egg $egg
|
||||
* @property ServerVariable $serverVariable
|
||||
* @property CarbonImmutable|null $created_at
|
||||
* @property CarbonImmutable|null $updated_at
|
||||
* @property int|null $sort
|
||||
* @property-read Egg|null $egg
|
||||
* @property-read bool $required
|
||||
* @property-read Collection<int, ServerVariable> $serverVariable
|
||||
* @property-read int|null $server_variable_count
|
||||
*
|
||||
* The "server_value" variable is only present on the object if you've loaded this model
|
||||
* using the server relationship.
|
||||
* @property string|null $server_value
|
||||
* @method static \Database\Factories\EggVariableFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereDefaultValue($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereEggId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereEnvVariable($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereRules($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereSort($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereUserEditable($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EggVariable whereUserViewable($value)
|
||||
*
|
||||
* @property string|null $server_value This variable is only present on the object if you've loaded this model using the server relationship.
|
||||
*/
|
||||
class EggVariable extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -6,12 +6,12 @@ use App\Enums\TablerIcon;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Repositories\Daemon\DaemonFileRepository;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Sushi\Sushi;
|
||||
|
||||
/**
|
||||
@@ -28,6 +28,21 @@ use Sushi\Sushi;
|
||||
* @property bool $is_file
|
||||
* @property bool $is_symlink
|
||||
* @property string $mime_type
|
||||
*
|
||||
* @method static Builder<static>|File newModelQuery()
|
||||
* @method static Builder<static>|File newQuery()
|
||||
* @method static Builder<static>|File query()
|
||||
* @method static Builder<static>|File whereCreatedAt($value)
|
||||
* @method static Builder<static>|File whereId($value)
|
||||
* @method static Builder<static>|File whereIsDirectory($value)
|
||||
* @method static Builder<static>|File whereIsFile($value)
|
||||
* @method static Builder<static>|File whereIsSymlink($value)
|
||||
* @method static Builder<static>|File whereMimeType($value)
|
||||
* @method static Builder<static>|File whereMode($value)
|
||||
* @method static Builder<static>|File whereModeBits($value)
|
||||
* @method static Builder<static>|File whereModifiedAt($value)
|
||||
* @method static Builder<static>|File whereName($value)
|
||||
* @method static Builder<static>|File whereSize($value)
|
||||
*/
|
||||
class File extends Model
|
||||
{
|
||||
@@ -138,6 +153,9 @@ class File extends Model
|
||||
return [
|
||||
'created_at' => 'datetime',
|
||||
'modified_at' => 'datetime',
|
||||
'is_directory' => 'boolean',
|
||||
'is_file' => 'boolean',
|
||||
'is_symlink' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,29 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
* @property int $id
|
||||
* @property string $uuid
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string|null $description
|
||||
* @property string $source
|
||||
* @property string $target
|
||||
* @property bool $read_only
|
||||
* @property bool $user_mountable
|
||||
* @property Egg[]|Collection $eggs
|
||||
* @property Node[]|Collection $nodes
|
||||
* @property Server[]|Collection $servers
|
||||
* @property-read Collection<int, Egg> $eggs
|
||||
* @property-read int|null $eggs_count
|
||||
* @property-read Collection<int, Node> $nodes
|
||||
* @property-read int|null $nodes_count
|
||||
* @property-read Collection<int, Server> $servers
|
||||
* @property-read int|null $servers_count
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereReadOnly($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereSource($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereTarget($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereUserMountable($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Mount whereUuid($value)
|
||||
*/
|
||||
class Mount extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Contracts\Validatable;
|
||||
use App\Exceptions\Service\HasActiveServersException;
|
||||
use App\Repositories\Daemon\DaemonSystemRepository;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -15,46 +14,84 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Notifications\DatabaseNotification;
|
||||
use Illuminate\Notifications\DatabaseNotificationCollection;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $uuid
|
||||
* @property bool $public
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property string $fqdn
|
||||
* @property string $scheme
|
||||
* @property bool $behind_proxy
|
||||
* @property bool $maintenance_mode
|
||||
* @property int $memory
|
||||
* @property int $memory_overallocate
|
||||
* @property int $disk
|
||||
* @property int $disk_overallocate
|
||||
* @property int $cpu
|
||||
* @property int $cpu_overallocate
|
||||
* @property int $upload_size
|
||||
* @property string $daemon_token_id
|
||||
* @property string $daemon_token
|
||||
* @property int $daemon_listen
|
||||
* @property int $daemon_connect
|
||||
* @property int $daemon_sftp
|
||||
* @property string|null $daemon_sftp_alias
|
||||
* @property string $daemon_base
|
||||
* @property string[] $tags
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Mount[]|Collection $mounts
|
||||
* @property int|null $mounts_count
|
||||
* @property Server[]|Collection $servers
|
||||
* @property int|null $servers_count
|
||||
* @property Allocation[]|Collection $allocations
|
||||
* @property int|null $allocations_count
|
||||
* @property Role[]|Collection $roles
|
||||
* @property int|null $roles_count
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property int $upload_size
|
||||
* @property bool $behind_proxy
|
||||
* @property string|null $description
|
||||
* @property bool $maintenance_mode
|
||||
* @property string|null $uuid
|
||||
* @property string|null $daemon_token_id
|
||||
* @property array<array-key, mixed>|null $tags
|
||||
* @property int $cpu
|
||||
* @property int $cpu_overallocate
|
||||
* @property string|null $daemon_sftp_alias
|
||||
* @property int $daemon_connect
|
||||
* @property-read Collection<int, Allocation> $allocations
|
||||
* @property-read int|null $allocations_count
|
||||
* @property-read Collection<int, DatabaseHost> $databaseHosts
|
||||
* @property-read int|null $database_hosts_count
|
||||
* @property-read Collection<int, Mount> $mounts
|
||||
* @property-read int|null $mounts_count
|
||||
* @property-read DatabaseNotificationCollection<int, DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @property-read Collection<int, Role> $roles
|
||||
* @property-read int|null $roles_count
|
||||
* @property-read Collection<int, Server> $servers
|
||||
* @property-read int|null $servers_count
|
||||
*
|
||||
* @method static \Database\Factories\NodeFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereBehindProxy($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereCpu($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereCpuOverallocate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDaemonBase($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDaemonConnect($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDaemonListen($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDaemonSftp($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDaemonSftpAlias($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDaemonToken($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDaemonTokenId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDisk($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereDiskOverallocate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereFqdn($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereMaintenanceMode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereMemory($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereMemoryOverallocate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node wherePublic($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereScheme($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereTags($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereUploadSize($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Node whereUuid($value)
|
||||
*/
|
||||
class Node extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -4,6 +4,16 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
/**
|
||||
* @property int $node_id
|
||||
* @property int $role_id
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NodeRole newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NodeRole newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NodeRole query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NodeRole whereNodeId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|NodeRole whereRoleId($value)
|
||||
*/
|
||||
class NodeRole extends Pivot
|
||||
{
|
||||
protected $table = 'node_role';
|
||||
|
||||
@@ -34,6 +34,26 @@ use Sushi\Sushi;
|
||||
* @property PluginStatus $status
|
||||
* @property string|null $status_message
|
||||
* @property int $load_order
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereAuthor($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereCategory($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereClass($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereComposerPackages($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereLoadOrder($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereNamespace($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin wherePanelVersion($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin wherePanels($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereStatusMessage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereUpdateUrl($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereUrl($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Plugin whereVersion($value)
|
||||
*/
|
||||
class Plugin extends Model implements HasPluginSettings
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role as BaseRole;
|
||||
|
||||
@@ -16,12 +17,27 @@ use Spatie\Permission\Models\Role as BaseRole;
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $guard_name
|
||||
* @property Collection|Permission[] $permissions
|
||||
* @property int|null $permissions_count
|
||||
* @property Collection|User[] $users
|
||||
* @property int|null $users_count
|
||||
* @property Collection|Node[] $nodes
|
||||
* @property int|null $nodes_count
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property-read NodeRole|null $pivot
|
||||
* @property-read Collection<int, Node> $nodes
|
||||
* @property-read int|null $nodes_count
|
||||
* @property-read Collection<int, Permission> $permissions
|
||||
* @property-read int|null $permissions_count
|
||||
* @property-read Collection<int, User> $users
|
||||
* @property-read int|null $users_count
|
||||
*
|
||||
* @method static \Database\Factories\RoleFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role permission($permissions, $without = false)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereGuardName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role withoutPermission($permissions)
|
||||
*/
|
||||
class Role extends BaseRole
|
||||
{
|
||||
|
||||
@@ -6,34 +6,55 @@ use App\Contracts\Validatable;
|
||||
use App\Enums\ScheduleStatus;
|
||||
use App\Helpers\Utilities;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $server_id
|
||||
* @property string $name
|
||||
* @property string $cron_day_of_week
|
||||
* @property string $cron_month
|
||||
* @property string $cron_day_of_month
|
||||
* @property string $cron_hour
|
||||
* @property string $cron_minute
|
||||
* @property bool $is_active
|
||||
* @property bool $is_processing
|
||||
* @property bool $only_when_online
|
||||
* @property Carbon|null $last_run_at
|
||||
* @property Carbon|null $next_run_at
|
||||
* @property ScheduleStatus $status
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Server $server
|
||||
* @property Task[]|Collection $tasks
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property string $cron_month
|
||||
* @property bool $only_when_online
|
||||
* @property-read Server $server
|
||||
* @property-read ScheduleStatus $status
|
||||
* @property-read Collection<int, Task> $tasks
|
||||
* @property-read int|null $tasks_count
|
||||
*
|
||||
* @method static \Database\Factories\ScheduleFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereCronDayOfMonth($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereCronDayOfWeek($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereCronHour($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereCronMinute($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereCronMonth($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereIsActive($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereIsProcessing($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereLastRunAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereNextRunAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereOnlyWhenOnline($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereServerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Schedule whereUpdatedAt($value)
|
||||
*/
|
||||
class Schedule extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -7,11 +7,12 @@ use App\Enums\ContainerStatus;
|
||||
use App\Enums\ServerResourceType;
|
||||
use App\Enums\ServerState;
|
||||
use App\Exceptions\Http\Server\ServerStateConflictException;
|
||||
use App\Models\Traits\HasIcon;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use App\Services\Subusers\SubuserDeletionService;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\CarbonInterface;
|
||||
use Database\Factories\ServerFactory;
|
||||
use Exception;
|
||||
use Filament\Models\Contracts\HasAvatar;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -30,111 +31,106 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* \App\Models\Server.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $external_id
|
||||
* @property string $uuid
|
||||
* @property string $uuid_short
|
||||
* @property int $node_id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property ServerState|null $status
|
||||
* @property bool $skip_scripts
|
||||
* @property int $owner_id
|
||||
* @property int $memory
|
||||
* @property int $swap
|
||||
* @property int $disk
|
||||
* @property int $io
|
||||
* @property int $cpu
|
||||
* @property string|null $threads
|
||||
* @property bool $oom_killer
|
||||
* @property int|null $allocation_id
|
||||
* @property int $egg_id
|
||||
* @property string $startup
|
||||
* @property string $image
|
||||
* @property string|null $icon
|
||||
* @property int|null $allocation_limit
|
||||
* @property int|null $database_limit
|
||||
* @property int|null $backup_limit
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property int|null $allocation_id
|
||||
* @property string $image
|
||||
* @property string|null $description
|
||||
* @property bool $skip_scripts
|
||||
* @property string|null $external_id
|
||||
* @property int|null $database_limit
|
||||
* @property int|null $allocation_limit
|
||||
* @property string|null $threads
|
||||
* @property int $backup_limit
|
||||
* @property ServerState|null $status
|
||||
* @property Carbon|null $installed_at
|
||||
* @property Collection|ActivityLog[] $activity
|
||||
* @property int|null $activity_count
|
||||
* @property Allocation|null $allocation
|
||||
* @property Collection|Allocation[] $allocations
|
||||
* @property int|null $allocations_count
|
||||
* @property Collection|Backup[] $backups
|
||||
* @property int|null $backups_count
|
||||
* @property Collection|Database[] $databases
|
||||
* @property int|null $databases_count
|
||||
* @property Egg $egg
|
||||
* @property Collection|Mount[] $mounts
|
||||
* @property int|null $mounts_count
|
||||
* @property Node $node
|
||||
* @property DatabaseNotificationCollection|DatabaseNotification[] $notifications
|
||||
* @property int|null $notifications_count
|
||||
* @property Collection|Schedule[] $schedules
|
||||
* @property int|null $schedules_count
|
||||
* @property Collection|Subuser[] $subusers
|
||||
* @property int|null $subusers_count
|
||||
* @property ServerTransfer|null $transfer
|
||||
* @property User $user
|
||||
* @property Collection|EggVariable[] $variables
|
||||
* @property int|null $variables_count
|
||||
*
|
||||
* @method static ServerFactory factory(...$parameters)
|
||||
* @method static Builder|Server newModelQuery()
|
||||
* @method static Builder|Server newQuery()
|
||||
* @method static Builder|Server query()
|
||||
* @method static Builder|Server whereAllocationId($value)
|
||||
* @method static Builder|Server whereAllocationLimit($value)
|
||||
* @method static Builder|Server whereBackupLimit($value)
|
||||
* @method static Builder|Server whereCpu($value)
|
||||
* @method static Builder|Server whereCreatedAt($value)
|
||||
* @method static Builder|Server whereDatabaseLimit($value)
|
||||
* @method static Builder|Server whereDescription($value)
|
||||
* @method static Builder|Server whereDisk($value)
|
||||
* @method static Builder|Server whereEggId($value)
|
||||
* @method static Builder|Server whereExternalId($value)
|
||||
* @method static Builder|Server whereId($value)
|
||||
* @method static Builder|Server whereImage($value)
|
||||
* @method static Builder|Server whereIo($value)
|
||||
* @method static Builder|Server whereMemory($value)
|
||||
* @method static Builder|Server whereName($value)
|
||||
* @method static Builder|Server whereNodeId($value)
|
||||
* @method static Builder|Server whereOomKiller($value)
|
||||
* @method static Builder|Server whereOwnerId($value)
|
||||
* @method static Builder|Server whereSkipScripts($value)
|
||||
* @method static Builder|Server whereStartup($value)
|
||||
* @method static Builder|Server whereStatus($value)
|
||||
* @method static Builder|Server whereSwap($value)
|
||||
* @method static Builder|Server whereThreads($value)
|
||||
* @method static Builder|Server whereUpdatedAt($value)
|
||||
* @method static Builder|Server whereUuid($value)
|
||||
* @method static Builder|Server whereuuid_short($value)
|
||||
*
|
||||
* @property string[]|null $docker_labels
|
||||
* @property string|null $ports
|
||||
* @property-read ContainerStatus|ServerState $condition
|
||||
* @property bool $oom_killer
|
||||
* @property array<array-key, mixed>|null $docker_labels
|
||||
* @property-read Collection<int, ActivityLog> $activity
|
||||
* @property-read int|null $activity_count
|
||||
* @property-read Allocation|null $allocation
|
||||
* @property-read Collection<int, Allocation> $allocations
|
||||
* @property-read int|null $allocations_count
|
||||
* @property-read Collection<int, Backup> $backups
|
||||
* @property-read int|null $backups_count
|
||||
* @property-read ServerState|ContainerStatus $condition
|
||||
* @property-read Collection<int, Database> $databases
|
||||
* @property-read int|null $databases_count
|
||||
* @property-read Egg $egg
|
||||
* @property-read Collection<int, EggVariable> $eggVariables
|
||||
* @property-read int|null $egg_variables_count
|
||||
* @property-read string|null $icon
|
||||
* @property-read Collection<int, Mount> $mounts
|
||||
* @property-read int|null $mounts_count
|
||||
* @property-read Node $node
|
||||
* @property-read DatabaseNotificationCollection<int, DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @property-read Collection<int, Schedule> $schedules
|
||||
* @property-read int|null $schedules_count
|
||||
* @property-read Collection<int, ServerVariable> $serverVariables
|
||||
* @property-read int|null $server_variables_count
|
||||
* @property-read Collection<int, Subuser> $subusers
|
||||
* @property-read int|null $subusers_count
|
||||
* @property-read ServerTransfer|null $transfer
|
||||
* @property-read User $user
|
||||
* @property-read Collection<int, EggVariable> $variables
|
||||
* @property-read int|null $variables_count
|
||||
*
|
||||
* @method static Builder|Server whereDockerLabels($value)
|
||||
* @method static Builder|Server whereInstalledAt($value)
|
||||
* @method static Builder|Server wherePorts($value)
|
||||
* @method static Builder|Server whereUuidShort($value)
|
||||
* @method static \Database\Factories\ServerFactory factory($count = null, $state = [])
|
||||
* @method static Builder<static>|Server newModelQuery()
|
||||
* @method static Builder<static>|Server newQuery()
|
||||
* @method static Builder<static>|Server query()
|
||||
* @method static Builder<static>|Server whereAllocationId($value)
|
||||
* @method static Builder<static>|Server whereAllocationLimit($value)
|
||||
* @method static Builder<static>|Server whereBackupLimit($value)
|
||||
* @method static Builder<static>|Server whereCpu($value)
|
||||
* @method static Builder<static>|Server whereCreatedAt($value)
|
||||
* @method static Builder<static>|Server whereDatabaseLimit($value)
|
||||
* @method static Builder<static>|Server whereDescription($value)
|
||||
* @method static Builder<static>|Server whereDisk($value)
|
||||
* @method static Builder<static>|Server whereDockerLabels($value)
|
||||
* @method static Builder<static>|Server whereEggId($value)
|
||||
* @method static Builder<static>|Server whereExternalId($value)
|
||||
* @method static Builder<static>|Server whereId($value)
|
||||
* @method static Builder<static>|Server whereImage($value)
|
||||
* @method static Builder<static>|Server whereInstalledAt($value)
|
||||
* @method static Builder<static>|Server whereIo($value)
|
||||
* @method static Builder<static>|Server whereMemory($value)
|
||||
* @method static Builder<static>|Server whereName($value)
|
||||
* @method static Builder<static>|Server whereNodeId($value)
|
||||
* @method static Builder<static>|Server whereOomKiller($value)
|
||||
* @method static Builder<static>|Server whereOwnerId($value)
|
||||
* @method static Builder<static>|Server whereSkipScripts($value)
|
||||
* @method static Builder<static>|Server whereStartup($value)
|
||||
* @method static Builder<static>|Server whereStatus($value)
|
||||
* @method static Builder<static>|Server whereSwap($value)
|
||||
* @method static Builder<static>|Server whereThreads($value)
|
||||
* @method static Builder<static>|Server whereUpdatedAt($value)
|
||||
* @method static Builder<static>|Server whereUuid($value)
|
||||
* @method static Builder<static>|Server whereUuidShort($value)
|
||||
*/
|
||||
class Server extends Model implements HasAvatar, Validatable
|
||||
{
|
||||
use HasFactory;
|
||||
use HasIcon;
|
||||
use HasValidation;
|
||||
use Notifiable;
|
||||
|
||||
@@ -144,22 +140,6 @@ class Server extends Model implements HasAvatar, Validatable
|
||||
*/
|
||||
public const RESOURCE_NAME = 'server';
|
||||
|
||||
/**
|
||||
* Path to store server icons relative to storage path.
|
||||
*/
|
||||
public const ICON_STORAGE_PATH = 'icons/server';
|
||||
|
||||
/**
|
||||
* Supported image formats: file extension => MIME type
|
||||
*/
|
||||
public const IMAGE_FORMATS = [
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'webp' => 'image/webp',
|
||||
'svg' => 'image/svg+xml',
|
||||
];
|
||||
|
||||
/**
|
||||
* Default values when creating the model. We want to switch to disabling OOM killer
|
||||
* on server instances unless the user specifies otherwise in the request.
|
||||
@@ -533,20 +513,8 @@ class Server extends Model implements HasAvatar, Validatable
|
||||
);
|
||||
}
|
||||
|
||||
public function getIconAttribute(): ?string
|
||||
{
|
||||
foreach (array_keys(static::IMAGE_FORMATS) as $ext) {
|
||||
$path = static::ICON_STORAGE_PATH . "/$this->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
return Storage::disk('public')->url($path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getFilamentAvatarUrl(): ?string
|
||||
{
|
||||
return $this->icon ?? $this->egg->image;
|
||||
return $this->icon ?? $this->egg->icon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,48 @@ namespace App\Models;
|
||||
|
||||
use App\Contracts\Validatable;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $server_id
|
||||
* @property bool|null $successful
|
||||
* @property int $old_node
|
||||
* @property int $new_node
|
||||
* @property int|null $old_allocation
|
||||
* @property int|null $new_allocation
|
||||
* @property array<int>|null $old_additional_allocations array of allocation.id's
|
||||
* @property array<int>|null $new_additional_allocations array of allocation.id's
|
||||
* @property bool|null $successful
|
||||
* @property int[]|null $old_additional_allocations
|
||||
* @property int[]|null $new_additional_allocations
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property bool $archived
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Server $server
|
||||
* @property Node $oldNode
|
||||
* @property Node $newNode
|
||||
* @property-read Node|null $newNode
|
||||
* @property-read Node|null $oldNode
|
||||
* @property-read Server $server
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereArchived($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereNewAdditionalAllocations($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereNewAllocation($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereNewNode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereOldAdditionalAllocations($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereOldAllocation($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereOldNode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereServerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereSuccessful($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerTransfer whereUpdatedAt($value)
|
||||
*/
|
||||
class ServerTransfer extends Model implements Validatable
|
||||
{
|
||||
use HasFactory;
|
||||
use HasValidation;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,18 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
* @property string $variable_value
|
||||
* @property CarbonImmutable|null $created_at
|
||||
* @property CarbonImmutable|null $updated_at
|
||||
* @property EggVariable $variable
|
||||
* @property Server $server
|
||||
* @property-read Server $server
|
||||
* @property-read EggVariable $variable
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable whereServerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable whereVariableId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerVariable whereVariableValue($value)
|
||||
*/
|
||||
class ServerVariable extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -5,21 +5,33 @@ namespace App\Models;
|
||||
use App\Contracts\Validatable;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $server_id
|
||||
* @property string[] $permissions
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property User $user
|
||||
* @property Server $server
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property string[]|null $permissions
|
||||
* @property-read Server $server
|
||||
* @property-read User $user
|
||||
*
|
||||
* @method static \Database\Factories\SubuserFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser wherePermissions($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser whereServerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Subuser whereUserId($value)
|
||||
*/
|
||||
class Subuser extends Model implements Validatable
|
||||
{
|
||||
@@ -33,17 +45,21 @@ class Subuser extends Model implements Validatable
|
||||
*/
|
||||
public const RESOURCE_NAME = 'server_subuser';
|
||||
|
||||
/** @var array<string, array{name: string, hidden: ?bool, icon: ?string, permissions: string[]}> */
|
||||
/** @var array<string, array{name: string, hidden: ?bool, icon: null|string|BackedEnum, translation_prefix: ?string, permissions: string[]}> */
|
||||
protected static array $customPermissions = [];
|
||||
|
||||
/** @param string[] $permissions */
|
||||
public static function registerCustomPermissions(string $name, array $permissions, ?string $icon = null, ?bool $hidden = null): void
|
||||
public static function registerCustomPermissions(string $name, array $permissions, ?string $translationPrefix = null, null|string|BackedEnum $icon = null, ?bool $hidden = null): void
|
||||
{
|
||||
$customPermission = static::$customPermissions[$name] ?? [];
|
||||
|
||||
$customPermission['name'] = $name;
|
||||
$customPermission['permissions'] = array_merge($customPermission['permissions'] ?? [], $permissions);
|
||||
|
||||
if (!is_null($translationPrefix)) {
|
||||
$customPermission['translation_prefix'] = $translationPrefix;
|
||||
}
|
||||
|
||||
if (!is_null($icon)) {
|
||||
$customPermission['icon'] = $icon;
|
||||
}
|
||||
@@ -93,7 +109,7 @@ class Subuser extends Model implements Validatable
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/** @return array<array{name: string, hidden: bool, icon: string, permissions: string[]}> */
|
||||
/** @return array<array{name: string, hidden: bool, icon: null|string|BackedEnum, translation_prefix: string, permissions: string[]}> */
|
||||
public static function allPermissionData(): array
|
||||
{
|
||||
$allPermissions = [];
|
||||
@@ -106,6 +122,7 @@ class Subuser extends Model implements Validatable
|
||||
'hidden' => $subuserPermission->isHidden(),
|
||||
'icon' => $subuserPermission->getIcon(),
|
||||
'permissions' => array_merge($allPermissions[$group]['permissions'] ?? [], [$permission]),
|
||||
'translation_prefix' => 'server/user.permissions',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -119,6 +136,7 @@ class Subuser extends Model implements Validatable
|
||||
'hidden' => $customPermission['hidden'] ?? $groupData['hidden'] ?? false,
|
||||
'icon' => $customPermission['icon'] ?? $groupData['icon'],
|
||||
'permissions' => array_unique(array_merge($groupData['permissions'] ?? [], $customPermission['permissions'])),
|
||||
'translation_prefix' => $customPermission['translation_prefix'] ?? $groupData['translation_prefix'] ?? 'server/user.permissions',
|
||||
];
|
||||
|
||||
$allPermissions[$name] = $groupData;
|
||||
|
||||
@@ -6,11 +6,11 @@ use App\Contracts\Validatable;
|
||||
use App\Extensions\Tasks\TaskSchemaInterface;
|
||||
use App\Extensions\Tasks\TaskService;
|
||||
use App\Traits\HasValidation;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@@ -20,11 +20,26 @@ use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
* @property string $payload
|
||||
* @property int $time_offset
|
||||
* @property bool $is_queued
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property bool $continue_on_failure
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Schedule $schedule
|
||||
* @property Server $server
|
||||
* @property-read Schedule $schedule
|
||||
* @property-read Server|null $server
|
||||
*
|
||||
* @method static \Database\Factories\TaskFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereAction($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereContinueOnFailure($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereIsQueued($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task wherePayload($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereScheduleId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereSequenceId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereTimeOffset($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereUpdatedAt($value)
|
||||
*/
|
||||
class Task extends Model implements Validatable
|
||||
{
|
||||
|
||||
@@ -4,13 +4,14 @@ namespace App\Models\Traits;
|
||||
|
||||
use App\Extensions\Laravel\Sanctum\NewAccessToken;
|
||||
use App\Models\ApiKey;
|
||||
use App\Models\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
/**
|
||||
* @mixin \App\Models\Model
|
||||
* @mixin Model
|
||||
*/
|
||||
trait HasAccessTokens
|
||||
{
|
||||
|
||||
74
app/Models/Traits/HasIcon.php
Normal file
74
app/Models/Traits/HasIcon.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
use App\Models\Model;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* @mixin Model
|
||||
*/
|
||||
trait HasIcon
|
||||
{
|
||||
/**
|
||||
* Supported icon formats: file extension => MIME type
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $iconFormats = [
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'webp' => 'image/webp',
|
||||
];
|
||||
|
||||
public static function getIconStoragePath(): string
|
||||
{
|
||||
return 'icons/' . static::RESOURCE_NAME;
|
||||
}
|
||||
|
||||
public function getIconAttribute(): ?string
|
||||
{
|
||||
foreach (array_keys(static::$iconFormats) as $ext) {
|
||||
$path = $this->getIconStoragePath() . "/$this->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
return Storage::disk('public')->url($path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function writeIcon(string $extension, string $data): string
|
||||
{
|
||||
$normalizedExtension = match (strtolower($extension)) {
|
||||
'jpeg', 'jpg' => 'jpg',
|
||||
'png' => 'png',
|
||||
'webp' => 'webp',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (is_null($normalizedExtension)) {
|
||||
throw new Exception(trans('admin/egg.import.unknown_extension', ['extension' => $extension]));
|
||||
}
|
||||
|
||||
$fileName = static::getIconStoragePath() . "/$this->uuid.$normalizedExtension";
|
||||
|
||||
if (!Storage::disk('public')->put($fileName, $data)) {
|
||||
throw new Exception(trans('admin/egg.import.could_not_write'));
|
||||
}
|
||||
|
||||
foreach (['png', 'jpg', 'jpeg', 'webp', 'svg'] as $ext) {
|
||||
if ($ext === $normalizedExtension) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = static::getIconStoragePath() . "/$this->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
return $fileName;
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,12 @@ namespace App\Models;
|
||||
use App\Contracts\Validatable;
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Events\User\Deleting;
|
||||
use App\Exceptions\DisplayException;
|
||||
use App\Extensions\Avatar\AvatarService;
|
||||
use App\Models\Traits\HasAccessTokens;
|
||||
use App\Traits\HasValidation;
|
||||
use BackedEnum;
|
||||
use Database\Factories\UserFactory;
|
||||
use DateTimeZone;
|
||||
use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthentication;
|
||||
use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthenticationRecovery;
|
||||
@@ -43,56 +43,75 @@ use Illuminate\Support\Facades\Context;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\In;
|
||||
use ResourceBundle;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
/**
|
||||
* App\Models\User.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $external_id
|
||||
* @property bool $is_managed_externally
|
||||
* @property string $uuid
|
||||
* @property string $username
|
||||
* @property string $email
|
||||
* @property string $password
|
||||
* @property string|null $remember_token
|
||||
* @property string $language
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property string $username
|
||||
* @property string|null $external_id
|
||||
* @property string $timezone
|
||||
* @property string[]|null $oauth
|
||||
* @property array<string, mixed>|null $oauth
|
||||
* @property string|array<string, mixed>|null $customization
|
||||
* @property string|null $mfa_app_secret
|
||||
* @property string[]|null $mfa_app_recovery_codes
|
||||
* @property bool $mfa_email_enabled
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property \Illuminate\Database\Eloquent\Collection|ApiKey[] $apiKeys
|
||||
* @property int|null $api_keys_count
|
||||
* @property DatabaseNotificationCollection|DatabaseNotification[] $notifications
|
||||
* @property int|null $notifications_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|Server[] $servers
|
||||
* @property int|null $servers_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|UserSSHKey[] $sshKeys
|
||||
* @property int|null $ssh_keys_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|ApiKey[] $tokens
|
||||
* @property int|null $tokens_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|Role[] $roles
|
||||
* @property int|null $roles_count
|
||||
* @property string|array<string, mixed>|null $customization
|
||||
* @property bool $is_managed_externally
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, ActivityLog> $activity
|
||||
* @property-read int|null $activity_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, ApiKey> $apiKeys
|
||||
* @property-read int|null $api_keys_count
|
||||
* @property-read DatabaseNotificationCollection<int, DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Permission> $permissions
|
||||
* @property-read int|null $permissions_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Role> $roles
|
||||
* @property-read int|null $roles_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Server> $servers
|
||||
* @property-read int|null $servers_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, UserSSHKey> $sshKeys
|
||||
* @property-read int|null $ssh_keys_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Server> $subServers
|
||||
* @property-read int|null $sub_servers_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Subuser> $subusers
|
||||
* @property-read int|null $subusers_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, ApiKey> $tokens
|
||||
* @property-read int|null $tokens_count
|
||||
*
|
||||
* @method static UserFactory factory(...$parameters)
|
||||
* @method static Builder|User newModelQuery()
|
||||
* @method static Builder|User newQuery()
|
||||
* @method static Builder|User query()
|
||||
* @method static Builder|User whereCreatedAt($value)
|
||||
* @method static Builder|User whereEmail($value)
|
||||
* @method static Builder|User whereExternalId($value)
|
||||
* @method static Builder|User whereId($value)
|
||||
* @method static Builder|User whereLanguage($value)
|
||||
* @method static Builder|User whereTimezone($value)
|
||||
* @method static Builder|User wherePassword($value)
|
||||
* @method static Builder|User whereRememberToken($value)
|
||||
* @method static Builder|User whereUpdatedAt($value)
|
||||
* @method static Builder|User whereUsername($value)
|
||||
* @method static Builder|User whereUuid($value)
|
||||
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
|
||||
* @method static Builder<static>|User newModelQuery()
|
||||
* @method static Builder<static>|User newQuery()
|
||||
* @method static Builder<static>|User permission($permissions, $without = false)
|
||||
* @method static Builder<static>|User query()
|
||||
* @method static Builder<static>|User role($roles, $guard = null, $without = false)
|
||||
* @method static Builder<static>|User whereCreatedAt($value)
|
||||
* @method static Builder<static>|User whereCustomization($value)
|
||||
* @method static Builder<static>|User whereEmail($value)
|
||||
* @method static Builder<static>|User whereExternalId($value)
|
||||
* @method static Builder<static>|User whereId($value)
|
||||
* @method static Builder<static>|User whereIsManagedExternally($value)
|
||||
* @method static Builder<static>|User whereLanguage($value)
|
||||
* @method static Builder<static>|User whereMfaAppRecoveryCodes($value)
|
||||
* @method static Builder<static>|User whereMfaAppSecret($value)
|
||||
* @method static Builder<static>|User whereMfaEmailEnabled($value)
|
||||
* @method static Builder<static>|User whereOauth($value)
|
||||
* @method static Builder<static>|User wherePassword($value)
|
||||
* @method static Builder<static>|User whereRememberToken($value)
|
||||
* @method static Builder<static>|User whereTimezone($value)
|
||||
* @method static Builder<static>|User whereUpdatedAt($value)
|
||||
* @method static Builder<static>|User whereUsername($value)
|
||||
* @method static Builder<static>|User whereUuid($value)
|
||||
* @method static Builder<static>|User withoutPermission($permissions)
|
||||
* @method static Builder<static>|User withoutRole($roles, $guard = null)
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAppAuthentication, HasAppAuthenticationRecovery, HasAvatar, HasEmailAuthentication, HasName, HasTenants, Validatable
|
||||
{
|
||||
@@ -183,6 +202,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
'is_managed_externally' => 'boolean',
|
||||
'mfa_app_secret' => 'encrypted',
|
||||
'mfa_app_recovery_codes' => 'encrypted:array',
|
||||
'mfa_email_enabled' => 'boolean',
|
||||
'oauth' => 'array',
|
||||
'customization' => 'array',
|
||||
];
|
||||
@@ -206,6 +226,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
throw_if($user->servers()->count() > 0, new DisplayException(trans('exceptions.users.has_servers')));
|
||||
|
||||
throw_if(request()->user()?->id === $user->id, new DisplayException(trans('exceptions.users.is_self')));
|
||||
|
||||
event(new Deleting($user));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasValidation;
|
||||
use Database\Factories\UserSSHKeyFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -22,23 +21,23 @@ use Illuminate\Support\Carbon;
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property User $user
|
||||
* @property-read User $user
|
||||
*
|
||||
* @method static Builder|UserSSHKey newModelQuery()
|
||||
* @method static Builder|UserSSHKey newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|UserSSHKey onlyTrashed()
|
||||
* @method static Builder|UserSSHKey query()
|
||||
* @method static Builder|UserSSHKey whereCreatedAt($value)
|
||||
* @method static Builder|UserSSHKey whereDeletedAt($value)
|
||||
* @method static Builder|UserSSHKey whereFingerprint($value)
|
||||
* @method static Builder|UserSSHKey whereId($value)
|
||||
* @method static Builder|UserSSHKey whereName($value)
|
||||
* @method static Builder|UserSSHKey wherePublicKey($value)
|
||||
* @method static Builder|UserSSHKey whereUpdatedAt($value)
|
||||
* @method static Builder|UserSSHKey whereUserId($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|UserSSHKey withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|UserSSHKey withoutTrashed()
|
||||
* @method static UserSSHKeyFactory factory(...$parameters)
|
||||
* @method static \Database\Factories\UserSSHKeyFactory factory($count = null, $state = [])
|
||||
* @method static Builder<static>|UserSSHKey newModelQuery()
|
||||
* @method static Builder<static>|UserSSHKey newQuery()
|
||||
* @method static Builder<static>|UserSSHKey onlyTrashed()
|
||||
* @method static Builder<static>|UserSSHKey query()
|
||||
* @method static Builder<static>|UserSSHKey whereCreatedAt($value)
|
||||
* @method static Builder<static>|UserSSHKey whereDeletedAt($value)
|
||||
* @method static Builder<static>|UserSSHKey whereFingerprint($value)
|
||||
* @method static Builder<static>|UserSSHKey whereId($value)
|
||||
* @method static Builder<static>|UserSSHKey whereName($value)
|
||||
* @method static Builder<static>|UserSSHKey wherePublicKey($value)
|
||||
* @method static Builder<static>|UserSSHKey whereUpdatedAt($value)
|
||||
* @method static Builder<static>|UserSSHKey whereUserId($value)
|
||||
* @method static Builder<static>|UserSSHKey withTrashed(bool $withTrashed = true)
|
||||
* @method static Builder<static>|UserSSHKey withoutTrashed()
|
||||
*/
|
||||
class UserSSHKey extends Model
|
||||
{
|
||||
|
||||
@@ -2,19 +2,33 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\MassPrunable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $webhook_configuration_id
|
||||
* @property string $event
|
||||
* @property string $endpoint
|
||||
* @property \Illuminate\Support\Carbon|null $successful_at
|
||||
* @property Carbon|null $successful_at
|
||||
* @property array<array-key, mixed> $payload
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*
|
||||
* @method static Builder<static>|Webhook newModelQuery()
|
||||
* @method static Builder<static>|Webhook newQuery()
|
||||
* @method static Builder<static>|Webhook query()
|
||||
* @method static Builder<static>|Webhook whereCreatedAt($value)
|
||||
* @method static Builder<static>|Webhook whereEndpoint($value)
|
||||
* @method static Builder<static>|Webhook whereEvent($value)
|
||||
* @method static Builder<static>|Webhook whereId($value)
|
||||
* @method static Builder<static>|Webhook wherePayload($value)
|
||||
* @method static Builder<static>|Webhook whereSuccessfulAt($value)
|
||||
* @method static Builder<static>|Webhook whereUpdatedAt($value)
|
||||
* @method static Builder<static>|Webhook whereWebhookConfigurationId($value)
|
||||
*/
|
||||
class Webhook extends Model
|
||||
{
|
||||
|
||||
@@ -14,15 +14,36 @@ use Illuminate\Support\Facades\File;
|
||||
use Livewire\Features\SupportEvents\HandlesEvents;
|
||||
|
||||
/**
|
||||
* @property string|array<string, mixed>|null $payload
|
||||
* @property int $id
|
||||
* @property string $endpoint
|
||||
* @property string $description
|
||||
* @property string[] $events
|
||||
* @property WebhookType|string|null $type
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property array<string, string>|null $headers
|
||||
* @property WebhookType|null $type
|
||||
* @property string|array<array-key, mixed>|null $payload
|
||||
* @property array<array-key, mixed>|null $headers
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Webhook> $webhooks
|
||||
* @property-read int|null $webhooks_count
|
||||
*
|
||||
* @method static \Database\Factories\WebhookConfigurationFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereEndpoint($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereEvents($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereHeaders($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration wherePayload($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|WebhookConfiguration withoutTrashed()
|
||||
*/
|
||||
class WebhookConfiguration extends Model
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Checks\NodeVersionsCheck;
|
||||
use App\Checks\PanelVersionCheck;
|
||||
use App\Checks\ScheduleCheck;
|
||||
use App\Checks\UsedDiskSpaceCheck;
|
||||
use App\Http\Responses\LoginResponse;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\ApiKey;
|
||||
use App\Models\Backup;
|
||||
@@ -126,7 +127,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->bind(LoginResponseContract::class, \App\Http\Responses\LoginResponse::class);
|
||||
$this->app->bind(LoginResponseContract::class, LoginResponse::class);
|
||||
|
||||
Scramble::ignoreDefaultRoutes();
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ use App\Enums\CustomizationKey;
|
||||
use App\Filament\Pages\Auth\EditProfile;
|
||||
use App\Filament\Pages\Auth\Login;
|
||||
use App\Http\Middleware\LanguageMiddleware;
|
||||
use App\Http\Middleware\PreventRequestForgery;
|
||||
use App\Http\Middleware\RequireTwoFactorAuthentication;
|
||||
use App\Http\Middleware\SetSecurityHeaders;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Auth\MultiFactor\App\AppAuthentication;
|
||||
use Filament\Auth\MultiFactor\Email\EmailAuthentication;
|
||||
@@ -17,7 +19,6 @@ use Filament\Panel;
|
||||
use Filament\PanelProvider as BasePanelProvider;
|
||||
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;
|
||||
@@ -65,11 +66,12 @@ abstract class PanelProvider extends BasePanelProvider
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
PreventRequestForgery::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
LanguageMiddleware::class,
|
||||
SetSecurityHeaders::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
|
||||
@@ -147,7 +147,7 @@ class DaemonServerRepository extends DaemonRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Deauthorizes a user (disconnects websockets and SFTP) on the Wings instance for the server.
|
||||
* Deauthorizes a user (disconnects websockets and SFTP) on the Wings instance for the server (or all servers of a node).
|
||||
*
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
@@ -156,7 +156,7 @@ class DaemonServerRepository extends DaemonRepository
|
||||
$this->getHttpClient()->post('/api/deauthorize-user', [
|
||||
'json' => [
|
||||
'user' => $user,
|
||||
'servers' => [$this->server->uuid],
|
||||
'servers' => $this->server ? [$this->server->uuid] : [],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ class FindAssignableAllocationService
|
||||
// those belonging to the current server (making it impossible to find unassigned ones)
|
||||
/** @var Allocation|null $allocation */
|
||||
$allocation = Allocation::withoutGlobalScopes()
|
||||
->lockForUpdate()
|
||||
->where('node_id', $server->node_id)
|
||||
->when($server->allocation, function ($query) use ($server) {
|
||||
$query->where('ip', $server->allocation->ip);
|
||||
@@ -125,6 +126,7 @@ class FindAssignableAllocationService
|
||||
|
||||
/** @var Allocation $allocation */
|
||||
$allocation = Allocation::withoutGlobalScopes()
|
||||
->lockForUpdate()
|
||||
->where('node_id', $server->node_id)
|
||||
->where('ip', $server->allocation->ip)
|
||||
->where('port', $port)
|
||||
|
||||
@@ -21,7 +21,7 @@ class DatabaseManagementService
|
||||
* The regex used to validate that the database name passed through to the function is
|
||||
* in the expected format.
|
||||
*
|
||||
* @see \App\Services\Databases\DatabaseManagementService::generateUniqueDatabaseName()
|
||||
* @see DatabaseManagementService::generateUniqueDatabaseName()
|
||||
*/
|
||||
private const MATCH_NAME_REGEX = '/^(s[\d]+_)(.*)$/';
|
||||
|
||||
@@ -136,7 +136,7 @@ class DatabaseManagementService
|
||||
/**
|
||||
* Updates a password for a given database.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function rotatePassword(Database $database): void
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ class EggExporterService
|
||||
public function handle(int $egg, EggFormat $format): string
|
||||
{
|
||||
$egg = Egg::with(['scriptFrom', 'configFrom', 'variables'])->findOrFail($egg);
|
||||
$imageBase64 = $this->getEggImageAsBase64($egg);
|
||||
$iconBase64 = $this->getEggIconAsBase64($egg);
|
||||
|
||||
$struct = [
|
||||
'_comment' => 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL',
|
||||
@@ -31,7 +31,7 @@ class EggExporterService
|
||||
'author' => $egg->author,
|
||||
'uuid' => $egg->uuid,
|
||||
'description' => $egg->description,
|
||||
'image' => $imageBase64,
|
||||
'icon' => $iconBase64,
|
||||
'tags' => $egg->tags,
|
||||
'features' => $egg->features,
|
||||
'docker_images' => $egg->docker_images,
|
||||
@@ -63,16 +63,14 @@ class EggExporterService
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the egg image as base64 for export.
|
||||
* Get the egg icon as base64 for export.
|
||||
*/
|
||||
private function getEggImageAsBase64(Egg $egg): ?string
|
||||
private function getEggIconAsBase64(Egg $egg): ?string
|
||||
{
|
||||
foreach (array_keys(Egg::IMAGE_FORMATS) as $ext) {
|
||||
$path = Egg::ICON_STORAGE_PATH . "/$egg->uuid.$ext";
|
||||
foreach (Egg::$iconFormats as $ext => $mimeType) {
|
||||
$path = Egg::getIconStoragePath() . "/$egg->uuid.$ext";
|
||||
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
$mimeType = Egg::IMAGE_FORMATS[$ext];
|
||||
|
||||
return 'data:' . $mimeType . ';base64,' . base64_encode(Storage::disk('public')->get($path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ use App\Enums\EggFormat;
|
||||
use App\Exceptions\Service\InvalidFileUploadException;
|
||||
use App\Models\Egg;
|
||||
use App\Models\EggVariable;
|
||||
use Exception;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use JsonException;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use stdClass;
|
||||
@@ -223,6 +223,11 @@ class EggImporterService
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($parsed['image']) && str_starts_with($parsed['image'], 'data:')) {
|
||||
$parsed['icon'] = $parsed['image'];
|
||||
unset($parsed['image']);
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
@@ -231,9 +236,9 @@ class EggImporterService
|
||||
*/
|
||||
protected function fillFromParsed(Egg $model, array $parsed): Egg
|
||||
{
|
||||
// Handle image data if present
|
||||
if (!empty($parsed['image']) && str_starts_with($parsed['image'], 'data:')) {
|
||||
$this->saveEggImageFromBase64($parsed['image'], $model);
|
||||
// Handle icon data if present
|
||||
if (!empty($parsed['icon']) && str_starts_with($parsed['icon'], 'data:')) {
|
||||
$this->saveEggIconFromBase64($parsed['icon'], $model);
|
||||
}
|
||||
|
||||
return $model->forceFill([
|
||||
@@ -256,28 +261,24 @@ class EggImporterService
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an egg image from base64 data to a file.
|
||||
* Save an egg icon from base64 data to a file.
|
||||
*/
|
||||
private function saveEggImageFromBase64(string $base64String, Egg $egg): void
|
||||
private function saveEggIconFromBase64(string $base64String, Egg $egg): void
|
||||
{
|
||||
if (!preg_match('/^data:image\/([\w+]+);base64,(.+)$/', $base64String, $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$extension = $matches[1];
|
||||
$data = base64_decode($matches[2]);
|
||||
try {
|
||||
$extension = strtolower($matches[1]);
|
||||
$data = base64_decode($matches[2]);
|
||||
|
||||
if (!$data) {
|
||||
return;
|
||||
if ($data) {
|
||||
$egg->writeIcon($extension, $data);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
}
|
||||
|
||||
$normalizedExtension = match ($extension) {
|
||||
'svg+xml' => 'svg',
|
||||
'jpeg' => 'jpg',
|
||||
default => $extension,
|
||||
};
|
||||
|
||||
Storage::disk('public')->put(Egg::ICON_STORAGE_PATH . "/$egg->uuid.$normalizedExtension", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -348,8 +348,7 @@ class PluginService
|
||||
|
||||
$this->manageComposerPackages(oldPackages: $pluginPackages);
|
||||
|
||||
// This throws an error when not called with qualifier
|
||||
foreach (\Filament\Facades\Filament::getPanels() as $panel) {
|
||||
foreach (Filament::getPanels() as $panel) {
|
||||
$panel->clearCachedComponents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class NodeUpdateService
|
||||
/** @var Node $updated */
|
||||
$updated = $node->replicate();
|
||||
$updated->exists = true;
|
||||
$data = array_merge($data, ['created_at' => $node->created_at, 'updated_at' => now()]);
|
||||
$updated->forceFill($data)->save();
|
||||
try {
|
||||
$node->fqdn = $updated->fqdn;
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Services\Servers;
|
||||
|
||||
use App\Jobs\RevokeSftpAccessJob;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use App\Traits\Services\ReturnsUpdatedModels;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Throwable;
|
||||
|
||||
@@ -17,7 +16,7 @@ class DetailsModificationService
|
||||
/**
|
||||
* DetailsModificationService constructor.
|
||||
*/
|
||||
public function __construct(private ConnectionInterface $connection, private DaemonServerRepository $serverRepository) {}
|
||||
public function __construct(private ConnectionInterface $connection) {}
|
||||
|
||||
/**
|
||||
* Update the details for a single server instance.
|
||||
@@ -34,7 +33,7 @@ class DetailsModificationService
|
||||
public function handle(Server $server, array $data): Server
|
||||
{
|
||||
return $this->connection->transaction(function () use ($data, $server) {
|
||||
$owner = $server->owner_id;
|
||||
$oldOwner = $server->user;
|
||||
|
||||
$server->forceFill([
|
||||
'external_id' => Arr::get($data, 'external_id'),
|
||||
@@ -46,14 +45,8 @@ class DetailsModificationService
|
||||
// If the owner_id value is changed we need to revoke any tokens that exist for the server
|
||||
// on the daemon instance so that the old owner no longer has any permission to access the
|
||||
// websockets.
|
||||
if ($server->owner_id !== $owner) {
|
||||
try {
|
||||
$this->serverRepository->setServer($server)->deauthorize($server->user->uuid);
|
||||
} catch (ConnectionException) {
|
||||
// Do nothing. A failure here is not ideal, but it is likely to be caused by daemon
|
||||
// being offline, or in an entirely broken state. Remember, these tokens reset every
|
||||
// few minutes by default, we're just trying to help it along a little quicker.
|
||||
}
|
||||
if ($server->owner_id !== $oldOwner->id) {
|
||||
RevokeSftpAccessJob::dispatch($oldOwner->uuid, $server);
|
||||
}
|
||||
|
||||
return $server;
|
||||
|
||||
@@ -4,17 +4,12 @@ namespace App\Services\Subusers;
|
||||
|
||||
use App\Events\Server\SubUserRemoved;
|
||||
use App\Facades\Activity;
|
||||
use App\Jobs\RevokeSftpAccessJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\Subuser;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
|
||||
class SubuserDeletionService
|
||||
{
|
||||
public function __construct(
|
||||
private DaemonServerRepository $serverRepository,
|
||||
) {}
|
||||
|
||||
public function handle(Subuser $subuser, Server $server): void
|
||||
{
|
||||
$log = Activity::event('server:subuser.delete')
|
||||
@@ -27,14 +22,7 @@ class SubuserDeletionService
|
||||
|
||||
event(new SubUserRemoved($subuser->server, $subuser->user));
|
||||
|
||||
try {
|
||||
$this->serverRepository->setServer($server)->deauthorize($subuser->user->uuid);
|
||||
} catch (ConnectionException $exception) {
|
||||
// Don't block this request if we can't connect to the daemon instance.
|
||||
logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]);
|
||||
|
||||
$instance->property('revoked', false);
|
||||
}
|
||||
RevokeSftpAccessJob::dispatch($subuser->user->uuid, $server);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,12 @@ namespace App\Services\Subusers;
|
||||
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Facades\Activity;
|
||||
use App\Jobs\RevokeSftpAccessJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\Subuser;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
|
||||
class SubuserUpdateService
|
||||
{
|
||||
public function __construct(
|
||||
private DaemonServerRepository $serverRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string[] $permissions
|
||||
*/
|
||||
@@ -42,18 +37,10 @@ class SubuserUpdateService
|
||||
// Only update the database and hit up the daemon instance to invalidate JTI's if the permissions
|
||||
// have actually changed for the user.
|
||||
if ($cleanedPermissions !== $current) {
|
||||
$log->transaction(function ($instance) use ($subuser, $cleanedPermissions, $server) {
|
||||
$log->transaction(function () use ($subuser, $cleanedPermissions, $server) {
|
||||
$subuser->update(['permissions' => $cleanedPermissions]);
|
||||
|
||||
try {
|
||||
$this->serverRepository->setServer($server)->deauthorize($subuser->user->uuid);
|
||||
} catch (ConnectionException $exception) {
|
||||
// Don't block this request if we can't connect to the daemon instance. Chances are it is
|
||||
// offline and the token will be invalid once daemon boots back.
|
||||
logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]);
|
||||
|
||||
$instance->property('revoked', false);
|
||||
}
|
||||
RevokeSftpAccessJob::dispatch($subuser->user->uuid, $server);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services\Users;
|
||||
|
||||
use App\Events\User\PasswordChanged;
|
||||
use App\Models\User;
|
||||
use App\Traits\Services\HasUserLevels;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
@@ -30,6 +31,10 @@ class UserUpdateService
|
||||
|
||||
$user->forceFill($data)->saveOrFail();
|
||||
|
||||
if (isset($data['password'])) {
|
||||
PasswordChanged::dispatch($user);
|
||||
}
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ class EggTransformer extends BaseTransformer
|
||||
'name' => $model->name,
|
||||
'author' => $model->author,
|
||||
'description' => $model->description,
|
||||
'image' => $model->image,
|
||||
'icon' => $model->icon,
|
||||
'features' => $model->features,
|
||||
'tags' => $model->tags,
|
||||
'docker_image' => Arr::first($model->docker_images, default: ''), // docker_images, use startup_commands
|
||||
'docker_image' => Arr::first($model->docker_images, default: ''), // deprecated, use docker_images
|
||||
'docker_images' => $model->docker_images,
|
||||
'config' => [
|
||||
'files' => $files,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
if (!function_exists('is_digit')) {
|
||||
/**
|
||||
* Deal with normal (and irritating) PHP behavior to determine if
|
||||
@@ -130,8 +132,29 @@ if (!function_exists('encode_path')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('convert_to_utf8')) {
|
||||
/**
|
||||
* Convert a string to UTF-8 from an unknown encoding
|
||||
*/
|
||||
function convert_to_utf8(string $contents): string
|
||||
{
|
||||
// Valid UTF-8 passes through unchanged
|
||||
if (mb_check_encoding($contents, 'UTF-8')) {
|
||||
return $contents;
|
||||
}
|
||||
|
||||
// Only detect UTF-16 by BOM instead of mb_check_encoding('UTF-16') which can cause false positives
|
||||
if (str_starts_with($contents, "\xFF\xFE") || str_starts_with($contents, "\xFE\xFF")) {
|
||||
return mb_convert_encoding($contents, 'UTF-8', 'UTF-16');
|
||||
}
|
||||
|
||||
// ISO-8859-1 serves as a universal fallback since any byte sequence is valid in it
|
||||
return mb_convert_encoding($contents, 'UTF-8', 'ISO-8859-1');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('user')) {
|
||||
function user(): ?App\Models\User
|
||||
function user(): ?User
|
||||
{
|
||||
return auth(config('auth.defaults.guard', 'web'))->user();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
<?php
|
||||
|
||||
use App\Console\Kernel;
|
||||
use App\Exceptions\Handler;
|
||||
use App\Http\Middleware\Activity\TrackAPIKey;
|
||||
use App\Http\Middleware\Api\Application\AuthenticateApplicationUser;
|
||||
use App\Http\Middleware\Api\AuthenticateIPAccess;
|
||||
use App\Http\Middleware\Api\Client\RequireClientApiKey;
|
||||
use App\Http\Middleware\Api\Daemon\DaemonAuthenticate;
|
||||
use App\Http\Middleware\Api\IsValidJson;
|
||||
use App\Http\Middleware\EnsureStatefulRequests;
|
||||
use App\Http\Middleware\LanguageMiddleware;
|
||||
use App\Http\Middleware\MaintenanceMiddleware;
|
||||
use App\Http\Middleware\PreventRequestForgery;
|
||||
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||
use App\Http\Middleware\SetSecurityHeaders;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as IlluminatePreventRequestForgery;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withProviders()
|
||||
@@ -12,46 +29,49 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->redirectGuestsTo(fn () => route('filament.app.auth.login'));
|
||||
|
||||
$middleware->web(\App\Http\Middleware\LanguageMiddleware::class);
|
||||
$middleware->web([
|
||||
LanguageMiddleware::class,
|
||||
SetSecurityHeaders::class,
|
||||
]);
|
||||
|
||||
$middleware->api([
|
||||
\App\Http\Middleware\EnsureStatefulRequests::class,
|
||||
EnsureStatefulRequests::class,
|
||||
'auth:sanctum',
|
||||
\App\Http\Middleware\Api\IsValidJson::class,
|
||||
\App\Http\Middleware\Activity\TrackAPIKey::class,
|
||||
\App\Http\Middleware\Api\AuthenticateIPAccess::class,
|
||||
IsValidJson::class,
|
||||
TrackAPIKey::class,
|
||||
AuthenticateIPAccess::class,
|
||||
]);
|
||||
|
||||
$middleware->group('application-api', [
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\Api\Application\AuthenticateApplicationUser::class,
|
||||
SubstituteBindings::class,
|
||||
AuthenticateApplicationUser::class,
|
||||
]);
|
||||
|
||||
$middleware->group('client-api', [
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\Api\Client\RequireClientApiKey::class,
|
||||
SubstituteBindings::class,
|
||||
RequireClientApiKey::class,
|
||||
]);
|
||||
|
||||
$middleware->group('daemon', [
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\Api\Daemon\DaemonAuthenticate::class,
|
||||
SubstituteBindings::class,
|
||||
DaemonAuthenticate::class,
|
||||
]);
|
||||
|
||||
$middleware->replaceInGroup('web', \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class);
|
||||
$middleware->replaceInGroup('web', IlluminatePreventRequestForgery::class, PreventRequestForgery::class);
|
||||
|
||||
$middleware->alias([
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'node.maintenance' => \App\Http\Middleware\MaintenanceMiddleware::class,
|
||||
'bindings' => SubstituteBindings::class,
|
||||
'guest' => RedirectIfAuthenticated::class,
|
||||
'node.maintenance' => MaintenanceMiddleware::class,
|
||||
]);
|
||||
|
||||
$middleware->priority([
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
SubstituteBindings::class,
|
||||
]);
|
||||
})
|
||||
->withSingletons([
|
||||
\Illuminate\Contracts\Console\Kernel::class => \App\Console\Kernel::class,
|
||||
\Illuminate\Contracts\Debug\ExceptionHandler::class => \App\Exceptions\Handler::class,
|
||||
Illuminate\Contracts\Console\Kernel::class => Kernel::class,
|
||||
ExceptionHandler::class => Handler::class,
|
||||
])
|
||||
->withExceptions(function (Exceptions $exceptions) {})
|
||||
->create();
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
<?php
|
||||
|
||||
use App\Providers\ActivityLogServiceProvider;
|
||||
use App\Providers\AppServiceProvider;
|
||||
use App\Providers\BackupsServiceProvider;
|
||||
use App\Providers\EventServiceProvider;
|
||||
use App\Providers\Extensions\AvatarServiceProvider;
|
||||
use App\Providers\Extensions\CaptchaServiceProvider;
|
||||
use App\Providers\Extensions\FeatureServiceProvider;
|
||||
use App\Providers\Extensions\OAuthServiceProvider;
|
||||
use App\Providers\Extensions\TaskServiceProvider;
|
||||
use App\Providers\Filament\AdminPanelProvider;
|
||||
use App\Providers\Filament\AppPanelProvider;
|
||||
use App\Providers\Filament\FilamentServiceProvider;
|
||||
use App\Providers\Filament\ServerPanelProvider;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use SocialiteProviders\Manager\ServiceProvider;
|
||||
|
||||
return [
|
||||
App\Providers\ActivityLogServiceProvider::class,
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\BackupsServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\Extensions\AvatarServiceProvider::class,
|
||||
App\Providers\Extensions\CaptchaServiceProvider::class,
|
||||
App\Providers\Extensions\FeatureServiceProvider::class,
|
||||
App\Providers\Extensions\OAuthServiceProvider::class,
|
||||
App\Providers\Extensions\TaskServiceProvider::class,
|
||||
App\Providers\Filament\FilamentServiceProvider::class,
|
||||
App\Providers\Filament\AdminPanelProvider::class,
|
||||
App\Providers\Filament\AppPanelProvider::class,
|
||||
App\Providers\Filament\ServerPanelProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
SocialiteProviders\Manager\ServiceProvider::class,
|
||||
ActivityLogServiceProvider::class,
|
||||
AppServiceProvider::class,
|
||||
BackupsServiceProvider::class,
|
||||
EventServiceProvider::class,
|
||||
AvatarServiceProvider::class,
|
||||
CaptchaServiceProvider::class,
|
||||
FeatureServiceProvider::class,
|
||||
OAuthServiceProvider::class,
|
||||
TaskServiceProvider::class,
|
||||
FilamentServiceProvider::class,
|
||||
AdminPanelProvider::class,
|
||||
AppPanelProvider::class,
|
||||
ServerPanelProvider::class,
|
||||
RouteServiceProvider::class,
|
||||
ServiceProvider::class,
|
||||
];
|
||||
|
||||
@@ -3,59 +3,59 @@
|
||||
"description": "The free, open-source game management panel. Supporting Minecraft, Spigot, BungeeCord, and SRCDS servers.",
|
||||
"license": "AGPL-3.0-only",
|
||||
"require": {
|
||||
"php": "^8.2 || ^8.3 || ^8.4 || ^8.5",
|
||||
"php": "^8.3 || ^8.4 || ^8.5",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-zip": "*",
|
||||
"aws/aws-sdk-php": "^3.369",
|
||||
"aws/aws-sdk-php": "^3.379",
|
||||
"calebporzio/sushi": "^2.5",
|
||||
"dedoc/scramble": "^0.13",
|
||||
"filament/filament": "^4.5",
|
||||
"gboquizosanchez/filament-log-viewer": "^2.2",
|
||||
"filament/filament": "^5.6",
|
||||
"gboquizosanchez/filament-log-viewer": "^2.3",
|
||||
"guzzlehttp/guzzle": "^7.10",
|
||||
"laravel/framework": "^12.52",
|
||||
"laravel/framework": "^13.7",
|
||||
"laravel/helpers": "^1.8",
|
||||
"laravel/sanctum": "^4.3",
|
||||
"laravel/socialite": "^5.24",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"laravel/socialite": "^5.27",
|
||||
"laravel/tinker": "^3.0",
|
||||
"laravel/ui": "^4.6",
|
||||
"lcobucci/jwt": "^5.6",
|
||||
"league/flysystem-aws-s3-v3": "^3.31",
|
||||
"league/flysystem-aws-s3-v3": "^3.32",
|
||||
"league/flysystem-memory": "^3.31",
|
||||
"phiki/phiki": "^2.0",
|
||||
"phpseclib/phpseclib": "~3.0.18",
|
||||
"predis/predis": "^2.3",
|
||||
"s1lentium/iptools": "~1.2.0",
|
||||
"secondnetwork/blade-tabler-icons": "^3.26",
|
||||
"socialiteproviders/authentik": "^5.2",
|
||||
"phiki/phiki": "^2.2",
|
||||
"phpseclib/phpseclib": "^3.0.52",
|
||||
"predis/predis": "^2.4",
|
||||
"s1lentium/iptools": "^1.2",
|
||||
"secondnetwork/blade-tabler-icons": "^3.41",
|
||||
"socialiteproviders/authentik": "^5.3",
|
||||
"socialiteproviders/discord": "^4.2",
|
||||
"socialiteproviders/steam": "^4.3",
|
||||
"spatie/laravel-data": "^4.19",
|
||||
"spatie/laravel-fractal": "^6.3",
|
||||
"spatie/laravel-health": "^1.37",
|
||||
"spatie/laravel-permission": "^6.24",
|
||||
"spatie/laravel-data": "^4.22",
|
||||
"spatie/laravel-fractal": "^6.4",
|
||||
"spatie/laravel-health": "^1.39",
|
||||
"spatie/laravel-permission": "^6.25",
|
||||
"spatie/laravel-query-builder": "^6.4",
|
||||
"spatie/temporary-directory": "^2.3",
|
||||
"symfony/http-client": "^7.2",
|
||||
"symfony/mailgun-mailer": "^7.2",
|
||||
"symfony/postmark-mailer": "^7.2",
|
||||
"symfony/yaml": "^7.2",
|
||||
"webmozart/assert": "~1.11.0"
|
||||
"symfony/http-client": "^7.4",
|
||||
"symfony/mailgun-mailer": "^7.4",
|
||||
"symfony/postmark-mailer": "^7.4",
|
||||
"symfony/yaml": "^7.4",
|
||||
"webmozart/assert": "^1.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.6",
|
||||
"fakerphp/faker": "^1.23.1",
|
||||
"larastan/larastan": "^3.4",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.15.3",
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6.11",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"pestphp/pest": "^3.7",
|
||||
"pestphp/pest-plugin-faker": "^3.0",
|
||||
"pestphp/pest-plugin-livewire": "^3.0"
|
||||
"barryvdh/laravel-ide-helper": "^3.7",
|
||||
"fakerphp/faker": "^1.24",
|
||||
"larastan/larastan": "^3.9",
|
||||
"laravel/pail": "^1.2.6",
|
||||
"laravel/pint": "^1.29",
|
||||
"laravel/sail": "^1.58",
|
||||
"mockery/mockery": "^1.6.12",
|
||||
"nunomaduro/collision": "^8.9",
|
||||
"pestphp/pest": "^4.0",
|
||||
"pestphp/pest-plugin-faker": "^4.0",
|
||||
"pestphp/pest-plugin-livewire": "^4.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
@@ -92,9 +92,9 @@
|
||||
"pestphp/pest-plugin": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "8.2"
|
||||
"php": "8.3"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
||||
2711
composer.lock
generated
2711
composer.lock
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user