Merge remote-tracking branch 'upstream/main' into boy132/multiple-startup-commands

This commit is contained in:
Boy132 2025-09-08 10:03:25 +02:00
commit d17d753c63
33 changed files with 237 additions and 177 deletions

View File

@ -3,11 +3,11 @@
namespace App\Console\Commands\Server; namespace App\Console\Commands\Server;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Factory as ValidatorFactory; use Illuminate\Validation\Factory as ValidatorFactory;
use App\Repositories\Daemon\DaemonPowerRepository;
use Exception; use Exception;
class BulkPowerActionCommand extends Command class BulkPowerActionCommand extends Command
@ -19,7 +19,7 @@ class BulkPowerActionCommand extends Command
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.'; protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator): void public function handle(DaemonServerRepository $serverRepository, ValidatorFactory $validator): void
{ {
$action = $this->argument('action'); $action = $this->argument('action');
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes')); $nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
@ -52,7 +52,7 @@ class BulkPowerActionCommand extends Command
$bar = $this->output->createProgressBar($count); $bar = $this->output->createProgressBar($count);
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $powerRepository, &$bar): mixed { $this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $serverRepository, &$bar): mixed {
$bar->clear(); $bar->clear();
if (!$server instanceof Server) { if (!$server instanceof Server) {
@ -60,7 +60,7 @@ class BulkPowerActionCommand extends Command
} }
try { try {
$powerRepository->setServer($server)->send($action); $serverRepository->setServer($server)->power($action);
} catch (Exception $exception) { } catch (Exception $exception) {
$this->output->error(trans('command/messages.server.power.action_failed', [ $this->output->error(trans('command/messages.server.power.action_failed', [
'name' => $server->name, 'name' => $server->name,

View File

@ -7,7 +7,7 @@ use App\Facades\Activity;
use App\Models\Permission; use App\Models\Permission;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerVariable; use App\Models\ServerVariable;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use Closure; use Closure;
use Exception; use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
@ -76,7 +76,7 @@ class GSLTokenSchema implements FeatureSchemaInterface
->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}') ->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}')
->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description), ->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description),
]) ])
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server, $serverVariable) { ->action(function (array $data, DaemonServerRepository $serverRepository) use ($server, $serverVariable) {
/** @var Server $server */ /** @var Server $server */
$server = Filament::getTenant(); $server = Filament::getTenant();
try { try {
@ -98,7 +98,7 @@ class GSLTokenSchema implements FeatureSchemaInterface
->log(); ->log();
} }
$powerRepository->setServer($server)->send('restart'); $serverRepository->setServer($server)->power('restart');
Notification::make() Notification::make()
->title('GSL Token updated') ->title('GSL Token updated')

View File

@ -6,7 +6,7 @@ use App\Extensions\Features\FeatureSchemaInterface;
use App\Facades\Activity; use App\Facades\Activity;
use App\Models\Permission; use App\Models\Permission;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use Exception; use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Facades\Filament; use Filament\Facades\Filament;
@ -59,7 +59,7 @@ class JavaVersionSchema implements FeatureSchemaInterface
->preload() ->preload()
->native(false), ->native(false),
]) ])
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server) { ->action(function (array $data, DaemonServerRepository $serverRepository) use ($server) {
try { try {
$new = $data['image']; $new = $data['image'];
$original = $server->image; $original = $server->image;
@ -71,7 +71,7 @@ class JavaVersionSchema implements FeatureSchemaInterface
->log(); ->log();
} }
$powerRepository->setServer($server)->send('restart'); $serverRepository->setServer($server)->power('restart');
Notification::make() Notification::make()
->title('Docker image updated') ->title('Docker image updated')

View File

@ -5,7 +5,7 @@ namespace App\Extensions\Features\Schemas;
use App\Extensions\Features\FeatureSchemaInterface; use App\Extensions\Features\FeatureSchemaInterface;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository; use App\Repositories\Daemon\DaemonFileRepository;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use Exception; use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Facades\Filament; use Filament\Facades\Filament;
@ -35,14 +35,14 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
->modalHeading('Minecraft EULA') ->modalHeading('Minecraft EULA')
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.'))) ->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.')))
->modalSubmitActionLabel('I Accept') ->modalSubmitActionLabel('I Accept')
->action(function (DaemonFileRepository $fileRepository, DaemonPowerRepository $powerRepository) { ->action(function (DaemonFileRepository $fileRepository, DaemonServerRepository $serverRepository) {
try { try {
/** @var Server $server */ /** @var Server $server */
$server = Filament::getTenant(); $server = Filament::getTenant();
$fileRepository->setServer($server)->putContent('eula.txt', 'eula=true'); $fileRepository->setServer($server)->putContent('eula.txt', 'eula=true');
$powerRepository->setServer($server)->send('restart'); $serverRepository->setServer($server)->power('restart');
Notification::make() Notification::make()
->title('Minecraft EULA accepted') ->title('Minecraft EULA accepted')

View File

@ -10,6 +10,7 @@ use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class CreateApiKey extends CreateRecord class CreateApiKey extends CreateRecord
{ {
@ -36,7 +37,7 @@ class CreateApiKey extends CreateRecord
protected function handleRecordCreation(array $data): Model protected function handleRecordCreation(array $data): Model
{ {
$data['identifier'] = ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION); $data['identifier'] = ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION);
$data['token'] = str_random(ApiKey::KEY_LENGTH); $data['token'] = Str::random(ApiKey::KEY_LENGTH);
$data['user_id'] = auth()->user()->id; $data['user_id'] = auth()->user()->id;
$data['key_type'] = ApiKey::TYPE_APPLICATION; $data['key_type'] = ApiKey::TYPE_APPLICATION;

View File

@ -709,8 +709,22 @@ class EditServer extends EditRecord
->modalHeading(trans('admin/server.delete_db_heading')) ->modalHeading(trans('admin/server.delete_db_heading'))
->modalSubmitActionLabel(trans('filament-actions::delete.single.label')) ->modalSubmitActionLabel(trans('filament-actions::delete.single.label'))
->modalDescription(fn (Get $get) => trans('admin/server.delete_db', ['name' => $get('database')])) ->modalDescription(fn (Get $get) => trans('admin/server.delete_db', ['name' => $get('database')]))
->action(function (DatabaseManagementService $databaseManagementService, $record) { ->action(function (DatabaseManagementService $service, $record) {
$databaseManagementService->delete($record); try {
$service->delete($record);
Notification::make()
->title(trans('server/database.delete_notification', ['database' => $record->database]))
->success()
->send();
} catch (Exception $e) {
Notification::make()
->title(trans('server/database.delete_notification_fail', ['database' => $record->database]))
->body($e->getMessage())
->danger()
->persistent()->send();
}
$this->fillForm(); $this->fillForm();
}) })
), ),
@ -759,21 +773,22 @@ class EditServer extends EditRecord
->label(fn () => DatabaseHost::query()->count() < 1 ? trans('admin/server.no_db_hosts') : trans('admin/server.create_database')) ->label(fn () => DatabaseHost::query()->count() < 1 ? trans('admin/server.no_db_hosts') : trans('admin/server.create_database'))
->color(fn () => DatabaseHost::query()->count() < 1 ? 'danger' : 'primary') ->color(fn () => DatabaseHost::query()->count() < 1 ? 'danger' : 'primary')
->modalSubmitActionLabel(trans('admin/server.create_database')) ->modalSubmitActionLabel(trans('admin/server.create_database'))
->action(function (array $data, DatabaseManagementService $service, Server $server, RandomWordService $randomWordService) { ->action(function (array $data, DatabaseManagementService $service, Server $server) {
if (empty($data['database'])) { $data['database'] ??= str_random(12);
$data['database'] = $randomWordService->word() . random_int(1, 420); $data['remote'] ??= '%';
}
if (empty($data['remote'])) {
$data['remote'] = '%';
}
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id); $data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
try { try {
$service->setValidateDatabaseLimit(false)->create($server, $data); $service->setValidateDatabaseLimit(false)->create($server, $data);
Notification::make()
->title(trans('server/database.create_notification', ['database' => $data['database']]))
->success()
->send();
} catch (Exception $e) { } catch (Exception $e) {
Notification::make() Notification::make()
->title(trans('admin/server.failed_to_create')) ->title(trans('server/database.create_notification_fail', ['database' => $data['database']]))
->body($e->getMessage()) ->body($e->getMessage())
->danger() ->danger()
->persistent()->send(); ->persistent()->send();

View File

@ -8,7 +8,7 @@ use App\Filament\Components\Tables\Columns\ServerEntryColumn;
use App\Filament\Server\Pages\Console; use App\Filament\Server\Pages\Console;
use App\Models\Permission; use App\Models\Permission;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
@ -38,11 +38,11 @@ class ListServers extends ListRecords
public const WARNING_THRESHOLD = 0.7; public const WARNING_THRESHOLD = 0.7;
private DaemonPowerRepository $daemonPowerRepository; private DaemonServerRepository $daemonServerRepository;
public function boot(): void public function boot(): void
{ {
$this->daemonPowerRepository = new DaemonPowerRepository(); $this->daemonServerRepository = new DaemonServerRepository();
} }
/** @return Stack[] */ /** @return Stack[] */
@ -204,7 +204,7 @@ class ListServers extends ListRecords
public function powerAction(Server $server, string $action): void public function powerAction(Server $server, string $action): void
{ {
try { try {
$this->daemonPowerRepository->setServer($server)->send($action); $this->daemonServerRepository->setServer($server)->power($action);
Notification::make() Notification::make()
->title(trans('server/dashboard.power_actions')) ->title(trans('server/dashboard.power_actions'))

View File

@ -4,9 +4,10 @@ namespace App\Filament\Components\Forms\Actions;
use App\Facades\Activity; use App\Facades\Activity;
use App\Models\Database; use App\Models\Database;
use App\Services\Databases\DatabasePasswordService; use App\Services\Databases\DatabaseManagementService;
use Exception; use Exception;
use Filament\Actions\StaticAction; use Filament\Actions\StaticAction;
use Filament\Facades\Filament;
use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Set; use Filament\Forms\Set;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
@ -36,10 +37,9 @@ class RotateDatabasePasswordAction extends Action
$this->requiresConfirmation(); $this->requiresConfirmation();
$this->action(function (DatabasePasswordService $service, Database $database, Set $set) { $this->action(function (DatabaseManagementService $service, Database $database, Set $set) {
try { try {
$service->handle($database); $service->rotatePassword($database);
$database->refresh(); $database->refresh();
$set('password', $database->password); $set('password', $database->password);
@ -57,7 +57,7 @@ class RotateDatabasePasswordAction extends Action
} catch (Exception $exception) { } catch (Exception $exception) {
Notification::make() Notification::make()
->title(trans('admin/databasehost.rotate_error')) ->title(trans('admin/databasehost.rotate_error'))
->body($exception->getMessage()) ->body(fn () => auth()->user()->canAccessPanel(Filament::getPanel('admin')) ? $exception->getMessage() : null)
->danger() ->danger()
->send(); ->send();

View File

@ -15,9 +15,11 @@ use App\Traits\Filament\CanCustomizeRelations;
use App\Traits\Filament\CanModifyForm; use App\Traits\Filament\CanModifyForm;
use App\Traits\Filament\CanModifyTable; use App\Traits\Filament\CanModifyTable;
use App\Traits\Filament\HasLimitBadge; use App\Traits\Filament\HasLimitBadge;
use Exception;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Actions\DeleteAction;
@ -122,7 +124,23 @@ class DatabaseResource extends Resource
ViewAction::make() ViewAction::make()
->modalHeading(fn (Database $database) => trans('server/database.viewing', ['database' => $database->database])), ->modalHeading(fn (Database $database) => trans('server/database.viewing', ['database' => $database->database])),
DeleteAction::make() DeleteAction::make()
->using(fn (Database $database, DatabaseManagementService $service) => $service->delete($database)), ->using(function (Database $database, DatabaseManagementService $service) {
try {
$service->delete($database);
Notification::make()
->title(trans('server/database.delete_notification', ['database' => $database->database]))
->success()
->send();
} catch (Exception $exception) {
Notification::make()
->title(trans('server/database.delete_notification_fail', ['database' => $database->database]))
->danger()
->send();
report($exception);
}
}),
]); ]);
} }

View File

@ -8,6 +8,7 @@ use App\Models\Server;
use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabaseManagementService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction; use Filament\Actions\CreateAction;
@ -15,8 +16,10 @@ use Filament\Facades\Filament;
use Filament\Forms\Components\Grid; use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Support\Enums\IconSize; use Filament\Support\Enums\IconSize;
use Illuminate\Support\Str;
class ListDatabases extends ListRecords class ListDatabases extends ListRecords
{ {
@ -62,12 +65,24 @@ class ListDatabases extends ListRecords
]), ]),
]) ])
->action(function ($data, DatabaseManagementService $service) use ($server) { ->action(function ($data, DatabaseManagementService $service) use ($server) {
if (empty($data['database'])) { $data['database'] ??= Str::random(12);
$data['database'] = str_random(12); $data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
}
$data['database'] = 's'. $server->id . '_' . $data['database'];
$service->create($server, $data); try {
$service->create($server, $data);
Notification::make()
->title(trans('server/database.create_notification', ['database' => $data['database']]))
->success()
->send();
} catch (Exception $exception) {
Notification::make()
->title(trans('server/database.create_notification_fail', ['database' => $data['database']]))
->danger()
->send();
report($exception);
}
}), }),
]; ];
} }

View File

@ -193,10 +193,10 @@ class ListFiles extends ListRecords
->required() ->required()
->live(), ->live(),
Placeholder::make('new_location') Placeholder::make('new_location')
->content(fn (Get $get, File $file) => resolve_path('./' . join_paths($this->path, $get('location') ?? '/', $file->name))), ->content(fn (Get $get, File $file) => resolve_path(join_paths($this->path, $get('location') ?? '/', $file->name))),
]) ])
->action(function ($data, File $file) { ->action(function ($data, File $file) {
$location = rtrim($data['location'], '/'); $location = $data['location'];
$files = [['to' => join_paths($location, $file->name), 'from' => $file->name]]; $files = [['to' => join_paths($location, $file->name), 'from' => $file->name]];
$this->getDaemonFileRepository()->renameFiles($this->path, $files); $this->getDaemonFileRepository()->renameFiles($this->path, $files);
@ -353,10 +353,10 @@ class ListFiles extends ListRecords
->required() ->required()
->live(), ->live(),
Placeholder::make('new_location') Placeholder::make('new_location')
->content(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location') ?? ''))), ->content(fn (Get $get) => resolve_path(join_paths($this->path, $get('location') ?? ''))),
]) ])
->action(function (Collection $files, $data) { ->action(function (Collection $files, $data) {
$location = rtrim($data['location'], '/'); $location = $data['location'];
$files = $files->map(fn ($file) => ['to' => join_paths($location, $file['name']), 'from' => $file['name']])->toArray(); $files = $files->map(fn ($file) => ['to' => join_paths($location, $file['name']), 'from' => $file['name']])->toArray();
$this->getDaemonFileRepository()->renameFiles($this->path, $files); $this->getDaemonFileRepository()->renameFiles($this->path, $files);
@ -432,11 +432,12 @@ class ListFiles extends ListRecords
->modalSubmitActionLabel(trans('server/file.actions.new_file.create')) ->modalSubmitActionLabel(trans('server/file.actions.new_file.create'))
->action(function ($data) { ->action(function ($data) {
$path = join_paths($this->path, $data['name']); $path = join_paths($this->path, $data['name']);
try { try {
$this->getDaemonFileRepository()->putContent($path, $data['editor'] ?? ''); $this->getDaemonFileRepository()->putContent($path, $data['editor'] ?? '');
Activity::event('server:file.write') Activity::event('server:file.write')
->property('file', join_paths($path, $data['name'])) ->property('file', $path)
->log(); ->log();
} catch (FileExistsException) { } catch (FileExistsException) {
AlertBanner::make('file_already_exists') AlertBanner::make('file_already_exists')

View File

@ -4,6 +4,7 @@ namespace App\Helpers;
use Carbon\Carbon; use Carbon\Carbon;
use Cron\CronExpression; use Cron\CronExpression;
use Illuminate\Support\Str;
use Illuminate\Support\ViewErrorBag; use Illuminate\Support\ViewErrorBag;
class Utilities class Utilities
@ -14,7 +15,7 @@ class Utilities
*/ */
public static function randomStringWithSpecialCharacters(int $length = 16): string public static function randomStringWithSpecialCharacters(int $length = 16): string
{ {
$string = str_random($length); $string = Str::random($length);
// Given a random string of characters, randomly loop through the characters and replace some // Given a random string of characters, randomly loop through the characters and replace some
// with special characters to avoid issues with MySQL password requirements on some servers. // with special characters to avoid issues with MySQL password requirements on some servers.
try { try {

View File

@ -6,7 +6,6 @@ use Illuminate\Http\Response;
use App\Models\Server; use App\Models\Server;
use App\Models\Database; use App\Models\Database;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Services\Databases\DatabasePasswordService;
use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabaseManagementService;
use App\Transformers\Api\Application\ServerDatabaseTransformer; use App\Transformers\Api\Application\ServerDatabaseTransformer;
use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Controllers\Api\Application\ApplicationApiController;
@ -24,7 +23,6 @@ class DatabaseController extends ApplicationApiController
*/ */
public function __construct( public function __construct(
private DatabaseManagementService $databaseManagementService, private DatabaseManagementService $databaseManagementService,
private DatabasePasswordService $databasePasswordService
) { ) {
parent::__construct(); parent::__construct();
} }
@ -66,7 +64,7 @@ class DatabaseController extends ApplicationApiController
*/ */
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse
{ {
$this->databasePasswordService->handle($database); $this->databaseManagementService->rotatePassword($database);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
} }

View File

@ -6,7 +6,6 @@ use Illuminate\Http\Response;
use App\Models\Server; use App\Models\Server;
use App\Models\Database; use App\Models\Database;
use App\Facades\Activity; use App\Facades\Activity;
use App\Services\Databases\DatabasePasswordService;
use App\Transformers\Api\Client\DatabaseTransformer; use App\Transformers\Api\Client\DatabaseTransformer;
use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabaseManagementService;
use App\Services\Databases\DeployServerDatabaseService; use App\Services\Databases\DeployServerDatabaseService;
@ -26,7 +25,6 @@ class DatabaseController extends ClientApiController
public function __construct( public function __construct(
private DeployServerDatabaseService $deployDatabaseService, private DeployServerDatabaseService $deployDatabaseService,
private DatabaseManagementService $managementService, private DatabaseManagementService $managementService,
private DatabasePasswordService $passwordService
) { ) {
parent::__construct(); parent::__construct();
} }
@ -83,7 +81,7 @@ class DatabaseController extends ClientApiController
*/ */
public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database): array public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database): array
{ {
$this->passwordService->handle($database); $this->managementService->rotatePassword($database);
$database->refresh(); $database->refresh();
Activity::event('server:database.rotate-password') Activity::event('server:database.rotate-password')

View File

@ -5,9 +5,9 @@ namespace App\Http\Controllers\Api\Client\Servers;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use App\Models\Server; use App\Models\Server;
use App\Facades\Activity; use App\Facades\Activity;
use App\Repositories\Daemon\DaemonPowerRepository;
use App\Http\Controllers\Api\Client\ClientApiController; use App\Http\Controllers\Api\Client\ClientApiController;
use App\Http\Requests\Api\Client\Servers\SendPowerRequest; use App\Http\Requests\Api\Client\Servers\SendPowerRequest;
use App\Repositories\Daemon\DaemonServerRepository;
use Dedoc\Scramble\Attributes\Group; use Dedoc\Scramble\Attributes\Group;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
@ -17,7 +17,7 @@ class PowerController extends ClientApiController
/** /**
* PowerController constructor. * PowerController constructor.
*/ */
public function __construct(private DaemonPowerRepository $repository) public function __construct(private DaemonServerRepository $repository)
{ {
parent::__construct(); parent::__construct();
} }
@ -31,7 +31,7 @@ class PowerController extends ClientApiController
*/ */
public function index(SendPowerRequest $request, Server $server): Response public function index(SendPowerRequest $request, Server $server): Response
{ {
$this->repository->setServer($server)->send( $this->repository->setServer($server)->power(
$request->input('signal') $request->input('signal')
); );

View File

@ -5,11 +5,11 @@ namespace App\Jobs\Schedule;
use App\Jobs\Job; use App\Jobs\Job;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use App\Models\Task; use App\Models\Task;
use App\Repositories\Daemon\DaemonServerRepository;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use App\Services\Backups\InitiateBackupService; use App\Services\Backups\InitiateBackupService;
use App\Repositories\Daemon\DaemonPowerRepository;
use App\Services\Files\DeleteFilesService; use App\Services\Files\DeleteFilesService;
use Exception; use Exception;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
@ -31,7 +31,7 @@ class RunTaskJob extends Job implements ShouldQueue
*/ */
public function handle( public function handle(
InitiateBackupService $backupService, InitiateBackupService $backupService,
DaemonPowerRepository $powerRepository, DaemonServerRepository $serverRepository,
DeleteFilesService $deleteFilesService DeleteFilesService $deleteFilesService
): void { ): void {
// Do not process a task that is not set to active, unless it's been manually triggered. // Do not process a task that is not set to active, unless it's been manually triggered.
@ -57,7 +57,7 @@ class RunTaskJob extends Job implements ShouldQueue
try { try {
switch ($this->task->action) { switch ($this->task->action) {
case Task::ACTION_POWER: case Task::ACTION_POWER:
$powerRepository->setServer($server)->send($this->task->payload); $serverRepository->setServer($server)->power($this->task->payload);
break; break;
case Task::ACTION_COMMAND: case Task::ACTION_COMMAND:
$server->send($this->task->payload); $server->send($this->task->payload);

View File

@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use PDOException;
/** /**
* @property int $id * @property int $id
@ -97,71 +98,97 @@ class Database extends Model implements Validatable
} }
/** /**
* @throws PDOException
*
* Run the provided statement against the database on a given connection. * Run the provided statement against the database on a given connection.
*/ */
private function run(string $statement): bool private function run(string $statement): void
{ {
return $this->host->buildConnection()->statement($statement); $this->host->buildConnection()->statement($statement);
} }
/** /**
* @throws PDOException
*
* Create a new database on a given connection. * Create a new database on a given connection.
*/ */
public function createDatabase(string $database): bool public function createDatabase(): self
{ {
return $this->run(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database)); $this->run(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $this->database));
return $this;
} }
/** /**
* @throws PDOException
*
* Create a new database user on a given connection. * Create a new database user on a given connection.
*/ */
public function createUser(string $username, string $remote, string $password, ?int $max_connections): bool public function createUser(): self
{ {
$args = [$username, $remote, $password]; $args = [$this->username, $this->remote, $this->password];
$command = 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\''; $command = 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'';
if (!empty($max_connections)) { if (!empty($this->max_connections)) {
$args[] = $max_connections; $args[] = $this->max_connections;
$command .= ' WITH MAX_USER_CONNECTIONS %s'; $command .= ' WITH MAX_USER_CONNECTIONS %s';
} }
return $this->run(sprintf($command, ...$args)); $this->run(sprintf($command, ...$args));
return $this;
} }
/** /**
* @throws PDOException
*
* Give a specific user access to a given database. * Give a specific user access to a given database.
*/ */
public function assignUserToDatabase(string $database, string $username, string $remote): bool public function assignUserToDatabase(): self
{ {
return $this->run(sprintf( $this->run(sprintf(
'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, REFERENCES, INDEX, LOCK TABLES, CREATE ROUTINE, ALTER ROUTINE, EXECUTE, CREATE TEMPORARY TABLES, CREATE VIEW, SHOW VIEW, EVENT, TRIGGER ON `%s`.* TO `%s`@`%s`', 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, REFERENCES, INDEX, LOCK TABLES, CREATE ROUTINE, ALTER ROUTINE, EXECUTE, CREATE TEMPORARY TABLES, CREATE VIEW, SHOW VIEW, EVENT, TRIGGER ON `%s`.* TO `%s`@`%s`',
$database, $this->database,
$username, $this->username,
$remote $this->remote
)); ));
return $this;
} }
/** /**
* @throws PDOException
*
* Flush the privileges for a given connection. * Flush the privileges for a given connection.
*/ */
public function flush(): bool public function flushPrivileges(): self
{ {
return $this->run('FLUSH PRIVILEGES'); $this->run('FLUSH PRIVILEGES');
return $this;
} }
/** /**
* @throws PDOException
*
* Drop a given database on a specific connection. * Drop a given database on a specific connection.
*/ */
public function dropDatabase(string $database): bool public function dropDatabase(): self
{ {
return $this->run(sprintf('DROP DATABASE IF EXISTS `%s`', $database)); $this->run(sprintf('DROP DATABASE IF EXISTS `%s`', $this->database));
return $this;
} }
/** /**
* @throws PDOException
*
* Drop a given user on a specific connection. * Drop a given user on a specific connection.
*/ */
public function dropUser(string $username, string $remote): bool public function dropUser(): self
{ {
return $this->run(sprintf('DROP USER IF EXISTS `%s`@`%s`', $username, $remote)); $this->run(sprintf('DROP USER IF EXISTS `%s`@`%s`', $this->username, $this->remote));
return $this;
} }
} }

View File

@ -1,21 +0,0 @@
<?php
namespace App\Repositories\Daemon;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\Response;
class DaemonPowerRepository extends DaemonRepository
{
/**
* Sends a power action to the server instance.
*
* @throws ConnectionException
*/
public function send(string $action): Response
{
return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/power",
['action' => $action],
);
}
}

View File

@ -8,6 +8,7 @@ use Exception;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException; use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\Response;
class DaemonServerRepository extends DaemonRepository class DaemonServerRepository extends DaemonRepository
{ {
@ -149,4 +150,16 @@ class DaemonServerRepository extends DaemonRepository
->throw() ->throw()
->json('data'); ->json('data');
} }
/**
* Sends a power action to the server instance.
*
* @throws ConnectionException
*/
public function power(string $action): Response
{
return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/power",
['action' => $action],
);
}
} }

View File

@ -3,6 +3,7 @@
namespace App\Services\Api; namespace App\Services\Api;
use App\Models\ApiKey; use App\Models\ApiKey;
use Illuminate\Support\Str;
class KeyCreationService class KeyCreationService
{ {
@ -33,7 +34,7 @@ class KeyCreationService
$data = array_merge($data, [ $data = array_merge($data, [
'key_type' => $this->keyType, 'key_type' => $this->keyType,
'identifier' => ApiKey::generateTokenIdentifier($this->keyType), 'identifier' => ApiKey::generateTokenIdentifier($this->keyType),
'token' => str_random(ApiKey::KEY_LENGTH), 'token' => Str::random(ApiKey::KEY_LENGTH),
]); ]);
if ($this->keyType !== ApiKey::TYPE_APPLICATION) { if ($this->keyType !== ApiKey::TYPE_APPLICATION) {

View File

@ -10,6 +10,7 @@ use Illuminate\Database\ConnectionInterface;
use App\Exceptions\Repository\DuplicateDatabaseNameException; use App\Exceptions\Repository\DuplicateDatabaseNameException;
use App\Exceptions\Service\Database\TooManyDatabasesException; use App\Exceptions\Service\Database\TooManyDatabasesException;
use App\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; use App\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException;
use Illuminate\Support\Str;
class DatabaseManagementService class DatabaseManagementService
{ {
@ -85,22 +86,18 @@ class DatabaseManagementService
$data = array_merge($data, [ $data = array_merge($data, [
'server_id' => $server->id, 'server_id' => $server->id,
'username' => sprintf('u%d_%s', $server->id, str_random(10)), 'username' => sprintf('u%d_%s', $server->id, Str::random(10)),
'password' => Utilities::randomStringWithSpecialCharacters(24), 'password' => Utilities::randomStringWithSpecialCharacters(24),
]); ]);
return $this->connection->transaction(function () use ($data) { return $this->connection->transaction(function () use ($data) {
$database = $this->createModel($data); $database = $this->createModel($data);
$database->createDatabase($database->database); $database
$database->createUser( ->createDatabase()
$database->username, ->createUser()
$database->remote, ->assignUserToDatabase()
$database->password, ->flushPrivileges();
$database->max_connections
);
$database->assignUserToDatabase($database->database, $database->username, $database->remote);
$database->flush();
Activity::event('server:database.create') Activity::event('server:database.create')
->subject($database) ->subject($database)
@ -114,14 +111,15 @@ class DatabaseManagementService
/** /**
* Delete a database from the given host server. * Delete a database from the given host server.
* *
* @throws \Exception * @throws \Throwable
*/ */
public function delete(Database $database): ?bool public function delete(Database $database): ?bool
{ {
return $this->connection->transaction(function () use ($database) { return $this->connection->transaction(function () use ($database) {
$database->dropDatabase($database->database); $database
$database->dropUser($database->username, $database->remote); ->dropDatabase()
$database->flush(); ->dropUser()
->flushPrivileges();
Activity::event('server:database.delete') Activity::event('server:database.delete')
->subject($database) ->subject($database)
@ -132,6 +130,28 @@ class DatabaseManagementService
}); });
} }
/**
* Updates a password for a given database.
*
* @throws \Exception
*/
public function rotatePassword(Database $database): void
{
$password = Utilities::randomStringWithSpecialCharacters(24);
$this->connection->transaction(function () use ($database, $password) {
$database->update([
'password' => $password,
]);
$database
->dropUser()
->createUser()
->assignUserToDatabase()
->flushPrivileges();
});
}
/** /**
* Create the database if there is not an identical match in the DB. While you can technically * Create the database if there is not an identical match in the DB. While you can technically
* have the same name across multiple hosts, for the sake of keeping this logic easy to understand * have the same name across multiple hosts, for the sake of keeping this logic easy to understand

View File

@ -1,40 +0,0 @@
<?php
namespace App\Services\Databases;
use App\Models\Database;
use App\Helpers\Utilities;
use Illuminate\Database\ConnectionInterface;
class DatabasePasswordService
{
/**
* DatabasePasswordService constructor.
*/
public function __construct(
private ConnectionInterface $connection,
) {}
/**
* Updates a password for a given database.
*/
public function handle(Database|int $database): void
{
if (is_int($database)) {
$database = Database::query()->findOrFail($database);
}
$password = Utilities::randomStringWithSpecialCharacters(24);
$this->connection->transaction(function () use ($database, $password) {
$database->update([
'password' => $password,
]);
$database->dropUser($database->username, $database->remote);
$database->createUser($database->username, $database->remote, $password, $database->max_connections);
$database->assignUserToDatabase($database->database, $database->username, $database->remote);
$database->flush();
});
}
}

View File

@ -3,6 +3,7 @@
namespace App\Services\Users; namespace App\Services\Users;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Str;
use PragmaRX\Google2FA\Google2FA; use PragmaRX\Google2FA\Google2FA;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid; use App\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
@ -48,7 +49,7 @@ class ToggleTwoFactorService
if ((!$toggleState && !$user->use_totp) || $toggleState) { if ((!$toggleState && !$user->use_totp) || $toggleState) {
$user->recoveryTokens()->delete(); $user->recoveryTokens()->delete();
for ($i = 0; $i < 10; $i++) { for ($i = 0; $i < 10; $i++) {
$token = str_random(10); $token = Str::random(10);
$user->recoveryTokens()->forceCreate([ $user->recoveryTokens()->forceCreate([
'token' => password_hash($token, PASSWORD_DEFAULT), 'token' => password_hash($token, PASSWORD_DEFAULT),
]); ]);

View File

@ -37,7 +37,7 @@ class UserCreationService
$this->connection->beginTransaction(); $this->connection->beginTransaction();
if (empty($data['password'])) { if (empty($data['password'])) {
$generateResetToken = true; $generateResetToken = true;
$data['password'] = $this->hasher->make(str_random(30)); $data['password'] = $this->hasher->make(Str::random(30));
} }
$isRootAdmin = array_key_exists('root_admin', $data) && $data['root_admin']; $isRootAdmin = array_key_exists('root_admin', $data) && $data['root_admin'];

View File

@ -52,11 +52,16 @@ if (!function_exists('convert_bytes_to_readable')) {
if (!function_exists('join_paths')) { if (!function_exists('join_paths')) {
function join_paths(string $base, string ...$paths): string function join_paths(string $base, string ...$paths): string
{ {
if ($base === '/') { $base = trim($base, '/');
return str_replace('//', '', implode('/', $paths));
$paths = array_map(fn (string $path) => trim($path, '/'), $paths);
$paths = array_filter($paths, fn (string $path) => strlen($path) > 0);
if (empty($base)) {
return implode('/', $paths);
} }
return str_replace('//', '', $base . '/' . implode('/', $paths)); return $base . '/' . implode('/', $paths);
} }
} }

View File

@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use App\Contracts\Repository\DaemonKeyRepositoryInterface; use App\Contracts\Repository\DaemonKeyRepositoryInterface;
use Illuminate\Support\Str;
return new class extends Migration return new class extends Migration
{ {
@ -21,7 +22,7 @@ return new class extends Migration
$inserts[] = [ $inserts[] = [
'user_id' => $server->owner_id, 'user_id' => $server->owner_id,
'server_id' => $server->id, 'server_id' => $server->id,
'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40), 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . Str::random(40),
'expires_at' => Carbon::now()->addMinutes(config('panel.api.key_expire_time', 720))->toDateTimeString(), 'expires_at' => Carbon::now()->addMinutes(config('panel.api.key_expire_time', 720))->toDateTimeString(),
'created_at' => Carbon::now()->toDateTimeString(), 'created_at' => Carbon::now()->toDateTimeString(),
'updated_at' => Carbon::now()->toDateTimeString(), 'updated_at' => Carbon::now()->toDateTimeString(),

View File

@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use App\Contracts\Repository\DaemonKeyRepositoryInterface; use App\Contracts\Repository\DaemonKeyRepositoryInterface;
use Illuminate\Support\Str;
return new class extends Migration return new class extends Migration
{ {
@ -19,7 +20,7 @@ return new class extends Migration
$inserts[] = [ $inserts[] = [
'user_id' => $subuser->user_id, 'user_id' => $subuser->user_id,
'server_id' => $subuser->server_id, 'server_id' => $subuser->server_id,
'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40), 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . Str::random(40),
'expires_at' => Carbon::now()->addMinutes(config('panel.api.key_expire_time', 720))->toDateTimeString(), 'expires_at' => Carbon::now()->addMinutes(config('panel.api.key_expire_time', 720))->toDateTimeString(),
'created_at' => Carbon::now()->toDateTimeString(), 'created_at' => Carbon::now()->toDateTimeString(),
'updated_at' => Carbon::now()->toDateTimeString(), 'updated_at' => Carbon::now()->toDateTimeString(),

View File

@ -1,5 +1,6 @@
<?php <?php
use Illuminate\Support\Str;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@ -51,7 +52,7 @@ return new class extends Migration
DB::transaction(function () { DB::transaction(function () {
DB::table('service_options')->select(['id', 'tag'])->get()->each(function ($option) { DB::table('service_options')->select(['id', 'tag'])->get()->each(function ($option) {
DB::table('service_options')->where('id', $option->id)->update([ DB::table('service_options')->where('id', $option->id)->update([
'tag' => str_random(10), 'tag' => Str::random(10),
]); ]);
}); });
}); });

View File

@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Str;
return new class extends Migration return new class extends Migration
{ {
@ -19,7 +20,7 @@ return new class extends Migration
try { try {
$decrypted = Crypt::decrypt($item->secret); $decrypted = Crypt::decrypt($item->secret);
} catch (DecryptException $exception) { } catch (DecryptException $exception) {
$decrypted = str_random(32); $decrypted = Str::random(32);
} finally { } finally {
DB::table('api_keys')->where('id', $item->id)->update([ DB::table('api_keys')->where('id', $item->id)->update([
'secret' => $decrypted, 'secret' => $decrypted,
@ -66,7 +67,7 @@ return new class extends Migration
DB::transaction(function () { DB::transaction(function () {
DB::table('api_keys')->get()->each(function ($item) { DB::table('api_keys')->get()->each(function ($item) {
DB::table('api_keys')->where('id', $item->id)->update([ DB::table('api_keys')->where('id', $item->id)->update([
'public' => str_random(16), 'public' => Str::random(16),
'secret' => Crypt::encrypt($item->secret), 'secret' => Crypt::encrypt($item->secret),
]); ]);
}); });

View File

@ -19,4 +19,8 @@ return [
'database_host' => 'Database Host', 'database_host' => 'Database Host',
'database_host_select' => 'Select Database Host', 'database_host_select' => 'Select Database Host',
'jdbc' => 'JDBC Connection String', 'jdbc' => 'JDBC Connection String',
'create_notification' => 'Created :database',
'create_notification_fail' => 'Failed to create :database',
'delete_notification' => 'Deleted :database',
'delete_notification_fail' => 'Failed to delete :database',
]; ];

View File

@ -5,7 +5,6 @@ namespace App\Tests\Integration\Api\Client\Server\Database;
use App\Models\Subuser; use App\Models\Subuser;
use App\Models\Database; use App\Models\Database;
use App\Models\DatabaseHost; use App\Models\DatabaseHost;
use App\Services\Databases\DatabasePasswordService;
use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabaseManagementService;
use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProvider;
@ -33,8 +32,8 @@ class DatabaseAuthorizationTest extends ClientApiIntegrationTestCase
$database3 = Database::factory()->create(['server_id' => $server3->id, 'database_host_id' => $host->id]); $database3 = Database::factory()->create(['server_id' => $server3->id, 'database_host_id' => $host->id]);
$this $this
->mock($method === 'POST' ? DatabasePasswordService::class : DatabaseManagementService::class) ->mock(DatabaseManagementService::class)
->expects($method === 'POST' ? 'handle' : 'delete') ->expects($method === 'POST' ? 'rotatePassword' : 'delete')
->andReturn($method === 'POST' ? 'foo' : null); ->andReturn($method === 'POST' ? 'foo' : null);
// This is the only valid call for this test, accessing the database for the same // This is the only valid call for this test, accessing the database for the same

View File

@ -4,7 +4,7 @@ namespace App\Tests\Integration\Api\Client\Server;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use App\Models\Permission; use App\Models\Permission;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProvider;
@ -49,8 +49,8 @@ class PowerControllerTest extends ClientApiIntegrationTestCase
#[DataProvider('validPowerActionDataProvider')] #[DataProvider('validPowerActionDataProvider')]
public function test_action_can_be_sent_to_server(string $action, string $permission): void public function test_action_can_be_sent_to_server(string $action, string $permission): void
{ {
$service = \Mockery::mock(DaemonPowerRepository::class); $service = \Mockery::mock(DaemonServerRepository::class);
$this->app->instance(DaemonPowerRepository::class, $service); $this->app->instance(DaemonServerRepository::class, $service);
[$user, $server] = $this->generateTestAccount([$permission]); [$user, $server] = $this->generateTestAccount([$permission]);
@ -60,7 +60,7 @@ class PowerControllerTest extends ClientApiIntegrationTestCase
})) }))
->andReturnSelf() ->andReturnSelf()
->getMock() ->getMock()
->expects('send') ->expects('power')
->with(trim($action)); ->with(trim($action));
$this->actingAs($user) $this->actingAs($user)

View File

@ -10,8 +10,8 @@ use App\Models\Server;
use App\Models\Schedule; use App\Models\Schedule;
use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Bus;
use App\Jobs\Schedule\RunTaskJob; use App\Jobs\Schedule\RunTaskJob;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Tests\Integration\IntegrationTestCase; use App\Tests\Integration\IntegrationTestCase;
use App\Repositories\Daemon\DaemonPowerRepository;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProvider;
@ -84,13 +84,13 @@ class RunTaskJobTest extends IntegrationTestCase
'continue_on_failure' => false, 'continue_on_failure' => false,
]); ]);
$mock = \Mockery::mock(DaemonPowerRepository::class); $mock = \Mockery::mock(DaemonServerRepository::class);
$this->instance(DaemonPowerRepository::class, $mock); $this->instance(DaemonServerRepository::class, $mock);
$mock->expects('setServer')->with(\Mockery::on(function ($value) use ($server) { $mock->expects('setServer')->with(\Mockery::on(function ($value) use ($server) {
return $value instanceof Server && $value->id === $server->id; return $value instanceof Server && $value->id === $server->id;
}))->andReturnSelf(); }))->andReturnSelf();
$mock->expects('send')->with('start'); $mock->expects('power')->with('start');
Bus::dispatchSync(new RunTaskJob($task, $isManualRun)); Bus::dispatchSync(new RunTaskJob($task, $isManualRun));
@ -117,10 +117,10 @@ class RunTaskJobTest extends IntegrationTestCase
'continue_on_failure' => $continueOnFailure, 'continue_on_failure' => $continueOnFailure,
]); ]);
$mock = \Mockery::mock(DaemonPowerRepository::class); $mock = \Mockery::mock(DaemonServerRepository::class);
$this->instance(DaemonPowerRepository::class, $mock); $this->instance(DaemonServerRepository::class, $mock);
$mock->expects('setServer->send')->andThrow(new ConnectionException()); $mock->expects('setServer->power')->andThrow(new ConnectionException());
if (!$continueOnFailure) { if (!$continueOnFailure) {
$this->expectException(ConnectionException::class); $this->expectException(ConnectionException::class);