mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-20 15:44:45 +02:00
Merge branch 'main' into charles/rework-server
This commit is contained in:
commit
118977c8c5
@ -17,9 +17,6 @@ CACHE_STORE=file
|
|||||||
QUEUE_CONNECTION=database
|
QUEUE_CONNECTION=database
|
||||||
SESSION_DRIVER=file
|
SESSION_DRIVER=file
|
||||||
|
|
||||||
HASHIDS_SALT=
|
|
||||||
HASHIDS_LENGTH=8
|
|
||||||
|
|
||||||
MAIL_MAILER=log
|
MAIL_MAILER=log
|
||||||
MAIL_HOST=smtp.example.com
|
MAIL_HOST=smtp.example.com
|
||||||
MAIL_PORT=25
|
MAIL_PORT=25
|
||||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -34,7 +34,6 @@ jobs:
|
|||||||
MAIL_MAILER: array
|
MAIL_MAILER: array
|
||||||
SESSION_DRIVER: array
|
SESSION_DRIVER: array
|
||||||
QUEUE_CONNECTION: sync
|
QUEUE_CONNECTION: sync
|
||||||
HASHIDS_SALT: alittlebitofsalt1234
|
|
||||||
DB_CONNECTION: mysql
|
DB_CONNECTION: mysql
|
||||||
DB_HOST: 127.0.0.1
|
DB_HOST: 127.0.0.1
|
||||||
DB_DATABASE: testing
|
DB_DATABASE: testing
|
||||||
@ -97,7 +96,6 @@ jobs:
|
|||||||
MAIL_MAILER: array
|
MAIL_MAILER: array
|
||||||
SESSION_DRIVER: array
|
SESSION_DRIVER: array
|
||||||
QUEUE_CONNECTION: sync
|
QUEUE_CONNECTION: sync
|
||||||
HASHIDS_SALT: alittlebitofsalt1234
|
|
||||||
DB_CONNECTION: sqlite
|
DB_CONNECTION: sqlite
|
||||||
DB_DATABASE: testing.sqlite
|
DB_DATABASE: testing.sqlite
|
||||||
steps:
|
steps:
|
||||||
|
@ -32,7 +32,6 @@ class AppSettingsCommand extends Command
|
|||||||
protected $description = 'Configure basic environment settings for the Panel.';
|
protected $description = 'Configure basic environment settings for the Panel.';
|
||||||
|
|
||||||
protected $signature = 'p:environment:setup
|
protected $signature = 'p:environment:setup
|
||||||
{--new-salt : Whether or not to generate a new salt for Hashids.}
|
|
||||||
{--url= : The URL that this Panel is running on.}
|
{--url= : The URL that this Panel is running on.}
|
||||||
{--cache= : The cache driver backend to use.}
|
{--cache= : The cache driver backend to use.}
|
||||||
{--session= : The session driver backend to use.}
|
{--session= : The session driver backend to use.}
|
||||||
@ -61,10 +60,6 @@ class AppSettingsCommand extends Command
|
|||||||
{
|
{
|
||||||
$this->variables['APP_TIMEZONE'] = 'UTC';
|
$this->variables['APP_TIMEZONE'] = 'UTC';
|
||||||
|
|
||||||
if (empty(config('hashids.salt')) || $this->option('new-salt')) {
|
|
||||||
$this->variables['HASHIDS_SALT'] = str_random(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->comment(__('commands.appsettings.comment.url'));
|
$this->output->comment(__('commands.appsettings.comment.url'));
|
||||||
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
|
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
|
||||||
'Application URL',
|
'Application URL',
|
||||||
|
@ -62,7 +62,7 @@ class ProcessRunnableCommand extends Command
|
|||||||
|
|
||||||
$this->line(trans('command/messages.schedule.output_line', [
|
$this->line(trans('command/messages.schedule.output_line', [
|
||||||
'schedule' => $schedule->name,
|
'schedule' => $schedule->name,
|
||||||
'hash' => $schedule->hashid,
|
'id' => $schedule->id,
|
||||||
]));
|
]));
|
||||||
} catch (\Throwable|\Exception $exception) {
|
} catch (\Throwable|\Exception $exception) {
|
||||||
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Contracts\Extensions;
|
|
||||||
|
|
||||||
use Hashids\HashidsInterface as VendorHashidsInterface;
|
|
||||||
|
|
||||||
interface HashidsInterface extends VendorHashidsInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Decode an encoded hashid and return the first result.
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function decodeFirst(string $encoded, string $default = null): mixed;
|
|
||||||
}
|
|
@ -48,7 +48,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
|||||||
*/
|
*/
|
||||||
public function render(Request $request)
|
public function render(Request $request)
|
||||||
{
|
{
|
||||||
if (str($request->url())->contains('livewire')) {
|
if ($request->is('livewire/update')) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title(static::class)
|
->title(static::class)
|
||||||
->body($this->getMessage())
|
->body($this->getMessage())
|
||||||
|
@ -25,7 +25,7 @@ class DynamicDatabaseConnection
|
|||||||
'port' => $host->port,
|
'port' => $host->port,
|
||||||
'database' => $database,
|
'database' => $database,
|
||||||
'username' => $host->username,
|
'username' => $host->username,
|
||||||
'password' => decrypt($host->password),
|
'password' => $host->password,
|
||||||
'charset' => self::DB_CHARSET,
|
'charset' => self::DB_CHARSET,
|
||||||
'collation' => self::DB_COLLATION,
|
'collation' => self::DB_COLLATION,
|
||||||
]);
|
]);
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Extensions;
|
|
||||||
|
|
||||||
use Hashids\Hashids as VendorHashids;
|
|
||||||
use App\Contracts\Extensions\HashidsInterface;
|
|
||||||
|
|
||||||
class Hashids extends VendorHashids implements HashidsInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function decodeFirst(string $encoded, string $default = null): mixed
|
|
||||||
{
|
|
||||||
$result = $this->decode($encoded);
|
|
||||||
if (!is_array($result)) {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_first($result, null, $default);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,9 +4,7 @@ namespace App\Filament\Resources;
|
|||||||
|
|
||||||
use App\Filament\Resources\ApiKeyResource\Pages;
|
use App\Filament\Resources\ApiKeyResource\Pages;
|
||||||
use App\Models\ApiKey;
|
use App\Models\ApiKey;
|
||||||
use Filament\Resources\Components\Tab;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
|
|
||||||
class ApiKeyResource extends Resource
|
class ApiKeyResource extends Resource
|
||||||
{
|
{
|
||||||
@ -16,7 +14,7 @@ class ApiKeyResource extends Resource
|
|||||||
|
|
||||||
public static function getNavigationBadge(): ?string
|
public static function getNavigationBadge(): ?string
|
||||||
{
|
{
|
||||||
return static::getModel()::count() ?: null;
|
return static::getModel()::where('key_type', '2')->count() ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function canEdit($record): bool
|
public static function canEdit($record): bool
|
||||||
@ -24,20 +22,6 @@ class ApiKeyResource extends Resource
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTabs(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'all' => Tab::make('All Keys'),
|
|
||||||
'application' => Tab::make('Application Keys')
|
|
||||||
->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDefaultActiveTab(): string|int|null
|
|
||||||
{
|
|
||||||
return 'application';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getRelations(): array
|
public static function getRelations(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -19,30 +19,16 @@ class CreateApiKey extends CreateRecord
|
|||||||
return $form
|
return $form
|
||||||
->schema([
|
->schema([
|
||||||
Forms\Components\Hidden::make('identifier')->default(ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION)),
|
Forms\Components\Hidden::make('identifier')->default(ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION)),
|
||||||
Forms\Components\Hidden::make('token')->default(encrypt(str_random(ApiKey::KEY_LENGTH))),
|
Forms\Components\Hidden::make('token')->default(str_random(ApiKey::KEY_LENGTH)),
|
||||||
|
|
||||||
Forms\Components\Hidden::make('user_id')
|
Forms\Components\Hidden::make('user_id')
|
||||||
->default(auth()->user()->id)
|
->default(auth()->user()->id)
|
||||||
->required(),
|
->required(),
|
||||||
|
|
||||||
Forms\Components\Select::make('key_type')
|
Forms\Components\Hidden::make('key_type')
|
||||||
->inlineLabel()
|
->inlineLabel()
|
||||||
->options(function (ApiKey $apiKey) {
|
->default(ApiKey::TYPE_APPLICATION)
|
||||||
$originalOptions = [
|
->required(),
|
||||||
//ApiKey::TYPE_NONE => 'None',
|
|
||||||
ApiKey::TYPE_ACCOUNT => 'Account',
|
|
||||||
ApiKey::TYPE_APPLICATION => 'Application',
|
|
||||||
//ApiKey::TYPE_DAEMON_USER => 'Daemon User',
|
|
||||||
//ApiKey::TYPE_DAEMON_APPLICATION => 'Daemon Application',
|
|
||||||
];
|
|
||||||
|
|
||||||
return collect($originalOptions)
|
|
||||||
->filter(fn ($value, $key) => $key <= ApiKey::TYPE_APPLICATION || $apiKey->key_type === $key)
|
|
||||||
->all();
|
|
||||||
})
|
|
||||||
->selectablePlaceholder(false)
|
|
||||||
->required()
|
|
||||||
->default(ApiKey::TYPE_APPLICATION),
|
|
||||||
|
|
||||||
Forms\Components\Fieldset::make('Permissions')
|
Forms\Components\Fieldset::make('Permissions')
|
||||||
->columns([
|
->columns([
|
||||||
|
@ -5,10 +5,8 @@ namespace App\Filament\Resources\ApiKeyResource\Pages;
|
|||||||
use App\Filament\Resources\ApiKeyResource;
|
use App\Filament\Resources\ApiKeyResource;
|
||||||
use App\Models\ApiKey;
|
use App\Models\ApiKey;
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Resources\Components\Tab;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
|
|
||||||
class ListApiKeys extends ListRecords
|
class ListApiKeys extends ListRecords
|
||||||
@ -19,16 +17,12 @@ class ListApiKeys extends ListRecords
|
|||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->searchable(false)
|
->searchable(false)
|
||||||
|
->modifyQueryUsing(fn ($query) => $query->where('key_type', ApiKey::TYPE_APPLICATION))
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('user.username')
|
|
||||||
->hidden()
|
|
||||||
->searchable()
|
|
||||||
->sortable(),
|
|
||||||
|
|
||||||
Tables\Columns\TextColumn::make('key')
|
Tables\Columns\TextColumn::make('key')
|
||||||
->copyable()
|
->copyable()
|
||||||
->icon('tabler-clipboard-text')
|
->icon('tabler-clipboard-text')
|
||||||
->state(fn (ApiKey $key) => $key->identifier . decrypt($key->token)),
|
->state(fn (ApiKey $key) => $key->identifier . $key->token),
|
||||||
|
|
||||||
Tables\Columns\TextColumn::make('memo')
|
Tables\Columns\TextColumn::make('memo')
|
||||||
->label('Description')
|
->label('Description')
|
||||||
@ -41,6 +35,7 @@ class ListApiKeys extends ListRecords
|
|||||||
|
|
||||||
Tables\Columns\TextColumn::make('last_used_at')
|
Tables\Columns\TextColumn::make('last_used_at')
|
||||||
->label('Last Used')
|
->label('Last Used')
|
||||||
|
->placeholder('Not Used')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
|
|
||||||
@ -48,13 +43,13 @@ class ListApiKeys extends ListRecords
|
|||||||
->label('Created')
|
->label('Created')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
|
||||||
->filters([
|
Tables\Columns\TextColumn::make('user.username')
|
||||||
//
|
->label('Created By')
|
||||||
|
->url(fn (ApiKey $apiKey): string => route('filament.admin.resources.users.edit', ['record' => $apiKey->user])),
|
||||||
])
|
])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\DeleteAction::make(),
|
Tables\Actions\DeleteAction::make(),
|
||||||
//Tables\Actions\EditAction::make()
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,22 +59,4 @@ class ListApiKeys extends ListRecords
|
|||||||
Actions\CreateAction::make(),
|
Actions\CreateAction::make(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTabs(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'all' => Tab::make('All Keys'),
|
|
||||||
'application' => Tab::make('Application Keys')
|
|
||||||
->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_APPLICATION)
|
|
||||||
),
|
|
||||||
'account' => Tab::make('Account Keys')
|
|
||||||
->modifyQueryUsing(fn (Builder $query) => $query->where('key_type', ApiKey::TYPE_ACCOUNT)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDefaultActiveTab(): string|int|null
|
|
||||||
{
|
|
||||||
return 'application';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -74,15 +74,6 @@ class CreateDatabaseHost extends CreateRecord
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function mutateFormDataBeforeCreate(array $data): array
|
|
||||||
{
|
|
||||||
if (isset($data['password'])) {
|
|
||||||
$data['password'] = encrypt($data['password']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -76,15 +76,6 @@ class EditDatabaseHost extends EditRecord
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function mutateFormDataBeforeSave(array $data): array
|
|
||||||
{
|
|
||||||
if (isset($data['password'])) {
|
|
||||||
$data['password'] = encrypt($data['password']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getFormActions(): array
|
protected function getFormActions(): array
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
|
@ -28,13 +28,13 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->action(fn (DatabasePasswordService $service, Database $database, $set, $get) => $this->rotatePassword($service, $database, $set, $get))
|
->action(fn (DatabasePasswordService $service, Database $database, $set, $get) => $this->rotatePassword($service, $database, $set, $get))
|
||||||
)
|
)
|
||||||
->formatStateUsing(fn (Database $database) => decrypt($database->password)),
|
->formatStateUsing(fn (Database $database) => $database->password),
|
||||||
Forms\Components\TextInput::make('remote')->label('Connections From'),
|
Forms\Components\TextInput::make('remote')->label('Connections From'),
|
||||||
Forms\Components\TextInput::make('max_connections'),
|
Forms\Components\TextInput::make('max_connections'),
|
||||||
Forms\Components\TextInput::make('JDBC')
|
Forms\Components\TextInput::make('JDBC')
|
||||||
->label('JDBC Connection String')
|
->label('JDBC Connection String')
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->formatStateUsing(fn (Forms\Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode(decrypt($database->password)) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
|
->formatStateUsing(fn (Forms\Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
public function table(Table $table): Table
|
public function table(Table $table): Table
|
||||||
|
@ -337,6 +337,7 @@ class CreateNode extends CreateRecord
|
|||||||
->suffix('%')
|
->suffix('%')
|
||||||
->columnSpan(2)
|
->columnSpan(2)
|
||||||
->numeric()
|
->numeric()
|
||||||
|
->default(0)
|
||||||
->minValue(0),
|
->minValue(0),
|
||||||
Forms\Components\TextInput::make('cpu_overallocate')
|
Forms\Components\TextInput::make('cpu_overallocate')
|
||||||
->dehydratedWhenHidden()
|
->dehydratedWhenHidden()
|
||||||
@ -346,6 +347,7 @@ class CreateNode extends CreateRecord
|
|||||||
->hintIconTooltip('The % allowable to go over the set limit.')
|
->hintIconTooltip('The % allowable to go over the set limit.')
|
||||||
->columnSpan(2)
|
->columnSpan(2)
|
||||||
->numeric()
|
->numeric()
|
||||||
|
->default(0)
|
||||||
->minValue(-1)
|
->minValue(-1)
|
||||||
->maxValue(100)
|
->maxValue(100)
|
||||||
->suffix('%'),
|
->suffix('%'),
|
||||||
|
@ -6,9 +6,11 @@ use App\Filament\Resources\NodeResource;
|
|||||||
use App\Filament\Resources\NodeResource\Widgets\NodeMemoryChart;
|
use App\Filament\Resources\NodeResource\Widgets\NodeMemoryChart;
|
||||||
use App\Filament\Resources\NodeResource\Widgets\NodeStorageChart;
|
use App\Filament\Resources\NodeResource\Widgets\NodeStorageChart;
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
|
use App\Services\Nodes\NodeUpdateService;
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Forms;
|
use Filament\Forms;
|
||||||
use Filament\Forms\Components\Tabs;
|
use Filament\Forms\Components\Tabs;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||||
@ -374,6 +376,18 @@ class EditNode extends EditRecord
|
|||||||
->rows(19)
|
->rows(19)
|
||||||
->hintAction(CopyAction::make())
|
->hintAction(CopyAction::make())
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
|
Forms\Components\Actions::make([
|
||||||
|
Forms\Components\Actions\Action::make('resetKey')
|
||||||
|
->label('Reset Daemon Token')
|
||||||
|
->color('danger')
|
||||||
|
->requiresConfirmation()
|
||||||
|
->modalHeading('Reset Daemon Token?')
|
||||||
|
->modalDescription('Resetting the daemon token will void any request coming from the old token. This token is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this token regularly for security.')
|
||||||
|
->action(fn (NodeUpdateService $nodeUpdateService, Node $node) => $nodeUpdateService->handle($node, [], true)
|
||||||
|
&& Notification::make()->success()->title('Daemon Key Reset')->send()
|
||||||
|
&& $this->fillForm()
|
||||||
|
),
|
||||||
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
@ -687,7 +687,7 @@ class CreateServer extends CreateRecord
|
|||||||
->label('Container Labels')
|
->label('Container Labels')
|
||||||
->keyLabel('Title')
|
->keyLabel('Title')
|
||||||
->valueLabel('Description')
|
->valueLabel('Description')
|
||||||
->columnSpan(1),
|
->columnSpan(3),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources\UserResource\Pages;
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
|
||||||
use App\Facades\Activity;
|
use App\Facades\Activity;
|
||||||
use App\Models\ActivityLog;
|
use App\Models\ActivityLog;
|
||||||
use App\Models\ApiKey;
|
use App\Models\ApiKey;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Users\ToggleTwoFactorService;
|
||||||
use App\Services\Users\TwoFactorSetupService;
|
use App\Services\Users\TwoFactorSetupService;
|
||||||
use chillerlan\QRCode\Common\EccLevel;
|
use chillerlan\QRCode\Common\EccLevel;
|
||||||
use chillerlan\QRCode\Common\Version;
|
use chillerlan\QRCode\Common\Version;
|
||||||
@ -20,8 +22,10 @@ use Filament\Forms\Components\Select;
|
|||||||
use Filament\Forms\Components\Tabs;
|
use Filament\Forms\Components\Tabs;
|
||||||
use Filament\Forms\Components\TagsInput;
|
use Filament\Forms\Components\TagsInput;
|
||||||
use Filament\Forms\Components\Tabs\Tab;
|
use Filament\Forms\Components\Tabs\Tab;
|
||||||
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Get;
|
use Filament\Forms\Get;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
@ -99,12 +103,26 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
|||||||
|
|
||||||
if ($this->getUser()->use_totp) {
|
if ($this->getUser()->use_totp) {
|
||||||
return [
|
return [
|
||||||
Placeholder::make('2FA already enabled!'),
|
Placeholder::make('2fa-already-enabled')
|
||||||
|
->label('Two Factor Authentication is currently enabled!'),
|
||||||
|
Textarea::make('backup-tokens')
|
||||||
|
->hidden(fn () => !cache()->get("users.{$this->getUser()->id}.2fa.tokens"))
|
||||||
|
->rows(10)
|
||||||
|
->readOnly()
|
||||||
|
->formatStateUsing(fn () => cache()->get("users.{$this->getUser()->id}.2fa.tokens"))
|
||||||
|
->helperText('These will not be shown again!')
|
||||||
|
->label('Backup Tokens:'),
|
||||||
|
TextInput::make('2fa-disable-code')
|
||||||
|
->label('Disable 2FA')
|
||||||
|
->helperText('Enter your current 2FA code to disable Two Factor Authentication'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$setupService = app(TwoFactorSetupService::class);
|
$setupService = app(TwoFactorSetupService::class);
|
||||||
|
|
||||||
['image_url_data' => $url] = $setupService->handle($this->getUser());
|
['image_url_data' => $url, 'secret' => $secret] = cache()->remember(
|
||||||
|
"users.{$this->getUser()->id}.2fa.state",
|
||||||
|
now()->addMinutes(5), fn () => $setupService->handle($this->getUser())
|
||||||
|
);
|
||||||
|
|
||||||
$options = new QROptions([
|
$options = new QROptions([
|
||||||
'svgLogo' => public_path('pelican.svg'),
|
'svgLogo' => public_path('pelican.svg'),
|
||||||
@ -147,9 +165,19 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
|||||||
Placeholder::make('qr')
|
Placeholder::make('qr')
|
||||||
->label('Scan QR Code')
|
->label('Scan QR Code')
|
||||||
->content(fn () => new HtmlString("
|
->content(fn () => new HtmlString("
|
||||||
<div style='width: 300px'>$image</div>
|
<div style='width: 300px; background-color: rgb(24, 24, 27);'>$image</div>
|
||||||
"))
|
"))
|
||||||
->default('asdfasdf'),
|
->helperText('Setup Key: '. $secret),
|
||||||
|
TextInput::make('2facode')
|
||||||
|
->label('Code')
|
||||||
|
->requiredWith('2fapassword')
|
||||||
|
->helperText('Scan the QR code above using your two-step authentication app, then enter the code generated.'),
|
||||||
|
TextInput::make('2fapassword')
|
||||||
|
->label('Current Password')
|
||||||
|
->requiredWith('2facode')
|
||||||
|
->currentPassword()
|
||||||
|
->password()
|
||||||
|
->helperText('Enter your current password to verify.'),
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -158,7 +186,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
|||||||
->schema([
|
->schema([
|
||||||
Grid::make('asdf')->columns(5)->schema([
|
Grid::make('asdf')->columns(5)->schema([
|
||||||
Section::make('Create API Key')->columnSpan(3)->schema([
|
Section::make('Create API Key')->columnSpan(3)->schema([
|
||||||
TextInput::make('description'),
|
TextInput::make('description')->required(),
|
||||||
TagsInput::make('allowed_ips')
|
TagsInput::make('allowed_ips')
|
||||||
->splitKeys([',', ' ', 'Tab'])
|
->splitKeys([',', ' ', 'Tab'])
|
||||||
->placeholder('Example: 127.0.0.1 or 192.168.1.1')
|
->placeholder('Example: 127.0.0.1 or 192.168.1.1')
|
||||||
@ -182,8 +210,9 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
|||||||
$action->success();
|
$action->success();
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
Section::make('API Keys')->columnSpan(2)->schema([
|
Section::make('Keys')->columnSpan(2)->schema([
|
||||||
Repeater::make('keys')
|
Repeater::make('keys')
|
||||||
|
->label('')
|
||||||
->relationship('apiKeys')
|
->relationship('apiKeys')
|
||||||
->addable(false)
|
->addable(false)
|
||||||
->itemLabel(fn ($state) => $state['identifier'])
|
->itemLabel(fn ($state) => $state['identifier'])
|
||||||
@ -235,4 +264,43 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function handleRecordUpdate($record, $data): \Illuminate\Database\Eloquent\Model
|
||||||
|
{
|
||||||
|
if ($token = $data['2facode'] ?? null) {
|
||||||
|
/** @var ToggleTwoFactorService $service */
|
||||||
|
$service = resolve(ToggleTwoFactorService::class);
|
||||||
|
|
||||||
|
$tokens = $service->handle($record, $token, true);
|
||||||
|
cache()->set("users.$record->id.2fa.tokens", implode("\n", $tokens), now()->addSeconds(15));
|
||||||
|
|
||||||
|
$this->redirectRoute('filament.admin.auth.profile', ['tab' => '-2fa-tab']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token = $data['2fa-disable-code'] ?? null) {
|
||||||
|
/** @var ToggleTwoFactorService $service */
|
||||||
|
$service = resolve(ToggleTwoFactorService::class);
|
||||||
|
|
||||||
|
$service->handle($record, $token, false);
|
||||||
|
|
||||||
|
cache()->forget("users.$record->id.2fa.state");
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::handleRecordUpdate($record, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exception($e, $stopPropagation): void
|
||||||
|
{
|
||||||
|
if ($e instanceof TwoFactorAuthenticationTokenInvalid) {
|
||||||
|
Notification::make()
|
||||||
|
->title('Invalid 2FA Code')
|
||||||
|
->body($e->getMessage())
|
||||||
|
->color('danger')
|
||||||
|
->icon('tabler-2fa')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
|
||||||
|
$stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class NodeAutoDeployController extends Controller
|
|||||||
|
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'node' => $node->id,
|
'node' => $node->id,
|
||||||
'token' => $key->identifier . decrypt($key->token),
|
'token' => $key->identifier . $key->token,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@ class NodeDeploymentController extends ApplicationApiController
|
|||||||
$data = $request->validated();
|
$data = $request->validated();
|
||||||
|
|
||||||
$nodes = $this->viableNodesService->handle(
|
$nodes = $this->viableNodesService->handle(
|
||||||
$data['disk'] ?? 0,
|
|
||||||
$data['memory'] ?? 0,
|
$data['memory'] ?? 0,
|
||||||
|
$data['disk'] ?? 0,
|
||||||
$data['cpu'] ?? 0,
|
$data['cpu'] ?? 0,
|
||||||
$data['tags'] ?? $data['location_ids'] ?? [],
|
$data['tags'] ?? $data['location_ids'] ?? [],
|
||||||
);
|
);
|
||||||
|
@ -65,9 +65,7 @@ class LoginCheckpointController extends AbstractLoginController
|
|||||||
return $this->sendLoginResponse($user, $request);
|
return $this->sendLoginResponse($user, $request);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$decrypted = decrypt($user->totp_secret);
|
if ($this->google2FA->verifyKey($user->totp_secret, (string) $request->input('authentication_code'), config('panel.auth.2fa.window'))) {
|
||||||
|
|
||||||
if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code'), config('panel.auth.2fa.window'))) {
|
|
||||||
Event::dispatch(new ProvidedAuthenticationToken($user));
|
Event::dispatch(new ProvidedAuthenticationToken($user));
|
||||||
|
|
||||||
return $this->sendLoginResponse($user, $request);
|
return $this->sendLoginResponse($user, $request);
|
||||||
|
@ -41,7 +41,7 @@ class DaemonAuthenticate
|
|||||||
/** @var Node $node */
|
/** @var Node $node */
|
||||||
$node = Node::query()->where('daemon_token_id', $parts[0])->firstOrFail();
|
$node = Node::query()->where('daemon_token_id', $parts[0])->firstOrFail();
|
||||||
|
|
||||||
if (hash_equals((string) decrypt($node->daemon_token), $parts[1])) {
|
if (hash_equals((string) $node->daemon_token, $parts[1])) {
|
||||||
$request->attributes->set('node', $node);
|
$request->attributes->set('node', $node);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
@ -56,11 +56,10 @@ class StoreServerRequest extends ApplicationApiRequest
|
|||||||
// Automatic deployment rules
|
// Automatic deployment rules
|
||||||
'deploy' => 'sometimes|required|array',
|
'deploy' => 'sometimes|required|array',
|
||||||
'deploy.locations' => 'array',
|
'deploy.locations' => 'array',
|
||||||
'deploy.locations.*' => 'integer|min:1',
|
'deploy.locations.*' => 'required_with:deploy.locations,integer|min:1',
|
||||||
'deploy.dedicated_ip' => 'required_with:deploy,boolean',
|
'deploy.dedicated_ip' => 'required_with:deploy,boolean',
|
||||||
'deploy.port_range' => 'array',
|
'deploy.port_range' => 'array',
|
||||||
'deploy.port_range.*' => 'string',
|
'deploy.port_range.*' => 'string',
|
||||||
|
|
||||||
'start_on_completion' => 'sometimes|boolean',
|
'start_on_completion' => 'sometimes|boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
* @property bool $has_alias
|
* @property bool $has_alias
|
||||||
* @property \App\Models\Server|null $server
|
* @property \App\Models\Server|null $server
|
||||||
* @property \App\Models\Node $node
|
* @property \App\Models\Node $node
|
||||||
* @property string $hashid
|
|
||||||
*
|
*
|
||||||
* @method static \Database\Factories\AllocationFactory factory(...$parameters)
|
* @method static \Database\Factories\AllocationFactory factory(...$parameters)
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|Allocation newModelQuery()
|
* @method static \Illuminate\Database\Eloquent\Builder|Allocation newModelQuery()
|
||||||
@ -88,14 +87,6 @@ class Allocation extends Model
|
|||||||
return $this->getKeyName();
|
return $this->getKeyName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a hashid encoded string to represent the ID of the allocation.
|
|
||||||
*/
|
|
||||||
public function getHashidAttribute(): string
|
|
||||||
{
|
|
||||||
return app()->make('hashids')->encode($this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessor to automatically provide the IP alias if defined.
|
* Accessor to automatically provide the IP alias if defined.
|
||||||
*/
|
*/
|
||||||
|
@ -149,6 +149,7 @@ class ApiKey extends Model
|
|||||||
'user_id' => 'int',
|
'user_id' => 'int',
|
||||||
'last_used_at' => 'datetime',
|
'last_used_at' => 'datetime',
|
||||||
'expires_at' => 'datetime',
|
'expires_at' => 'datetime',
|
||||||
|
'token' => 'encrypted',
|
||||||
self::CREATED_AT => 'datetime',
|
self::CREATED_AT => 'datetime',
|
||||||
self::UPDATED_AT => 'datetime',
|
self::UPDATED_AT => 'datetime',
|
||||||
'r_' . AdminAcl::RESOURCE_USERS => 'int',
|
'r_' . AdminAcl::RESOURCE_USERS => 'int',
|
||||||
@ -188,7 +189,7 @@ class ApiKey extends Model
|
|||||||
$identifier = substr($token, 0, self::IDENTIFIER_LENGTH);
|
$identifier = substr($token, 0, self::IDENTIFIER_LENGTH);
|
||||||
|
|
||||||
$model = static::where('identifier', $identifier)->first();
|
$model = static::where('identifier', $identifier)->first();
|
||||||
if (!is_null($model) && decrypt($model->token) === substr($token, strlen($identifier))) {
|
if (!is_null($model) && $model->token === substr($token, strlen($identifier))) {
|
||||||
return $model;
|
return $model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Container\Container;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use App\Contracts\Extensions\HashidsInterface;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,6 +62,7 @@ class Database extends Model
|
|||||||
'server_id' => 'integer',
|
'server_id' => 'integer',
|
||||||
'database_host_id' => 'integer',
|
'database_host_id' => 'integer',
|
||||||
'max_connections' => 'integer',
|
'max_connections' => 'integer',
|
||||||
|
'password' => 'encrypted',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,26 +71,6 @@ class Database extends Model
|
|||||||
return $this->getKeyName();
|
return $this->getKeyName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the database using the ID by checking if the value provided is a HashID
|
|
||||||
* string value, or just the ID to the database itself.
|
|
||||||
*
|
|
||||||
* @param mixed $value
|
|
||||||
* @param string|null $field
|
|
||||||
*
|
|
||||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
|
||||||
*/
|
|
||||||
public function resolveRouteBinding($value, $field = null): ?\Illuminate\Database\Eloquent\Model
|
|
||||||
{
|
|
||||||
if (is_scalar($value) && ($field ?? $this->getRouteKeyName()) === 'id') {
|
|
||||||
$value = ctype_digit((string) $value)
|
|
||||||
? $value
|
|
||||||
: Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->where($field ?? $this->getRouteKeyName(), $value)->firstOrFail();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the host database server associated with a database.
|
* Gets the host database server associated with a database.
|
||||||
*/
|
*/
|
||||||
|
@ -60,6 +60,7 @@ class DatabaseHost extends Model
|
|||||||
'id' => 'integer',
|
'id' => 'integer',
|
||||||
'max_databases' => 'integer',
|
'max_databases' => 'integer',
|
||||||
'node_id' => 'integer',
|
'node_id' => 'integer',
|
||||||
|
'password' => 'encrypted',
|
||||||
'created_at' => 'immutable_datetime',
|
'created_at' => 'immutable_datetime',
|
||||||
'updated_at' => 'immutable_datetime',
|
'updated_at' => 'immutable_datetime',
|
||||||
];
|
];
|
||||||
|
@ -63,10 +63,6 @@ class Node extends Model
|
|||||||
*/
|
*/
|
||||||
protected $hidden = ['daemon_token_id', 'daemon_token'];
|
protected $hidden = ['daemon_token_id', 'daemon_token'];
|
||||||
|
|
||||||
public int $sum_memory;
|
|
||||||
public int $sum_disk;
|
|
||||||
public int $sum_cpu;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fields that are mass assignable.
|
* Fields that are mass assignable.
|
||||||
*/
|
*/
|
||||||
@ -127,6 +123,7 @@ class Node extends Model
|
|||||||
'cpu' => 'integer',
|
'cpu' => 'integer',
|
||||||
'daemon_listen' => 'integer',
|
'daemon_listen' => 'integer',
|
||||||
'daemon_sftp' => 'integer',
|
'daemon_sftp' => 'integer',
|
||||||
|
'daemon_token' => 'encrypted',
|
||||||
'behind_proxy' => 'boolean',
|
'behind_proxy' => 'boolean',
|
||||||
'public' => 'boolean',
|
'public' => 'boolean',
|
||||||
'maintenance_mode' => 'boolean',
|
'maintenance_mode' => 'boolean',
|
||||||
@ -143,7 +140,7 @@ class Node extends Model
|
|||||||
{
|
{
|
||||||
static::creating(function (self $node) {
|
static::creating(function (self $node) {
|
||||||
$node->uuid = Str::uuid();
|
$node->uuid = Str::uuid();
|
||||||
$node->daemon_token = encrypt(Str::random(self::DAEMON_TOKEN_LENGTH));
|
$node->daemon_token = Str::random(self::DAEMON_TOKEN_LENGTH);
|
||||||
$node->daemon_token_id = Str::random(self::DAEMON_TOKEN_ID_LENGTH);
|
$node->daemon_token_id = Str::random(self::DAEMON_TOKEN_ID_LENGTH);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -171,7 +168,7 @@ class Node extends Model
|
|||||||
'debug' => false,
|
'debug' => false,
|
||||||
'uuid' => $this->uuid,
|
'uuid' => $this->uuid,
|
||||||
'token_id' => $this->daemon_token_id,
|
'token_id' => $this->daemon_token_id,
|
||||||
'token' => decrypt($this->daemon_token),
|
'token' => $this->daemon_token,
|
||||||
'api' => [
|
'api' => [
|
||||||
'host' => '0.0.0.0',
|
'host' => '0.0.0.0',
|
||||||
'port' => $this->daemon_listen,
|
'port' => $this->daemon_listen,
|
||||||
@ -209,16 +206,6 @@ class Node extends Model
|
|||||||
return json_encode($this->getConfiguration(), $pretty ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES);
|
return json_encode($this->getConfiguration(), $pretty ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to return the decrypted key for a node.
|
|
||||||
*/
|
|
||||||
public function getDecryptedKey(): string
|
|
||||||
{
|
|
||||||
return (string) decrypt(
|
|
||||||
$this->daemon_token
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isUnderMaintenance(): bool
|
public function isUnderMaintenance(): bool
|
||||||
{
|
{
|
||||||
return $this->maintenance_mode;
|
return $this->maintenance_mode;
|
||||||
@ -250,11 +237,28 @@ class Node extends Model
|
|||||||
*/
|
*/
|
||||||
public function isViable(int $memory, int $disk, int $cpu): bool
|
public function isViable(int $memory, int $disk, int $cpu): bool
|
||||||
{
|
{
|
||||||
$memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100));
|
if ($this->memory_overallocate >= 0) {
|
||||||
$diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100));
|
$memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100));
|
||||||
$cpuLimit = $this->cpu * (1 + ($this->cpu_overallocate / 100));
|
if ($this->servers_sum_memory + $memory > $memoryLimit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit && ($this->sum_cpu + $cpu) <= $cpuLimit;
|
if ($this->disk_overallocate >= 0) {
|
||||||
|
$diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100));
|
||||||
|
if ($this->servers_sum_disk + $disk > $diskLimit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cpu_overallocate >= 0) {
|
||||||
|
$cpuLimit = $this->cpu * (1 + ($this->cpu_overallocate / 100));
|
||||||
|
if ($this->servers_sum_cpu + $cpu > $cpuLimit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getForServerCreation()
|
public static function getForServerCreation()
|
||||||
|
@ -4,10 +4,8 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Cron\CronExpression;
|
use Cron\CronExpression;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Container\Container;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use App\Contracts\Extensions\HashidsInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
@ -25,7 +23,6 @@ use App\Contracts\Extensions\HashidsInterface;
|
|||||||
* @property \Carbon\Carbon|null $next_run_at
|
* @property \Carbon\Carbon|null $next_run_at
|
||||||
* @property \Carbon\Carbon $created_at
|
* @property \Carbon\Carbon $created_at
|
||||||
* @property \Carbon\Carbon $updated_at
|
* @property \Carbon\Carbon $updated_at
|
||||||
* @property string $hashid
|
|
||||||
* @property \App\Models\Server $server
|
* @property \App\Models\Server $server
|
||||||
* @property \App\Models\Task[]|\Illuminate\Support\Collection $tasks
|
* @property \App\Models\Task[]|\Illuminate\Support\Collection $tasks
|
||||||
*/
|
*/
|
||||||
@ -124,14 +121,6 @@ class Schedule extends Model
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a hashid encoded string to represent the ID of the schedule.
|
|
||||||
*/
|
|
||||||
public function getHashidAttribute(): string
|
|
||||||
{
|
|
||||||
return Container::getInstance()->make(HashidsInterface::class)->encode($this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return tasks belonging to a schedule.
|
* Return tasks belonging to a schedule.
|
||||||
*/
|
*/
|
||||||
|
@ -52,14 +52,6 @@ class Subuser extends Model
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a hashid encoded string to represent the ID of the subuser.
|
|
||||||
*/
|
|
||||||
public function getHashidAttribute(): string
|
|
||||||
{
|
|
||||||
return app()->make('hashids')->encode($this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server associated with a subuser.
|
* Gets the server associated with a subuser.
|
||||||
*/
|
*/
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Container\Container;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use App\Contracts\Extensions\HashidsInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
@ -18,7 +16,6 @@ use App\Contracts\Extensions\HashidsInterface;
|
|||||||
* @property bool $continue_on_failure
|
* @property bool $continue_on_failure
|
||||||
* @property \Carbon\Carbon $created_at
|
* @property \Carbon\Carbon $created_at
|
||||||
* @property \Carbon\Carbon $updated_at
|
* @property \Carbon\Carbon $updated_at
|
||||||
* @property string $hashid
|
|
||||||
* @property \App\Models\Schedule $schedule
|
* @property \App\Models\Schedule $schedule
|
||||||
* @property \App\Models\Server $server
|
* @property \App\Models\Server $server
|
||||||
*/
|
*/
|
||||||
@ -96,14 +93,6 @@ class Task extends Model
|
|||||||
return $this->getKeyName();
|
return $this->getKeyName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a hashid encoded string to represent the ID of the task.
|
|
||||||
*/
|
|
||||||
public function getHashidAttribute(): string
|
|
||||||
{
|
|
||||||
return Container::getInstance()->make(HashidsInterface::class)->encode($this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the schedule that a task belongs to.
|
* Return the schedule that a task belongs to.
|
||||||
*/
|
*/
|
||||||
|
@ -31,7 +31,7 @@ trait HasAccessTokens
|
|||||||
'user_id' => $this->id,
|
'user_id' => $this->id,
|
||||||
'key_type' => ApiKey::TYPE_ACCOUNT,
|
'key_type' => ApiKey::TYPE_ACCOUNT,
|
||||||
'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_ACCOUNT),
|
'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_ACCOUNT),
|
||||||
'token' => encrypt($plain = Str::random(ApiKey::KEY_LENGTH)),
|
'token' => $plain = Str::random(ApiKey::KEY_LENGTH),
|
||||||
'memo' => $memo ?? '',
|
'memo' => $memo ?? '',
|
||||||
'allowed_ips' => $ips ?? [],
|
'allowed_ips' => $ips ?? [],
|
||||||
]);
|
]);
|
||||||
|
@ -171,6 +171,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||||||
'use_totp' => 'boolean',
|
'use_totp' => 'boolean',
|
||||||
'gravatar' => 'boolean',
|
'gravatar' => 'boolean',
|
||||||
'totp_authenticated_at' => 'datetime',
|
'totp_authenticated_at' => 'datetime',
|
||||||
|
'totp_secret' => 'encrypted',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
'daemon',
|
'daemon',
|
||||||
fn (Node $node, array $headers = []) => Http::acceptJson()
|
fn (Node $node, array $headers = []) => Http::acceptJson()
|
||||||
->asJson()
|
->asJson()
|
||||||
->withToken($node->getDecryptedKey())
|
->withToken($node->daemon_token)
|
||||||
->withHeaders($headers)
|
->withHeaders($headers)
|
||||||
->withOptions(['verify' => (bool) app()->environment('production')])
|
->withOptions(['verify' => (bool) app()->environment('production')])
|
||||||
->timeout(config('panel.guzzle.timeout'))
|
->timeout(config('panel.guzzle.timeout'))
|
||||||
|
@ -35,11 +35,13 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
->default()
|
->default()
|
||||||
->id('admin')
|
->id('admin')
|
||||||
->path('admin')
|
->path('admin')
|
||||||
->topNavigation(config('panel.filament.top-navigation', false))
|
->topNavigation(config('panel.filament.top-navigation', true))
|
||||||
->login()
|
->login()
|
||||||
->homeUrl('/')
|
->homeUrl('/')
|
||||||
->favicon('/pelican.ico')
|
->favicon(config('app.favicon', '/pelican.ico'))
|
||||||
->brandName('Pelican')
|
->brandName(config('app.name', 'Pelican'))
|
||||||
|
->brandLogo(config('app.logo'))
|
||||||
|
->brandLogoHeight('2rem')
|
||||||
->profile(EditProfile::class, false)
|
->profile(EditProfile::class, false)
|
||||||
->colors([
|
->colors([
|
||||||
'danger' => Color::Red,
|
'danger' => Color::Red,
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Providers;
|
|
||||||
|
|
||||||
use App\Extensions\Hashids;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
use App\Contracts\Extensions\HashidsInterface;
|
|
||||||
|
|
||||||
class HashidsServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Register the ability to use Hashids.
|
|
||||||
*/
|
|
||||||
public function register(): void
|
|
||||||
{
|
|
||||||
$this->app->singleton(HashidsInterface::class, function () {
|
|
||||||
return new Hashids(
|
|
||||||
config('hashids.salt', ''),
|
|
||||||
config('hashids.length', 0),
|
|
||||||
config('hashids.alphabet', 'abcdefghijkmlnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->app->alias(HashidsInterface::class, 'hashids');
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\Database;
|
|
||||||
use Illuminate\Foundation\Http\Middleware\TrimStrings;
|
use Illuminate\Foundation\Http\Middleware\TrimStrings;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Cache\RateLimiting\Limit;
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
@ -29,11 +28,6 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
return preg_match(self::FILE_PATH_REGEX, $request->getPathInfo()) === 1;
|
return preg_match(self::FILE_PATH_REGEX, $request->getPathInfo()) === 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
// This is needed to make use of the "resolveRouteBinding" functionality in the
|
|
||||||
// model. Without it you'll never trigger that logic flow thus resulting in a 404
|
|
||||||
// error because we request databases with a HashID, and not with a normal ID.
|
|
||||||
Route::model('database', Database::class);
|
|
||||||
|
|
||||||
$this->routes(function () {
|
$this->routes(function () {
|
||||||
Route::middleware('web')->group(function () {
|
Route::middleware('web')->group(function () {
|
||||||
Route::middleware(['auth.session', RequireTwoFactorAuthentication::class])
|
Route::middleware(['auth.session', RequireTwoFactorAuthentication::class])
|
||||||
|
@ -31,7 +31,7 @@ class KeyCreationService
|
|||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'key_type' => $this->keyType,
|
'key_type' => $this->keyType,
|
||||||
'identifier' => ApiKey::generateTokenIdentifier($this->keyType),
|
'identifier' => ApiKey::generateTokenIdentifier($this->keyType),
|
||||||
'token' => encrypt(str_random(ApiKey::KEY_LENGTH)),
|
'token' => str_random(ApiKey::KEY_LENGTH),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($this->keyType === ApiKey::TYPE_APPLICATION) {
|
if ($this->keyType === ApiKey::TYPE_APPLICATION) {
|
||||||
|
@ -86,9 +86,7 @@ class DatabaseManagementService
|
|||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
'username' => sprintf('u%d_%s', $server->id, str_random(10)),
|
'username' => sprintf('u%d_%s', $server->id, str_random(10)),
|
||||||
'password' => encrypt(
|
'password' => Utilities::randomStringWithSpecialCharacters(24),
|
||||||
Utilities::randomStringWithSpecialCharacters(24)
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $this->connection->transaction(function () use ($data, &$database) {
|
return $this->connection->transaction(function () use ($data, &$database) {
|
||||||
@ -100,7 +98,7 @@ class DatabaseManagementService
|
|||||||
$database->createUser(
|
$database->createUser(
|
||||||
$database->username,
|
$database->username,
|
||||||
$database->remote,
|
$database->remote,
|
||||||
decrypt($database->password),
|
$database->password,
|
||||||
$database->max_connections
|
$database->max_connections
|
||||||
);
|
);
|
||||||
$database->assignUserToDatabase($database->database, $database->username, $database->remote);
|
$database->assignUserToDatabase($database->database, $database->username, $database->remote);
|
||||||
|
@ -33,7 +33,7 @@ class DatabasePasswordService
|
|||||||
$this->dynamic->set('dynamic', $database->database_host_id);
|
$this->dynamic->set('dynamic', $database->database_host_id);
|
||||||
|
|
||||||
$database->update([
|
$database->update([
|
||||||
'password' => encrypt($password),
|
'password' => $password,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$database->dropUser($database->username, $database->remote);
|
$database->dropUser($database->username, $database->remote);
|
||||||
|
@ -28,7 +28,7 @@ class HostCreationService
|
|||||||
{
|
{
|
||||||
return $this->connection->transaction(function () use ($data) {
|
return $this->connection->transaction(function () use ($data) {
|
||||||
$host = DatabaseHost::query()->create([
|
$host = DatabaseHost::query()->create([
|
||||||
'password' => encrypt(array_get($data, 'password')),
|
'password' => array_get($data, 'password'),
|
||||||
'name' => array_get($data, 'name'),
|
'name' => array_get($data, 'name'),
|
||||||
'host' => array_get($data, 'host'),
|
'host' => array_get($data, 'host'),
|
||||||
'port' => array_get($data, 'port'),
|
'port' => array_get($data, 'port'),
|
||||||
|
@ -26,9 +26,7 @@ class HostUpdateService
|
|||||||
*/
|
*/
|
||||||
public function handle(int $hostId, array $data): DatabaseHost
|
public function handle(int $hostId, array $data): DatabaseHost
|
||||||
{
|
{
|
||||||
if (!empty(array_get($data, 'password'))) {
|
if (empty(array_get($data, 'password'))) {
|
||||||
$data['password'] = encrypt($data['password']);
|
|
||||||
} else {
|
|
||||||
unset($data['password']);
|
unset($data['password']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,19 +17,17 @@ class FindViableNodesService
|
|||||||
* are tossed out, as are any nodes marked as non-public, meaning automatic
|
* are tossed out, as are any nodes marked as non-public, meaning automatic
|
||||||
* deployments should not be done against them.
|
* deployments should not be done against them.
|
||||||
*/
|
*/
|
||||||
public function handle(int $disk = 0, int $memory = 0, int $cpu = 0, $tags = []): Collection
|
public function handle(int $memory = 0, int $disk = 0, int $cpu = 0, $tags = []): Collection
|
||||||
{
|
{
|
||||||
$nodes = Node::query()
|
$nodes = Node::query()
|
||||||
->withSum('servers', 'disk')
|
|
||||||
->withSum('servers', 'memory')
|
->withSum('servers', 'memory')
|
||||||
|
->withSum('servers', 'disk')
|
||||||
->withSum('servers', 'cpu')
|
->withSum('servers', 'cpu')
|
||||||
->where('public', true)
|
->where('public', true)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return $nodes
|
return $nodes
|
||||||
->filter(fn (Node $node) => !$tags || collect($node->tags)->intersect($tags))
|
->filter(fn (Node $node) => !$tags || collect($node->tags)->intersect($tags))
|
||||||
->filter(fn (Node $node) => $node->servers_sum_disk + $disk <= $node->disk * (1 + $node->disk_overallocate / 100))
|
->filter(fn (Node $node) => $node->isViable($memory, $disk, $cpu));
|
||||||
->filter(fn (Node $node) => $node->servers_sum_memory + $memory <= $node->memory * (1 + $node->memory_overallocate / 100))
|
|
||||||
->filter(fn (Node $node) => $node->servers_sum_cpu + $cpu <= $node->cpu * (1 + $node->cpu_overallocate / 100));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ class EggExporterService
|
|||||||
'exported_at' => Carbon::now()->toAtomString(),
|
'exported_at' => Carbon::now()->toAtomString(),
|
||||||
'name' => $egg->name,
|
'name' => $egg->name,
|
||||||
'author' => $egg->author,
|
'author' => $egg->author,
|
||||||
|
'uuid' => $egg->uuid,
|
||||||
'description' => $egg->description,
|
'description' => $egg->description,
|
||||||
'features' => $egg->features,
|
'features' => $egg->features,
|
||||||
'docker_images' => $egg->docker_images,
|
'docker_images' => $egg->docker_images,
|
||||||
|
@ -26,8 +26,11 @@ class EggImporterService
|
|||||||
$parsed = $this->parser->handle($file);
|
$parsed = $this->parser->handle($file);
|
||||||
|
|
||||||
return $this->connection->transaction(function () use ($parsed) {
|
return $this->connection->transaction(function () use ($parsed) {
|
||||||
$egg = (new Egg())->forceFill([
|
$uuid = $parsed['uuid'] ?? Uuid::uuid4()->toString();
|
||||||
'uuid' => Uuid::uuid4()->toString(),
|
$egg = Egg::where('uuid', $uuid)->first() ?? new Egg();
|
||||||
|
|
||||||
|
$egg = $egg->forceFill([
|
||||||
|
'uuid' => $uuid,
|
||||||
'author' => Arr::get($parsed, 'author'),
|
'author' => Arr::get($parsed, 'author'),
|
||||||
'copy_script_from' => null,
|
'copy_script_from' => null,
|
||||||
]);
|
]);
|
||||||
|
@ -16,7 +16,7 @@ class NodeCreationService
|
|||||||
public function handle(array $data): Node
|
public function handle(array $data): Node
|
||||||
{
|
{
|
||||||
$data['uuid'] = Uuid::uuid4()->toString();
|
$data['uuid'] = Uuid::uuid4()->toString();
|
||||||
$data['daemon_token'] = encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH));
|
$data['daemon_token'] = Str::random(Node::DAEMON_TOKEN_LENGTH);
|
||||||
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
|
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
|
||||||
|
|
||||||
return Node::query()->create($data);
|
return Node::query()->create($data);
|
||||||
|
@ -63,7 +63,7 @@ class NodeJWTService
|
|||||||
public function handle(Node $node, ?string $identifiedBy, string $algo = 'md5'): Plain
|
public function handle(Node $node, ?string $identifiedBy, string $algo = 'md5'): Plain
|
||||||
{
|
{
|
||||||
$identifier = hash($algo, $identifiedBy);
|
$identifier = hash($algo, $identifiedBy);
|
||||||
$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($node->getDecryptedKey()));
|
$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($node->daemon_token));
|
||||||
|
|
||||||
$builder = $config->builder(new TimestampDates())
|
$builder = $config->builder(new TimestampDates())
|
||||||
->issuedBy(config('app.url'))
|
->issuedBy(config('app.url'))
|
||||||
|
@ -28,14 +28,14 @@ class NodeUpdateService
|
|||||||
public function handle(Node $node, array $data, bool $resetToken = false): Node
|
public function handle(Node $node, array $data, bool $resetToken = false): Node
|
||||||
{
|
{
|
||||||
if ($resetToken) {
|
if ($resetToken) {
|
||||||
$data['daemon_token'] = encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH));
|
$data['daemon_token'] = Str::random(Node::DAEMON_TOKEN_LENGTH);
|
||||||
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
|
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
[$updated, $exception] = $this->connection->transaction(function () use ($data, $node) {
|
[$updated, $exception] = $this->connection->transaction(function () use ($data, $node) {
|
||||||
/** @var \App\Models\Node $updated */
|
/** @var \App\Models\Node $updated */
|
||||||
$updated = $node->replicate()->forceFill($data)->save();
|
$updated = $node->replicate();
|
||||||
|
$updated->forceFill($data)->save();
|
||||||
try {
|
try {
|
||||||
// If we're changing the FQDN for the node, use the newly provided FQDN for the connection
|
// If we're changing the FQDN for the node, use the newly provided FQDN for the connection
|
||||||
// address. This should alleviate issues where the node gets pointed to a "valid" FQDN that
|
// address. This should alleviate issues where the node gets pointed to a "valid" FQDN that
|
||||||
|
@ -109,8 +109,8 @@ class ServerCreationService
|
|||||||
{
|
{
|
||||||
/** @var Collection<\App\Models\Node> $nodes */
|
/** @var Collection<\App\Models\Node> $nodes */
|
||||||
$nodes = $this->findViableNodesService->handle(
|
$nodes = $this->findViableNodesService->handle(
|
||||||
Arr::get($data, 'disk', 0),
|
|
||||||
Arr::get($data, 'memory', 0),
|
Arr::get($data, 'memory', 0),
|
||||||
|
Arr::get($data, 'disk', 0),
|
||||||
Arr::get($data, 'cpu', 0),
|
Arr::get($data, 'cpu', 0),
|
||||||
Arr::get($data, 'tags', []),
|
Arr::get($data, 'tags', []),
|
||||||
);
|
);
|
||||||
@ -154,6 +154,7 @@ class ServerCreationService
|
|||||||
'database_limit' => Arr::get($data, 'database_limit') ?? 0,
|
'database_limit' => Arr::get($data, 'database_limit') ?? 0,
|
||||||
'allocation_limit' => Arr::get($data, 'allocation_limit') ?? 0,
|
'allocation_limit' => Arr::get($data, 'allocation_limit') ?? 0,
|
||||||
'backup_limit' => Arr::get($data, 'backup_limit') ?? 0,
|
'backup_limit' => Arr::get($data, 'backup_limit') ?? 0,
|
||||||
|
'docker_labels' => Arr::get($data, 'docker_labels'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,9 @@ class TransferServerService
|
|||||||
// Check if the node is viable for the transfer.
|
// Check if the node is viable for the transfer.
|
||||||
$node = Node::query()
|
$node = Node::query()
|
||||||
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemon_listen', 'nodes.memory', 'nodes.disk', 'nodes.cpu', 'nodes.memory_overallocate', 'nodes.disk_overallocate', 'nodes.cpu_overallocate'])
|
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemon_listen', 'nodes.memory', 'nodes.disk', 'nodes.cpu', 'nodes.memory_overallocate', 'nodes.disk_overallocate', 'nodes.cpu_overallocate'])
|
||||||
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk, IFNULL(SUM(servers.cpu), 0) as sum_cpu')
|
->withSum('servers', 'disk')
|
||||||
|
->withSum('servers', 'memory')
|
||||||
|
->withSum('servers', 'cpu')
|
||||||
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
|
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
|
||||||
->where('nodes.id', $node_id)
|
->where('nodes.id', $node_id)
|
||||||
->first();
|
->first();
|
||||||
|
@ -32,9 +32,7 @@ class ToggleTwoFactorService
|
|||||||
*/
|
*/
|
||||||
public function handle(User $user, string $token, bool $toggleState = null): array
|
public function handle(User $user, string $token, bool $toggleState = null): array
|
||||||
{
|
{
|
||||||
$secret = decrypt($user->totp_secret);
|
$isValidToken = $this->google2FA->verifyKey($user->totp_secret, $token, config()->get('panel.auth.2fa.window'));
|
||||||
|
|
||||||
$isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('panel.auth.2fa.window'));
|
|
||||||
|
|
||||||
if (!$isValidToken) {
|
if (!$isValidToken) {
|
||||||
throw new TwoFactorAuthenticationTokenInvalid();
|
throw new TwoFactorAuthenticationTokenInvalid();
|
||||||
|
@ -26,7 +26,7 @@ class TwoFactorSetupService
|
|||||||
throw new \RuntimeException($exception->getMessage(), 0, $exception);
|
throw new \RuntimeException($exception->getMessage(), 0, $exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->totp_secret = encrypt($secret);
|
$user->totp_secret = $secret;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
$company = urlencode(preg_replace('/\s/', '', config('app.name')));
|
$company = urlencode(preg_replace('/\s/', '', config('app.name')));
|
||||||
|
@ -45,7 +45,7 @@ class ServerDatabaseTransformer extends BaseTransformer
|
|||||||
{
|
{
|
||||||
return $this->item($model, function (Database $model) {
|
return $this->item($model, function (Database $model) {
|
||||||
return [
|
return [
|
||||||
'password' => decrypt($model->password),
|
'password' => $model->password,
|
||||||
];
|
];
|
||||||
}, 'database_password');
|
}, 'database_password');
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ class ActivityLogTransformer extends BaseClientTransformer
|
|||||||
|
|
||||||
$properties = $model->properties
|
$properties = $model->properties
|
||||||
->mapWithKeys(function ($value, $key) use ($model) {
|
->mapWithKeys(function ($value, $key) use ($model) {
|
||||||
if ($key === 'ip' && !$model->actor->is($this->request->user())) {
|
if ($key === 'ip' && $model->actor && !$model->actor->is($this->request->user())) {
|
||||||
return [$key => '[hidden]'];
|
return [$key => '[hidden]'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,22 +6,11 @@ use App\Models\Database;
|
|||||||
use League\Fractal\Resource\Item;
|
use League\Fractal\Resource\Item;
|
||||||
use App\Models\Permission;
|
use App\Models\Permission;
|
||||||
use League\Fractal\Resource\NullResource;
|
use League\Fractal\Resource\NullResource;
|
||||||
use App\Contracts\Extensions\HashidsInterface;
|
|
||||||
|
|
||||||
class DatabaseTransformer extends BaseClientTransformer
|
class DatabaseTransformer extends BaseClientTransformer
|
||||||
{
|
{
|
||||||
protected array $availableIncludes = ['password'];
|
protected array $availableIncludes = ['password'];
|
||||||
|
|
||||||
private HashidsInterface $hashids;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle dependency injection.
|
|
||||||
*/
|
|
||||||
public function handle(HashidsInterface $hashids)
|
|
||||||
{
|
|
||||||
$this->hashids = $hashids;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getResourceName(): string
|
public function getResourceName(): string
|
||||||
{
|
{
|
||||||
return Database::RESOURCE_NAME;
|
return Database::RESOURCE_NAME;
|
||||||
@ -32,7 +21,7 @@ class DatabaseTransformer extends BaseClientTransformer
|
|||||||
$model->loadMissing('host');
|
$model->loadMissing('host');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $this->hashids->encode($model->id),
|
'id' => $model->id,
|
||||||
'host' => [
|
'host' => [
|
||||||
'address' => $model->getRelation('host')->host,
|
'address' => $model->getRelation('host')->host,
|
||||||
'port' => $model->getRelation('host')->port,
|
'port' => $model->getRelation('host')->port,
|
||||||
@ -55,7 +44,7 @@ class DatabaseTransformer extends BaseClientTransformer
|
|||||||
|
|
||||||
return $this->item($database, function (Database $model) {
|
return $this->item($database, function (Database $model) {
|
||||||
return [
|
return [
|
||||||
'password' => decrypt($model->password),
|
'password' => $model->password,
|
||||||
];
|
];
|
||||||
}, 'database_password');
|
}, 'database_password');
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ return [
|
|||||||
App\Providers\BackupsServiceProvider::class,
|
App\Providers\BackupsServiceProvider::class,
|
||||||
App\Providers\EventServiceProvider::class,
|
App\Providers\EventServiceProvider::class,
|
||||||
App\Providers\Filament\AdminPanelProvider::class,
|
App\Providers\Filament\AdminPanelProvider::class,
|
||||||
App\Providers\HashidsServiceProvider::class,
|
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
App\Providers\ViewComposerServiceProvider::class,
|
App\Providers\ViewComposerServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
"doctrine/dbal": "~3.6.0",
|
"doctrine/dbal": "~3.6.0",
|
||||||
"filament/filament": "^3.2",
|
"filament/filament": "^3.2",
|
||||||
"guzzlehttp/guzzle": "^7.8.1",
|
"guzzlehttp/guzzle": "^7.8.1",
|
||||||
"hashids/hashids": "~5.0.0",
|
|
||||||
"laracasts/utilities": "~3.2.2",
|
"laracasts/utilities": "~3.2.2",
|
||||||
"laravel/framework": "^11.7",
|
"laravel/framework": "^11.7",
|
||||||
"laravel/helpers": "^1.7",
|
"laravel/helpers": "^1.7",
|
||||||
@ -34,7 +33,8 @@
|
|||||||
"s1lentium/iptools": "~1.2.0",
|
"s1lentium/iptools": "~1.2.0",
|
||||||
"spatie/laravel-fractal": "^6.2",
|
"spatie/laravel-fractal": "^6.2",
|
||||||
"spatie/laravel-query-builder": "^5.8.1",
|
"spatie/laravel-query-builder": "^5.8.1",
|
||||||
"symfony/mailgun-mailer": "^7.0.7",
|
"symfony/http-client": "^7.1",
|
||||||
|
"symfony/mailgun-mailer": "^7.1",
|
||||||
"symfony/postmark-mailer": "^7.0.7",
|
"symfony/postmark-mailer": "^7.0.7",
|
||||||
"symfony/yaml": "^7.0.7",
|
"symfony/yaml": "^7.0.7",
|
||||||
"webbingbrasil/filament-copyactions": "^3.0.1",
|
"webbingbrasil/filament-copyactions": "^3.0.1",
|
||||||
|
257
composer.lock
generated
257
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "dc1c1e5ee766f2e31e84c50670fa0c98",
|
"content-hash": "328bdd29cb83793ec0a99b2210941740",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "abdelhamiderrahmouni/filament-monaco-editor",
|
"name": "abdelhamiderrahmouni/filament-monaco-editor",
|
||||||
@ -2613,75 +2613,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-12-03T19:50:20+00:00"
|
"time": "2023-12-03T19:50:20+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "hashids/hashids",
|
|
||||||
"version": "5.0.2",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/vinkla/hashids.git",
|
|
||||||
"reference": "197171016b77ddf14e259e186559152eb3f8cf33"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/vinkla/hashids/zipball/197171016b77ddf14e259e186559152eb3f8cf33",
|
|
||||||
"reference": "197171016b77ddf14e259e186559152eb3f8cf33",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"ext-mbstring": "*",
|
|
||||||
"php": "^8.1"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^10.0"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"ext-bcmath": "Required to use BC Math arbitrary precision mathematics (*).",
|
|
||||||
"ext-gmp": "Required to use GNU multiple precision mathematics (*)."
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "5.0-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Hashids\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Ivan Akimov",
|
|
||||||
"email": "ivan@barreleye.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Vincent Klaiber",
|
|
||||||
"email": "hello@doubledip.se"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Generate short, unique, non-sequential ids (like YouTube and Bitly) from numbers",
|
|
||||||
"homepage": "https://hashids.org/php",
|
|
||||||
"keywords": [
|
|
||||||
"bitly",
|
|
||||||
"decode",
|
|
||||||
"encode",
|
|
||||||
"hash",
|
|
||||||
"hashid",
|
|
||||||
"hashids",
|
|
||||||
"ids",
|
|
||||||
"obfuscate",
|
|
||||||
"youtube"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/vinkla/hashids/issues",
|
|
||||||
"source": "https://github.com/vinkla/hashids/tree/5.0.2"
|
|
||||||
},
|
|
||||||
"time": "2023-02-23T15:00:54+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "kirschbaum-development/eloquent-power-joins",
|
"name": "kirschbaum-development/eloquent-power-joins",
|
||||||
"version": "3.5.6",
|
"version": "3.5.6",
|
||||||
@ -7722,6 +7653,178 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-18T09:29:19+00:00"
|
"time": "2024-04-18T09:29:19+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/http-client",
|
||||||
|
"version": "v7.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/http-client.git",
|
||||||
|
"reference": "2266f9813ed7d8c84e04627edead7b7fd249d6e9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/http-client/zipball/2266f9813ed7d8c84e04627edead7b7fd249d6e9",
|
||||||
|
"reference": "2266f9813ed7d8c84e04627edead7b7fd249d6e9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/log": "^1|^2|^3",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/http-client-contracts": "^3.4.1",
|
||||||
|
"symfony/service-contracts": "^2.5|^3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"php-http/discovery": "<1.15",
|
||||||
|
"symfony/http-foundation": "<6.4"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"php-http/async-client-implementation": "*",
|
||||||
|
"php-http/client-implementation": "*",
|
||||||
|
"psr/http-client-implementation": "1.0",
|
||||||
|
"symfony/http-client-implementation": "3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/amp": "^2.5",
|
||||||
|
"amphp/http-client": "^4.2.1",
|
||||||
|
"amphp/http-tunnel": "^1.0",
|
||||||
|
"amphp/socket": "^1.1",
|
||||||
|
"guzzlehttp/promises": "^1.4|^2.0",
|
||||||
|
"nyholm/psr7": "^1.0",
|
||||||
|
"php-http/httplug": "^1.0|^2.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"symfony/dependency-injection": "^6.4|^7.0",
|
||||||
|
"symfony/http-kernel": "^6.4|^7.0",
|
||||||
|
"symfony/messenger": "^6.4|^7.0",
|
||||||
|
"symfony/process": "^6.4|^7.0",
|
||||||
|
"symfony/rate-limiter": "^6.4|^7.0",
|
||||||
|
"symfony/stopwatch": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\HttpClient\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/http-client/tree/v7.1.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-05-13T15:35:37+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/http-client-contracts",
|
||||||
|
"version": "v3.5.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/http-client-contracts.git",
|
||||||
|
"reference": "20414d96f391677bf80078aa55baece78b82647d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d",
|
||||||
|
"reference": "20414d96f391677bf80078aa55baece78b82647d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.5-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/contracts",
|
||||||
|
"url": "https://github.com/symfony/contracts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Contracts\\HttpClient\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Test/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Generic abstractions related to HTTP clients",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"abstractions",
|
||||||
|
"contracts",
|
||||||
|
"decoupling",
|
||||||
|
"interfaces",
|
||||||
|
"interoperability",
|
||||||
|
"standards"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-04-18T09:32:20+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/http-foundation",
|
"name": "symfony/http-foundation",
|
||||||
"version": "v7.0.7",
|
"version": "v7.0.7",
|
||||||
@ -7994,16 +8097,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/mailgun-mailer",
|
"name": "symfony/mailgun-mailer",
|
||||||
"version": "v7.0.7",
|
"version": "v7.1.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/mailgun-mailer.git",
|
"url": "https://github.com/symfony/mailgun-mailer.git",
|
||||||
"reference": "e9bb8fdbdd79334a8a88bdd233204315abd992c5"
|
"reference": "aa5afbe846bbc8bde6afe2602f0427834b872f55"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/e9bb8fdbdd79334a8a88bdd233204315abd992c5",
|
"url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/aa5afbe846bbc8bde6afe2602f0427834b872f55",
|
||||||
"reference": "e9bb8fdbdd79334a8a88bdd233204315abd992c5",
|
"reference": "aa5afbe846bbc8bde6afe2602f0427834b872f55",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -8043,7 +8146,7 @@
|
|||||||
"description": "Symfony Mailgun Mailer Bridge",
|
"description": "Symfony Mailgun Mailer Bridge",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/mailgun-mailer/tree/v7.0.7"
|
"source": "https://github.com/symfony/mailgun-mailer/tree/v7.1.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -8059,7 +8162,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-04-18T09:29:19+00:00"
|
"time": "2024-04-18T09:32:20+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/mime",
|
"name": "symfony/mime",
|
||||||
@ -13061,5 +13164,5 @@
|
|||||||
"ext-zip": "*"
|
"ext-zip": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"plugin-api-version": "2.3.0"
|
"plugin-api-version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Hashids Configuration
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here are the settings that control the Hashids setup and usage in the panel.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'salt' => env('HASHIDS_SALT'),
|
|
||||||
'length' => env('HASHIDS_LENGTH', 8),
|
|
||||||
'alphabet' => env('HASHIDS_ALPHABET', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'),
|
|
||||||
];
|
|
@ -26,7 +26,7 @@ class ApiKeyFactory extends Factory
|
|||||||
return [
|
return [
|
||||||
'key_type' => ApiKey::TYPE_APPLICATION,
|
'key_type' => ApiKey::TYPE_APPLICATION,
|
||||||
'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION),
|
'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION),
|
||||||
'token' => $token ?: $token = encrypt(Str::random(ApiKey::KEY_LENGTH)),
|
'token' => $token ?: $token = Str::random(ApiKey::KEY_LENGTH),
|
||||||
'allowed_ips' => null,
|
'allowed_ips' => null,
|
||||||
'memo' => 'Test Function Key',
|
'memo' => 'Test Function Key',
|
||||||
'created_at' => Carbon::now(),
|
'created_at' => Carbon::now(),
|
||||||
|
@ -27,7 +27,7 @@ class DatabaseFactory extends Factory
|
|||||||
'database' => Str::random(10),
|
'database' => Str::random(10),
|
||||||
'username' => Str::random(10),
|
'username' => Str::random(10),
|
||||||
'remote' => '%',
|
'remote' => '%',
|
||||||
'password' => $password ?: encrypt('test123'),
|
'password' => $password ?: 'test123',
|
||||||
'created_at' => Carbon::now(),
|
'created_at' => Carbon::now(),
|
||||||
'updated_at' => Carbon::now(),
|
'updated_at' => Carbon::now(),
|
||||||
];
|
];
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
use App\Models\DatabaseHost;
|
use App\Models\DatabaseHost;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
class DatabaseHostFactory extends Factory
|
class DatabaseHostFactory extends Factory
|
||||||
@ -25,7 +24,7 @@ class DatabaseHostFactory extends Factory
|
|||||||
'host' => $this->faker->unique()->ipv4(),
|
'host' => $this->faker->unique()->ipv4(),
|
||||||
'port' => 3306,
|
'port' => 3306,
|
||||||
'username' => $this->faker->colorName(),
|
'username' => $this->faker->colorName(),
|
||||||
'password' => Crypt::encrypt($this->faker->word()),
|
'password' => $this->faker->word(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ namespace Database\Factories;
|
|||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
class NodeFactory extends Factory
|
class NodeFactory extends Factory
|
||||||
@ -37,7 +36,7 @@ class NodeFactory extends Factory
|
|||||||
'cpu_overallocate' => 0,
|
'cpu_overallocate' => 0,
|
||||||
'upload_size' => 100,
|
'upload_size' => 100,
|
||||||
'daemon_token_id' => Str::random(Node::DAEMON_TOKEN_ID_LENGTH),
|
'daemon_token_id' => Str::random(Node::DAEMON_TOKEN_ID_LENGTH),
|
||||||
'daemon_token' => Crypt::encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)),
|
'daemon_token' => Str::random(Node::DAEMON_TOKEN_LENGTH),
|
||||||
'daemon_listen' => 8080,
|
'daemon_listen' => 8080,
|
||||||
'daemon_sftp' => 2022,
|
'daemon_sftp' => 2022,
|
||||||
'daemon_base' => '/var/lib/panel/volumes',
|
'daemon_base' => '/var/lib/panel/volumes',
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$keys = DB::table('api_keys')->get();
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
try {
|
||||||
|
$reEncrypted = encrypt(decrypt($key->token), false);
|
||||||
|
DB::table('api_keys')
|
||||||
|
->where('id', $key->id)
|
||||||
|
->update(['token' => $reEncrypted]);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
logger()->error($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$databases = DB::table('databases')->get();
|
||||||
|
foreach ($databases as $database) {
|
||||||
|
try {
|
||||||
|
$reEncrypted = encrypt(decrypt($database->password), false);
|
||||||
|
DB::table('databases')
|
||||||
|
->where('id', $database->id)
|
||||||
|
->update(['password' => $reEncrypted]);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
logger()->error($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$databaseHosts = DB::table('database_hosts')->get();
|
||||||
|
foreach ($databaseHosts as $host) {
|
||||||
|
try {
|
||||||
|
$reEncrypted = encrypt(decrypt($host->password), false);
|
||||||
|
DB::table('database_hosts')
|
||||||
|
->where('id', $host->id)
|
||||||
|
->update(['password' => $reEncrypted]);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
logger()->error($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$nodes = DB::table('nodes')->get();
|
||||||
|
foreach ($nodes as $node) {
|
||||||
|
try {
|
||||||
|
$reEncrypted = encrypt(decrypt($node->daemon_token), false);
|
||||||
|
DB::table('nodes')
|
||||||
|
->where('id', $node->id)
|
||||||
|
->update(['daemon_token' => $reEncrypted]);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
logger()->error($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = DB::table('users')->get();
|
||||||
|
foreach ($users as $user) {
|
||||||
|
try {
|
||||||
|
$reEncrypted = encrypt(decrypt($user->totp_secret), false);
|
||||||
|
DB::table('users')
|
||||||
|
->where('id', $user->id)
|
||||||
|
->update(['totp_secret' => $reEncrypted]);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
logger()->error($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// No need to do anything
|
||||||
|
}
|
||||||
|
};
|
@ -23,7 +23,7 @@ return [
|
|||||||
'2fa_disabled' => '2-Factor authentication has been disabled for :email.',
|
'2fa_disabled' => '2-Factor authentication has been disabled for :email.',
|
||||||
],
|
],
|
||||||
'schedule' => [
|
'schedule' => [
|
||||||
'output_line' => 'Dispatching job for first task in `:schedule` (:hash).',
|
'output_line' => 'Dispatching job for first task in `:schedule` (:id).',
|
||||||
],
|
],
|
||||||
'maintenance' => [
|
'maintenance' => [
|
||||||
'deleting_service_backup' => 'Deleting service backup file :file.',
|
'deleting_service_backup' => 'Deleting service backup file :file.',
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
# Pelican Panel
|
# Pelican Panel
|
||||||
|
|
||||||
|
&color=rgba(255%2C%20255%2C%20255%2C%201))
|
||||||
|
&color=rgba(255%2C%20255%2C%20255%2C%201))
|
||||||
|
|
||||||
|
|
||||||
Pelican Panel is an open-source, web-based application designed for easy management of game servers.
|
Pelican Panel is an open-source, web-based application designed for easy management of game servers.
|
||||||
It offers a user-friendly interface for deploying, configuring, and managing servers, with features like real-time resource monitoring, Docker container isolation, and extensive customization options.
|
It offers a user-friendly interface for deploying, configuring, and managing servers, with features like real-time resource monitoring, Docker container isolation, and extensive customization options.
|
||||||
Ideal for both individual gamers and hosting companies, it simplifies server administration without requiring deep technical knowledge.
|
Ideal for both individual gamers and hosting companies, it simplifies server administration without requiring deep technical knowledge.
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
@foreach($keys as $key)
|
@foreach($keys as $key)
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>{{ $key->identifier }}{{ decrypt($key->token) }}</code></td>
|
<td><code>{{ $key->identifier }}{{ $key->token }}</code></td>
|
||||||
<td>{{ $key->memo }}</td>
|
<td>{{ $key->memo }}</td>
|
||||||
<td>
|
<td>
|
||||||
@if(!is_null($key->last_used_at))
|
@if(!is_null($key->last_used_at))
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
@foreach ($nodes as $node)
|
@foreach ($nodes as $node)
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->getDecryptedKey() }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemon_listen }}/api/system"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->daemon_token }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemon_listen }}/api/system"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
||||||
<td>{!! $node->maintenance_mode ? '<span class="label label-warning"><i class="fa fa-wrench"></i></span> ' : '' !!}<a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></td>
|
<td>{!! $node->maintenance_mode ? '<span class="label label-warning"><i class="fa fa-wrench"></i></span> ' : '' !!}<a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></td>
|
||||||
<td>{{ $node->memory }} MiB</td>
|
<td>{{ $node->memory }} MiB</td>
|
||||||
<td>{{ $node->disk }} MiB</td>
|
<td>{{ $node->disk }} MiB</td>
|
||||||
|
@ -37,7 +37,7 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase
|
|||||||
|
|
||||||
$this
|
$this
|
||||||
->withHeader('Accept', 'application/vnd.panel.v1+json')
|
->withHeader('Accept', 'application/vnd.panel.v1+json')
|
||||||
->withHeader('Authorization', 'Bearer ' . $this->key->identifier . decrypt($this->key->token));
|
->withHeader('Authorization', 'Bearer ' . $this->key->identifier . $this->key->token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getApiUser(): User
|
public function getApiUser(): User
|
||||||
@ -57,7 +57,7 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase
|
|||||||
{
|
{
|
||||||
$this->key = $this->createApiKey($user, $permissions);
|
$this->key = $this->createApiKey($user, $permissions);
|
||||||
|
|
||||||
$this->withHeader('Authorization', 'Bearer ' . $this->key->identifier . decrypt($this->key->token));
|
$this->withHeader('Authorization', 'Bearer ' . $this->key->identifier . $this->key->token);
|
||||||
|
|
||||||
return $this->key;
|
return $this->key;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase
|
|||||||
$key = ApiKey::query()->where('identifier', $response->json('attributes.identifier'))->firstOrFail();
|
$key = ApiKey::query()->where('identifier', $response->json('attributes.identifier'))->firstOrFail();
|
||||||
|
|
||||||
$this->assertJsonTransformedWith($response->json('attributes'), $key);
|
$this->assertJsonTransformedWith($response->json('attributes'), $key);
|
||||||
$response->assertJsonPath('meta.secret_token', decrypt($key->token));
|
$response->assertJsonPath('meta.secret_token', $key->token);
|
||||||
|
|
||||||
$this->assertActivityFor('user:api-key.create', $user, [$key, $user]);
|
$this->assertActivityFor('user:api-key.create', $user, [$key, $user]);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ namespace App\Tests\Integration\Api\Client\Server\Database;
|
|||||||
use App\Models\Subuser;
|
use App\Models\Subuser;
|
||||||
use App\Models\Database;
|
use App\Models\Database;
|
||||||
use App\Models\DatabaseHost;
|
use App\Models\DatabaseHost;
|
||||||
use App\Contracts\Extensions\HashidsInterface;
|
|
||||||
use App\Services\Databases\DatabasePasswordService;
|
use App\Services\Databases\DatabasePasswordService;
|
||||||
use App\Services\Databases\DatabaseManagementService;
|
use App\Services\Databases\DatabaseManagementService;
|
||||||
use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
|
use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
|
||||||
@ -39,23 +38,22 @@ class DatabaseAuthorizationTest extends ClientApiIntegrationTestCase
|
|||||||
->expects($method === 'POST' ? 'handle' : 'delete')
|
->expects($method === 'POST' ? 'handle' : 'delete')
|
||||||
->andReturn($method === 'POST' ? 'foo' : null);
|
->andReturn($method === 'POST' ? 'foo' : null);
|
||||||
|
|
||||||
$hashids = $this->app->make(HashidsInterface::class);
|
|
||||||
// This is the only valid call for this test, accessing the database for the same
|
// This is the only valid call for this test, accessing the database for the same
|
||||||
// server that the API user is the owner of.
|
// server that the API user is the owner of.
|
||||||
$this->actingAs($user)->json($method, $this->link($server1, '/databases/' . $hashids->encode($database1->id) . $endpoint))
|
$this->actingAs($user)->json($method, $this->link($server1, '/databases/' . $database1->id . $endpoint))
|
||||||
->assertStatus($method === 'DELETE' ? 204 : 200);
|
->assertStatus($method === 'DELETE' ? 204 : 200);
|
||||||
|
|
||||||
// This request fails because the database is valid for that server but the user
|
// This request fails because the database is valid for that server but the user
|
||||||
// making the request is not authorized to perform that action.
|
// making the request is not authorized to perform that action.
|
||||||
$this->actingAs($user)->json($method, $this->link($server2, '/databases/' . $hashids->encode($database2->id) . $endpoint))->assertForbidden();
|
$this->actingAs($user)->json($method, $this->link($server2, '/databases/' . $database2->id . $endpoint))->assertForbidden();
|
||||||
|
|
||||||
// Both of these should report a 404 error due to the database being linked to
|
// Both of these should report a 404 error due to the database being linked to
|
||||||
// servers that are not the same as the server in the request, or are assigned
|
// servers that are not the same as the server in the request, or are assigned
|
||||||
// to a server for which the user making the request has no access to.
|
// to a server for which the user making the request has no access to.
|
||||||
$this->actingAs($user)->json($method, $this->link($server1, '/databases/' . $hashids->encode($database2->id) . $endpoint))->assertNotFound();
|
$this->actingAs($user)->json($method, $this->link($server1, '/databases/' . $database2->id . $endpoint))->assertNotFound();
|
||||||
$this->actingAs($user)->json($method, $this->link($server1, '/databases/' . $hashids->encode($database3->id) . $endpoint))->assertNotFound();
|
$this->actingAs($user)->json($method, $this->link($server1, '/databases/' . $database3->id . $endpoint))->assertNotFound();
|
||||||
$this->actingAs($user)->json($method, $this->link($server2, '/databases/' . $hashids->encode($database3->id) . $endpoint))->assertNotFound();
|
$this->actingAs($user)->json($method, $this->link($server2, '/databases/' . $database3->id . $endpoint))->assertNotFound();
|
||||||
$this->actingAs($user)->json($method, $this->link($server3, '/databases/' . $hashids->encode($database3->id) . $endpoint))->assertNotFound();
|
$this->actingAs($user)->json($method, $this->link($server3, '/databases/' . $database3->id . $endpoint))->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function methodDataProvider(): array
|
public static function methodDataProvider(): array
|
||||||
|
@ -62,7 +62,7 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase
|
|||||||
$this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.');
|
$this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.');
|
||||||
$this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection, 'Failed asserting that websocket connection address uses expected Daemon endpoint.');
|
$this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection, 'Failed asserting that websocket connection address uses expected Daemon endpoint.');
|
||||||
|
|
||||||
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey()));
|
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->daemon_token));
|
||||||
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
|
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
|
||||||
/** @var \Lcobucci\JWT\Token\Plain $token */
|
/** @var \Lcobucci\JWT\Token\Plain $token */
|
||||||
$token = $config->parser()->parse($response->json('data.token'));
|
$token = $config->parser()->parse($response->json('data.token'));
|
||||||
@ -107,7 +107,7 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase
|
|||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertJsonStructure(['data' => ['token', 'socket']]);
|
$response->assertJsonStructure(['data' => ['token', 'socket']]);
|
||||||
|
|
||||||
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey()));
|
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->daemon_token));
|
||||||
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
|
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
|
||||||
/** @var \Lcobucci\JWT\Token\Plain $token */
|
/** @var \Lcobucci\JWT\Token\Plain $token */
|
||||||
$token = $config->parser()->parse($response->json('data.token'));
|
$token = $config->parser()->parse($response->json('data.token'));
|
||||||
|
@ -85,8 +85,7 @@ class TwoFactorControllerTest extends ClientApiIntegrationTestCase
|
|||||||
/** @var \PragmaRX\Google2FA\Google2FA $service */
|
/** @var \PragmaRX\Google2FA\Google2FA $service */
|
||||||
$service = $this->app->make(Google2FA::class);
|
$service = $this->app->make(Google2FA::class);
|
||||||
|
|
||||||
$secret = decrypt($user->totp_secret);
|
$token = $service->getCurrentOtp($user->totp_secret);
|
||||||
$token = $service->getCurrentOtp($secret);
|
|
||||||
|
|
||||||
$response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
|
$response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
|
||||||
'code' => $token,
|
'code' => $token,
|
||||||
|
@ -94,7 +94,7 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
|
|||||||
public function testSuccessfulMiddlewareProcess(): void
|
public function testSuccessfulMiddlewareProcess(): void
|
||||||
{
|
{
|
||||||
$node = Node::factory()->create();
|
$node = Node::factory()->create();
|
||||||
$node->daemon_token = encrypt('the_same');
|
$node->daemon_token = 'the_same';
|
||||||
$node->save();
|
$node->save();
|
||||||
|
|
||||||
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
||||||
|
@ -229,6 +229,6 @@ class SftpAuthenticationControllerTest extends IntegrationTestCase
|
|||||||
{
|
{
|
||||||
$node = $node ?? $this->server->node;
|
$node = $node ?? $this->server->node;
|
||||||
|
|
||||||
$this->withHeader('Authorization', 'Bearer ' . $node->daemon_token_id . '.' . decrypt($node->daemon_token));
|
$this->withHeader('Authorization', 'Bearer ' . $node->daemon_token_id . '.' . $node->daemon_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user