mirror of
https://github.com/pelican-dev/panel.git
synced 2025-09-09 16:28:37 +02:00
Merge branch 'main' into issue/68
# Conflicts: # app/Filament/Resources/EggResource/RelationManagers/ServersRelationManager.php # app/Filament/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php # app/Filament/Resources/NodeResource/RelationManagers/NodesRelationManager.php # app/Filament/Resources/ServerResource/Pages/CreateServer.php # app/Filament/Resources/ServerResource/Pages/ListServers.php # app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php # app/Filament/Resources/UserResource/RelationManagers/ServersRelationManager.php # app/Models/Allocation.php # app/Models/ApiKey.php # app/Models/Server.php # app/Models/User.php
This commit is contained in:
commit
0bd2935885
3
.github/workflows/build.yaml
vendored
3
.github/workflows/build.yaml
vendored
@ -1,6 +1,9 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
|
77
.github/workflows/ci.yaml
vendored
77
.github/workflows/ci.yaml
vendored
@ -1,6 +1,9 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
@ -13,7 +16,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3]
|
||||
database: ["mariadb:10.2", "mysql:8"]
|
||||
database: ["mysql:8"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@ -78,6 +81,78 @@ jobs:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
mariadb:
|
||||
name: MariaDB
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3]
|
||||
database: ["mariadb:10.3", "mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
APP_ENVIRONMENT_ONLY: "true"
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: mariadb
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/phpunit tests/Unit
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/phpunit tests/Integration
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
sqlite:
|
||||
name: SQLite
|
||||
runs-on: ubuntu-latest
|
||||
|
29
.github/workflows/lint.yaml
vendored
29
.github/workflows/lint.yaml
vendored
@ -6,8 +6,8 @@ on:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
pint:
|
||||
name: Pint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.2"
|
||||
php-version: "8.3"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@ -29,3 +29,26 @@ jobs:
|
||||
|
||||
- name: Pint
|
||||
run: vendor/bin/pint --test
|
||||
phpstan:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Setup .env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-progress --prefer-dist
|
||||
|
||||
- name: PHPStan
|
||||
run: vendor/bin/phpstan --memory-limit=-1
|
||||
|
27
.github/workflows/release.yaml
vendored
27
.github/workflows/release.yaml
vendored
@ -54,31 +54,12 @@ jobs:
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||
|
||||
- name: Upload release archive
|
||||
id: upload-release-archive
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: panel.tar.gz
|
||||
asset_name: panel.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: Upload release checksum
|
||||
id: upload-release-checksum
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./checksum.txt
|
||||
asset_name: checksum.txt
|
||||
asset_content_type: text/plain
|
||||
files: |
|
||||
panel.tar.gz
|
||||
checksum.txt
|
||||
|
@ -13,6 +13,7 @@ class DatabaseSettingsCommand extends Command
|
||||
|
||||
public const DATABASE_DRIVERS = [
|
||||
'sqlite' => 'SQLite (recommended)',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
];
|
||||
|
||||
@ -21,10 +22,10 @@ class DatabaseSettingsCommand extends Command
|
||||
protected $signature = 'p:environment:database
|
||||
{--driver= : The database driver backend to use.}
|
||||
{--database= : The database to use.}
|
||||
{--host= : The connection address for the MySQL server.}
|
||||
{--port= : The connection port for the MySQL server.}
|
||||
{--username= : Username to use when connecting to the MySQL server.}
|
||||
{--password= : Password to use for the MySQL database.}';
|
||||
{--host= : The connection address for the MySQL/ MariaDB server.}
|
||||
{--port= : The connection port for the MySQL/ MariaDB server.}
|
||||
{--username= : Username to use when connecting to the MySQL/ MariaDB server.}
|
||||
{--password= : Password to use for the MySQL/ MariaDB database.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
@ -82,7 +83,20 @@ class DatabaseSettingsCommand extends Command
|
||||
}
|
||||
|
||||
try {
|
||||
$this->testMySQLConnection();
|
||||
// Test connection
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'mysql',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'strict' => true,
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (\PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(__('commands.database_settings.DB_error_2'));
|
||||
@ -93,6 +107,66 @@ class DatabaseSettingsCommand extends Command
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'mariadb') {
|
||||
$this->output->note(__('commands.database_settings.DB_HOST_note'));
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.mariadb.host', '127.0.0.1')
|
||||
);
|
||||
|
||||
$this->variables['DB_PORT'] = $this->option('port') ?? $this->ask(
|
||||
'Database Port',
|
||||
config('database.connections.mariadb.port', 3306)
|
||||
);
|
||||
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Name',
|
||||
config('database.connections.mariadb.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.mariadb.username', 'pelican')
|
||||
);
|
||||
|
||||
$askForMariaDBPassword = true;
|
||||
if (!empty(config('database.connections.mariadb.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.mariadb.password');
|
||||
$askForMariaDBPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
|
||||
}
|
||||
|
||||
if ($askForMariaDBPassword) {
|
||||
$this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password');
|
||||
}
|
||||
|
||||
try {
|
||||
// Test connection
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'mariadb',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'strict' => true,
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (\PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(__('commands.database_settings.DB_error_2'));
|
||||
|
||||
if ($this->confirm(__('commands.database_settings.go_back'))) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
|
||||
@ -108,24 +182,4 @@ class DatabaseSettingsCommand extends Command
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can connect to the provided MySQL instance and perform a selection.
|
||||
*/
|
||||
private function testMySQLConnection()
|
||||
{
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'mysql',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'strict' => true,
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,12 @@ use App\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class InfoCommand extends Command
|
||||
{
|
||||
protected $description = 'Displays the application, database, and email configurations along with the panel version.';
|
||||
protected $description = 'Displays the application, database, email and backup configurations along with the panel version.';
|
||||
|
||||
protected $signature = 'p:info';
|
||||
|
||||
/**
|
||||
* VersionCommand constructor.
|
||||
* InfoCommand constructor.
|
||||
*/
|
||||
public function __construct(private SoftwareVersionService $versionService)
|
||||
{
|
||||
@ -33,38 +33,69 @@ class InfoCommand extends Command
|
||||
|
||||
$this->output->title('Application Configuration');
|
||||
$this->table([], [
|
||||
['Environment', $this->formatText(config('app.env'), config('app.env') === 'production' ?: 'bg=red')],
|
||||
['Debug Mode', $this->formatText(config('app.debug') ? 'Yes' : 'No', !config('app.debug') ?: 'bg=red')],
|
||||
['Installation URL', config('app.url')],
|
||||
['Environment', config('app.env') === 'production' ? config('app.env') : $this->formatText(config('app.env'), 'bg=red')],
|
||||
['Debug Mode', config('app.debug') ? $this->formatText('Yes', 'bg=red') : 'No'],
|
||||
['Application Name', config('app.name')],
|
||||
['Application URL', config('app.url')],
|
||||
['Installation Directory', base_path()],
|
||||
['Cache Driver', config('cache.default')],
|
||||
['Queue Driver', config('queue.default')],
|
||||
['Queue Driver', config('queue.default') === 'sync' ? $this->formatText(config('queue.default'), 'bg=red') : config('queue.default')],
|
||||
['Session Driver', config('session.driver')],
|
||||
['Filesystem Driver', config('filesystems.default')],
|
||||
['Default Theme', config('themes.active')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Database Configuration');
|
||||
$driver = config('database.default');
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("database.connections.$driver.host")],
|
||||
['Port', config("database.connections.$driver.port")],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
['Username', config("database.connections.$driver.username")],
|
||||
], 'compact');
|
||||
if ($driver === 'sqlite') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("database.connections.$driver.host")],
|
||||
['Port', config("database.connections.$driver.port")],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
['Username', config("database.connections.$driver.username")],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
// TODO: Update this to handle other mail drivers
|
||||
$this->output->title('Email Configuration');
|
||||
$this->table([], [
|
||||
['Driver', config('mail.default')],
|
||||
['Host', config('mail.mailers.smtp.host')],
|
||||
['Port', config('mail.mailers.smtp.port')],
|
||||
['Username', config('mail.mailers.smtp.username')],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
['Encryption', config('mail.mailers.smtp.encryption')],
|
||||
], 'compact');
|
||||
$driver = config('mail.default');
|
||||
if ($driver === 'smtp') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("mail.mailers.$driver.host")],
|
||||
['Port', config("mail.mailers.$driver.port")],
|
||||
['Username', config("mail.mailers.$driver.username")],
|
||||
['Encryption', config("mail.mailers.$driver.encryption")],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
$this->output->title('Backup Configuration');
|
||||
$driver = config('backups.default');
|
||||
if ($driver === 's3') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Region', config("backups.disks.$driver.region")],
|
||||
['Bucket', config("backups.disks.$driver.bucket")],
|
||||
['Endpoint', config("backups.disks.$driver.endpoint")],
|
||||
['Use path style endpoint', config("backups.disks.$driver.use_path_style_endpoint") ? 'Yes' : 'No'],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
], 'compact');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -215,7 +215,7 @@ class Handler extends ExceptionHandler
|
||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||
->all(),
|
||||
'previous' => Collection::make($this->extractPrevious($e))
|
||||
->map(fn ($exception) => $e->getTrace())
|
||||
->map(fn ($exception) => $exception->getTrace())
|
||||
->map(fn ($trace) => Arr::except($trace, ['args']))
|
||||
->all(),
|
||||
],
|
||||
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Helper;
|
||||
|
||||
class CdnVersionFetchingException extends \Exception
|
||||
{
|
||||
}
|
@ -71,9 +71,7 @@ class CreateApiKey extends CreateRecord
|
||||
->placeholder('Example: 127.0.0.1 or 192.168.1.1')
|
||||
->label('Whitelisted IPv4 Addresses')
|
||||
->helperText('Press enter to add a new IP address or leave blank to allow any IP address')
|
||||
->columnSpanFull()
|
||||
->hidden()
|
||||
->default(null),
|
||||
->columnSpanFull(),
|
||||
|
||||
Forms\Components\Textarea::make('memo')
|
||||
->required()
|
||||
|
@ -7,6 +7,7 @@ use App\Models\Egg;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
|
||||
@ -201,12 +202,13 @@ class EditEgg extends EditRecord
|
||||
Actions\DeleteAction::make()
|
||||
->disabled(fn (Egg $egg): bool => $egg->servers()->count() > 0)
|
||||
->label(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? 'Delete Egg' : 'Egg In Use'),
|
||||
Actions\ExportAction::make()
|
||||
Actions\Action::make('export')
|
||||
->icon('tabler-download')
|
||||
->label('Export Egg')
|
||||
->color('primary')
|
||||
// TODO uses old admin panel export service
|
||||
->url(fn (Egg $egg): string => route('admin.eggs.export', ['egg' => $egg['id']])),
|
||||
->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
|
||||
echo $service->handle($egg->id);
|
||||
}, 'egg-' . $egg->getKebabName() . '.json')),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace App\Filament\Resources\EggResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EggResource;
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
@ -22,19 +23,20 @@ class ListEggs extends ListRecords
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->searchable(false)
|
||||
->searchable(true)
|
||||
->defaultPaginationPageOption(25)
|
||||
->checkIfRecordIsSelectableUsing(fn (Egg $egg) => $egg->servers_count <= 0)
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')
|
||||
->label('Id')
|
||||
->hidden()
|
||||
->searchable(),
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->icon('tabler-egg')
|
||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
->wrap()
|
||||
->searchable(),
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('servers_count')
|
||||
->counts('servers')
|
||||
->icon('tabler-server')
|
||||
@ -42,12 +44,13 @@ class ListEggs extends ListRecords
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\ExportAction::make()
|
||||
Tables\Actions\Action::make('export')
|
||||
->icon('tabler-download')
|
||||
->label('Export')
|
||||
->color('primary')
|
||||
// TODO uses old admin panel export service
|
||||
->url(fn (Egg $egg): string => route('admin.eggs.export', ['egg' => $egg])),
|
||||
->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
|
||||
echo $service->handle($egg->id);
|
||||
}, 'egg-' . $egg->getKebabName() . '.json')),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
@ -88,7 +91,6 @@ class ListEggs extends ListRecords
|
||||
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
|
||||
/** @var EggImporterService $eggImportService */
|
||||
$eggImportService = resolve(EggImporterService::class);
|
||||
|
||||
|
@ -212,6 +212,7 @@ class CreateNode extends CreateRecord
|
||||
false => 'success',
|
||||
]),
|
||||
Forms\Components\ToggleButtons::make('public')
|
||||
->default(true)
|
||||
->columnSpan(1)
|
||||
->label('Automatic Allocation')->inline()
|
||||
->options([
|
||||
@ -238,7 +239,7 @@ class CreateNode extends CreateRecord
|
||||
->default(256)
|
||||
->minValue(1)
|
||||
->maxValue(1024)
|
||||
->suffix('MiB'),
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||
Forms\Components\TextInput::make('daemon_sftp')
|
||||
->columnSpan(1)
|
||||
->label('SFTP Port')
|
||||
@ -274,7 +275,7 @@ class CreateNode extends CreateRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_mem'))
|
||||
->label('Memory Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
@ -315,7 +316,7 @@ class CreateNode extends CreateRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_disk'))
|
||||
->label('Disk Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
|
@ -214,7 +214,7 @@ class EditNode extends EditRecord
|
||||
->numeric()->required()
|
||||
->minValue(1)
|
||||
->maxValue(1024)
|
||||
->suffix('MiB'),
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||
Forms\Components\TextInput::make('daemon_sftp')
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 3])
|
||||
->label('SFTP Port')
|
||||
@ -274,7 +274,7 @@ class EditNode extends EditRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_mem'))
|
||||
->label('Memory Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->required()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
|
||||
->numeric()
|
||||
@ -314,7 +314,7 @@ class EditNode extends EditRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_disk'))
|
||||
->label('Disk Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->required()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
|
||||
->numeric()
|
||||
@ -395,10 +395,11 @@ class EditNode extends EditRecord
|
||||
->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()
|
||||
),
|
||||
->action(function (NodeUpdateService $nodeUpdateService, Node $node) {
|
||||
$nodeUpdateService->handle($node, [], true);
|
||||
Notification::make()->success()->title('Daemon Key Reset')->send();
|
||||
$this->fillForm();
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
@ -42,15 +42,15 @@ class ListNodes extends ListRecords
|
||||
->visibleFrom('sm')
|
||||
->icon('tabler-device-desktop-analytics')
|
||||
->numeric()
|
||||
->suffix(' GiB')
|
||||
->formatStateUsing(fn ($state) => number_format($state / 1024, 2))
|
||||
->suffix(config('panel.use_binary_prefix') ? ' GiB' : ' GB')
|
||||
->formatStateUsing(fn ($state) => number_format($state / (config('panel.use_binary_prefix') ? 1024 : 1000), 2))
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('disk')
|
||||
->visibleFrom('sm')
|
||||
->icon('tabler-file')
|
||||
->numeric()
|
||||
->suffix(' GiB')
|
||||
->formatStateUsing(fn ($state) => number_format($state / 1024, 2))
|
||||
->suffix(config('panel.use_binary_prefix') ? ' GiB' : ' GB')
|
||||
->formatStateUsing(fn ($state) => number_format($state / (config('panel.use_binary_prefix') ? 1024 : 1000), 2))
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('cpu')
|
||||
->visibleFrom('sm')
|
||||
|
@ -290,7 +290,6 @@ class CreateServer extends CreateRecord
|
||||
|
||||
$components = [$text, $select];
|
||||
|
||||
/** @var Forms\Components\Component $component */
|
||||
foreach ($components as &$component) {
|
||||
$component = $component
|
||||
->live(onBlur: true)
|
||||
@ -354,7 +353,7 @@ class CreateServer extends CreateRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_mem'))
|
||||
->label('Memory Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->default(0)
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
@ -385,7 +384,7 @@ class CreateServer extends CreateRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_disk'))
|
||||
->label('Disk Space Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->default(0)
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
@ -441,6 +440,7 @@ class CreateServer extends CreateRecord
|
||||
'unlimited' => -1,
|
||||
'disabled' => 0,
|
||||
'limited' => 128,
|
||||
default => throw new \LogicException('Invalid state'),
|
||||
};
|
||||
|
||||
$set('swap', $value);
|
||||
@ -460,11 +460,11 @@ class CreateServer extends CreateRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => match ($get('swap_support')) {
|
||||
'disabled', 'unlimited' => true,
|
||||
'limited' => false,
|
||||
default => false,
|
||||
})
|
||||
->label('Swap Memory')
|
||||
->default(0)
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->minValue(-1)
|
||||
->columnSpan(2)
|
||||
->inlineLabel()
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources\ServerResource\Pages;
|
||||
|
||||
use LogicException;
|
||||
use App\Filament\Resources\ServerResource;
|
||||
use App\Http\Controllers\Admin\ServersController;
|
||||
use App\Services\Servers\RandomWordService;
|
||||
@ -219,7 +220,7 @@ class EditServer extends EditRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_mem'))
|
||||
->label('Memory Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
@ -249,7 +250,7 @@ class EditServer extends EditRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => $get('unlimited_disk'))
|
||||
->label('Disk Space Limit')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
@ -299,6 +300,7 @@ class EditServer extends EditRecord
|
||||
'unlimited' => -1,
|
||||
'disabled' => 0,
|
||||
'limited' => 128,
|
||||
default => throw new LogicException('Invalid state')
|
||||
};
|
||||
|
||||
$set('swap', $value);
|
||||
@ -308,6 +310,7 @@ class EditServer extends EditRecord
|
||||
$get('swap') > 0 => 'limited',
|
||||
$get('swap') == 0 => 'disabled',
|
||||
$get('swap') < 0 => 'unlimited',
|
||||
default => throw new LogicException('Invalid state')
|
||||
};
|
||||
})
|
||||
->options([
|
||||
@ -325,10 +328,10 @@ class EditServer extends EditRecord
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Forms\Get $get) => match ($get('swap_support')) {
|
||||
'disabled', 'unlimited', true => true,
|
||||
'limited', false => false,
|
||||
default => false,
|
||||
})
|
||||
->label('Swap Memory')->inlineLabel()
|
||||
->suffix('MiB')
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->minValue(-1)
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
@ -553,7 +556,6 @@ class EditServer extends EditRecord
|
||||
|
||||
$components = [$text, $select];
|
||||
|
||||
/** @var Forms\Components\Component $component */
|
||||
foreach ($components as &$component) {
|
||||
$component = $component
|
||||
->live(onBlur: true)
|
||||
@ -606,7 +608,7 @@ class EditServer extends EditRecord
|
||||
->action(function (ServersController $serversController, Server $server) {
|
||||
$serversController->toggleInstall($server);
|
||||
|
||||
return $this->refreshFormData(['status', 'docker']);
|
||||
$this->refreshFormData(['status', 'docker']);
|
||||
}),
|
||||
])->fullWidth(),
|
||||
Forms\Components\ToggleButtons::make('')
|
||||
@ -624,7 +626,7 @@ class EditServer extends EditRecord
|
||||
$suspensionService->toggle($server, 'suspend');
|
||||
Notification::make()->success()->title('Server Suspended!')->send();
|
||||
|
||||
return $this->refreshFormData(['status', 'docker']);
|
||||
$this->refreshFormData(['status', 'docker']);
|
||||
}),
|
||||
Forms\Components\Actions\Action::make('toggleUnsuspend')
|
||||
->label('Unsuspend')
|
||||
@ -634,7 +636,7 @@ class EditServer extends EditRecord
|
||||
$suspensionService->toggle($server, 'unsuspend');
|
||||
Notification::make()->success()->title('Server Unsuspended!')->send();
|
||||
|
||||
return $this->refreshFormData(['status', 'docker']);
|
||||
$this->refreshFormData(['status', 'docker']);
|
||||
}),
|
||||
])->fullWidth(),
|
||||
Forms\Components\ToggleButtons::make('')
|
||||
@ -650,7 +652,7 @@ class EditServer extends EditRecord
|
||||
Forms\Components\Actions::make([
|
||||
Forms\Components\Actions\Action::make('transfer')
|
||||
->label('Transfer Soon™')
|
||||
->action(fn (TransferServerService $transfer, Server $server) => $transfer->handle($server, $data))
|
||||
->action(fn (TransferServerService $transfer, Server $server) => $transfer->handle($server, []))
|
||||
->disabled() //TODO!
|
||||
->form([ //TODO!
|
||||
Forms\Components\Select::make('newNode')
|
||||
|
@ -22,13 +22,7 @@ class ListServers extends ListRecords
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->default('unknown')
|
||||
->badge()
|
||||
->default(function (Server $server) {
|
||||
if ($server->status !== null) {
|
||||
return $server->status;
|
||||
}
|
||||
|
||||
return $server->retrieveStatus() ?? 'node_fail';
|
||||
})
|
||||
->default(fn (Server $server) => $server->status ?? $server->retrieveStatus())
|
||||
->icon(fn ($state) => match ($state) {
|
||||
'node_fail' => 'tabler-server-off',
|
||||
'running' => 'tabler-heartbeat',
|
||||
@ -58,11 +52,13 @@ class ListServers extends ListRecords
|
||||
Tables\Columns\TextColumn::make('node.name')
|
||||
->icon('tabler-server-2')
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
|
||||
->sortable(),
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('egg.name')
|
||||
->icon('tabler-egg')
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
|
||||
->sortable(),
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('user.username')
|
||||
->icon('tabler-user')
|
||||
->label('Owner')
|
||||
@ -77,9 +73,6 @@ class ListServers extends ListRecords
|
||||
->numeric()
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\Action::make('View')
|
||||
->icon('tabler-terminal')
|
||||
@ -87,6 +80,7 @@ class ListServers extends ListRecords
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->emptyStateIcon('tabler-brand-docker')
|
||||
->searchable()
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading('No Servers')
|
||||
->emptyStateActions([
|
||||
|
@ -193,8 +193,10 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
->schema([
|
||||
Grid::make('asdf')->columns(5)->schema([
|
||||
Section::make('Create API Key')->columnSpan(3)->schema([
|
||||
TextInput::make('description')->required(),
|
||||
TextInput::make('description')
|
||||
->live(),
|
||||
TagsInput::make('allowed_ips')
|
||||
->live()
|
||||
->splitKeys([',', ' ', 'Tab'])
|
||||
->placeholder('Example: 127.0.0.1 or 192.168.1.1')
|
||||
->label('Whitelisted IP\'s')
|
||||
@ -202,6 +204,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
->columnSpanFull(),
|
||||
])->headerActions([
|
||||
Action::make('Create')
|
||||
->disabled(fn (Get $get) => $get('description') === null)
|
||||
->successRedirectUrl(route('filament.admin.auth.profile', ['tab' => '-api-keys-tab']))
|
||||
->action(function (Get $get, Action $action, $user) {
|
||||
$token = $user->createToken(
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin\Eggs;
|
||||
|
||||
use App\Exceptions\Service\Egg\NoParentConfigurationFoundException;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Egg;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Eggs\EggUpdateService;
|
||||
use App\Services\Eggs\EggCreationService;
|
||||
use App\Http\Requests\Admin\Egg\EggFormRequest;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class EggController extends Controller
|
||||
{
|
||||
@ -19,8 +19,6 @@ class EggController extends Controller
|
||||
*/
|
||||
public function __construct(
|
||||
protected AlertsMessageBag $alert,
|
||||
protected EggCreationService $creationService,
|
||||
protected EggUpdateService $updateService,
|
||||
protected ViewFactory $view
|
||||
) {
|
||||
}
|
||||
@ -58,7 +56,16 @@ class EggController extends Controller
|
||||
$data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null);
|
||||
$data['author'] = $request->user()->email;
|
||||
|
||||
$egg = $this->creationService->handle($data);
|
||||
$data['config_from'] = array_get($data, 'config_from');
|
||||
if (!is_null($data['config_from'])) {
|
||||
$parentEgg = Egg::query()->find(array_get($data, 'config_from'));
|
||||
throw_unless($parentEgg, new NoParentConfigurationFoundException(trans('exceptions.egg.invalid_copy_id')));
|
||||
}
|
||||
|
||||
$egg = Egg::query()->create(array_merge($data, [
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
]));
|
||||
|
||||
$this->alert->success(trans('admin/eggs.notices.egg_created'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', $egg->id);
|
||||
@ -90,7 +97,13 @@ class EggController extends Controller
|
||||
$data = $request->validated();
|
||||
$data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null);
|
||||
|
||||
$this->updateService->handle($egg, $data);
|
||||
$eggId = array_get($data, 'config_from');
|
||||
$copiedFromEgg = Egg::query()->find($eggId);
|
||||
|
||||
throw_unless($copiedFromEgg, new NoParentConfigurationFoundException(trans('exceptions.egg.invalid_copy_id')));
|
||||
|
||||
$egg->update($data);
|
||||
|
||||
$this->alert->success(trans('admin/eggs.notices.updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', $egg->id);
|
||||
|
@ -10,7 +10,6 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use App\Http\Requests\Admin\Egg\EggImportFormRequest;
|
||||
use App\Services\Eggs\Sharing\EggUpdateImporterService;
|
||||
|
||||
class EggShareController extends Controller
|
||||
{
|
||||
@ -21,7 +20,6 @@ class EggShareController extends Controller
|
||||
protected AlertsMessageBag $alert,
|
||||
protected EggExporterService $exporterService,
|
||||
protected EggImporterService $importerService,
|
||||
protected EggUpdateImporterService $updateImporterService
|
||||
) {
|
||||
}
|
||||
|
||||
@ -61,7 +59,7 @@ class EggShareController extends Controller
|
||||
*/
|
||||
public function update(EggImportFormRequest $request, Egg $egg): RedirectResponse
|
||||
{
|
||||
$this->updateImporterService->fromFile($egg, $request->file('import_file'));
|
||||
$this->importerService->fromFile($request->file('import_file'), $egg);
|
||||
$this->alert->success(trans('admin/eggs.notices.updated_via_import'))->flash();
|
||||
|
||||
return redirect()->route('admin.eggs.view', ['egg' => $egg]);
|
||||
|
@ -73,11 +73,11 @@ class ServerManagementController extends ApplicationApiController
|
||||
|
||||
if ($this->transferServerService->handle($server, $validatedData)) {
|
||||
// Transfer started
|
||||
$this->returnNoContent();
|
||||
} else {
|
||||
// Node was not viable
|
||||
return new Response('', Response::HTTP_NOT_ACCEPTABLE);
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
|
||||
// Node was not viable
|
||||
return new Response('', Response::HTTP_NOT_ACCEPTABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
61
app/Http/Controllers/Auth/OAuthController.php
Normal file
61
app/Http/Controllers/Auth/OAuthController.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Services\Users\UserUpdateService;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class OAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* OAuthController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AuthManager $auth,
|
||||
private UserUpdateService $updateService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect user to the OAuth provider
|
||||
*/
|
||||
protected function redirect(string $driver): RedirectResponse
|
||||
{
|
||||
return Socialite::with($driver)->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from OAuth provider.
|
||||
*/
|
||||
protected function callback(Request $request, string $driver): RedirectResponse
|
||||
{
|
||||
$oauthUser = Socialite::driver($driver)->user();
|
||||
|
||||
// User is already logged in and wants to link a new OAuth Provider
|
||||
if ($request->user()) {
|
||||
$oauth = $request->user()->oauth;
|
||||
$oauth[$driver] = $oauthUser->getId();
|
||||
|
||||
$this->updateService->handle($request->user(), ['oauth' => $oauth]);
|
||||
|
||||
return redirect()->route('account');
|
||||
}
|
||||
|
||||
try {
|
||||
$user = User::query()->whereJsonContains('oauth->'. $driver, $oauthUser->getId())->firstOrFail();
|
||||
|
||||
$this->auth->guard()->login($user, true);
|
||||
} catch (Exception $e) {
|
||||
// No user found - redirect to normal login
|
||||
return redirect()->route('auth.login');
|
||||
}
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
44
app/Http/Controllers/Base/OAuthController.php
Normal file
44
app/Http/Controllers/Base/OAuthController.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Base;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Users\UserUpdateService;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class OAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* OAuthController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private UserUpdateService $updateService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Link a new OAuth
|
||||
*/
|
||||
protected function link(Request $request): RedirectResponse
|
||||
{
|
||||
$driver = $request->get('driver');
|
||||
|
||||
return Socialite::with($driver)->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a OAuth link
|
||||
*/
|
||||
protected function unlink(Request $request): Response
|
||||
{
|
||||
$oauth = $request->user()->oauth;
|
||||
unset($oauth[$request->get('driver')]);
|
||||
|
||||
$this->updateService->handle($request->user(), ['oauth' => $oauth]);
|
||||
|
||||
return new Response('', Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
@ -7,14 +7,13 @@ use App\Models\Mount;
|
||||
class UpdateMountRequest extends StoreMountRequest
|
||||
{
|
||||
/**
|
||||
* Apply validation rules to this request. Uses the parent class rules()
|
||||
* function but passes in the rules for updating rather than creating.
|
||||
* Apply validation rules to this request.
|
||||
*/
|
||||
public function rules(array $rules = null): array
|
||||
{
|
||||
/** @var Mount $mount */
|
||||
$mount = $this->route()->parameter('mount');
|
||||
|
||||
return parent::rules(Mount::getRulesForUpdate($mount->id));
|
||||
return Mount::getRulesForUpdate($mount->id);
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ class StoreServerRequest extends ApplicationApiRequest
|
||||
'description' => array_merge(['nullable'], $rules['description']),
|
||||
'user' => $rules['owner_id'],
|
||||
'egg' => $rules['egg_id'],
|
||||
'docker_image' => $rules['image'],
|
||||
'startup' => $rules['startup'],
|
||||
'docker_image' => 'sometimes|string',
|
||||
'startup' => 'sometimes|string',
|
||||
'environment' => 'present|array',
|
||||
'skip_scripts' => 'sometimes|boolean',
|
||||
'oom_killer' => 'sometimes|boolean',
|
||||
|
@ -20,10 +20,10 @@ class UpdateServerStartupRequest extends ApplicationApiRequest
|
||||
$data = Server::getRulesForUpdate($this->parameter('server', Server::class));
|
||||
|
||||
return [
|
||||
'startup' => $data['startup'],
|
||||
'startup' => 'sometimes|string',
|
||||
'environment' => 'present|array',
|
||||
'egg' => $data['egg_id'],
|
||||
'image' => $data['image'],
|
||||
'image' => 'sometimes|string',
|
||||
'skip_scripts' => 'present|boolean',
|
||||
];
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ class AssetComposer
|
||||
'enabled' => config('recaptcha.enabled', false),
|
||||
'siteKey' => config('recaptcha.website_key') ?? '',
|
||||
],
|
||||
'usesSyncDriver' => config('queue.default') === 'sync',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,55 @@ use Webmozart\Assert\Assert;
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* App\Models\ApiKey.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $key_type
|
||||
* @property string $identifier
|
||||
* @property string $token
|
||||
* @property array $allowed_ips
|
||||
* @property string|null $memo
|
||||
* @property \Illuminate\Support\Carbon|null $last_used_at
|
||||
* @property \Illuminate\Support\Carbon|null $expires_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property int $r_servers
|
||||
* @property int $r_nodes
|
||||
* @property int $r_allocations
|
||||
* @property int $r_users
|
||||
* @property int $r_eggs
|
||||
* @property int $r_database_hosts
|
||||
* @property int $r_server_databases
|
||||
* @property int $r_mounts
|
||||
* @property \App\Models\User $tokenable
|
||||
* @property \App\Models\User $user
|
||||
*
|
||||
* @method static \Database\Factories\ApiKeyFactory factory(...$parameters)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereAllowedIps($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereIdentifier($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereKeyType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereLastUsedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereMemo($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRAllocations($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRDatabaseHosts($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereREggs($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNodes($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServerDatabases($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServers($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRUsers($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereToken($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUserId($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ApiKey extends Model
|
||||
{
|
||||
/**
|
||||
@ -64,6 +113,13 @@ class ApiKey extends Model
|
||||
'r_' . AdminAcl::RESOURCE_MOUNTS,
|
||||
];
|
||||
|
||||
/**
|
||||
* Default attributes when creating a new model.
|
||||
*/
|
||||
protected $attributes = [
|
||||
'allowed_ips' => '[]',
|
||||
];
|
||||
|
||||
/**
|
||||
* Fields that should not be included when calling toArray() or toJson()
|
||||
* on this model.
|
||||
@ -79,7 +135,7 @@ class ApiKey extends Model
|
||||
'identifier' => 'required|string|size:16|unique:api_keys,identifier',
|
||||
'token' => 'required|string',
|
||||
'memo' => 'required|nullable|string|max:500',
|
||||
'allowed_ips' => 'nullable|array',
|
||||
'allowed_ips' => 'array',
|
||||
'allowed_ips.*' => 'string',
|
||||
'last_used_at' => 'nullable|date',
|
||||
'expires_at' => 'nullable|date',
|
||||
|
@ -2,10 +2,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\EndpointCollection;
|
||||
use App\Enums\ServerState;
|
||||
use App\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
use App\Models\Objects\Endpoint;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@ -18,6 +16,93 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use App\Exceptions\Http\Server\ServerStateConflictException;
|
||||
|
||||
/**
|
||||
* \App\Models\Server.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $external_id
|
||||
* @property string $uuid
|
||||
* @property string $uuid_short
|
||||
* @property int $node_id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property ServerState|null $status
|
||||
* @property bool $skip_scripts
|
||||
* @property int $owner_id
|
||||
* @property int $memory
|
||||
* @property int $swap
|
||||
* @property int $disk
|
||||
* @property int $io
|
||||
* @property int $cpu
|
||||
* @property string|null $threads
|
||||
* @property bool $oom_killer
|
||||
* @property int $allocation_id
|
||||
* @property int $egg_id
|
||||
* @property string $startup
|
||||
* @property string $image
|
||||
* @property int|null $allocation_limit
|
||||
* @property int|null $database_limit
|
||||
* @property int $backup_limit
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $installed_at
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\ActivityLog[] $activity
|
||||
* @property int|null $activity_count
|
||||
* @property \App\Models\Allocation|null $allocation
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Allocation[] $allocations
|
||||
* @property int|null $allocations_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Backup[] $backups
|
||||
* @property int|null $backups_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Database[] $databases
|
||||
* @property int|null $databases_count
|
||||
* @property \App\Models\Egg|null $egg
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Mount[] $mounts
|
||||
* @property int|null $mounts_count
|
||||
* @property \App\Models\Node $node
|
||||
* @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
|
||||
* @property int|null $notifications_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Schedule[] $schedules
|
||||
* @property int|null $schedules_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Subuser[] $subusers
|
||||
* @property int|null $subusers_count
|
||||
* @property \App\Models\ServerTransfer|null $transfer
|
||||
* @property \App\Models\User $user
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\EggVariable[] $variables
|
||||
* @property int|null $variables_count
|
||||
*
|
||||
* @method static \Database\Factories\ServerFactory factory(...$parameters)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationLimit($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereBackupLimit($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereCpu($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereDatabaseLimit($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereDisk($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereEggId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereExternalId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereImage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereIo($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereMemory($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereNodeId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereOomKiller($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereOwnerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereSkipScripts($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereStartup($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereSwap($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereThreads($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereUuid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Server whereuuid_short($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Server extends Model
|
||||
{
|
||||
use Notifiable;
|
||||
@ -43,6 +128,11 @@ class Server extends Model
|
||||
'installed_at' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* The default relationships to load for all server models.
|
||||
*/
|
||||
protected $with = ['allocation'];
|
||||
|
||||
/**
|
||||
* Fields that are not mass assignable.
|
||||
*/
|
||||
@ -62,6 +152,7 @@ class Server extends Model
|
||||
'threads' => 'nullable|regex:/^[0-9-,]+$/',
|
||||
'oom_killer' => 'sometimes|boolean',
|
||||
'disk' => 'required|numeric|min:0',
|
||||
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
|
||||
'egg_id' => 'required|exists:eggs,id',
|
||||
'startup' => 'required|string',
|
||||
'skip_scripts' => 'sometimes|boolean',
|
||||
@ -84,33 +175,27 @@ class Server extends Model
|
||||
'io' => 'integer',
|
||||
'cpu' => 'integer',
|
||||
'oom_killer' => 'boolean',
|
||||
'allocation_id' => 'integer',
|
||||
'egg_id' => 'integer',
|
||||
'database_limit' => 'integer',
|
||||
'allocation_limit' => 'integer',
|
||||
'backup_limit' => 'integer',
|
||||
self::CREATED_AT => 'datetime',
|
||||
self::UPDATED_AT => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
'installed_at' => 'datetime',
|
||||
'docker_labels' => 'array',
|
||||
'ports' => EndpointCollection::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the format for server allocations when communicating with the Daemon.
|
||||
*/
|
||||
public function getPortMappings(): array
|
||||
public function getAllocationMappings(): array
|
||||
{
|
||||
$defaultIp = '0.0.0.0';
|
||||
|
||||
$ports = collect($this->ports)
|
||||
->map(fn ($port) => str_contains($port, ':') ? $port : "$defaultIp:$port")
|
||||
->mapToGroups(function ($port) {
|
||||
[$ip, $port] = explode(':', $port);
|
||||
|
||||
return [$ip => (int) $port];
|
||||
});
|
||||
|
||||
return $ports->all();
|
||||
return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) {
|
||||
return $item->pluck('port');
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
public function isInstalled(): bool
|
||||
@ -139,6 +224,22 @@ class Server extends Model
|
||||
return $this->hasMany(Subuser::class, 'server_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default allocation for a server.
|
||||
*/
|
||||
public function allocation(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Allocation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all allocations associated with this server.
|
||||
*/
|
||||
public function allocations(): HasMany
|
||||
{
|
||||
return $this->hasMany(Allocation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information for the egg associated with this server.
|
||||
*/
|
||||
@ -309,17 +410,4 @@ class Server extends Model
|
||||
|
||||
return cache()->get("servers.$this->uuid.container.status") ?? 'missing';
|
||||
}
|
||||
|
||||
public function getPrimaryEndpoint(): ?Endpoint
|
||||
{
|
||||
$endpoint = $this->ports->first();
|
||||
|
||||
$portEggVariable = $this->variables->firstWhere('env_variable', 'SERVER_PORT');
|
||||
if ($portEggVariable) {
|
||||
$portServerVariable = $this->serverVariables->firstWhere('variable_id', $portEggVariable->id);
|
||||
$endpoint = new Endpoint($portServerVariable->variable_value);
|
||||
}
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,65 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
|
||||
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||
use App\Notifications\SendPasswordReset as ResetPasswordNotification;
|
||||
|
||||
/**
|
||||
* App\Models\User.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $external_id
|
||||
* @property string $uuid
|
||||
* @property string $username
|
||||
* @property string $email
|
||||
* @property string|null $name_first
|
||||
* @property string|null $name_last
|
||||
* @property string $password
|
||||
* @property string|null $remember_token
|
||||
* @property string $language
|
||||
* @property bool $root_admin
|
||||
* @property bool $use_totp
|
||||
* @property string|null $totp_secret
|
||||
* @property \Illuminate\Support\Carbon|null $totp_authenticated_at
|
||||
* @property array $oauth
|
||||
* @property bool $gravatar
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\ApiKey[] $apiKeys
|
||||
* @property int|null $api_keys_count
|
||||
* @property string $name
|
||||
* @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
|
||||
* @property int|null $notifications_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\RecoveryToken[] $recoveryTokens
|
||||
* @property int|null $recovery_tokens_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Server[] $servers
|
||||
* @property int|null $servers_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\UserSSHKey[] $sshKeys
|
||||
* @property int|null $ssh_keys_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\ApiKey[] $tokens
|
||||
* @property int|null $tokens_count
|
||||
*
|
||||
* @method static \Database\Factories\UserFactory factory(...$parameters)
|
||||
* @method static Builder|User newModelQuery()
|
||||
* @method static Builder|User newQuery()
|
||||
* @method static Builder|User query()
|
||||
* @method static Builder|User whereCreatedAt($value)
|
||||
* @method static Builder|User whereEmail($value)
|
||||
* @method static Builder|User whereExternalId($value)
|
||||
* @method static Builder|User whereGravatar($value)
|
||||
* @method static Builder|User whereId($value)
|
||||
* @method static Builder|User whereLanguage($value)
|
||||
* @method static Builder|User whereNameFirst($value)
|
||||
* @method static Builder|User whereNameLast($value)
|
||||
* @method static Builder|User wherePassword($value)
|
||||
* @method static Builder|User whereRememberToken($value)
|
||||
* @method static Builder|User whereRootAdmin($value)
|
||||
* @method static Builder|User whereTotpAuthenticatedAt($value)
|
||||
* @method static Builder|User whereTotpSecret($value)
|
||||
* @method static Builder|User whereUpdatedAt($value)
|
||||
* @method static Builder|User whereUseTotp($value)
|
||||
* @method static Builder|User whereUsername($value)
|
||||
* @method static Builder|User whereUuid($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName
|
||||
{
|
||||
use Authenticatable;
|
||||
@ -69,12 +128,13 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
'totp_authenticated_at',
|
||||
'gravatar',
|
||||
'root_admin',
|
||||
'oauth',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*/
|
||||
protected $hidden = ['password', 'remember_token', 'totp_secret', 'totp_authenticated_at'];
|
||||
protected $hidden = ['password', 'remember_token', 'totp_secret', 'totp_authenticated_at', 'oauth'];
|
||||
|
||||
/**
|
||||
* Default values for specific fields in the database.
|
||||
@ -87,6 +147,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
'totp_secret' => null,
|
||||
'name_first' => '',
|
||||
'name_last' => '',
|
||||
'oauth' => '[]',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -104,6 +165,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
'language' => 'string',
|
||||
'use_totp' => 'boolean',
|
||||
'totp_secret' => 'nullable|string',
|
||||
'oauth' => 'array',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
@ -114,6 +176,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
'gravatar' => 'boolean',
|
||||
'totp_authenticated_at' => 'datetime',
|
||||
'totp_secret' => 'encrypted',
|
||||
'oauth' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@ -81,6 +82,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
Scramble::registerApi('application', ['api_path' => 'api/application', 'info' => ['version' => '1.0']]);
|
||||
Scramble::registerApi('client', ['api_path' => 'api/client', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);
|
||||
Scramble::registerApi('remote', ['api_path' => 'api/remote', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);
|
||||
|
||||
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
|
||||
$event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,9 +25,9 @@ class DaemonServerRepository extends DaemonRepository
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
try {
|
||||
$response = $this->getHttpClient()->get(
|
||||
return $this->getHttpClient()->get(
|
||||
sprintf('/api/servers/%s', $this->server->uuid)
|
||||
)->throw();
|
||||
)->throw()->json();
|
||||
} catch (RequestException $exception) {
|
||||
$cfId = $exception->response->header('Cf-Ray');
|
||||
$cfCache = $exception->response->header('Cf-Cache-Status');
|
||||
@ -48,7 +48,7 @@ class DaemonServerRepository extends DaemonRepository
|
||||
report($exception);
|
||||
}
|
||||
|
||||
return $response?->json() ?? ['state' => ContainerStatus::Missing->value];
|
||||
return ['state' => ContainerStatus::Missing->value];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Eggs;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use App\Models\Egg;
|
||||
use App\Exceptions\Service\Egg\NoParentConfigurationFoundException;
|
||||
|
||||
class EggCreationService
|
||||
{
|
||||
/**
|
||||
* Create a new egg.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws \App\Exceptions\Service\Egg\NoParentConfigurationFoundException
|
||||
*/
|
||||
public function handle(array $data): Egg
|
||||
{
|
||||
$data['config_from'] = array_get($data, 'config_from');
|
||||
if (!is_null($data['config_from'])) {
|
||||
$parentEgg = Egg::query()->find(array_get($data, 'config_from'));
|
||||
throw_unless($parentEgg, new NoParentConfigurationFoundException(trans('exceptions.egg.invalid_copy_id')));
|
||||
}
|
||||
|
||||
return Egg::query()->create(array_merge($data, [
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
]));
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Eggs;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Models\Egg;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Exceptions\Service\InvalidFileUploadException;
|
||||
|
||||
class EggParserService
|
||||
{
|
||||
public const UPGRADE_VARIABLES = [
|
||||
'server.build.env.SERVER_IP' => 'server.allocations.default.ip',
|
||||
'server.build.default.ip' => 'server.allocations.default.ip',
|
||||
'server.build.env.SERVER_PORT' => 'server.allocations.default.port',
|
||||
'server.build.default.port' => 'server.allocations.default.port',
|
||||
'server.build.env.SERVER_MEMORY' => 'server.build.memory_limit',
|
||||
'server.build.memory' => 'server.build.memory_limit',
|
||||
'server.build.env.' => 'server.environment.',
|
||||
'server.build.environment.' => 'server.environment.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Takes an uploaded file and parses out the egg configuration from within.
|
||||
*
|
||||
* @throws \JsonException
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException
|
||||
*/
|
||||
public function handle(UploadedFile $file): array
|
||||
{
|
||||
if ($file->getError() !== UPLOAD_ERR_OK) {
|
||||
throw new InvalidFileUploadException('The selected file was not uploaded successfully');
|
||||
}
|
||||
|
||||
$parsed = json_decode($file->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
$version = $parsed['meta']['version'] ?? '';
|
||||
|
||||
$parsed = match ($version) {
|
||||
'PTDL_v1' => $this->convertToV2($parsed),
|
||||
'PTDL_v2' => $parsed,
|
||||
default => throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.')
|
||||
};
|
||||
|
||||
// Make sure we only use recent variable format from now on
|
||||
$parsed['config']['files'] = str_replace(
|
||||
array_keys(self::UPGRADE_VARIABLES),
|
||||
array_values(self::UPGRADE_VARIABLES),
|
||||
$parsed['config']['files'] ?? '',
|
||||
);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the provided model with the parsed JSON data.
|
||||
*/
|
||||
public function fillFromParsed(Egg $model, array $parsed): Egg
|
||||
{
|
||||
return $model->forceFill([
|
||||
'name' => Arr::get($parsed, 'name'),
|
||||
'description' => Arr::get($parsed, 'description'),
|
||||
'features' => Arr::get($parsed, 'features'),
|
||||
'docker_images' => Arr::get($parsed, 'docker_images'),
|
||||
'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist'))
|
||||
->filter(fn ($value) => !empty($value)),
|
||||
'update_url' => Arr::get($parsed, 'meta.update_url'),
|
||||
'config_files' => Arr::get($parsed, 'config.files'),
|
||||
'config_startup' => Arr::get($parsed, 'config.startup'),
|
||||
'config_logs' => Arr::get($parsed, 'config.logs'),
|
||||
'config_stop' => Arr::get($parsed, 'config.stop'),
|
||||
'startup' => Arr::get($parsed, 'startup'),
|
||||
'script_install' => Arr::get($parsed, 'scripts.installation.script'),
|
||||
'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'),
|
||||
'script_container' => Arr::get($parsed, 'scripts.installation.container'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles
|
||||
* the "docker_images" field potentially not being present, and not being in the
|
||||
* expected "key => value" format.
|
||||
*/
|
||||
protected function convertToV2(array $parsed): array
|
||||
{
|
||||
// Maintain backwards compatability for eggs that are still using the old single image
|
||||
// string format. New eggs can provide an array of Docker images that can be used.
|
||||
if (!isset($parsed['images'])) {
|
||||
$images = [Arr::get($parsed, 'image') ?? 'nil'];
|
||||
} else {
|
||||
$images = $parsed['images'];
|
||||
}
|
||||
|
||||
unset($parsed['images'], $parsed['image']);
|
||||
|
||||
$parsed['docker_images'] = [];
|
||||
foreach ($images as $image) {
|
||||
$parsed['docker_images'][$image] = $image;
|
||||
}
|
||||
|
||||
$parsed['variables'] = array_map(function ($value) {
|
||||
return array_merge($value, ['field_type' => 'text']);
|
||||
}, $parsed['variables']);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Eggs;
|
||||
|
||||
use App\Models\Egg;
|
||||
use App\Exceptions\Service\Egg\NoParentConfigurationFoundException;
|
||||
|
||||
class EggUpdateService
|
||||
{
|
||||
/**
|
||||
* Update an egg.
|
||||
*/
|
||||
public function handle(Egg $egg, array $data): void
|
||||
{
|
||||
$eggId = array_get($data, 'config_from');
|
||||
$copiedFromEgg = Egg::query()->find($eggId);
|
||||
|
||||
throw_unless($copiedFromEgg, new NoParentConfigurationFoundException(trans('exceptions.egg.invalid_copy_id')));
|
||||
|
||||
// TODO: Once the admin UI is done being reworked and this is exposed
|
||||
// in said UI, remove this so that you can actually update the denylist.
|
||||
unset($data['file_denylist']);
|
||||
|
||||
$egg->update($data);
|
||||
}
|
||||
}
|
@ -2,18 +2,30 @@
|
||||
|
||||
namespace App\Services\Eggs\Sharing;
|
||||
|
||||
use App\Exceptions\Service\InvalidFileUploadException;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Models\Egg;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use App\Models\EggVariable;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use App\Services\Eggs\EggParserService;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
|
||||
class EggImporterService
|
||||
{
|
||||
public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser)
|
||||
public const UPGRADE_VARIABLES = [
|
||||
'server.build.env.SERVER_IP' => 'server.allocations.default.ip',
|
||||
'server.build.default.ip' => 'server.allocations.default.ip',
|
||||
'server.build.env.SERVER_PORT' => 'server.allocations.default.port',
|
||||
'server.build.default.port' => 'server.allocations.default.port',
|
||||
'server.build.env.SERVER_MEMORY' => 'server.build.memory_limit',
|
||||
'server.build.memory' => 'server.build.memory_limit',
|
||||
'server.build.env.' => 'server.environment.',
|
||||
'server.build.environment.' => 'server.environment.',
|
||||
];
|
||||
|
||||
public function __construct(protected ConnectionInterface $connection)
|
||||
{
|
||||
}
|
||||
|
||||
@ -22,13 +34,13 @@ class EggImporterService
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function fromFile(UploadedFile $file): Egg
|
||||
public function fromFile(UploadedFile $file, Egg $egg = null): Egg
|
||||
{
|
||||
$parsed = $this->parser->handle($file);
|
||||
$parsed = $this->parseFile($file);
|
||||
|
||||
return $this->connection->transaction(function () use ($parsed) {
|
||||
return $this->connection->transaction(function () use ($egg, $parsed) {
|
||||
$uuid = $parsed['uuid'] ?? Uuid::uuid4()->toString();
|
||||
$egg = Egg::where('uuid', $uuid)->first() ?? new Egg();
|
||||
$egg = $egg ?? Egg::where('uuid', $uuid)->first() ?? new Egg();
|
||||
|
||||
$egg = $egg->forceFill([
|
||||
'uuid' => $uuid,
|
||||
@ -36,23 +48,32 @@ class EggImporterService
|
||||
'copy_script_from' => null,
|
||||
]);
|
||||
|
||||
$egg = $this->parser->fillFromParsed($egg, $parsed);
|
||||
$egg = $this->fillFromParsed($egg, $parsed);
|
||||
$egg->save();
|
||||
|
||||
// Update existing variables or create new ones.
|
||||
foreach ($parsed['variables'] ?? [] as $variable) {
|
||||
EggVariable::query()->forceCreate(array_merge($variable, ['egg_id' => $egg->id]));
|
||||
EggVariable::unguarded(function () use ($egg, $variable) {
|
||||
$egg->variables()->updateOrCreate([
|
||||
'env_variable' => $variable['env_variable'],
|
||||
], Collection::make($variable)->except(['egg_id', 'env_variable'])->toArray());
|
||||
});
|
||||
}
|
||||
|
||||
return $egg;
|
||||
$imported = array_map(fn ($value) => $value['env_variable'], $parsed['variables'] ?? []);
|
||||
|
||||
$egg->variables()->whereNotIn('env_variable', $imported)->delete();
|
||||
|
||||
return $egg->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an url and parse it into a new egg.
|
||||
* Take an url and parse it into a new egg or update an existing one.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function fromUrl(string $url): Egg
|
||||
public function fromUrl(string $url, Egg $egg = null): Egg
|
||||
{
|
||||
$info = pathinfo($url);
|
||||
$tmpDir = TemporaryDirectory::make()->deleteWhenDestroyed();
|
||||
@ -60,6 +81,91 @@ class EggImporterService
|
||||
|
||||
file_put_contents($tmpPath, file_get_contents($url));
|
||||
|
||||
return $this->fromFile(new UploadedFile($tmpPath, $info['basename'], 'application/json'));
|
||||
return $this->fromFile(new UploadedFile($tmpPath, $info['basename'], 'application/json'), $egg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an uploaded file and parses out the egg configuration from within.
|
||||
*
|
||||
* @throws \JsonException
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException
|
||||
*/
|
||||
protected function parseFile(UploadedFile $file): array
|
||||
{
|
||||
if ($file->getError() !== UPLOAD_ERR_OK) {
|
||||
throw new InvalidFileUploadException('The selected file was not uploaded successfully');
|
||||
}
|
||||
|
||||
$parsed = json_decode($file->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
$version = $parsed['meta']['version'] ?? '';
|
||||
|
||||
$parsed = match ($version) {
|
||||
'PTDL_v1' => $this->convertToV2($parsed),
|
||||
'PTDL_v2' => $parsed,
|
||||
default => throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.')
|
||||
};
|
||||
|
||||
// Make sure we only use recent variable format from now on
|
||||
$parsed['config']['files'] = str_replace(
|
||||
array_keys(self::UPGRADE_VARIABLES),
|
||||
array_values(self::UPGRADE_VARIABLES),
|
||||
$parsed['config']['files'] ?? '',
|
||||
);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the provided model with the parsed JSON data.
|
||||
*/
|
||||
protected function fillFromParsed(Egg $model, array $parsed): Egg
|
||||
{
|
||||
return $model->forceFill([
|
||||
'name' => Arr::get($parsed, 'name'),
|
||||
'description' => Arr::get($parsed, 'description'),
|
||||
'features' => Arr::get($parsed, 'features'),
|
||||
'docker_images' => Arr::get($parsed, 'docker_images'),
|
||||
'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist'))
|
||||
->filter(fn ($value) => !empty($value)),
|
||||
'update_url' => Arr::get($parsed, 'meta.update_url'),
|
||||
'config_files' => Arr::get($parsed, 'config.files'),
|
||||
'config_startup' => Arr::get($parsed, 'config.startup'),
|
||||
'config_logs' => Arr::get($parsed, 'config.logs'),
|
||||
'config_stop' => Arr::get($parsed, 'config.stop'),
|
||||
'startup' => Arr::get($parsed, 'startup'),
|
||||
'script_install' => Arr::get($parsed, 'scripts.installation.script'),
|
||||
'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'),
|
||||
'script_container' => Arr::get($parsed, 'scripts.installation.container'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles
|
||||
* the "docker_images" field potentially not being present, and not being in the
|
||||
* expected "key => value" format.
|
||||
*/
|
||||
protected function convertToV2(array $parsed): array
|
||||
{
|
||||
// Maintain backwards compatability for eggs that are still using the old single image
|
||||
// string format. New eggs can provide an array of Docker images that can be used.
|
||||
if (!isset($parsed['images'])) {
|
||||
$images = [Arr::get($parsed, 'image') ?? 'nil'];
|
||||
} else {
|
||||
$images = $parsed['images'];
|
||||
}
|
||||
|
||||
unset($parsed['images'], $parsed['image']);
|
||||
|
||||
$parsed['docker_images'] = [];
|
||||
foreach ($images as $image) {
|
||||
$parsed['docker_images'][$image] = $image;
|
||||
}
|
||||
|
||||
$parsed['variables'] = array_map(function ($value) {
|
||||
return array_merge($value, ['field_type' => 'text']);
|
||||
}, $parsed['variables']);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Eggs\Sharing;
|
||||
|
||||
use App\Models\Egg;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Models\EggVariable;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use App\Services\Eggs\EggParserService;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
|
||||
class EggUpdateImporterService
|
||||
{
|
||||
/**
|
||||
* EggUpdateImporterService constructor.
|
||||
*/
|
||||
public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing Egg using an uploaded JSON file.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function fromFile(Egg $egg, UploadedFile $file): Egg
|
||||
{
|
||||
$parsed = $this->parser->handle($file);
|
||||
|
||||
return $this->connection->transaction(function () use ($egg, $parsed) {
|
||||
$egg = $this->parser->fillFromParsed($egg, $parsed);
|
||||
$egg->save();
|
||||
|
||||
// Update existing variables or create new ones.
|
||||
foreach ($parsed['variables'] ?? [] as $variable) {
|
||||
EggVariable::unguarded(function () use ($egg, $variable) {
|
||||
$egg->variables()->updateOrCreate([
|
||||
'env_variable' => $variable['env_variable'],
|
||||
], Collection::make($variable)->except(['egg_id', 'env_variable'])->toArray());
|
||||
});
|
||||
}
|
||||
|
||||
$imported = array_map(fn ($value) => $value['env_variable'], $parsed['variables'] ?? []);
|
||||
|
||||
$egg->variables()->whereNotIn('env_variable', $imported)->delete();
|
||||
|
||||
return $egg->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing Egg using an url.
|
||||
*
|
||||
* @throws \App\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
*/
|
||||
public function fromUrl(Egg $egg, string $url): Egg
|
||||
{
|
||||
$info = pathinfo($url);
|
||||
$tmpDir = TemporaryDirectory::make()->deleteWhenDestroyed();
|
||||
$tmpPath = $tmpDir->path($info['basename']);
|
||||
|
||||
file_put_contents($tmpPath, file_get_contents($url));
|
||||
|
||||
return $this->fromFile($egg, new UploadedFile($tmpPath, $info['basename'], 'application/json'));
|
||||
}
|
||||
}
|
@ -2,12 +2,11 @@
|
||||
|
||||
namespace App\Services\Helpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Carbon\CarbonImmutable;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||
use App\Exceptions\Service\Helper\CdnVersionFetchingException;
|
||||
|
||||
class SoftwareVersionService
|
||||
{
|
||||
@ -87,17 +86,27 @@ class SoftwareVersionService
|
||||
protected function cacheVersionData(): array
|
||||
{
|
||||
return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config('panel.cdn.cache_time', 60)), function () {
|
||||
try {
|
||||
$response = $this->client->request('GET', config('panel.cdn.url'));
|
||||
$versionData = [];
|
||||
|
||||
try {
|
||||
$response = $this->client->request('GET', 'https://api.github.com/repos/pelican-dev/panel/releases/latest');
|
||||
if ($response->getStatusCode() === 200) {
|
||||
return json_decode($response->getBody(), true);
|
||||
$panelData = json_decode($response->getBody(), true);
|
||||
$versionData['panel'] = trim($panelData['tag_name'], 'v');
|
||||
}
|
||||
|
||||
throw new CdnVersionFetchingException();
|
||||
} catch (Exception) {
|
||||
return [];
|
||||
$response = $this->client->request('GET', 'https://api.github.com/repos/pelican-dev/wings/releases/latest');
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$wingsData = json_decode($response->getBody(), true);
|
||||
$versionData['daemon'] = trim($wingsData['tag_name'], 'v');
|
||||
}
|
||||
} catch (ClientException $e) {
|
||||
}
|
||||
|
||||
$versionData['discord'] = 'https://pelican.dev/discord';
|
||||
$versionData['donate'] = 'https://pelican.dev/donate';
|
||||
|
||||
return $versionData;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -51,12 +51,12 @@ class ServerConfigurationStructureService
|
||||
'invocation' => $server->startup,
|
||||
'skip_egg_scripts' => $server->skip_scripts,
|
||||
'build' => [
|
||||
'memory_limit' => $server->memory,
|
||||
'swap' => $server->swap,
|
||||
'memory_limit' => config('panel.use_binary_prefix') ? $server->memory : $server->memory / 1.048576,
|
||||
'swap' => config('panel.use_binary_prefix') ? $server->swap : $server->swap / 1.048576,
|
||||
'io_weight' => $server->io,
|
||||
'cpu_limit' => $server->cpu,
|
||||
'threads' => $server->threads,
|
||||
'disk_space' => $server->disk,
|
||||
'disk_space' => config('panel.use_binary_prefix') ? $server->disk : $server->disk / 1.048576,
|
||||
'oom_killer' => $server->oom_killer,
|
||||
],
|
||||
'container' => [
|
||||
|
@ -14,6 +14,7 @@ use Illuminate\Database\ConnectionInterface;
|
||||
use App\Models\Objects\DeploymentObject;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use App\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
use App\Models\Egg;
|
||||
|
||||
class ServerCreationService
|
||||
{
|
||||
@ -43,6 +44,13 @@ class ServerCreationService
|
||||
$data['oom_killer'] = !$data['oom_disabled'];
|
||||
}
|
||||
|
||||
/** @var Egg $egg */
|
||||
$egg = Egg::query()->findOrFail($data['egg_id']);
|
||||
|
||||
// Fill missing fields from egg
|
||||
$data['image'] = $data['image'] ?? collect($egg->docker_images)->first();
|
||||
$data['startup'] = $data['startup'] ?? $egg->startup;
|
||||
|
||||
// If a deployment object has been passed we need to get the allocation
|
||||
// that the server should use, and assign the node from that allocation.
|
||||
if ($deployment instanceof DeploymentObject) {
|
||||
|
@ -76,6 +76,10 @@ class StartupModificationService
|
||||
$server = $server->forceFill([
|
||||
'egg_id' => $egg->id,
|
||||
]);
|
||||
|
||||
// Fill missing fields from egg
|
||||
$data['docker_image'] = $data['docker_image'] ?? collect($egg->docker_images)->first();
|
||||
$data['startup'] = $data['startup'] ?? $egg->startup;
|
||||
}
|
||||
|
||||
$server->fill([
|
||||
|
@ -55,7 +55,7 @@ class ActivityLogTransformer extends BaseClientTransformer
|
||||
|
||||
$properties = $model->properties
|
||||
->mapWithKeys(function ($value, $key) use ($model) {
|
||||
if ($key === 'ip' && $model->actor && !$model->actor->is($this->request->user())) {
|
||||
if ($key === 'ip' && !$model->actor->is($this->request->user())) {
|
||||
return [$key => '[hidden]'];
|
||||
}
|
||||
|
||||
|
@ -8,4 +8,6 @@ return [
|
||||
App\Providers\Filament\AdminPanelProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\ViewComposerServiceProvider::class,
|
||||
|
||||
SocialiteProviders\Manager\ServiceProvider::class,
|
||||
];
|
||||
|
20
bounties.md
Normal file
20
bounties.md
Normal file
@ -0,0 +1,20 @@
|
||||
# [Bounties](https://github.com/pelican-dev/panel/issues?q=is%3Aopen+is%3Aissue+label%3A%22%F0%9F%92%B5+bounty%22)
|
||||
|
||||
Get paid to improve Pelican!
|
||||
|
||||
## Rules
|
||||
|
||||
* code must be merged into the main branch
|
||||
* bounty eligibility is solely at our discretion
|
||||
* open a ticket at [hub.pelican.dev](https://hub.pelican.dev/tickets) with links to your PRs to claim
|
||||
* get an extra 25% if you redeem your bounty in Donor credit
|
||||
* for bounties >=$100, the first PR gets a lock, which times out after a week of no progress
|
||||
|
||||
We put up each bounty with the intention that it'll get merged, but occasionally the right resolution is to close the bounty, which only becomes clear once some effort is put in.
|
||||
This is still valuable work, so we'll pay out $50 for getting any bounty closed with a good explanation.
|
||||
|
||||
## Issue bounties
|
||||
|
||||
We've tagged bounty-eligible issues across openpilot and the rest of our repos; check out all the open ones [here](https://github.com/pelican-dev/panel/issues?q=is%3Aopen+is%3Aissue+label%3A%22%F0%9F%92%B5+bounty%22).
|
||||
|
||||
New bounties can be proposed in the [**#feedback**](https://discord.com/channels/1218730176297439332/1218732581797892220) channel in Discord.
|
@ -20,6 +20,7 @@
|
||||
"laravel/framework": "^11.7",
|
||||
"laravel/helpers": "^1.7",
|
||||
"laravel/sanctum": "^4.0.2",
|
||||
"laravel/socialite": "^5.14",
|
||||
"laravel/tinker": "^2.9",
|
||||
"laravel/ui": "^4.5.1",
|
||||
"lcobucci/jwt": "~4.3.0",
|
||||
@ -31,6 +32,7 @@
|
||||
"prologue/alerts": "^1.2",
|
||||
"ryangjchandler/blade-tabler-icons": "^2.3",
|
||||
"s1lentium/iptools": "~1.2.0",
|
||||
"socialiteproviders/discord": "^4.2",
|
||||
"spatie/laravel-fractal": "^6.2",
|
||||
"spatie/laravel-query-builder": "^5.8.1",
|
||||
"spatie/temporary-directory": "^2.2",
|
||||
@ -70,6 +72,7 @@
|
||||
"scripts": {
|
||||
"cs:fix": "php-cs-fixer fix",
|
||||
"cs:check": "php-cs-fixer fix --dry-run --diff --verbose",
|
||||
"phpstan": "phpstan --memory-limit=-1",
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump"
|
||||
],
|
||||
|
339
composer.lock
generated
339
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bf44faee3aae2b1d4c1b57893c1aba98",
|
||||
"content-hash": "443ec1d95b892b261af5481f27b31083",
|
||||
"packages": [
|
||||
{
|
||||
"name": "abdelhamiderrahmouni/filament-monaco-editor",
|
||||
@ -2069,6 +2069,69 @@
|
||||
},
|
||||
"time": "2024-06-05T09:38:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"version": "v6.10.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/firebase/php-jwt.git",
|
||||
"reference": "500501c2ce893c824c801da135d02661199f60c5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5",
|
||||
"reference": "500501c2ce893c824c801da135d02661199f60c5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psr/cache": "^2.0||^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Firebase\\JWT\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Neuman Vong",
|
||||
"email": "neuman+pear@twilio.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Anant Narayanan",
|
||||
"email": "anant@php.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||
"homepage": "https://github.com/firebase/php-jwt",
|
||||
"keywords": [
|
||||
"jwt",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v6.10.1"
|
||||
},
|
||||
"time": "2024-05-18T18:05:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fruitcake/php-cors",
|
||||
"version": "v1.3.0",
|
||||
@ -3180,6 +3243,78 @@
|
||||
},
|
||||
"time": "2023-11-08T14:08:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/socialite",
|
||||
"version": "v5.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/socialite.git",
|
||||
"reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/socialite/zipball/c7b0193a3753a29aff8ce80aa2f511917e6ed68a",
|
||||
"reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"firebase/php-jwt": "^6.4",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
|
||||
"illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
|
||||
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
|
||||
"league/oauth1-client": "^1.10.1",
|
||||
"php": "^7.2|^8.0",
|
||||
"phpseclib/phpseclib": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^8.0|^9.3|^10.4"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Socialite\\SocialiteServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Socialite\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
|
||||
"homepage": "https://laravel.com",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"oauth"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/socialite/issues",
|
||||
"source": "https://github.com/laravel/socialite"
|
||||
},
|
||||
"time": "2024-05-03T20:31:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
"version": "v2.9.0",
|
||||
@ -4114,6 +4249,82 @@
|
||||
],
|
||||
"time": "2024-01-28T23:22:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/oauth1-client",
|
||||
"version": "v1.10.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/oauth1-client.git",
|
||||
"reference": "d6365b901b5c287dd41f143033315e2f777e1167"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167",
|
||||
"reference": "d6365b901b5c287dd41f143033315e2f777e1167",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||
"guzzlehttp/psr7": "^1.7|^2.0",
|
||||
"php": ">=7.1||>=8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-simplexml": "*",
|
||||
"friendsofphp/php-cs-fixer": "^2.17",
|
||||
"mockery/mockery": "^1.3.3",
|
||||
"phpstan/phpstan": "^0.12.42",
|
||||
"phpunit/phpunit": "^7.5||9.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-simplexml": "For decoding XML-based responses."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev",
|
||||
"dev-develop": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\OAuth1\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Corlett",
|
||||
"email": "bencorlett@me.com",
|
||||
"homepage": "http://www.webcomm.com.au",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "OAuth 1.0 Client Library",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"SSO",
|
||||
"authorization",
|
||||
"bitbucket",
|
||||
"identity",
|
||||
"idp",
|
||||
"oauth",
|
||||
"oauth1",
|
||||
"single sign on",
|
||||
"trello",
|
||||
"tumblr",
|
||||
"twitter"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/oauth1-client/issues",
|
||||
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1"
|
||||
},
|
||||
"time": "2022-04-15T14:02:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/uri",
|
||||
"version": "7.4.1",
|
||||
@ -6579,6 +6790,130 @@
|
||||
},
|
||||
"time": "2022-08-17T14:28:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/discord",
|
||||
"version": "4.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Discord.git",
|
||||
"reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Discord/zipball/c71c379acfdca5ba4aa65a3db5ae5222852a919c",
|
||||
"reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"socialiteproviders/manager": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SocialiteProviders\\Discord\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christopher Eklund",
|
||||
"email": "eklundchristopher@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Discord OAuth2 Provider for Laravel Socialite",
|
||||
"keywords": [
|
||||
"discord",
|
||||
"laravel",
|
||||
"oauth",
|
||||
"provider",
|
||||
"socialite"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://socialiteproviders.com/discord",
|
||||
"issues": "https://github.com/socialiteproviders/providers/issues",
|
||||
"source": "https://github.com/socialiteproviders/providers"
|
||||
},
|
||||
"time": "2023-07-24T23:28:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/manager",
|
||||
"version": "v4.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Manager.git",
|
||||
"reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/dea5190981c31b89e52259da9ab1ca4e2b258b21",
|
||||
"reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0",
|
||||
"laravel/socialite": "^5.5",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.2",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"SocialiteProviders\\Manager\\ServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SocialiteProviders\\Manager\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Andy Wendt",
|
||||
"email": "andy@awendt.com"
|
||||
},
|
||||
{
|
||||
"name": "Anton Komarev",
|
||||
"email": "a.komarev@cybercog.su"
|
||||
},
|
||||
{
|
||||
"name": "Miguel Piedrafita",
|
||||
"email": "soy@miguelpiedrafita.com"
|
||||
},
|
||||
{
|
||||
"name": "atymic",
|
||||
"email": "atymicq@gmail.com",
|
||||
"homepage": "https://atymic.dev"
|
||||
}
|
||||
],
|
||||
"description": "Easily add new or override built-in providers in Laravel Socialite.",
|
||||
"homepage": "https://socialiteproviders.com",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"manager",
|
||||
"oauth",
|
||||
"providers",
|
||||
"socialite"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/socialiteproviders/manager/issues",
|
||||
"source": "https://github.com/socialiteproviders/manager"
|
||||
},
|
||||
"time": "2024-05-04T07:57:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/color",
|
||||
"version": "1.5.3",
|
||||
@ -13141,5 +13476,5 @@
|
||||
"ext-zip": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
use App\Helpers\Time;
|
||||
|
||||
return [
|
||||
|
||||
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||
@ -17,25 +15,41 @@ return [
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DB_URL', env('DATABASE_URL')),
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'panel'),
|
||||
'username' => env('DB_USERNAME', 'pelican'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => env('DB_PREFIX', ''),
|
||||
'prefix_indexes' => true,
|
||||
'strict' => env('DB_STRICT_MODE', false),
|
||||
'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))),
|
||||
'sslmode' => env('DB_SSLMODE', 'prefer'),
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'mariadb' => [
|
||||
'driver' => 'mariadb',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'panel'),
|
||||
'username' => env('DB_USERNAME', 'pelican'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => env('DB_PREFIX', ''),
|
||||
'prefix_indexes' => true,
|
||||
'strict' => env('DB_STRICT_MODE', false),
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
PDO::MYSQL_ATTR_SSL_CERT => env('MYSQL_ATTR_SSL_CERT'),
|
||||
PDO::MYSQL_ATTR_SSL_KEY => env('MYSQL_ATTR_SSL_KEY'),
|
||||
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true),
|
||||
]) : [],
|
||||
],
|
||||
],
|
||||
|
@ -175,4 +175,6 @@ return [
|
||||
'filament' => [
|
||||
'top-navigation' => env('FILAMENT_TOP_NAVIGATION', false),
|
||||
],
|
||||
|
||||
'use_binary_prefix' => env('PANEL_USE_BINARY_PREFIX', true),
|
||||
];
|
||||
|
@ -9,4 +9,16 @@ return [
|
||||
'scheme' => 'https',
|
||||
],
|
||||
|
||||
'github' => [
|
||||
'client_id' => env('OAUTH_GITHUB_CLIENT_ID'),
|
||||
'client_secret' => env('OAUTH_GITHUB_CLIENT_SECRET'),
|
||||
'redirect' => '/auth/oauth/callback/github',
|
||||
],
|
||||
|
||||
'discord' => [
|
||||
'client_id' => env('OAUTH_DISCORD_CLIENT_ID'),
|
||||
'client_secret' => env('OAUTH_DISCORD_CLIENT_SECRET'),
|
||||
'redirect' => '/auth/oauth/callback/discord',
|
||||
],
|
||||
|
||||
];
|
||||
|
@ -27,7 +27,7 @@ class ApiKeyFactory extends Factory
|
||||
'key_type' => ApiKey::TYPE_APPLICATION,
|
||||
'identifier' => ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION),
|
||||
'token' => $token ?: $token = Str::random(ApiKey::KEY_LENGTH),
|
||||
'allowed_ips' => null,
|
||||
'allowed_ips' => [],
|
||||
'memo' => 'Test Function Key',
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
|
@ -35,6 +35,7 @@ class UserFactory extends Factory
|
||||
'language' => 'en',
|
||||
'root_admin' => false,
|
||||
'use_totp' => false,
|
||||
'oauth' => [],
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
|
@ -7,14 +7,11 @@ use Exception;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use App\Services\Eggs\Sharing\EggUpdateImporterService;
|
||||
|
||||
class EggSeeder extends Seeder
|
||||
{
|
||||
protected EggImporterService $importerService;
|
||||
|
||||
protected EggUpdateImporterService $updateImporterService;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
@ -29,11 +26,9 @@ class EggSeeder extends Seeder
|
||||
* EggSeeder constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
EggImporterService $importerService,
|
||||
EggUpdateImporterService $updateImporterService
|
||||
EggImporterService $importerService
|
||||
) {
|
||||
$this->importerService = $importerService;
|
||||
$this->updateImporterService = $updateImporterService;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +70,7 @@ class EggSeeder extends Seeder
|
||||
->first();
|
||||
|
||||
if ($egg instanceof Egg) {
|
||||
$this->updateImporterService->fromFile($egg, $file);
|
||||
$this->importerService->fromFile($file, $egg);
|
||||
$this->command->info('Updated ' . $decoded['name']);
|
||||
} else {
|
||||
$this->importerService->fromFile($file);
|
||||
|
@ -13,7 +13,7 @@ return new class extends Migration
|
||||
{
|
||||
Schema::create('activity_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('batch')->nullable();
|
||||
$table->char('batch', 36)->nullable();
|
||||
$table->string('event')->index();
|
||||
$table->string('ip');
|
||||
$table->text('description')->nullable();
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('api_keys')->whereNull('allowed_ips')->update([
|
||||
'allowed_ips' => '[]',
|
||||
]);
|
||||
|
||||
Schema::table('api_keys', function (Blueprint $table) {
|
||||
$table->text('allowed_ips')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('api_keys', function (Blueprint $table) {
|
||||
$table->text('allowed_ips')->nullable()->change();
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->json('oauth')->after('totp_authenticated_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('oauth');
|
||||
});
|
||||
}
|
||||
};
|
@ -20,6 +20,7 @@ import { ServerContext } from '@/state/server';
|
||||
import tw from 'twin.macro';
|
||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||
import Icon from '@/components/elements/Icon';
|
||||
import { useStoreState } from 'easy-peasy';
|
||||
|
||||
interface Props {
|
||||
schedule: Schedule;
|
||||
@ -46,6 +47,7 @@ export default ({ schedule, task }: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule);
|
||||
const usesSyncDriver = useStoreState((state) => state.settings.data!.usesSyncDriver);
|
||||
|
||||
const onConfirmDeletion = () => {
|
||||
setIsLoading(true);
|
||||
@ -109,7 +111,7 @@ export default ({ schedule, task }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{task.sequenceId > 1 && task.timeOffset > 0 && (
|
||||
{!usesSyncDriver && task.sequenceId > 1 && task.timeOffset > 0 && (
|
||||
<div css={tw`mr-6`}>
|
||||
<div css={tw`flex items-center px-2 py-1 bg-neutral-500 text-sm rounded-full`}>
|
||||
<Icon icon={faClock} css={tw`w-3 h-3 mr-2`} />
|
||||
|
@ -17,6 +17,7 @@ import Select from '@/components/elements/Select';
|
||||
import ModalContext from '@/context/ModalContext';
|
||||
import asModal from '@/hoc/asModal';
|
||||
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||
import { useStoreState } from 'easy-peasy';
|
||||
|
||||
interface Props {
|
||||
schedule: Schedule;
|
||||
@ -71,6 +72,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
|
||||
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
|
||||
const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule);
|
||||
const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups);
|
||||
const usesSyncDriver = useStoreState((state) => state.settings.data!.usesSyncDriver);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@ -121,7 +123,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
|
||||
<FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`} />
|
||||
<h2 css={tw`text-2xl mb-6`}>{task ? 'Edit Task' : 'Create Task'}</h2>
|
||||
<div css={tw`flex`}>
|
||||
<div css={tw`mr-2 w-1/3`}>
|
||||
<div className={!usesSyncDriver ? 'mr-2 w-1/3' : 'w-full'}>
|
||||
<Label>Action</Label>
|
||||
<ActionListener />
|
||||
<FormikFieldWrapper name={'action'}>
|
||||
@ -132,15 +134,17 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
|
||||
</FormikField>
|
||||
</FormikFieldWrapper>
|
||||
</div>
|
||||
<div css={tw`flex-1 ml-6`}>
|
||||
<Field
|
||||
name={'timeOffset'}
|
||||
label={'Time offset (in seconds)'}
|
||||
description={
|
||||
'The amount of time to wait after the previous task executes before running this one. If this is the first task on a schedule this will not be applied.'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{!usesSyncDriver && (
|
||||
<div css={tw`flex-1 ml-6`}>
|
||||
<Field
|
||||
name={'timeOffset'}
|
||||
label={'Time offset (in seconds)'}
|
||||
description={
|
||||
'The amount of time to wait after the previous task executes before running this one. If this is the first task on a schedule this will not be applied.'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div css={tw`mt-6`}>
|
||||
{values.action === 'command' ? (
|
||||
|
@ -55,8 +55,9 @@ export default () => {
|
||||
</div>
|
||||
<div css={tw`ml-4`}>
|
||||
<a
|
||||
href={`sftp://${username}.${id}@${sftp.alias ? sftp.alias : ip(sftp.ip)}:${sftp.port
|
||||
}`}
|
||||
href={`sftp://${username}.${id}@${sftp.alias ? sftp.alias : ip(sftp.ip)}:${
|
||||
sftp.port
|
||||
}`}
|
||||
>
|
||||
<Button.Text variant={Button.Variants.Secondary}>Launch SFTP</Button.Text>
|
||||
</a>
|
||||
|
@ -7,6 +7,7 @@ export interface SiteSettings {
|
||||
enabled: boolean;
|
||||
siteKey: string;
|
||||
};
|
||||
usesSyncDriver: boolean;
|
||||
}
|
||||
|
||||
export interface SettingsStore {
|
||||
|
@ -18,6 +18,10 @@ Route::get('/login', [Auth\LoginController::class, 'index'])->name('auth.login')
|
||||
Route::get('/password', [Auth\LoginController::class, 'index'])->name('auth.forgot-password');
|
||||
Route::get('/password/reset/{token}', [Auth\LoginController::class, 'index'])->name('auth.reset');
|
||||
|
||||
// Endpoints for OAuth
|
||||
Route::get('/oauth/redirect/{driver}', [Auth\OAuthController::class, 'redirect'])->name('auth.oauth.redirect');
|
||||
Route::get('/oauth/callback/{driver}', [Auth\OAuthController::class, 'callback'])->name('auth.oauth.callback');
|
||||
|
||||
// Apply a throttle to authentication action endpoints, in addition to the
|
||||
// recaptcha endpoints to slow down manual attack spammers even more. 🤷
|
||||
//
|
||||
|
@ -9,6 +9,9 @@ Route::get('/account', [Base\IndexController::class, 'index'])
|
||||
->withoutMiddleware(RequireTwoFactorAuthentication::class)
|
||||
->name('account');
|
||||
|
||||
Route::get('/account/oauth/link', [Base\OAuthController::class, 'link'])->name('account.oauth.link');
|
||||
Route::get('/account/oauth/unlink', [Base\OAuthController::class, 'unlink'])->name('account.oauth.unlink');
|
||||
|
||||
Route::get('/locales/locale.json', Base\LocaleController::class)
|
||||
->withoutMiddleware(['auth', RequireTwoFactorAuthentication::class])
|
||||
->where('namespace', '.*');
|
||||
|
Loading…
x
Reference in New Issue
Block a user