mirror of
https://github.com/pelican-dev/panel.git
synced 2026-02-24 11:20:41 +03:00
Compare commits
15 Commits
charles/sp
...
lance/1022
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea978f3485 | ||
|
|
c8dcff2ee9 | ||
|
|
0579fd6df5 | ||
|
|
789c4c7284 | ||
|
|
b1a39f1724 | ||
|
|
6a548c09a0 | ||
|
|
55bda569cc | ||
|
|
adf1249086 | ||
|
|
dbf77bf146 | ||
|
|
a34bf9fd49 | ||
|
|
7a9deba0e1 | ||
|
|
159bfe2210 | ||
|
|
a821db8aae | ||
|
|
1556f8efb8 | ||
|
|
57c2aa6f21 |
@@ -2,11 +2,18 @@
|
||||
|
||||
namespace App\Extensions\Filesystem;
|
||||
|
||||
use Aws\CommandInterface;
|
||||
use Aws\Result;
|
||||
use Aws\S3\S3ClientInterface;
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
|
||||
use RuntimeException;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class S3Filesystem extends AwsS3V3Adapter
|
||||
{
|
||||
private ?GuzzleClient $guzzle = null;
|
||||
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
@@ -26,6 +33,18 @@ class S3Filesystem extends AwsS3V3Adapter
|
||||
);
|
||||
}
|
||||
|
||||
private function getGuzzleClient(): GuzzleClient
|
||||
{
|
||||
if ($this->guzzle === null) {
|
||||
$this->guzzle = new GuzzleClient([
|
||||
'timeout' => 30,
|
||||
'connect_timeout' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->guzzle;
|
||||
}
|
||||
|
||||
public function getClient(): S3ClientInterface
|
||||
{
|
||||
return $this->client;
|
||||
@@ -35,4 +54,78 @@ class S3Filesystem extends AwsS3V3Adapter
|
||||
{
|
||||
return $this->bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an S3 command using a presigned URL for maximum compatibility
|
||||
* with S3-compatible providers.
|
||||
*
|
||||
* @return Result<array<string, mixed>>
|
||||
*/
|
||||
public function executeS3Command(CommandInterface $command): Result
|
||||
{
|
||||
$presignedRequest = $this->client->createPresignedRequest($command, '+60 minutes');
|
||||
|
||||
$response = $this->getGuzzleClient()->send($presignedRequest);
|
||||
|
||||
$body = (string) $response->getBody();
|
||||
$commandName = $command->getName();
|
||||
|
||||
// S3's CompleteMultipartUpload can return HTTP 200 with an <Error> body
|
||||
if ($body !== '' && str_contains($body, '<Error>')) {
|
||||
throw new RuntimeException("S3 returned an error for $commandName: $body");
|
||||
}
|
||||
|
||||
return new Result($this->parseS3Response($commandName, $body));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the XML response body based on the S3 command type.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function parseS3Response(string $commandName, string $body): array
|
||||
{
|
||||
if ($body === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$xml = @simplexml_load_string($body);
|
||||
if ($xml === false) {
|
||||
throw new RuntimeException("Failed to parse S3 XML response for $commandName: $body");
|
||||
}
|
||||
|
||||
return match ($commandName) {
|
||||
'CreateMultipartUpload' => $this->parseCreateMultipartUpload($xml),
|
||||
'ListParts' => $this->parseListParts($xml),
|
||||
'CompleteMultipartUpload' => [],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{UploadId: string}
|
||||
*/
|
||||
private function parseCreateMultipartUpload(SimpleXMLElement $xml): array
|
||||
{
|
||||
return [
|
||||
'UploadId' => (string) $xml->UploadId,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{Parts: array<int, array{ETag: string, PartNumber: int}>}
|
||||
*/
|
||||
private function parseListParts(SimpleXMLElement $xml): array
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
foreach ($xml->Part as $part) {
|
||||
$parts[] = [
|
||||
'ETag' => (string) $part->ETag,
|
||||
'PartNumber' => (int) $part->PartNumber,
|
||||
];
|
||||
}
|
||||
|
||||
return ['Parts' => $parts];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
@@ -113,12 +114,44 @@ class ApiKeyResource extends Resource
|
||||
*/
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
$permissionList = ApiKey::getPermissionList();
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Section::make(trans('admin/apikey.permissions.all'))
|
||||
->description(trans('admin/apikey.permissions.all_description'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('permissions_all')
|
||||
->hiddenLabel()
|
||||
->inline()
|
||||
->options([
|
||||
0 => trans('admin/apikey.permissions.none'),
|
||||
1 => trans('admin/apikey.permissions.read'),
|
||||
3 => trans('admin/apikey.permissions.read_write'),
|
||||
])
|
||||
->icons([
|
||||
0 => TablerIcon::BookOff,
|
||||
1 => TablerIcon::Book,
|
||||
3 => TablerIcon::Writing,
|
||||
])
|
||||
->colors([
|
||||
0 => 'success',
|
||||
1 => 'warning',
|
||||
3 => 'danger',
|
||||
])
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, callable $set) use ($permissionList) {
|
||||
foreach ($permissionList as $resource) {
|
||||
$set('permissions_' . $resource, $state);
|
||||
}
|
||||
})
|
||||
->default(0),
|
||||
]),
|
||||
Fieldset::make('Permissions')
|
||||
->columnSpanFull()
|
||||
->schema(
|
||||
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
collect($permissionList)->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||
->options([
|
||||
0 => trans('admin/apikey.permissions.none'),
|
||||
|
||||
@@ -450,17 +450,7 @@ class EditEgg extends EditRecord
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->disabled(fn (Egg $egg): bool => $egg->servers()->count() > 0)
|
||||
->tooltip(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? trans('filament-actions::delete.single.label') : trans('admin/egg.in_use'))
|
||||
->successNotification(fn (Egg $egg) => Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/egg.delete_success'))
|
||||
->body(trans('admin/egg.deleted', ['egg' => $egg->name]))
|
||||
)
|
||||
->failureNotification(fn (Egg $egg) => Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/egg.delete_failed'))
|
||||
->body(trans('admin/egg.could_not_delete', ['egg' => $egg->name]))
|
||||
),
|
||||
->tooltip(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? trans('filament-actions::delete.single.label') : trans('admin/egg.in_use')),
|
||||
ExportEggAction::make(),
|
||||
ImportEggAction::make()
|
||||
->multiple(false),
|
||||
|
||||
@@ -278,6 +278,14 @@ class CreateNode extends CreateRecord
|
||||
->default(256)
|
||||
->minValue(1)
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||
TextInput::make('daemon_base')
|
||||
->label(trans('admin/node.daemon_base'))
|
||||
->placeholder('/var/lib/pelican/volumes')
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/node.daemon_base_help'))
|
||||
->columnSpan(1)
|
||||
->required()
|
||||
->default('/var/lib/pelican/volumes')
|
||||
->rule('regex:/^([\/][\d\w.\-\/]+)$/'),
|
||||
TextInput::make('daemon_sftp')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.sftp_port'))
|
||||
@@ -287,7 +295,7 @@ class CreateNode extends CreateRecord
|
||||
->required()
|
||||
->integer(),
|
||||
TextInput::make('daemon_sftp_alias')
|
||||
->columnSpan(2)
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.sftp_alias'))
|
||||
->helperText(trans('admin/node.sftp_alias_help')),
|
||||
Grid::make()
|
||||
|
||||
@@ -314,7 +314,7 @@ class EditNode extends EditRecord
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 2,
|
||||
'lg' => 2,
|
||||
'lg' => 3,
|
||||
]),
|
||||
TextInput::make('upload_size')
|
||||
->columnSpan([
|
||||
@@ -329,12 +329,24 @@ class EditNode extends EditRecord
|
||||
->required()
|
||||
->minValue(1)
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||
TextInput::make('daemon_base')
|
||||
->label(trans('admin/node.daemon_base'))
|
||||
->placeholder('/var/lib/pelican/volumes')
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/node.daemon_base_help'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 2,
|
||||
'lg' => 2,
|
||||
])
|
||||
->required()
|
||||
->rule('regex:/^([\/][\d\w.\-\/]+)$/'),
|
||||
TextInput::make('daemon_sftp')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 3,
|
||||
'md' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->label(trans('admin/node.sftp_port'))
|
||||
->minValue(1)
|
||||
@@ -346,8 +358,8 @@ class EditNode extends EditRecord
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 3,
|
||||
'md' => 2,
|
||||
'lg' => 2,
|
||||
])
|
||||
->label(trans('admin/node.sftp_alias'))
|
||||
->helperText(trans('admin/node.sftp_alias_help')),
|
||||
@@ -356,7 +368,7 @@ class EditNode extends EditRecord
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 3,
|
||||
'lg' => 2,
|
||||
])
|
||||
->label(trans('admin/node.use_for_deploy'))
|
||||
->inline()
|
||||
@@ -374,7 +386,7 @@ class EditNode extends EditRecord
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 3,
|
||||
'lg' => 2,
|
||||
])
|
||||
->label(trans('admin/node.maintenance_mode'))
|
||||
->inline()
|
||||
@@ -572,7 +584,7 @@ class EditNode extends EditRecord
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Actions::make([
|
||||
Action::make('autoDeploy')
|
||||
Action::make('exclude_autoDeploy')
|
||||
->label(trans('admin/node.auto_deploy'))
|
||||
->color('primary')
|
||||
->modalHeading(trans('admin/node.auto_deploy'))
|
||||
@@ -610,7 +622,7 @@ class EditNode extends EditRecord
|
||||
}),
|
||||
])->fullWidth(),
|
||||
Actions::make([
|
||||
Action::make('resetKey')
|
||||
Action::make('exclude_resetKey')
|
||||
->label(trans('admin/node.reset_token'))
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
|
||||
@@ -66,6 +66,14 @@ class Login extends BaseLogin
|
||||
->extraInputAttributes(['tabindex' => 1]);
|
||||
}
|
||||
|
||||
protected function getPasswordFormComponent(): Component
|
||||
{
|
||||
/** @var TextInput $component */
|
||||
$component = parent::getPasswordFormComponent();
|
||||
|
||||
return $component->extraInputAttributes(['tabindex' => 2]);
|
||||
}
|
||||
|
||||
protected function getOAuthFormComponent(): Component
|
||||
{
|
||||
$actions = [];
|
||||
|
||||
@@ -78,11 +78,15 @@ class Console extends Page
|
||||
$feature = data_get($data, 'key');
|
||||
|
||||
$feature = $this->featureService->get($feature);
|
||||
if (!$feature || $this->getMountedAction()) {
|
||||
if (!$feature) {
|
||||
return;
|
||||
}
|
||||
$this->mountAction($feature->getId());
|
||||
sleep(2); // TODO find a better way
|
||||
|
||||
if ($this->getMountedAction()) {
|
||||
$this->replaceMountedAction($feature->getId());
|
||||
} else {
|
||||
$this->mountAction($feature->getId());
|
||||
}
|
||||
}
|
||||
|
||||
public function getWidgetData(): array
|
||||
|
||||
@@ -209,16 +209,18 @@ class ListFiles extends ListRecords
|
||||
->required()
|
||||
->live(),
|
||||
TextEntry::make('new_location')
|
||||
->state(fn (Get $get, File $file) => resolve_path(join_paths($this->path, $get('location') ?? '/', $file->name))),
|
||||
->state(fn (Get $get, File $file) => resolve_path(join_paths($this->path, str_ends_with($get('location') ?? '/', '/') ? join_paths($get('location') ?? '/', $file->name) : $get('location') ?? '/'))),
|
||||
])
|
||||
->action(function ($data, File $file) {
|
||||
$location = $data['location'];
|
||||
$files = [['to' => join_paths($location, $file->name), 'from' => $file->name]];
|
||||
$endsWithSlash = str_ends_with($location, '/');
|
||||
$to = $endsWithSlash ? join_paths($location, $file->name) : $location;
|
||||
$files = [['to' => $to, 'from' => $file->name]];
|
||||
|
||||
$this->getDaemonFileRepository()->renameFiles($this->path, $files);
|
||||
|
||||
$oldLocation = join_paths($this->path, $file->name);
|
||||
$newLocation = resolve_path(join_paths($this->path, $location, $file->name));
|
||||
$newLocation = resolve_path(join_paths($this->path, $to));
|
||||
|
||||
Activity::event('server:file.rename')
|
||||
->property('directory', $this->path)
|
||||
|
||||
@@ -91,7 +91,7 @@ class BackupRemoteUploadController extends Controller
|
||||
}
|
||||
|
||||
// Execute the CreateMultipartUpload request
|
||||
$result = $client->execute($client->getCommand('CreateMultipartUpload', $params));
|
||||
$result = $adapter->executeS3Command($client->getCommand('CreateMultipartUpload', $params));
|
||||
|
||||
// Get the UploadId from the CreateMultipartUpload request, this is needed to create
|
||||
// the other presigned urls.
|
||||
|
||||
@@ -138,7 +138,7 @@ class BackupStatusController extends Controller
|
||||
|
||||
$client = $adapter->getClient();
|
||||
if (!$successful) {
|
||||
$client->execute($client->getCommand('AbortMultipartUpload', $params));
|
||||
$adapter->executeS3Command($client->getCommand('AbortMultipartUpload', $params));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class BackupStatusController extends Controller
|
||||
];
|
||||
|
||||
if (is_null($parts)) {
|
||||
$params['MultipartUpload']['Parts'] = $client->execute($client->getCommand('ListParts', $params))['Parts'];
|
||||
$params['MultipartUpload']['Parts'] = $adapter->executeS3Command($client->getCommand('ListParts', $params))['Parts'];
|
||||
} else {
|
||||
foreach ($parts as $part) {
|
||||
$params['MultipartUpload']['Parts'][] = [
|
||||
@@ -159,6 +159,6 @@ class BackupStatusController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
$client->execute($client->getCommand('CompleteMultipartUpload', $params));
|
||||
$adapter->executeS3Command($client->getCommand('CompleteMultipartUpload', $params));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class OAuthController extends Controller
|
||||
$email = $oauthUser->getEmail();
|
||||
|
||||
if (!$email) {
|
||||
return $this->errorRedirect();
|
||||
return $this->errorRedirect('No email was linked to your account on the OAuth provider.');
|
||||
}
|
||||
|
||||
$user = User::whereEmail($email)->first();
|
||||
|
||||
@@ -358,7 +358,7 @@ class Node extends Model implements Validatable
|
||||
'disk_used' => 0,
|
||||
];
|
||||
|
||||
return cache()->remember("nodes.$this->id.statistics", now()->addSeconds(360), function () use ($default) {
|
||||
return cache()->flexible("nodes.$this->id.statistics", [5, 30], function () use ($default) {
|
||||
try {
|
||||
|
||||
$data = Http::daemon($this)
|
||||
|
||||
@@ -23,14 +23,16 @@ class AccountCreated extends Notification implements ShouldQueue
|
||||
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
$locale = $notifiable->language ?? 'en';
|
||||
|
||||
$message = (new MailMessage())
|
||||
->greeting('Hello ' . $notifiable->username . '!')
|
||||
->line('You are receiving this email because an account has been created for you on ' . config('app.name') . '.')
|
||||
->line('Username: ' . $notifiable->username)
|
||||
->line('Email: ' . $notifiable->email);
|
||||
->greeting(trans('mail.greeting', ['name' => $notifiable->username], $locale))
|
||||
->line(trans('mail.account_created.body', ['app' => config('app.name')], $locale))
|
||||
->line(trans('mail.account_created.username', ['username' => $notifiable->username], $locale))
|
||||
->line(trans('mail.account_created.email', ['email' => $notifiable->email], $locale));
|
||||
|
||||
if (!is_null($this->token)) {
|
||||
return $message->action('Setup Your Account', Filament::getPanel('app')->getResetPasswordUrl($this->token, $notifiable));
|
||||
return $message->action(trans('mail.account_created.action', locale: $locale), Filament::getPanel('app')->getResetPasswordUrl($this->token, $notifiable));
|
||||
}
|
||||
|
||||
return $message;
|
||||
|
||||
@@ -24,10 +24,12 @@ class AddedToServer extends Notification implements ShouldQueue
|
||||
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
$locale = $notifiable->language ?? 'en';
|
||||
|
||||
return (new MailMessage())
|
||||
->greeting('Hello ' . $notifiable->username . '!')
|
||||
->line('You have been added as a subuser for the following server, allowing you certain control over the server.')
|
||||
->line('Server Name: ' . $this->server->name)
|
||||
->action('Visit Server', Console::getUrl(panel: 'server', tenant: $this->server));
|
||||
->greeting(trans('mail.greeting', ['name' => $notifiable->username], $locale))
|
||||
->line(trans('mail.added_to_server.body', locale: $locale))
|
||||
->line(trans('mail.added_to_server.server_name', ['name' => $this->server->name], $locale))
|
||||
->action(trans('mail.added_to_server.action', locale: $locale), Console::getUrl(panel: 'server', tenant: $this->server));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,11 @@ class MailTested extends Notification
|
||||
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$locale = $this->user->language ?? 'en';
|
||||
|
||||
return (new MailMessage())
|
||||
->subject('Panel Test Message')
|
||||
->greeting('Hello ' . $this->user->username . '!')
|
||||
->line('This is a test of the Panel mail system. You\'re good to go!');
|
||||
->subject(trans('mail.mail_tested.subject', locale: $locale))
|
||||
->greeting(trans('mail.greeting', ['name' => $this->user->username], $locale))
|
||||
->line(trans('mail.mail_tested.body', locale: $locale));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,13 @@ class RemovedFromServer extends Notification implements ShouldQueue
|
||||
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
$locale = $notifiable->language ?? 'en';
|
||||
|
||||
return (new MailMessage())
|
||||
->error()
|
||||
->greeting('Hello ' . $notifiable->username . '.')
|
||||
->line('You have been removed as a subuser for the following server.')
|
||||
->line('Server Name: ' . $this->server->name)
|
||||
->action('Visit Panel', url(''));
|
||||
->greeting(trans('mail.greeting', ['name' => $notifiable->username], $locale))
|
||||
->line(trans('mail.removed_from_server.body', locale: $locale))
|
||||
->line(trans('mail.removed_from_server.server_name', ['name' => $this->server->name], $locale))
|
||||
->action(trans('mail.removed_from_server.action', locale: $locale), url(''));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,12 @@ class ServerInstalled extends Notification implements ShouldQueue
|
||||
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
$locale = $notifiable->language ?? 'en';
|
||||
|
||||
return (new MailMessage())
|
||||
->greeting('Hello ' . $notifiable->username . '.')
|
||||
->line('Your server has finished installing and is now ready for you to use.')
|
||||
->line('Server Name: ' . $this->server->name)
|
||||
->action('Login and Begin Using', Console::getUrl(panel: 'server', tenant: $this->server));
|
||||
->greeting(trans('mail.greeting', ['name' => $notifiable->username], $locale))
|
||||
->line(trans('mail.server_installed.body', locale: $locale))
|
||||
->line(trans('mail.server_installed.server_name', ['name' => $this->server->name], $locale))
|
||||
->action(trans('mail.server_installed.action', locale: $locale), Console::getUrl(panel: 'server', tenant: $this->server));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use pxlrbt\FilamentSpotlight\SpotlightPlugin;
|
||||
|
||||
abstract class PanelProvider extends BasePanelProvider
|
||||
{
|
||||
@@ -72,9 +71,6 @@ abstract class PanelProvider extends BasePanelProvider
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
RequireTwoFactorAuthentication::class,
|
||||
])
|
||||
->plugins([
|
||||
SpotlightPlugin::make(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use Filament\Facades\Filament;
|
||||
use Illuminate\Contracts\Auth\Factory as AuthFactory;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Throwable;
|
||||
@@ -71,7 +70,7 @@ class ActivityLogService
|
||||
*/
|
||||
public function subject(...$subjects): self
|
||||
{
|
||||
foreach (Arr::wrap($subjects) as $subject) {
|
||||
foreach ($subjects as $subject) {
|
||||
if (is_null($subject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Extensions\Backups\BackupManager;
|
||||
use App\Extensions\Filesystem\S3Filesystem;
|
||||
use App\Models\Backup;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use Aws\S3\S3Client;
|
||||
use Exception;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Http\Response;
|
||||
@@ -72,14 +71,12 @@ class DeleteBackupService
|
||||
|
||||
/** @var S3Filesystem $adapter */
|
||||
$adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3);
|
||||
|
||||
/** @var S3Client $client */
|
||||
$client = $adapter->getClient();
|
||||
|
||||
$client->deleteObject([
|
||||
$adapter->executeS3Command($client->getCommand('DeleteObject', [
|
||||
'Bucket' => $adapter->getBucket(),
|
||||
'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid),
|
||||
]);
|
||||
]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"phiki/phiki": "^2.0",
|
||||
"phpseclib/phpseclib": "~3.0.18",
|
||||
"predis/predis": "^2.3",
|
||||
"pxlrbt/filament-spotlight": "^2.0",
|
||||
"s1lentium/iptools": "~1.2.0",
|
||||
"secondnetwork/blade-tabler-icons": "^3.26",
|
||||
"socialiteproviders/authentik": "^5.2",
|
||||
|
||||
142
composer.lock
generated
142
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b237f8d4615d4112d48a3bb424fd5358",
|
||||
"content-hash": "15f89930db77693b2d692dbadf22fb9f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -6603,72 +6603,6 @@
|
||||
},
|
||||
"time": "2026-01-30T17:33:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pxlrbt/filament-spotlight",
|
||||
"version": "v2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pxlrbt/filament-spotlight.git",
|
||||
"reference": "6f76af3b14304aff00d5ae65d16f5d46916b89f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pxlrbt/filament-spotlight/zipball/6f76af3b14304aff00d5ae65d16f5d46916b89f5",
|
||||
"reference": "6f76af3b14304aff00d5ae65d16f5d46916b89f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"filament/filament": "^3.0|^4.0",
|
||||
"php": "^8.0",
|
||||
"wire-elements/spotlight": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.10"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"pxlrbt\\FilamentSpotlight\\SpotlightServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"pxlrbt\\FilamentSpotlight\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dennis Koch",
|
||||
"email": "info@pixelarbeit.de"
|
||||
}
|
||||
],
|
||||
"description": "Spotlight for Filament Admin",
|
||||
"keywords": [
|
||||
"alfred",
|
||||
"filament",
|
||||
"laravel",
|
||||
"laravel-filament",
|
||||
"spotlight",
|
||||
"wire-elements"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/pxlrbt/filament-spotlight/issues",
|
||||
"source": "https://github.com/pxlrbt/filament-spotlight/tree/v2.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/pxlrbt",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-20T22:43:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
@@ -11688,80 +11622,6 @@
|
||||
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
|
||||
},
|
||||
"time": "2022-06-03T18:03:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "wire-elements/spotlight",
|
||||
"version": "2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wire-elements/spotlight.git",
|
||||
"reference": "69839f33d082c49306161946dbaedc69e525695e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/wire-elements/spotlight/zipball/69839f33d082c49306161946dbaedc69e525695e",
|
||||
"reference": "69839f33d082c49306161946dbaedc69e525695e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||
"livewire/livewire": "^3.0",
|
||||
"php": "^8.1",
|
||||
"spatie/laravel-package-tools": "^1.4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^6.2|^7.4",
|
||||
"nunomaduro/collision": "^5.3|^8.0",
|
||||
"orchestra/testbench": "^6.15|^7.0|^8.0|^9.0|^10.0",
|
||||
"phpunit/phpunit": "^9.3|^10.5|^11.5.3",
|
||||
"vimeo/psalm": "^4.4|^5.22|^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Spotlight": "LivewireUI\\Spotlight\\SpotlightFacade"
|
||||
},
|
||||
"providers": [
|
||||
"LivewireUI\\Spotlight\\SpotlightServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"LivewireUI\\Spotlight\\": "src",
|
||||
"LivewireUI\\Spotlight\\Database\\Factories\\": "database/factories"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Philo Hermans",
|
||||
"email": "support@wire-elements.dev",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Livewire component that provides Spotlight/Alfred-like functionality to your Laravel application.",
|
||||
"homepage": "https://github.com/wire-elements/spotlight",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"livewire-ui",
|
||||
"spotlight"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/wire-elements/spotlight/issues",
|
||||
"source": "https://github.com/wire-elements/spotlight/tree/2.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/PhiloNL",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-26T07:06:17+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
||||
@@ -20,6 +20,8 @@ return [
|
||||
'never_used' => 'Never Used',
|
||||
],
|
||||
'permissions' => [
|
||||
'all' => 'Set All Permissions',
|
||||
'all_description' => 'Quickly set all permissions below to the same level.',
|
||||
'none' => 'None',
|
||||
'read' => 'Read',
|
||||
'read_write' => 'Read & Write',
|
||||
|
||||
@@ -115,10 +115,6 @@ return [
|
||||
'no_update_url' => 'The following eggs do not have a working update URL set: :eggs',
|
||||
'cannot_delete' => 'Cannot delete :count egg(s)',
|
||||
'eggs_have_servers' => 'The following eggs have servers and cannot be deleted: :eggs',
|
||||
'delete_success' => 'Egg deleted successfully',
|
||||
'deleted' => 'Deleted: :egg',
|
||||
'delete_failed' => 'Failed to delete egg',
|
||||
'could_not_delete' => 'Could not delete: :egg',
|
||||
'updated_from' => 'Successfully updated from: :url',
|
||||
'update_error' => 'Error: :error',
|
||||
'updated_eggs' => 'Updated: :eggs',
|
||||
|
||||
@@ -65,6 +65,8 @@ return [
|
||||
'sftp_port' => 'SFTP Port',
|
||||
'sftp_alias' => 'SFTP Alias',
|
||||
'sftp_alias_help' => 'Display alias for the SFTP address. Leave empty to use the Node FQDN.',
|
||||
'daemon_base' => 'Daemon Base Directory',
|
||||
'daemon_base_help' => 'The directory where server data will be stored.',
|
||||
'use_for_deploy' => 'Use for Deployments?',
|
||||
'maintenance_mode' => 'Maintenance Mode',
|
||||
'maintenance_mode_help' => 'If the node is marked \'Under Maintenance\' users won\'t be able to access servers that are on that node',
|
||||
|
||||
35
lang/en/mail.php
Normal file
35
lang/en/mail.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'greeting' => 'Hello :name!',
|
||||
|
||||
'account_created' => [
|
||||
'body' => 'You are receiving this email because an account has been created for you on :app.',
|
||||
'username' => 'Username: :username',
|
||||
'email' => 'Email: :email',
|
||||
'action' => 'Setup Your Account',
|
||||
],
|
||||
|
||||
'added_to_server' => [
|
||||
'body' => 'You have been added as a subuser for the following server, allowing you certain control over the server.',
|
||||
'server_name' => 'Server Name: :name',
|
||||
'action' => 'Visit Server',
|
||||
],
|
||||
|
||||
'removed_from_server' => [
|
||||
'body' => 'You have been removed as a subuser for the following server.',
|
||||
'server_name' => 'Server Name: :name',
|
||||
'action' => 'Visit Panel',
|
||||
],
|
||||
|
||||
'server_installed' => [
|
||||
'body' => 'Your server has finished installing and is now ready for you to use.',
|
||||
'server_name' => 'Server Name: :name',
|
||||
'action' => 'Login and Begin Using',
|
||||
],
|
||||
|
||||
'mail_tested' => [
|
||||
'subject' => 'Panel Test Message',
|
||||
'body' => 'This is a test of the Panel mail system. You\'re good to go!',
|
||||
],
|
||||
];
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle X-Forwarded-Proto Header
|
||||
RewriteCond %{HTTP:X-Forwarded-Proto} =https [NC]
|
||||
RewriteRule .* - [E=HTTPS:on]
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,6 +25,5 @@
|
||||
@import '../../vendor/filament/widgets/resources/css/index.css';
|
||||
|
||||
@source '../../vendor/gboquizosanchez/filament-log-viewer/resources/views/**/*.blade.php';
|
||||
@source '../../vendor/wire-elements/**/*.blade.php';
|
||||
|
||||
@variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
<x-filament-panels::page>
|
||||
@once
|
||||
<style>
|
||||
.files-selection-merged .fi-ta-header-ctn {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
</style>
|
||||
@endonce
|
||||
|
||||
<div
|
||||
x-data="
|
||||
{
|
||||
|
||||
@@ -9,8 +9,10 @@ use App\Models\Backup;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use App\Services\Backups\DeleteBackupService;
|
||||
use App\Tests\Integration\IntegrationTestCase;
|
||||
use Aws\CommandInterface;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Mockery;
|
||||
|
||||
class DeleteBackupServiceTest extends IntegrationTestCase
|
||||
{
|
||||
@@ -92,10 +94,12 @@ class DeleteBackupServiceTest extends IntegrationTestCase
|
||||
$manager->expects('adapter')->with(Backup::ADAPTER_AWS_S3)->andReturn($adapter);
|
||||
|
||||
$adapter->expects('getBucket')->andReturn('foobar');
|
||||
$adapter->expects('getClient->deleteObject')->with([
|
||||
$mockCommand = Mockery::mock(CommandInterface::class);
|
||||
$adapter->expects('getClient->getCommand')->with('DeleteObject', [
|
||||
'Bucket' => 'foobar',
|
||||
'Key' => sprintf('%s/%s.tar.gz', $server->uuid, $backup->uuid),
|
||||
]);
|
||||
])->andReturn($mockCommand);
|
||||
$adapter->expects('executeS3Command')->with($mockCommand);
|
||||
|
||||
$this->app->make(DeleteBackupService::class)->handle($backup);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user