From c068f57e4e3d8fb6e25d504edec87404fb525330 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 7 Nov 2022 00:15:12 +0200 Subject: [PATCH 001/106] fix(forge): validate only input and not length (#4528) Only allows digits, dots and dashes in the input, which is what Forge versions consists of. Removes arbitrary char limit. --- database/Seeders/eggs/minecraft/egg-forge-minecraft.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index fbb097f06..189cafad9 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": null }, - "exported_at": "2022-06-17T08:11:14+03:00", + "exported_at": "2022-11-06T06:33:01-05:00", "name": "Forge Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.", @@ -67,12 +67,12 @@ }, { "name": "Forge Version", - "description": "Gets an exact version.\r\n\r\nEx. 1.15.2-31.2.4\r\n\r\nOverrides MC_VERSION and BUILD_TYPE. If it fails to download the server files it will fail to install.", + "description": "The full exact version.\r\n\r\nEx. 1.15.2-31.2.4\r\n\r\nOverrides MC_VERSION and BUILD_TYPE. If it fails to download the server files it will fail to install.", "env_variable": "FORGE_VERSION", "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|max:25", + "rules": "nullable|regex:\/^[0-9\\.\\-]+$\/", "field_type": "text" } ] From 9b218f219062fedf4ff318ebe966da10e8421f79 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Sun, 6 Nov 2022 17:24:33 -0500 Subject: [PATCH 002/106] URL encode password in JDBC connection string (#4527) --- resources/scripts/components/server/databases/DatabaseRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 1b486959e..9d2c05a73 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -35,7 +35,7 @@ export default ({ database, className }: Props) => { const removeDatabase = ServerContext.useStoreActions((actions) => actions.databases.removeDatabase); const jdbcConnectionString = `jdbc:mysql://${database.username}${ - database.password ? `:${database.password}` : '' + database.password ? `:${encodeURIComponent(database.password)}` : '' }@${database.connectionString}/${database.name}`; const schema = object().shape({ From 4032481a4f2f62e34b12f2e33f462559f73776de Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Sun, 6 Nov 2022 17:42:48 -0500 Subject: [PATCH 003/106] Update validation rules for remote activity logs (#4526) --- app/Http/Requests/Api/Remote/ActivityEventRequest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/Api/Remote/ActivityEventRequest.php b/app/Http/Requests/Api/Remote/ActivityEventRequest.php index 32ad11ad3..43f527dfa 100644 --- a/app/Http/Requests/Api/Remote/ActivityEventRequest.php +++ b/app/Http/Requests/Api/Remote/ActivityEventRequest.php @@ -17,11 +17,11 @@ class ActivityEventRequest extends FormRequest return [ 'data' => ['required', 'array'], 'data.*' => ['array'], - 'data.*.user' => ['present', 'uuid'], + 'data.*.user' => ['sometimes', 'nullable', 'uuid'], 'data.*.server' => ['required', 'uuid'], 'data.*.event' => ['required', 'string'], 'data.*.metadata' => ['present', 'nullable', 'array'], - 'data.*.ip' => ['present', 'ip'], + 'data.*.ip' => ['sometimes', 'nullable', 'ip'], 'data.*.timestamp' => ['required', 'string'], ]; } From 032e4f2e31a9de60be734702d1f158ef8d07ebf6 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 7 Nov 2022 00:02:30 +0100 Subject: [PATCH 004/106] Apply node maintenance mode to servers (#4421) --- .../Http/Server/ServerStateConflictException.php | 2 ++ .../Api/Client/Server/AuthenticateServerAccess.php | 2 +- .../Requests/Api/Application/Nodes/StoreNodeRequest.php | 1 + app/Models/Node.php | 5 +++++ app/Models/Server.php | 1 + app/Transformers/Api/Client/ServerTransformer.php | 1 + resources/scripts/api/server/getServer.ts | 2 ++ .../scripts/components/server/ConflictStateRenderer.tsx | 9 +++++++++ .../components/server/console/ServerConsoleContainer.tsx | 7 +++++-- resources/scripts/state/server/index.ts | 2 +- resources/views/admin/nodes/view/index.blade.php | 2 +- 11 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/Exceptions/Http/Server/ServerStateConflictException.php b/app/Exceptions/Http/Server/ServerStateConflictException.php index f0eb096b1..ea6a60a55 100644 --- a/app/Exceptions/Http/Server/ServerStateConflictException.php +++ b/app/Exceptions/Http/Server/ServerStateConflictException.php @@ -17,6 +17,8 @@ class ServerStateConflictException extends ConflictHttpException $message = 'This server is currently in an unsupported state, please try again later.'; if ($server->isSuspended()) { $message = 'This server is currently suspended and the functionality requested is unavailable.'; + } elseif ($server->node->isUnderMaintenance()) { + $message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.'; } elseif (!$server->isInstalled()) { $message = 'This server has not yet completed its installation process, please try again later.'; } elseif ($server->status === Server::STATUS_RESTORING_BACKUP) { diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 40a4d0cf1..358fb6d57 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -53,7 +53,7 @@ class AuthenticateServerAccess // Still allow users to get information about their server if it is installing or // being transferred. if (!$request->routeIs('api:client:server.view')) { - if ($server->isSuspended() && !$request->routeIs('api:client:server.resources')) { + if (($server->isSuspended() || $server->node->isUnderMaintenance()) && !$request->routeIs('api:client:server.resources')) { throw $exception; } if (!$user->root_admin || !$request->routeIs($this->except)) { diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index bc559083e..4fe705448 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -24,6 +24,7 @@ class StoreNodeRequest extends ApplicationApiRequest 'fqdn', 'scheme', 'behind_proxy', + 'maintenance_mode', 'memory', 'memory_overallocate', 'disk', diff --git a/app/Models/Node.php b/app/Models/Node.php index 37aec760d..62ec82871 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -186,6 +186,11 @@ class Node extends Model ); } + public function isUnderMaintenance(): bool + { + return $this->maintenance_mode; + } + public function mounts(): HasManyThrough { return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); diff --git a/app/Models/Server.php b/app/Models/Server.php index 5ad99151a..d7cc649c0 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -354,6 +354,7 @@ class Server extends Model { if ( $this->isSuspended() || + $this->node->isUnderMaintenance() || !$this->isInstalled() || $this->status === self::STATUS_RESTORING_BACKUP || !is_null($this->transfer) diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 8ae4ed4a6..9f7bce958 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -43,6 +43,7 @@ class ServerTransformer extends BaseClientTransformer 'uuid' => $server->uuid, 'name' => $server->name, 'node' => $server->node->name, + 'is_node_under_maintenance' => $server->node->isUnderMaintenance(), 'sftp_details' => [ 'ip' => $server->node->fqdn, 'port' => $server->node->daemonSFTP, diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index 5ed92a427..d2aa2e054 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -17,6 +17,7 @@ export interface Server { uuid: string; name: string; node: string; + isNodeUnderMaintenance: boolean; status: ServerStatus; sftpDetails: { ip: string; @@ -50,6 +51,7 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData) uuid: data.uuid, name: data.name, node: data.node, + isNodeUnderMaintenance: data.is_node_under_maintenance, status: data.status, invocation: data.invocation, dockerImage: data.docker_image, diff --git a/resources/scripts/components/server/ConflictStateRenderer.tsx b/resources/scripts/components/server/ConflictStateRenderer.tsx index 70475a4ee..95e70bbaa 100644 --- a/resources/scripts/components/server/ConflictStateRenderer.tsx +++ b/resources/scripts/components/server/ConflictStateRenderer.tsx @@ -8,6 +8,9 @@ import ServerRestoreSvg from '@/assets/images/server_restore.svg'; export default () => { const status = ServerContext.useStoreState((state) => state.server.data?.status || null); const isTransferring = ServerContext.useStoreState((state) => state.server.data?.isTransferring || false); + const isNodeUnderMaintenance = ServerContext.useStoreState( + (state) => state.server.data?.isNodeUnderMaintenance || false + ); return status === 'installing' || status === 'install_failed' ? ( { image={ServerErrorSvg} message={'This server is suspended and cannot be accessed.'} /> + ) : isNodeUnderMaintenance ? ( + ) : ( { const isInstalling = ServerContext.useStoreState((state) => state.server.isInstalling); const isTransferring = ServerContext.useStoreState((state) => state.server.data!.isTransferring); const eggFeatures = ServerContext.useStoreState((state) => state.server.data!.eggFeatures, isEqual); + const isNodeUnderMaintenance = ServerContext.useStoreState((state) => state.server.data!.isNodeUnderMaintenance); return ( - {(isInstalling || isTransferring) && ( + {(isNodeUnderMaintenance || isInstalling || isTransferring) && ( - {isInstalling + {isNodeUnderMaintenance + ? 'The node of this server is currently under maintenance and all actions are unavailable.' + : isInstalling ? 'This server is currently running its installation process and most actions are unavailable.' : 'This server is currently being transferred to another node and all actions are unavailable.'} diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index 8d8eba749..f9806b649 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -30,7 +30,7 @@ const server: ServerDataStore = { return false; } - return state.data.status !== null || state.data.isTransferring; + return state.data.status !== null || state.data.isTransferring || state.data.isNodeUnderMaintenance; }), isInstalling: computed((state) => { diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php index 9ef461076..ce90b30b2 100644 --- a/resources/views/admin/nodes/view/index.blade.php +++ b/resources/views/admin/nodes/view/index.blade.php @@ -93,7 +93,7 @@
@if($node->maintenance_mode)
-
+
This node is under From df2402b54f8db10e8319b4250a2960c97e22b296 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 14 Nov 2022 18:25:07 -0700 Subject: [PATCH 005/106] Streaming Transfers (#4548) --- .../Servers/ServerTransferController.php | 42 ++++++++++------ .../Servers/ServerTransferController.php | 48 +------------------ .../Wings/DaemonTransferRepository.php | 12 ++--- app/Services/Servers/TransferService.php | 27 ----------- .../components/server/TransferListener.tsx | 8 ++-- .../components/server/console/Console.tsx | 7 --- 6 files changed, 38 insertions(+), 106 deletions(-) delete mode 100644 app/Services/Servers/TransferService.php diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index 096217418..8941ce10c 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -2,15 +2,17 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; +use Carbon\CarbonImmutable; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Models\ServerTransfer; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Servers\TransferService; +use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Repositories\Eloquent\NodeRepository; -use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; +use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class ServerTransferController extends Controller @@ -21,9 +23,10 @@ class ServerTransferController extends Controller public function __construct( private AlertsMessageBag $alert, private AllocationRepositoryInterface $allocationRepository, - private NodeRepository $nodeRepository, - private TransferService $transferService, - private DaemonConfigurationRepository $daemonConfigurationRepository + private ConnectionInterface $connection, + private DaemonTransferRepository $daemonTransferRepository, + private NodeJWTService $nodeJWTService, + private NodeRepository $nodeRepository ) { } @@ -46,12 +49,15 @@ class ServerTransferController extends Controller // Check if the node is viable for the transfer. $node = $this->nodeRepository->getNodeWithResourceUsage($node_id); - if ($node->isViable($server->memory, $server->disk)) { - // Check if the selected daemon is online. - $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(); + if (!$node->isViable($server->memory, $server->disk)) { + $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); - $server->validateTransferState(); + return redirect()->route('admin.servers.view.manage', $server->id); + } + $server->validateTransferState(); + + $this->connection->transaction(function () use ($server, $node_id, $allocation_id, $additional_allocations) { // Create a new ServerTransfer entry. $transfer = new ServerTransfer(); @@ -68,13 +74,19 @@ class ServerTransferController extends Controller // Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress. $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); - // Request an archive from the server's current daemon. (this also checks if the daemon is online) - $this->transferService->requestArchive($server); + // Generate a token for the destination node that the source node can use to authenticate with. + $token = $this->nodeJWTService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setSubject($server->uuid) + ->handle($transfer->newNode, $server->uuid, 'sha256'); - $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); - } else { - $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); - } + // Notify the source node of the pending outgoing transfer. + $this->daemonTransferRepository->setServer($server)->notify($transfer->newNode, $token); + + return $transfer; + }); + + $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); return redirect()->route('admin.servers.view.manage', $server->id); } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 6f49d66e0..72153bf25 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; -use Carbon\CarbonImmutable; -use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Allocation; @@ -11,10 +9,8 @@ use Illuminate\Support\Facades\Log; use Pterodactyl\Models\ServerTransfer; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class ServerTransferController extends Controller @@ -25,52 +21,10 @@ class ServerTransferController extends Controller public function __construct( private ConnectionInterface $connection, private ServerRepository $repository, - private DaemonServerRepository $daemonServerRepository, - private DaemonTransferRepository $daemonTransferRepository, - private NodeJWTService $jwtService + private DaemonServerRepository $daemonServerRepository ) { } - /** - * The daemon notifies us about the archive status. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Throwable - */ - public function archive(Request $request, string $uuid): JsonResponse - { - $server = $this->repository->getByUuid($uuid); - - // Unsuspend the server and don't continue the transfer. - if (!$request->input('successful')) { - return $this->processFailedTransfer($server->transfer); - } - - $this->connection->transaction(function () use ($server) { - // This token is used by the new node the server is being transferred to. It allows - // that node to communicate with the old node during the process to initiate the - // actual file transfer. - $token = $this->jwtService - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setSubject($server->uuid) - ->handle($server->node, $server->uuid, 'sha256'); - - // Update the archived field on the transfer to make clients connect to the websocket - // on the new node to be able to receive transfer logs. - $server->transfer->forceFill(['archived' => true])->saveOrFail(); - - // On the daemon transfer repository, make sure to set the node after the server - // because setServer() tells the repository to use the server's node and not the one - // we want to specify. - $this->daemonTransferRepository - ->setServer($server) - ->setNode($server->transfer->newNode) - ->notify($server, $token); - }); - - return new JsonResponse([], Response::HTTP_NO_CONTENT); - } - /** * The daemon notifies us about a transfer failure. * diff --git a/app/Repositories/Wings/DaemonTransferRepository.php b/app/Repositories/Wings/DaemonTransferRepository.php index 3939a47cd..9c8745232 100644 --- a/app/Repositories/Wings/DaemonTransferRepository.php +++ b/app/Repositories/Wings/DaemonTransferRepository.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Repositories\Wings; +use Pterodactyl\Models\Node; use Lcobucci\JWT\Token\Plain; -use Pterodactyl\Models\Server; use GuzzleHttp\Exception\GuzzleException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; @@ -12,16 +12,16 @@ class DaemonTransferRepository extends DaemonRepository /** * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function notify(Server $server, Plain $token): void + public function notify(Node $targetNode, Plain $token): void { try { - $this->getHttpClient()->post('/api/transfer', [ + $this->getHttpClient()->post(sprintf('/api/servers/%s/transfer', $this->server->uuid), [ 'json' => [ - 'server_id' => $server->uuid, - 'url' => $server->node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid), + 'server_id' => $this->server->uuid, + 'url' => $targetNode->getConnectionAddress() . '/api/transfers', 'token' => 'Bearer ' . $token->toString(), 'server' => [ - 'uuid' => $server->uuid, + 'uuid' => $this->server->uuid, 'start_on_completion' => false, ], ], diff --git a/app/Services/Servers/TransferService.php b/app/Services/Servers/TransferService.php deleted file mode 100644 index 24ef0a588..000000000 --- a/app/Services/Servers/TransferService.php +++ /dev/null @@ -1,27 +0,0 @@ -daemonServerRepository->setServer($server)->requestArchive(); - } -} diff --git a/resources/scripts/components/server/TransferListener.tsx b/resources/scripts/components/server/TransferListener.tsx index 64460aa69..4d9421745 100644 --- a/resources/scripts/components/server/TransferListener.tsx +++ b/resources/scripts/components/server/TransferListener.tsx @@ -7,19 +7,19 @@ const TransferListener = () => { const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); - // Listen for the transfer status event so we can update the state of the server. + // Listen for the transfer status event, so we can update the state of the server. useWebsocketEvent(SocketEvent.TRANSFER_STATUS, (status: string) => { - if (status === 'starting') { + if (status === 'pending' || status === 'processing') { setServerFromState((s) => ({ ...s, isTransferring: true })); return; } - if (status === 'failure') { + if (status === 'failed') { setServerFromState((s) => ({ ...s, isTransferring: false })); return; } - if (status !== 'success') { + if (status !== 'completed') { return; } diff --git a/resources/scripts/components/server/console/Console.tsx b/resources/scripts/components/server/console/Console.tsx index 9468d0a56..e3cb43ab2 100644 --- a/resources/scripts/components/server/console/Console.tsx +++ b/resources/scripts/components/server/console/Console.tsx @@ -76,13 +76,6 @@ export default () => { case 'failure': terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m'); return; - - // Sent by the source node whenever the server was archived successfully. - case 'archive': - terminal.writeln( - TERMINAL_PRELUDE + - 'Server has been archived successfully, attempting connection to target node..\u001b[0m' - ); } }; From 897ca48ded10743febb0e1f3895ae7429a17ff8d Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 14 Nov 2022 18:28:02 -0700 Subject: [PATCH 006/106] github: re-enable blank issues --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 09011627c..fee5ad948 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - name: Installation Help url: https://discord.gg/pterodactyl From c1584d9a5bc8e8a284ddd7d3de7e38015401651b Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 14 Nov 2022 20:31:03 -0700 Subject: [PATCH 007/106] Update CHANGELOG.md --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7433419..371916bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,17 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. -## [Unreleased] +## v1.11.0-rc.1 ### Changed * Changed minimum PHP version is now 8.0 instead of `7.4`. * Upgraded from Laravel 8 to Laravel 9. +* This release requires Wings v1.11.x in order for Server Transfers to work. + +### Fixed +* Node maintenance mode now properly blocks access to servers. +* Fixed the length validation on the Minecraft Forge egg. +* Fixed the password in the JDBC string not being properly URL encoded. +* Fixed an issue where Wings would throw a validation error while attempting to upload activity logs. ## v1.10.4 ### Fixed From 2f4a60c9615af0f06b025c3936ade89dc87ba974 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 21 Nov 2022 13:10:00 -0700 Subject: [PATCH 008/106] ui(admin): change `MB` suffixes to `MiB` Closes #4542 --- resources/views/admin/nodes/index.blade.php | 4 ++-- resources/views/admin/nodes/new.blade.php | 4 ++-- resources/views/admin/nodes/view/index.blade.php | 4 ++-- resources/views/admin/nodes/view/settings.blade.php | 6 +++--- resources/views/admin/servers/new.blade.php | 6 +++--- resources/views/admin/servers/view/build.blade.php | 6 +++--- resources/views/admin/servers/view/index.blade.php | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php index a4747b3fc..3b3a3fa48 100644 --- a/resources/views/admin/nodes/index.blade.php +++ b/resources/views/admin/nodes/index.blade.php @@ -53,8 +53,8 @@ {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }} {{ $node->location->short }} - {{ $node->memory }} MB - {{ $node->disk }} MB + {{ $node->memory }} MiB + {{ $node->disk }} MiB {{ $node->servers_count }} diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php index 3e10be5a1..b10d08a76 100644 --- a/resources/views/admin/nodes/new.blade.php +++ b/resources/views/admin/nodes/new.blade.php @@ -110,7 +110,7 @@
- MB + MiB
@@ -129,7 +129,7 @@
- MB + MiB
diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php index ce90b30b2..2d0bb3287 100644 --- a/resources/views/admin/nodes/view/index.blade.php +++ b/resources/views/admin/nodes/view/index.blade.php @@ -107,7 +107,7 @@
Disk Space Allocated - {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} MB + {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} MiB
@@ -119,7 +119,7 @@
Memory Allocated - {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} MB + {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} MiB
diff --git a/resources/views/admin/nodes/view/settings.blade.php b/resources/views/admin/nodes/view/settings.blade.php index e4848aca3..bfc9d8caf 100644 --- a/resources/views/admin/nodes/view/settings.blade.php +++ b/resources/views/admin/nodes/view/settings.blade.php @@ -132,7 +132,7 @@
- MB + MiB
@@ -151,7 +151,7 @@
- MB + MiB
@@ -177,7 +177,7 @@
- MB + MiB

Enter the maximum size of files that can be uploaded through the web-based file manager.

diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index bbe779ce6..4198634f1 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -170,7 +170,7 @@
- MB + MiB

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

@@ -181,7 +181,7 @@
- MB + MiB

Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

@@ -194,7 +194,7 @@
- MB + MiB

This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

diff --git a/resources/views/admin/servers/view/build.blade.php b/resources/views/admin/servers/view/build.blade.php index f7ba9a2d2..655ea36af 100644 --- a/resources/views/admin/servers/view/build.blade.php +++ b/resources/views/admin/servers/view/build.blade.php @@ -43,7 +43,7 @@
- MB + MiB

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

@@ -51,7 +51,7 @@
- MB + MiB

Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

@@ -59,7 +59,7 @@
- MB + MiB

This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php index e9b95f985..f94c6d42f 100644 --- a/resources/views/admin/servers/view/index.blade.php +++ b/resources/views/admin/servers/view/index.blade.php @@ -78,7 +78,7 @@ @if($server->memory === 0) Unlimited @else - {{ $server->memory }}MB + {{ $server->memory }}MiB @endif / @if($server->swap === 0) @@ -86,7 +86,7 @@ @elseif($server->swap === -1) Unlimited @else - {{ $server->swap }}MB + {{ $server->swap }}MiB @endif From c3521e022161606c115d8a900040cf2cae190a96 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 21 Nov 2022 13:15:49 -0700 Subject: [PATCH 009/106] api(server): fix undefined header --- app/Repositories/Wings/DaemonFileRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Repositories/Wings/DaemonFileRepository.php b/app/Repositories/Wings/DaemonFileRepository.php index eb3649641..3bd897881 100644 --- a/app/Repositories/Wings/DaemonFileRepository.php +++ b/app/Repositories/Wings/DaemonFileRepository.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Repositories\Wings; +use Illuminate\Support\Arr; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Psr\Http\Message\ResponseInterface; @@ -35,8 +36,7 @@ class DaemonFileRepository extends DaemonRepository throw new DaemonConnectionException($exception); } - $length = (int) $response->getHeader('Content-Length')[0] ?? 0; - + $length = (int) Arr::get($response->getHeader('Content-Length'), 0, 0); if ($notLargerThan && $length > $notLargerThan) { throw new FileSizeTooLargeException(); } From 634c9353e309c603064530824dfbb3c870a1f7b6 Mon Sep 17 00:00:00 2001 From: Devonte W Date: Mon, 21 Nov 2022 20:28:46 +0000 Subject: [PATCH 010/106] fix(transformers): force object type for properties (#4544) --- app/Transformers/Api/Client/ActivityLogTransformer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php index 849fdc865..57c8ac30e 100644 --- a/app/Transformers/Api/Client/ActivityLogTransformer.php +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -47,10 +47,10 @@ class ActivityLogTransformer extends BaseClientTransformer * Transforms any array values in the properties into a countable field for easier * use within the translation outputs. */ - protected function properties(ActivityLog $model): array + protected function properties(ActivityLog $model): object { if (!$model->properties || $model->properties->isEmpty()) { - return []; + return (object) []; } $properties = $model->properties @@ -76,7 +76,7 @@ class ActivityLogTransformer extends BaseClientTransformer $properties = $properties->merge(['count' => $properties->get($keys[0])])->except($keys[0]); } - return $properties->toArray(); + return (object) $properties->toArray(); } /** From 039ad4abf04586011de484ce2942429d0ecbad75 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Mon, 21 Nov 2022 13:41:26 -0700 Subject: [PATCH 011/106] api(server): log activity when server description is changed --- .../Api/Client/Servers/SettingsController.php | 16 ++++++++++++---- app/Services/Activity/ActivityLogService.php | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index d1377d67a..6e6451836 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -34,14 +34,22 @@ class SettingsController extends ClientApiController */ public function rename(RenameServerRequest $request, Server $server): JsonResponse { + $name = $request->input('name'); + $description = $request->input('description') ?? ''; $this->repository->update($server->id, [ - 'name' => $request->input('name'), - 'description' => $request->input('description') ?? '', + 'name' => $name, + 'description' => $description, ]); - if ($server->name !== $request->input('name')) { + if ($server->name !== $name) { Activity::event('server:settings.rename') - ->property(['old' => $server->name, 'new' => $request->input('name')]) + ->property(['old' => $server->name, 'new' => $name]) + ->log(); + } + + if ($server->description !== $description) { + Activity::event('server:settings.description') + ->property(['old' => $server->description, 'new' => $description]) ->log(); } diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index fa4f46936..f86385214 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -99,7 +99,7 @@ class ActivityLogService } /** - * Sets a custom property on the activty log instance. + * Sets a custom property on the activity log instance. * * @param string|array $key * @param mixed $value @@ -115,7 +115,7 @@ class ActivityLogService } /** - * Attachs the instance request metadata to the activity log event. + * Attaches the instance request metadata to the activity log event. */ public function withRequestMetadata(): self { From a4f6870518f10855a063f19fc4dc69b9e83c070f Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Mon, 21 Nov 2022 15:53:54 -0500 Subject: [PATCH 012/106] server: track reinstall failures differently from initial install failures (#4531) --- .../Api/Remote/Servers/ServerInstallController.php | 12 +++++++++++- .../Requests/Api/Remote/InstallationDataRequest.php | 1 + app/Models/Server.php | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php index e78843763..8fcfbe064 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php @@ -48,8 +48,18 @@ class ServerInstallController extends Controller public function store(InstallationDataRequest $request, string $uuid): JsonResponse { $server = $this->repository->getByUuid($uuid); + $status = null; - $status = $request->boolean('successful') ? null : Server::STATUS_INSTALL_FAILED; + // Make sure the type of failure is accurate + if (!$request->boolean('successful')) { + $status = Server::STATUS_INSTALL_FAILED; + + if ($request->boolean('reinstall')) { + $status = Server::STATUS_REINSTALL_FAILED; + } + } + + // Keep the server suspended if it's already suspended if ($server->status === Server::STATUS_SUSPENDED) { $status = Server::STATUS_SUSPENDED; } diff --git a/app/Http/Requests/Api/Remote/InstallationDataRequest.php b/app/Http/Requests/Api/Remote/InstallationDataRequest.php index c5d1973ec..13b3e77d7 100644 --- a/app/Http/Requests/Api/Remote/InstallationDataRequest.php +++ b/app/Http/Requests/Api/Remote/InstallationDataRequest.php @@ -15,6 +15,7 @@ class InstallationDataRequest extends FormRequest { return [ 'successful' => 'present|boolean', + 'reinstall' => 'sometimes|boolean', ]; } } diff --git a/app/Models/Server.php b/app/Models/Server.php index d7cc649c0..5f2d6a49e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -115,6 +115,7 @@ class Server extends Model public const STATUS_INSTALLING = 'installing'; public const STATUS_INSTALL_FAILED = 'install_failed'; + public const STATUS_REINSTALL_FAILED = 'reinstall_failed'; public const STATUS_SUSPENDED = 'suspended'; public const STATUS_RESTORING_BACKUP = 'restoring_backup'; From df9a7f71f99a7afcd93de02b6b96fdf2ecc7dabf Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 21 Nov 2022 12:58:55 -0800 Subject: [PATCH 013/106] Support canceling file uploads (#4441) Closes #4440 --- package.json | 1 + .../components/elements/dialog/Dialog.tsx | 2 +- .../server/files/FileManagerStatus.tsx | 41 +++++++++++------ .../components/server/files/UploadButton.tsx | 45 +++++++++---------- resources/scripts/state/server/files.ts | 35 ++++++++++----- yarn.lock | 18 ++++++++ 6 files changed, 92 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 562c22f7e..e716485cb 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@headlessui/react": "^1.6.4", "@heroicons/react": "^1.0.6", "@hot-loader/react-dom": "^16.14.0", + "@preact/signals-react": "^1.2.1", "@tailwindcss/forms": "^0.5.2", "@tailwindcss/line-clamp": "^0.4.0", "axios": "^0.27.2", diff --git a/resources/scripts/components/elements/dialog/Dialog.tsx b/resources/scripts/components/elements/dialog/Dialog.tsx index b280f0944..bb35fe8fb 100644 --- a/resources/scripts/components/elements/dialog/Dialog.tsx +++ b/resources/scripts/components/elements/dialog/Dialog.tsx @@ -90,7 +90,7 @@ export default ({ >
{iconPosition === 'container' && icon} -
+
{iconPosition !== 'container' && icon}
diff --git a/resources/scripts/components/server/files/FileManagerStatus.tsx b/resources/scripts/components/server/files/FileManagerStatus.tsx index 2e4144d0f..620e067c1 100644 --- a/resources/scripts/components/server/files/FileManagerStatus.tsx +++ b/resources/scripts/components/server/files/FileManagerStatus.tsx @@ -1,11 +1,12 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import { ServerContext } from '@/state/server'; -import { CloudUploadIcon } from '@heroicons/react/solid'; +import { CloudUploadIcon, XIcon } from '@heroicons/react/solid'; import asDialog from '@/hoc/asDialog'; import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; import Tooltip from '@/components/elements/tooltip/Tooltip'; import Code from '@/components/elements/Code'; +import { useSignal } from '@preact/signals-react'; const svgProps = { cx: 16, @@ -31,23 +32,34 @@ const Spinner = ({ progress, className }: { progress: number; className?: string const FileUploadList = () => { const { close } = useContext(DialogWrapperContext); + const removeFileUpload = ServerContext.useStoreActions((actions) => actions.files.removeFileUpload); + const clearFileUploads = ServerContext.useStoreActions((actions) => actions.files.clearFileUploads); const uploads = ServerContext.useStoreState((state) => - state.files.uploads.sort((a, b) => a.name.localeCompare(b.name)) + Object.entries(state.files.uploads).sort(([a], [b]) => a.localeCompare(b)) ); return (
- {uploads.map((file) => ( -
+ {uploads.map(([name, file]) => ( +
- {file.name} + {name} +
))} + clearFileUploads()}> + Cancel Uploads + Close
@@ -60,17 +72,17 @@ const FileUploadListDialog = asDialog({ })(FileUploadList); export default () => { - const [open, setOpen] = useState(false); + const open = useSignal(false); - const count = ServerContext.useStoreState((state) => state.files.uploads.length); + const count = ServerContext.useStoreState((state) => Object.keys(state.files.uploads).length); const progress = ServerContext.useStoreState((state) => ({ - uploaded: state.files.uploads.reduce((count, file) => count + file.loaded, 0), - total: state.files.uploads.reduce((count, file) => count + file.total, 0), + uploaded: Object.values(state.files.uploads).reduce((count, file) => count + file.loaded, 0), + total: Object.values(state.files.uploads).reduce((count, file) => count + file.total, 0), })); useEffect(() => { if (count === 0) { - setOpen(false); + open.value = false; } }, [count]); @@ -78,13 +90,16 @@ export default () => { <> {count > 0 && ( - )} - + (open.value = false)} /> ); }; diff --git a/resources/scripts/components/server/files/UploadButton.tsx b/resources/scripts/components/server/files/UploadButton.tsx index 25277456d..c5cac0e29 100644 --- a/resources/scripts/components/server/files/UploadButton.tsx +++ b/resources/scripts/components/server/files/UploadButton.tsx @@ -2,7 +2,7 @@ import axios from 'axios'; import getFileUploadUrl from '@/api/server/files/getFileUploadUrl'; import tw from 'twin.macro'; import { Button } from '@/components/elements/button/index'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import { ModalMask } from '@/components/elements/Modal'; import Fade from '@/components/elements/Fade'; import useEventListener from '@/plugins/useEventListener'; @@ -12,6 +12,7 @@ import { ServerContext } from '@/state/server'; import { WithClassname } from '@/components/types'; import Portal from '@/components/elements/Portal'; import { CloudUploadIcon } from '@heroicons/react/outline'; +import { useSignal } from '@preact/signals-react'; function isFileOrDirectory(event: DragEvent): boolean { if (!event.dataTransfer?.types) { @@ -23,14 +24,16 @@ function isFileOrDirectory(event: DragEvent): boolean { export default ({ className }: WithClassname) => { const fileUploadInput = useRef(null); - const [timeouts, setTimeouts] = useState([]); - const [visible, setVisible] = useState(false); + + const visible = useSignal(false); + const timeouts = useSignal([]); + const { mutate } = useFileManagerSwr(); const { addError, clearAndAddHttpError } = useFlashKey('files'); const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const directory = ServerContext.useStoreState((state) => state.files.directory); - const { clearFileUploads, appendFileUpload, removeFileUpload } = ServerContext.useStoreActions( + const { clearFileUploads, removeFileUpload, pushFileUpload, setUploadProgress } = ServerContext.useStoreActions( (actions) => actions.files ); @@ -40,27 +43,24 @@ export default ({ className }: WithClassname) => { e.preventDefault(); e.stopPropagation(); if (isFileOrDirectory(e)) { - return setVisible(true); + visible.value = true; } }, { capture: true } ); - useEventListener('dragexit', () => setVisible(false), { capture: true }); + useEventListener('dragexit', () => (visible.value = false), { capture: true }); - useEventListener('keydown', () => { - visible && setVisible(false); - }); + useEventListener('keydown', () => (visible.value = false)); useEffect(() => { - return () => timeouts.forEach(clearTimeout); + return () => timeouts.value.forEach(clearTimeout); }, []); const onUploadProgress = (data: ProgressEvent, name: string) => { - appendFileUpload({ name, loaded: data.loaded, total: data.total }); + setUploadProgress({ name, loaded: data.loaded }); if (data.loaded >= data.total) { - const timeout = setTimeout(() => removeFileUpload(name), 500); - setTimeouts((t) => [...t, timeout]); + timeouts.value.push(setTimeout(() => removeFileUpload(name), 500)); } }; @@ -71,23 +71,20 @@ export default ({ className }: WithClassname) => { return addError('Folder uploads are not supported at this time.', 'Error'); } - if (!list.length) { - return; - } - const uploads = list.map((file) => { - appendFileUpload({ name: file.name, loaded: 0, total: file.size }); + const controller = new AbortController(); + pushFileUpload({ name: file.name, data: { abort: controller, loaded: 0, total: file.size } }); + return () => getFileUploadUrl(uuid).then((url) => axios.post( url, { files: file }, { + signal: controller.signal, headers: { 'Content-Type': 'multipart/form-data' }, params: { directory }, - onUploadProgress: (data) => { - onUploadProgress(data, file.name); - }, + onUploadProgress: (data) => onUploadProgress(data, file.name), } ) ); @@ -104,15 +101,15 @@ export default ({ className }: WithClassname) => { return ( <> - + setVisible(false)} + onClick={() => (visible.value = false)} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { e.preventDefault(); e.stopPropagation(); - setVisible(false); + visible.value = false; if (!e.dataTransfer?.files.length) return; onFileSubmission(e.dataTransfer.files); diff --git a/resources/scripts/state/server/files.ts b/resources/scripts/state/server/files.ts index 4a4b7fb93..7e4786ae4 100644 --- a/resources/scripts/state/server/files.ts +++ b/resources/scripts/state/server/files.ts @@ -1,31 +1,32 @@ import { action, Action } from 'easy-peasy'; import { cleanDirectoryPath } from '@/helpers'; -export interface FileUpload { - name: string; +export interface FileUploadData { loaded: number; + readonly abort: AbortController; readonly total: number; } export interface ServerFileStore { directory: string; selectedFiles: string[]; - uploads: FileUpload[]; + uploads: Record; setDirectory: Action; setSelectedFiles: Action; appendSelectedFile: Action; removeSelectedFile: Action; + pushFileUpload: Action; + setUploadProgress: Action; clearFileUploads: Action; - appendFileUpload: Action; removeFileUpload: Action; } const files: ServerFileStore = { directory: '/', selectedFiles: [], - uploads: [], + uploads: {}, setDirectory: action((state, payload) => { state.directory = cleanDirectoryPath(payload); @@ -44,19 +45,29 @@ const files: ServerFileStore = { }), clearFileUploads: action((state) => { - state.uploads = []; + Object.values(state.uploads).forEach((upload) => upload.abort.abort()); + + state.uploads = {}; }), - appendFileUpload: action((state, payload) => { - if (!state.uploads.some(({ name }) => name === payload.name)) { - state.uploads = [...state.uploads, payload]; - } else { - state.uploads = state.uploads.map((file) => (file.name === payload.name ? payload : file)); + pushFileUpload: action((state, payload) => { + state.uploads[payload.name] = payload.data; + }), + + setUploadProgress: action((state, { name, loaded }) => { + if (state.uploads[name]) { + state.uploads[name].loaded = loaded; } }), removeFileUpload: action((state, payload) => { - state.uploads = state.uploads.filter(({ name }) => name !== payload); + if (state.uploads[payload]) { + // Abort the request if it is still in flight. If it already completed this is + // a no-op. + state.uploads[payload].abort.abort(); + + delete state.uploads[payload]; + } }), }; diff --git a/yarn.lock b/yarn.lock index e29b8781d..3142ac755 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1493,6 +1493,19 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@preact/signals-core@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.2.2.tgz#279dcc5ab249de2f2e8f6e6779b1958256ba843e" + integrity sha512-z3/bCj7rRA21RJb4FeJ4guCrD1CQbaURHkCTunUWQpxUMAFOPXCD8tSFqERyGrrcSb4T3Hrmdc1OAl0LXBHwiw== + +"@preact/signals-react@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@preact/signals-react/-/signals-react-1.2.1.tgz#6d5d305ebdb38c879043acebc65c0d9351e663c1" + integrity sha512-73J8sL1Eru7Ot4yBYOCPj1izEZjzCEXlembRgk6C7PkwsqoAVbCxMlDOFfCLoPFuJ6qeGatrJzRkcycXppMqVQ== + dependencies: + "@preact/signals-core" "^1.2.2" + use-sync-external-store "^1.2.0" + "@sinclair/typebox@^0.23.3": version "0.23.5" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" @@ -9121,6 +9134,11 @@ use-memo-one@^1.1.1: resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + use@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" From ee033d6d08b68b710c5401741de0238f75435c0b Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Tue, 22 Nov 2022 13:39:43 -0700 Subject: [PATCH 014/106] Telemetry (#4564) --- app/Console/Commands/TelemetryCommand.php | 34 ++++ app/Console/Kernel.php | 32 ++++ .../Wings/DaemonConfigurationRepository.php | 4 +- .../Telemetry/TelemetryCollectionService.php | 175 ++++++++++++++++++ config/pterodactyl.php | 12 ++ 5 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/TelemetryCommand.php create mode 100644 app/Services/Telemetry/TelemetryCollectionService.php diff --git a/app/Console/Commands/TelemetryCommand.php b/app/Console/Commands/TelemetryCommand.php new file mode 100644 index 000000000..3e1b0c2f8 --- /dev/null +++ b/app/Console/Commands/TelemetryCommand.php @@ -0,0 +1,34 @@ +output->info('Collecting telemetry data, this may take a while...'); + + VarDumper::dump($this->telemetryCollectionService->collect()); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 17b3d4de4..a00b17b6d 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,10 +2,13 @@ namespace Pterodactyl\Console; +use Ramsey\Uuid\Uuid; use Pterodactyl\Models\ActivityLog; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Database\Console\PruneCommand; +use Pterodactyl\Repositories\Eloquent\SettingsRepository; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use Pterodactyl\Services\Telemetry\TelemetryCollectionService; use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand; use Pterodactyl\Console\Commands\Maintenance\PruneOrphanedBackupsCommand; use Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand; @@ -37,5 +40,34 @@ class Kernel extends ConsoleKernel if (config('activity.prune_days')) { $schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily(); } + + if (config('pterodactyl.telemetry.enabled')) { + $this->registerTelemetry($schedule); + } + } + + /** + * I wonder what this does. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + private function registerTelemetry(Schedule $schedule): void + { + $settingsRepository = app()->make(SettingsRepository::class); + + $uuid = $settingsRepository->get('app:uuid'); + if (is_null($uuid)) { + $uuid = Uuid::uuid4()->toString(); + $settingsRepository->set('app:uuid', $uuid); + } + + // Calculate a fixed time to run the data push at, this will be the same time every day. + $time = hexdec(str_replace('-', '', substr($uuid, 27))) % 1440; + $hour = floor($time / 60); + $minute = $time % 60; + + // Run the telemetry collector. + $schedule->call(app()->make(TelemetryCollectionService::class))->description('Collect Telemetry')->dailyAt("$hour:$minute"); } } diff --git a/app/Repositories/Wings/DaemonConfigurationRepository.php b/app/Repositories/Wings/DaemonConfigurationRepository.php index d24fb7e50..5580f9a8b 100644 --- a/app/Repositories/Wings/DaemonConfigurationRepository.php +++ b/app/Repositories/Wings/DaemonConfigurationRepository.php @@ -14,10 +14,10 @@ class DaemonConfigurationRepository extends DaemonRepository * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function getSystemInformation(): array + public function getSystemInformation(?int $version = null): array { try { - $response = $this->getHttpClient()->get('/api/system'); + $response = $this->getHttpClient()->get('/api/system' . (!is_null($version) ? '?v=' . $version : '')); } catch (TransferException $exception) { throw new DaemonConnectionException($exception); } diff --git a/app/Services/Telemetry/TelemetryCollectionService.php b/app/Services/Telemetry/TelemetryCollectionService.php new file mode 100644 index 000000000..c23f41dc0 --- /dev/null +++ b/app/Services/Telemetry/TelemetryCollectionService.php @@ -0,0 +1,175 @@ +collect(); + } catch (Exception) { + return; + } + + Http::post('https://telemetry.pterodactyl.io', $data); + } + + /** + * Collects telemetry data and returns it as an array. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function collect(): array + { + $uuid = $this->settingsRepository->get('app:uuid'); + if (is_null($uuid)) { + $uuid = Uuid::uuid4()->toString(); + $this->settingsRepository->set('app:uuid', $uuid); + } + + $nodes = Node::all()->map(function ($node) { + try { + $info = $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(2); + } catch (Exception) { + return null; + } + + return [ + 'id' => $node->uuid, + 'version' => Arr::get($info, 'version', ''), + + 'docker' => [ + 'version' => Arr::get($info, 'docker.version', ''), + + 'cgroups' => [ + 'driver' => Arr::get($info, 'docker.cgroups.driver', ''), + 'version' => Arr::get($info, 'docker.cgroups.version', ''), + ], + + 'containers' => [ + 'total' => Arr::get($info, 'docker.containers.total', -1), + 'running' => Arr::get($info, 'docker.containers.running', -1), + 'paused' => Arr::get($info, 'docker.containers.paused', -1), + 'stopped' => Arr::get($info, 'docker.containers.stopped', -1), + ], + + 'storage' => [ + 'driver' => Arr::get($info, 'docker.storage.driver', ''), + 'filesystem' => Arr::get($info, 'docker.storage.filesystem', ''), + ], + + 'runc' => [ + 'version' => Arr::get($info, 'docker.runc.version', ''), + ], + ], + + 'system' => [ + 'architecture' => Arr::get($info, 'system.architecture', ''), + 'cpuThreads' => Arr::get($info, 'system.cpu_threads', ''), + 'memoryBytes' => Arr::get($info, 'system.memory_bytes', ''), + 'kernelVersion' => Arr::get($info, 'system.kernel_version', ''), + 'os' => Arr::get($info, 'system.os', ''), + 'osType' => Arr::get($info, 'system.os_type', ''), + ], + ]; + })->filter(fn ($node) => !is_null($node))->toArray(); + + return [ + 'id' => $uuid, + + 'panel' => [ + 'version' => config('app.version'), + 'phpVersion' => phpversion(), + + 'drivers' => [ + 'backup' => [ + 'type' => config('backups.default'), + ], + 'cache' => [ + 'type' => config('cache.default'), + ], + 'database' => [ + 'type' => config('database.default'), + 'version' => DB::getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), + ], + ], + ], + + 'resources' => [ + 'allocations' => [ + 'count' => Allocation::count(), + 'used' => Allocation::whereNotNull('server_id')->count(), + ], + + 'backups' => [ + 'count' => Backup::count(), + 'bytes' => Backup::sum('bytes'), + ], + + 'eggs' => [ + 'count' => Egg::count(), + 'ids' => Egg::pluck('uuid')->toArray(), + ], + + 'locations' => [ + 'count' => Location::count(), + ], + + 'mounts' => [ + 'count' => Mount::count(), + ], + + 'nests' => [ + 'count' => Nest::count(), + ], + + 'nodes' => [ + 'count' => Node::count(), + ], + + 'servers' => [ + 'count' => Server::count(), + 'suspended' => Server::where('status', Server::STATUS_SUSPENDED)->count(), + ], + + 'users' => [ + 'count' => User::count(), + 'admins' => User::where('root_admin', true)->count(), + ], + ], + + 'nodes' => $nodes, + ]; + } +} diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 58c58bc9e..e5ceb3525 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -177,4 +177,16 @@ return [ // Should an email be sent to a server owner whenever their server is reinstalled? 'send_reinstall_notification' => env('PTERODACTYL_SEND_REINSTALL_NOTIFICATION', true), ], + + /* + |-------------------------------------------------------------------------- + | Telemetry Settings + |-------------------------------------------------------------------------- + | + | This section controls the telemetry sent by Pterodactyl. + */ + + 'telemetry' => [ + 'enabled' => env('PTERODACTYL_TELEMETRY_ENABLED', false), + ], ]; From 5b36313c579ed0fe5fa151c16ce0c6f2be32580f Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Tue, 22 Nov 2022 13:37:46 -0700 Subject: [PATCH 015/106] Update README.md --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 79efc44e6..9163c4962 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,18 @@ Stop settling for less. Make game servers a first class citizen on your platform I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement. [Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi) -| Company | About | -|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [**WISP**](https://wisp.gg) | Extra features. | -| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. | -| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. | -| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | -| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | -| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! | -| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | -| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. | -| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. | +| Company | About | +|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**WISP**](https://wisp.gg) | Extra features. | +| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. | +| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. | +| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | +| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | +| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! | +| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | +| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. | +| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. | +| [**UltraServers**](https://ultraservers.com/) | Deploy premium games hosting with the click of a button. Manage and swap games with ease and let us take care of the rest. We currently support Minecraft, Rust, ARK, 7 Days to Die, Garys MOD, CS:GO, Satisfactory and others. | ### Supported Games From 1bb1b13f6d1247a051f249016c5b8fe985e64817 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Tue, 22 Nov 2022 13:40:58 -0700 Subject: [PATCH 016/106] Update CHANGELOG.md --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371916bc9..6ad1ebca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v1.11.0-rc.2 +### Changed +* `MB` byte suffix are now `MiB` to more accurately reflect the actual value. +* Server reinstallation failures are tracked independently of the initial installation process. + +### Fixed +* Properly handle a missing `Content-Length` header in the response from the daemon. +* Ensure activity log properties are always returned as an object instead of an empty array. + +### Added +* Added the `server:settings.description` activity log event for when a server description is changed. +* Added the ability to cancel file uploads in the file manager for a server. +* Added a telemetry service to collect anonymous metrics from the panel, this feature is disabled by default and can be toggled using the `PTERODACTYL_TELEMETRY_ENABLED` environment variable. + ## v1.11.0-rc.1 ### Changed * Changed minimum PHP version is now 8.0 instead of `7.4`. From 21613fa60256b5668ce7d79de18cb1540d1c52f4 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Fri, 25 Nov 2022 13:25:03 -0700 Subject: [PATCH 017/106] React 18 and Vite (#4510) --- .eslintrc.js | 11 +- .github/workflows/{build.yaml => ui.yaml} | 13 +- .gitignore | 5 + .prettierignore | 4 + .prettierrc.json | 22 + app/Http/ViewComposers/AssetComposer.php | 9 - app/Services/Helpers/AssetHashService.php | 117 - babel.config.js | 34 - docker-compose.development.yaml | 125 + flake.lock | 12 +- flake.nix | 112 +- jest.config.js | 28 - package.json | 255 +- postcss.config.js => postcss.config.cjs | 0 resources/scripts/TransitionRouter.tsx | 30 - resources/scripts/__mocks__/file.ts | 1 - resources/scripts/api/account/activity.ts | 14 +- resources/scripts/api/account/createApiKey.ts | 2 +- resources/scripts/api/account/ssh-keys.ts | 10 +- resources/scripts/api/auth/login.ts | 4 +- resources/scripts/api/auth/loginCheckpoint.ts | 4 +- .../scripts/api/auth/performPasswordReset.ts | 4 +- .../api/auth/requestPasswordResetEmail.ts | 2 +- resources/scripts/api/definitions/helpers.ts | 8 +- resources/scripts/api/getServers.ts | 2 +- resources/scripts/api/http.ts | 8 +- resources/scripts/api/interceptors.ts | 15 +- resources/scripts/api/server/activity.ts | 20 +- .../server/databases/createServerDatabase.ts | 4 +- .../server/databases/getServerDatabases.ts | 4 +- .../databases/rotateDatabasePassword.ts | 2 +- .../scripts/api/server/files/compressFiles.ts | 2 +- .../api/server/files/decompressFiles.ts | 2 +- .../api/server/files/getFileContents.ts | 2 +- resources/scripts/api/server/getServer.ts | 8 +- .../api/server/getServerResourceUsage.ts | 2 +- .../scripts/api/server/getWebsocketToken.ts | 2 +- .../schedules/createOrUpdateScheduleTask.ts | 2 +- .../api/server/users/createOrUpdateSubuser.ts | 2 +- .../api/server/users/getServerSubusers.ts | 2 +- .../scripts/api/swr/getServerAllocations.ts | 9 +- resources/scripts/api/swr/getServerBackups.ts | 10 +- resources/scripts/api/swr/getServerStartup.ts | 11 +- resources/scripts/api/transformers.ts | 2 +- .../scripts/assets/css/GlobalStylesheet.ts | 2 +- resources/scripts/components/App.tsx | 102 +- resources/scripts/components/Avatar.tsx | 3 +- .../scripts/components/FlashMessageRender.tsx | 10 +- resources/scripts/components/MessageBox.tsx | 5 +- .../scripts/components/NavigationBar.tsx | 38 +- .../auth/ForgotPasswordContainer.tsx | 37 +- .../auth/LoginCheckpointContainer.tsx | 44 +- .../components/auth/LoginContainer.tsx | 34 +- .../components/auth/LoginFormContainer.tsx | 5 +- .../auth/ResetPasswordContainer.tsx | 18 +- .../dashboard/AccountApiContainer.tsx | 12 +- .../dashboard/AccountOverviewContainer.tsx | 17 +- .../components/dashboard/ApiKeyModal.tsx | 2 +- .../dashboard/DashboardContainer.tsx | 10 +- .../components/dashboard/ServerRow.tsx | 19 +- .../activity/ActivityLogContainer.tsx | 10 +- .../forms/ConfigureTwoFactorForm.tsx | 2 +- .../dashboard/forms/CreateApiKeyForm.tsx | 6 +- .../dashboard/forms/DisableTOTPDialog.tsx | 9 +- .../dashboard/forms/RecoveryTokensDialog.tsx | 3 +- .../dashboard/forms/SetupTOTPDialog.tsx | 15 +- .../forms/UpdateEmailAddressForm.tsx | 12 +- .../dashboard/forms/UpdatePasswordForm.tsx | 16 +- .../dashboard/search/SearchContainer.tsx | 5 +- .../dashboard/search/SearchModal.tsx | 18 +- .../dashboard/ssh/AccountSSHContainer.tsx | 2 +- .../dashboard/ssh/CreateSSHKeyForm.tsx | 9 +- .../dashboard/ssh/DeleteSSHKeyButton.tsx | 6 +- .../elements/AuthenticatedRoute.tsx | 28 +- .../scripts/components/elements/Button.tsx | 36 +- resources/scripts/components/elements/Can.tsx | 21 +- .../scripts/components/elements/Checkbox.tsx | 3 +- .../scripts/components/elements/Code.tsx | 2 +- .../components/elements/CodemirrorEditor.tsx | 21 +- .../components/elements/ConfirmationModal.tsx | 25 +- .../components/elements/ContentBox.tsx | 2 +- .../components/elements/ContentContainer.tsx | 2 +- .../components/elements/CopyOnClick.tsx | 29 +- .../components/elements/DropdownMenu.tsx | 52 +- .../components/elements/ErrorBoundary.tsx | 22 +- .../scripts/components/elements/Fade.tsx | 47 - .../scripts/components/elements/Field.tsx | 5 +- .../elements/FormikFieldWrapper.tsx | 2 +- .../components/elements/FormikSwitch.tsx | 1 - .../components/elements/GreyRowBox.tsx | 4 +- .../scripts/components/elements/Icon.tsx | 2 +- .../scripts/components/elements/Input.tsx | 10 +- .../components/elements/InputError.tsx | 3 +- .../components/elements/InputSpinner.tsx | 34 +- .../scripts/components/elements/Label.tsx | 4 +- .../scripts/components/elements/Modal.tsx | 66 +- .../components/elements/PageContentBlock.tsx | 54 +- .../components/elements/Pagination.tsx | 10 +- .../components/elements/PermissionRoute.tsx | 48 +- .../scripts/components/elements/Portal.tsx | 3 +- .../components/elements/ProgressBar.tsx | 52 +- .../components/elements/ScreenBlock.tsx | 3 +- .../scripts/components/elements/Select.tsx | 4 +- .../elements/ServerContentBlock.tsx | 4 +- .../scripts/components/elements/Spinner.tsx | 18 +- .../components/elements/SpinnerOverlay.tsx | 20 +- .../components/elements/SubNavigation.tsx | 2 +- .../scripts/components/elements/Switch.tsx | 15 +- .../components/elements/TitledGreyBox.tsx | 3 +- .../scripts/components/elements/Translate.tsx | 12 +- .../elements/activity/ActivityLogEntry.tsx | 2 +- .../activity/ActivityLogMetaButton.tsx | 2 +- .../components/elements/alert/Alert.tsx | 4 +- .../components/elements/button/Button.tsx | 6 +- .../components/elements/button/types.ts | 6 +- .../elements/dialog/ConfirmationDialog.tsx | 2 +- .../components/elements/dialog/Dialog.tsx | 3 +- .../elements/dialog/DialogFooter.tsx | 5 +- .../components/elements/dialog/DialogIcon.tsx | 4 +- .../components/elements/dialog/context.ts | 6 +- .../components/elements/dialog/types.d.ts | 2 +- .../components/elements/dropdown/Dropdown.tsx | 7 +- .../elements/dropdown/DropdownButton.tsx | 2 +- .../elements/dropdown/DropdownItem.tsx | 7 +- .../components/elements/editor/Editor.tsx | 206 + .../components/elements/editor/index.ts | 1 + .../components/elements/editor/theme.ts | 148 + .../components/elements/inputs/Checkbox.tsx | 3 +- .../components/elements/inputs/InputField.tsx | 5 +- .../components/elements/inputs/index.ts | 2 +- .../elements/table/PaginationFooter.tsx | 5 +- .../components/elements/tooltip/Tooltip.tsx | 5 +- .../elements/transitions/FadeTransition.tsx | 14 +- resources/scripts/components/history.ts | 3 - .../server/ConflictStateRenderer.tsx | 13 +- .../components/server/InstallListener.tsx | 12 +- .../server/ServerActivityLogContainer.tsx | 10 +- .../components/server/TransferListener.tsx | 12 +- .../components/server/UptimeDuration.tsx | 2 - .../components/server/WebsocketHandler.tsx | 49 +- .../server/backups/BackupContainer.tsx | 4 +- .../server/backups/BackupContextMenu.tsx | 74 +- .../components/server/backups/BackupRow.tsx | 15 +- .../server/backups/CreateBackupButton.tsx | 14 +- .../components/server/console/ChartBlock.tsx | 2 +- .../components/server/console/Console.tsx | 71 +- .../server/console/PowerButtons.tsx | 9 +- .../server/console/ServerConsoleContainer.tsx | 29 +- .../server/console/ServerDetailsBlock.tsx | 50 +- .../components/server/console/StatBlock.tsx | 20 +- .../components/server/console/StatGraphs.tsx | 6 +- .../components/server/console/chart.ts | 28 +- .../server/databases/CreateDatabaseButton.tsx | 12 +- .../server/databases/DatabaseRow.tsx | 12 +- .../server/databases/DatabasesContainer.tsx | 20 +- .../server/databases/RotatePasswordButton.tsx | 8 +- .../components/server/features/Features.tsx | 14 +- .../server/features/GSLTokenModalFeature.tsx | 12 +- .../features/JavaVersionModalFeature.tsx | 22 +- .../server/features/PIDLimitModalFeature.tsx | 10 +- .../server/features/SteamDiskSpaceFeature.tsx | 10 +- .../server/features/eula/EulaModalFeature.tsx | 12 +- .../server/files/ChmodFileModal.tsx | 25 +- .../server/files/FileDropdownMenu.tsx | 31 +- .../server/files/FileEditContainer.tsx | 68 +- .../server/files/FileManagerBreadcrumbs.tsx | 18 +- .../server/files/FileManagerContainer.tsx | 34 +- .../server/files/FileManagerStatus.tsx | 23 +- .../components/server/files/FileNameModal.tsx | 5 +- .../components/server/files/FileObjectRow.tsx | 95 +- .../server/files/MassActionsBar.tsx | 52 +- .../server/files/NewDirectoryButton.tsx | 12 +- .../server/files/RenameFileModal.tsx | 19 +- .../server/files/SelectFileCheckbox.tsx | 10 +- .../components/server/files/UploadButton.tsx | 57 +- .../server/network/AllocationRow.tsx | 16 +- .../server/network/DeleteAllocationButton.tsx | 12 +- .../server/network/NetworkContainer.tsx | 20 +- .../server/schedules/DeleteScheduleButton.tsx | 6 +- .../server/schedules/EditScheduleModal.tsx | 12 +- .../server/schedules/NewTaskButton.tsx | 2 +- .../server/schedules/RunScheduleButton.tsx | 8 +- .../schedules/ScheduleCheatsheetCards.tsx | 1 - .../server/schedules/ScheduleContainer.tsx | 35 +- .../server/schedules/ScheduleCronRow.tsx | 1 - .../schedules/ScheduleEditContainer.tsx | 34 +- .../server/schedules/ScheduleRow.tsx | 1 - .../server/schedules/ScheduleTaskRow.tsx | 12 +- .../server/schedules/TaskDetailsModal.tsx | 18 +- .../server/settings/ReinstallServerBox.tsx | 6 +- .../server/settings/RenameServerBox.tsx | 9 +- .../server/settings/SettingsContainer.tsx | 11 +- .../server/startup/StartupContainer.tsx | 23 +- .../components/server/startup/VariableBox.tsx | 32 +- .../server/users/AddSubuserButton.tsx | 2 +- .../server/users/EditSubuserModal.tsx | 34 +- .../components/server/users/PermissionRow.tsx | 11 +- .../server/users/PermissionTitleBox.tsx | 32 +- .../server/users/RemoveSubuserButton.tsx | 8 +- .../components/server/users/UserRow.tsx | 6 +- .../server/users/UsersContainer.tsx | 16 +- resources/scripts/context/ModalContext.ts | 6 +- resources/scripts/helpers.ts | 2 +- .../scripts/hoc/RequireServerPermission.tsx | 9 +- resources/scripts/hoc/asDialog.tsx | 9 +- resources/scripts/hoc/asModal.tsx | 21 +- resources/scripts/i18n.ts | 2 +- resources/scripts/index.tsx | 15 +- resources/scripts/lib/formatters.spec.ts | 42 +- resources/scripts/lib/helpers.spec.ts | 38 +- resources/scripts/lib/helpers.ts | 2 +- resources/scripts/lib/objects.spec.ts | 24 +- resources/scripts/lib/strings.spec.ts | 16 +- resources/scripts/macros.d.ts | 4 +- resources/scripts/plugins/Websocket.ts | 6 +- resources/scripts/plugins/useEventListener.ts | 2 +- .../scripts/plugins/useFileManagerSwr.ts | 6 +- resources/scripts/plugins/useFlash.ts | 2 +- resources/scripts/plugins/useLocationHash.ts | 6 +- resources/scripts/plugins/usePermissions.ts | 8 +- .../scripts/plugins/usePersistedState.ts | 2 +- resources/scripts/plugins/useSWRKey.ts | 6 +- .../scripts/plugins/useWebsocketEvent.ts | 2 +- .../scripts/routers/AuthenticationRouter.tsx | 28 +- resources/scripts/routers/DashboardRouter.tsx | 51 +- resources/scripts/routers/ServerRouter.tsx | 126 +- resources/scripts/routers/routes.ts | 82 +- resources/scripts/setup-tests.ts | 1 - resources/scripts/state/flashes.ts | 2 +- resources/scripts/state/permissions.ts | 2 +- resources/scripts/state/progress.ts | 4 +- resources/scripts/state/server/databases.ts | 6 +- resources/scripts/state/server/files.ts | 39 +- resources/scripts/state/server/index.ts | 83 +- resources/scripts/state/server/schedules.ts | 6 +- resources/scripts/state/server/subusers.ts | 4 +- resources/scripts/theme.ts | 3 +- resources/views/templates/wrapper.blade.php | 8 +- shell.nix | 19 +- tailwind.config.js | 6 +- tsconfig.json | 96 +- vite.config.ts | 62 + webpack.config.js | 156 - yarn.lock | 8570 ++++------------- 244 files changed, 4547 insertions(+), 8933 deletions(-) rename .github/workflows/{build.yaml => ui.yaml} (78%) create mode 100644 .prettierignore create mode 100644 .prettierrc.json delete mode 100644 app/Services/Helpers/AssetHashService.php delete mode 100644 babel.config.js create mode 100644 docker-compose.development.yaml delete mode 100644 jest.config.js rename postcss.config.js => postcss.config.cjs (100%) delete mode 100644 resources/scripts/TransitionRouter.tsx delete mode 100644 resources/scripts/__mocks__/file.ts delete mode 100644 resources/scripts/components/elements/Fade.tsx create mode 100644 resources/scripts/components/elements/editor/Editor.tsx create mode 100644 resources/scripts/components/elements/editor/index.ts create mode 100644 resources/scripts/components/elements/editor/theme.ts delete mode 100644 resources/scripts/components/history.ts delete mode 100644 resources/scripts/setup-tests.ts create mode 100644 vite.config.ts delete mode 100644 webpack.config.js diff --git a/.eslintrc.js b/.eslintrc.js index 77547d879..21dabafcd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,9 +1,3 @@ -const prettier = { - singleQuote: true, - jsxSingleQuote: true, - printWidth: 120, -}; - /** @type {import('eslint').Linter.Config} */ module.exports = { parser: '@typescript-eslint/parser', @@ -39,15 +33,16 @@ module.exports = { // 'standard', 'eslint:recommended', 'plugin:react/recommended', + 'plugin:react/jsx-runtime', 'plugin:@typescript-eslint/recommended', - 'plugin:jest-dom/recommended', ], rules: { eqeqeq: 'error', - 'prettier/prettier': ['error', prettier], + 'prettier/prettier': ['error', {}, {usePrettierrc: true}], // TypeScript can infer this significantly better than eslint ever can. 'react/prop-types': 0, 'react/display-name': 0, + 'react/no-unknown-property': ['error', {ignore: ['css']}], '@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-non-null-assertion': 0, // This setup is required to avoid a spam of errors when running eslint about React being diff --git a/.github/workflows/build.yaml b/.github/workflows/ui.yaml similarity index 78% rename from .github/workflows/build.yaml rename to .github/workflows/ui.yaml index 4aca84ce8..e743de466 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/ui.yaml @@ -1,4 +1,4 @@ -name: Build +name: UI on: push: @@ -11,13 +11,13 @@ on: - "1.0-develop" jobs: - ui: - name: UI + build-and-test: + name: Build and Test runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - node-version: [16] + node-version: [16, 18] steps: - name: Code Checkout uses: actions/checkout@v3 @@ -32,4 +32,7 @@ jobs: run: yarn install --frozen-lockfile - name: Build - run: yarn build:production + run: yarn build + + - name: Tests + run: yarn test diff --git a/.gitignore b/.gitignore index 4b5d02246..2c9fda4ca 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,8 @@ misc coverage.xml resources/lang/locales.js .phpunit.result.cache + +/public/build +/public/hot +result +docker-compose.yaml diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..e5eee8ad4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +.github +public +node_modules +resources/views diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..296147a80 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,22 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf", + "overrides": [ + { + "files": ["**/*.md"], + "options": { + "tabWidth": 2, + "useTabs": false, + "singleQuote": false + } + } + ] +} diff --git a/app/Http/ViewComposers/AssetComposer.php b/app/Http/ViewComposers/AssetComposer.php index d42f8a80a..2bbb39d5c 100644 --- a/app/Http/ViewComposers/AssetComposer.php +++ b/app/Http/ViewComposers/AssetComposer.php @@ -3,23 +3,14 @@ namespace Pterodactyl\Http\ViewComposers; use Illuminate\View\View; -use Pterodactyl\Services\Helpers\AssetHashService; class AssetComposer { - /** - * AssetComposer constructor. - */ - public function __construct(private AssetHashService $assetHashService) - { - } - /** * Provide access to the asset service in the views. */ public function compose(View $view): void { - $view->with('asset', $this->assetHashService); $view->with('siteConfiguration', [ 'name' => config('app.name') ?? 'Pterodactyl', 'locale' => config('app.locale') ?? 'en', diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php deleted file mode 100644 index 725a56669..000000000 --- a/app/Services/Helpers/AssetHashService.php +++ /dev/null @@ -1,117 +0,0 @@ -filesystem = $filesystem->createLocalDriver(['root' => public_path()]); - } - - /** - * Modify a URL to append the asset hash. - */ - public function url(string $resource): string - { - $file = last(explode('/', $resource)); - $data = Arr::get($this->manifest(), $file) ?? $file; - - return str_replace($file, Arr::get($data, 'src') ?? $file, $resource); - } - - /** - * Return the data integrity hash for a resource. - */ - public function integrity(string $resource): string - { - $file = last(explode('/', $resource)); - $data = array_get($this->manifest(), $file, $file); - - return Arr::get($data, 'integrity') ?? ''; - } - - /** - * Return a built CSS import using the provided URL. - */ - public function css(string $resource): string - { - $attributes = [ - 'href' => $this->url($resource), - 'rel' => 'stylesheet preload', - 'as' => 'style', - 'crossorigin' => 'anonymous', - 'referrerpolicy' => 'no-referrer', - ]; - - if (config('pterodactyl.assets.use_hash')) { - $attributes['integrity'] = $this->integrity($resource); - } - - $output = ' $value) { - $output .= " $key=\"$value\""; - } - - return $output . '>'; - } - - /** - * Return a built JS import using the provided URL. - */ - public function js(string $resource): string - { - $attributes = [ - 'src' => $this->url($resource), - 'crossorigin' => 'anonymous', - ]; - - if (config('pterodactyl.assets.use_hash')) { - $attributes['integrity'] = $this->integrity($resource); - } - - $output = ' $value) { - $output .= " $key=\"$value\""; - } - - return $output . '>'; - } - - /** - * Get the asset manifest and store it in the cache for quicker lookups. - */ - protected function manifest(): array - { - if (static::$manifest === null) { - self::$manifest = json_decode( - $this->filesystem->get(self::MANIFEST_PATH), - true - ); - } - - $manifest = static::$manifest; - if ($manifest === null) { - throw new ManifestDoesNotExistException(); - } - - return $manifest; - } -} diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 808bb186f..000000000 --- a/babel.config.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = function (api) { - let targets = {}; - const plugins = [ - 'babel-plugin-macros', - 'styled-components', - 'react-hot-loader/babel', - '@babel/transform-runtime', - '@babel/transform-react-jsx', - '@babel/proposal-class-properties', - '@babel/proposal-object-rest-spread', - '@babel/proposal-optional-chaining', - '@babel/proposal-nullish-coalescing-operator', - '@babel/syntax-dynamic-import', - ]; - - if (api.env('test')) { - targets = { node: 'current' }; - plugins.push('@babel/transform-modules-commonjs'); - } - - return { - plugins, - presets: [ - '@babel/typescript', - ['@babel/env', { - modules: false, - useBuiltIns: 'entry', - corejs: 3, - targets, - }], - '@babel/react', - ] - }; -}; diff --git a/docker-compose.development.yaml b/docker-compose.development.yaml new file mode 100644 index 000000000..35b4f1530 --- /dev/null +++ b/docker-compose.development.yaml @@ -0,0 +1,125 @@ +# For more information: https://laravel.com/docs/sail +version: '3' + +services: + caddy: + image: localhost/pterodactyl/development:panel + network_mode: host + command: + - caddy + - run + - --config + - /etc/caddy/Caddyfile + volumes: + - '.:/var/www/html' + depends_on: + - laravel + + laravel: + image: localhost/pterodactyl/development:panel + network_mode: host + command: + - 'php-fpm' + - '--nodaemonize' + - '-y' + - '/etc/php-fpm.conf' + volumes: + - '.:/var/www/html' + tmpfs: + - '/tmp' + depends_on: + - pgsql + - mariadb + - redis + - mailhog + + pgsql: + image: docker.io/library/postgres:14 + ports: + - '127.0.0.1:${FORWARD_DB_PORT:-5432}:5432' + environment: + PGPASSWORD: '${DB_PASSWORD:-secret}' + POSTGRES_DB: '${DB_DATABASE}' + POSTGRES_USER: '${DB_USERNAME}' + POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' + volumes: + - 'sail-pgsql:/var/lib/postgresql/data' + - './vendor/laravel/sail/database/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' + networks: + - sail + healthcheck: + test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE}", "-U", "${DB_USERNAME}"] + retries: 3 + timeout: 5s + + mariadb: + image: docker.io/library/mariadb:10 + ports: + - '127.0.0.1:${FORWARD_DB_PORT:-3306}:3306' + environment: + MYSQL_DATABASE: '${DB_DATABASE}' + MYSQL_USER: '${DB_USERNAME}' + MYSQL_PASSWORD: '${DB_PASSWORD}' + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + volumes: + - 'sail-mariadb:/var/lib/mysql' + - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' + networks: + - sail + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"] + retries: 3 + timeout: 5s + + redis: + image: docker.io/library/redis:7 + ports: + - '127.0.0.1:${FORWARD_REDIS_PORT:-6379}:6379' + volumes: + - 'sail-redis:/data' + networks: + - sail + healthcheck: + test: ["CMD", "redis-cli", "ping"] + retries: 3 + timeout: 5s + + minio: + image: docker.io/minio/minio:latest + ports: + - '127.0.0.1:${FORWARD_MINIO_PORT:-9001}:9000' + - '127.0.0.1:${FORWARD_MINIO_CONSOLE_PORT:-8900}:8900' + environment: + MINIO_ROOT_USER: 'sail' + MINIO_ROOT_PASSWORD: 'password' + volumes: + - 'sail-minio:/data/minio' + networks: + - sail + command: minio server /data/minio --console-address ":8900" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + retries: 3 + timeout: 5s + + mailhog: + image: docker.io/mailhog/mailhog:latest + ports: + - '127.0.0.1:${FORWARD_MAILHOG_PORT:-1025}:1025' + - '127.0.0.1:${FORWARD_MAILHOG_DASHBOARD_PORT:-8025}:8025' + networks: + - sail + +networks: + sail: + driver: bridge + +volumes: + sail-pgsql: + driver: local + sail-mariadb: + driver: local + sail-redis: + driver: local + sail-minio: + driver: local diff --git a/flake.lock b/flake.lock index 4a64ac964..ec3c2152a 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1659877975, - "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "owner": "numtide", "repo": "flake-utils", - "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "type": "github" }, "original": { @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1666539104, - "narHash": "sha256-jeuC+d375wHHxMOFLgu7etseCQVJuPNKoEc9X9CsErg=", + "lastModified": 1669140675, + "narHash": "sha256-npzfyfLECsJWgzK/M4gWhykP2DNAJTYjgY2BWkz/oEQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "0e6df35f39651504249a05191f9a78d251707e22", + "rev": "2788904d26dda6cfa1921c5abb7a2466ffe3cb8c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 050cc2415..f05682ef1 100644 --- a/flake.nix +++ b/flake.nix @@ -15,8 +15,118 @@ flake-utils.lib.eachDefaultSystem ( system: let pkgs = import nixpkgs {inherit system;}; + + php81WithExtensions = with pkgs; (php81.buildEnv { + extensions = { + enabled, + all, + }: + enabled + ++ (with all; [ + redis + xdebug + ]); + extraConfig = '' + xdebug.mode=debug + ''; + }); + + caCertificates = pkgs.runCommand "ca-certificates" {} '' + mkdir -p $out/etc/ssl/certs $out/etc/pki/tls/certs + ln -s ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-bundle.crt + ln -s ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-certificates.crt + ln -s ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/pki/tls/certs/ca-bundle.crt + ''; + + caddyfile = pkgs.writeText "Caddyfile" '' + :80 { + root * /var/www/html/public/ + file_server + + header { + -Server + -X-Powered-By + Referrer-Policy "same-origin" + X-Frame-Options "deny" + X-XSS-Protection "1; mode=block" + X-Content-Type-Options "nosniff" + } + + encode gzip zstd + + php_fastcgi localhost:9000 + + @startsWithDot { + path \/\. + not path .well-known + } + rewrite @startsWithDot /index.php{uri} + + @phpRewrite { + not file favicon.ico + } + try_files @phpRewrite {path} {path}/ /index.php?{query} + } + ''; + + phpfpmConf = pkgs.writeText "php-fpm.conf" '' + [global] + error_log = /dev/stderr + daemonize = no + + [www] + user = nobody + group = nobody + + listen = 0.0.0.0:9000 + + pm = dynamic + pm.start_servers = 4 + pm.min_spare_servers = 4 + pm.max_spare_servers = 16 + pm.max_children = 64 + pm.max_requests = 256 + + clear_env = no + catch_workers_output = yes + + decorate_workers_output = no + ''; + + configs = pkgs.runCommand "configs" {} '' + mkdir -p $out/etc/caddy + ln -s ${caddyfile} $out/etc/caddy/Caddyfile + ln -s ${phpfpmConf} $out/etc/php-fpm.conf + ''; in { - devShell = import ./shell.nix {inherit pkgs;}; + devShell = import ./shell.nix {inherit pkgs php81WithExtensions;}; + + packages = { + development = pkgs.dockerTools.buildImage { + name = "pterodactyl/development"; + tag = "panel"; + + copyToRoot = pkgs.buildEnv { + name = "image-root"; + paths = with pkgs; [ + dockerTools.fakeNss + caCertificates + caddy + configs + coreutils + mysql80 + nodejs-18_x + nodePackages.npm + nodePackages.pnpm + nodePackages.yarn + php81WithExtensions + (php81Packages.composer.override {php = php81WithExtensions;}) + postgresql_14 + ]; + pathsToLink = ["/bin" "/etc"]; + }; + }; + }; } ); } diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index abb02f386..000000000 --- a/jest.config.js +++ /dev/null @@ -1,28 +0,0 @@ -const { pathsToModuleNameMapper } = require('ts-jest'); -const { compilerOptions } = require('./tsconfig'); - -/** @type {import('ts-jest').InitialOptionsTsJest} */ -module.exports = { - preset: 'ts-jest', - globals: { - 'ts-jest': { - isolatedModules: true, - }, - }, - moduleFileExtensions: ['js', 'ts', 'tsx', 'd.ts', 'json', 'node'], - moduleNameMapper: { - '\\.(jpe?g|png|gif|svg)$': '/resources/scripts/__mocks__/file.ts', - '\\.(s?css|less)$': 'identity-obj-proxy', - ...pathsToModuleNameMapper(compilerOptions.paths, { - prefix: '/', - }), - }, - setupFilesAfterEnv: [ - '/resources/scripts/setup-tests.ts', - ], - transform: { - '.*\\.[t|j]sx$': 'babel-jest', - '.*\\.ts$': 'ts-jest', - }, - testPathIgnorePatterns: ['/node_modules/'], -}; diff --git a/package.json b/package.json index e716485cb..ef5fb95d6 100644 --- a/package.json +++ b/package.json @@ -1,142 +1,128 @@ { - "name": "pterodactyl-panel", + "name": "@pterodactyl/panel", + "license": "MIT", + "private": true, "engines": { - "node": ">=14" - }, - "dependencies": { - "@floating-ui/react-dom-interactions": "^0.6.6", - "@fortawesome/fontawesome-svg-core": "^1.2.32", - "@fortawesome/free-solid-svg-icons": "^5.15.1", - "@fortawesome/react-fontawesome": "^0.1.11", - "@headlessui/react": "^1.6.4", - "@heroicons/react": "^1.0.6", - "@hot-loader/react-dom": "^16.14.0", - "@preact/signals-react": "^1.2.1", - "@tailwindcss/forms": "^0.5.2", - "@tailwindcss/line-clamp": "^0.4.0", - "axios": "^0.27.2", - "boring-avatars": "^1.7.0", - "chart.js": "^3.8.0", - "classnames": "^2.3.1", - "codemirror": "^5.57.0", - "copy-to-clipboard": "^3.3.1", - "date-fns": "^2.28.0", - "debounce": "^1.2.0", - "deepmerge-ts": "^4.2.1", - "easy-peasy": "^4.0.1", - "events": "^3.0.0", - "formik": "^2.2.6", - "framer-motion": "^6.3.10", - "i18next": "^21.8.9", - "i18next-http-backend": "^1.4.1", - "i18next-multiload-backend-adapter": "^1.0.0", - "qrcode.react": "^1.0.1", - "react": "^16.14.0", - "react-chartjs-2": "^4.2.0", - "react-dom": "npm:@hot-loader/react-dom", - "react-fast-compare": "^3.2.0", - "react-hot-loader": "^4.12.21", - "react-i18next": "^11.2.1", - "react-router-dom": "^5.1.2", - "react-transition-group": "^4.4.1", - "reaptcha": "^1.7.2", - "sockette": "^2.0.6", - "styled-components": "^5.2.1", - "styled-components-breakpoint": "^3.0.0-preview.20", - "swr": "^0.2.3", - "tailwindcss": "^3.0.24", - "use-fit-text": "^2.4.0", - "uuid": "^8.3.2", - "xterm": "^4.19.0", - "xterm-addon-fit": "^0.5.0", - "xterm-addon-search": "^0.9.0", - "xterm-addon-search-bar": "^0.2.0", - "xterm-addon-web-links": "^0.6.0", - "yup": "^0.29.1" - }, - "devDependencies": { - "@babel/core": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.18.2", - "@babel/plugin-transform-react-jsx": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.1", - "@babel/preset-typescript": "^7.12.1", - "@babel/runtime": "^7.12.1", - "@testing-library/dom": "^8.14.0", - "@testing-library/jest-dom": "^5.16.4", - "@testing-library/react": "12.1.5", - "@testing-library/user-event": "^14.2.1", - "@types/codemirror": "^0.0.98", - "@types/debounce": "^1.2.0", - "@types/events": "^3.0.0", - "@types/jest": "^28.1.3", - "@types/node": "^14.11.10", - "@types/qrcode.react": "^1.0.1", - "@types/react": "^16.14.0", - "@types/react-copy-to-clipboard": "^4.3.0", - "@types/react-dom": "^16.9.16", - "@types/react-redux": "^7.1.1", - "@types/react-router": "^5.1.3", - "@types/react-router-dom": "^5.1.3", - "@types/react-transition-group": "^4.4.0", - "@types/styled-components": "^5.1.7", - "@types/uuid": "^3.4.5", - "@types/webpack-env": "^1.15.2", - "@types/yup": "^0.29.3", - "@typescript-eslint/eslint-plugin": "^5.29.0", - "@typescript-eslint/parser": "^5.29.0", - "autoprefixer": "^10.4.7", - "babel-jest": "^28.1.1", - "babel-loader": "^8.2.5", - "babel-plugin-styled-components": "^2.0.7", - "cross-env": "^7.0.2", - "css-loader": "^5.2.7", - "eslint": "^8.18.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-jest-dom": "^4.0.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "fork-ts-checker-webpack-plugin": "^6.2.10", - "identity-obj-proxy": "^3.0.0", - "jest": "^28.1.1", - "postcss": "^8.4.14", - "postcss-import": "^14.1.0", - "postcss-loader": "^4.0.0", - "postcss-nesting": "^10.1.8", - "postcss-preset-env": "^7.7.1", - "prettier": "^2.7.1", - "redux-devtools-extension": "^2.13.8", - "source-map-loader": "^1.1.3", - "style-loader": "^2.0.0", - "svg-url-loader": "^7.1.1", - "terser-webpack-plugin": "^4.2.3", - "ts-essentials": "^9.1.2", - "ts-jest": "^28.0.5", - "twin.macro": "^2.8.2", - "typescript": "^4.7.3", - "webpack": "^4.43.0", - "webpack-assets-manifest": "^3.1.1", - "webpack-bundle-analyzer": "^3.8.0", - "webpack-cli": "^3.3.12", - "webpack-dev-server": "^3.11.0", - "yarn-deduplicate": "^1.1.1" + "node": ">=16.0" }, "scripts": { - "clean": "cd public/assets && find . \\( -name \"*.js\" -o -name \"*.map\" \\) -type f -delete", - "test": "jest", + "build": "vite build", + "clean": "rimraf public/build", + "coverage": "vitest run --coverage", + "dev": "vite", "lint": "eslint ./resources/scripts/**/*.{ts,tsx} --ext .ts,.tsx", - "watch": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", - "build": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --progress", - "build:production": "yarn run clean && cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode production", - "serve": "yarn run clean && cross-env WEBPACK_PUBLIC_PATH=/webpack@hmr/ NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8080 --public https://pterodactyl.test --hot" + "test": "vitest run", + "test:ui": "vitest --ui" + }, + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-lezer": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/language-data": "^6.0.0", + "@codemirror/legacy-modes": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@floating-ui/react-dom-interactions": "0.10.2", + "@fortawesome/fontawesome-svg-core": "6.2.0", + "@fortawesome/free-solid-svg-icons": "6.2.0", + "@fortawesome/react-fontawesome": "0.2.0", + "@flyyer/use-fit-text": "3.0.1", + "@headlessui/react": "1.7.3", + "@heroicons/react": "1.0.6", + "@lezer/highlight": "1.1.2", + "@preact/signals-react": "1.1.1", + "@tailwindcss/forms": "0.5.3", + "@tailwindcss/line-clamp": "0.4.2", + "axios": "0.27.2", + "boring-avatars": "1.7.0", + "chart.js": "3.9.1", + "classnames": "2.3.2", + "codemirror": "5.57.0", + "copy-to-clipboard": "3.3.2", + "date-fns": "2.29.3", + "debounce": "1.2.1", + "deepmerge-ts": "4.2.2", + "easy-peasy": "5.1.0", + "events": "3.3.0", + "formik": "2.2.9", + "framer-motion": "7.6.2", + "i18next": "22.0.3", + "i18next-http-backend": "2.0.0", + "i18next-multiload-backend-adapter": "1.0.0", + "nanoid": "4.0.0", + "qrcode.react": "3.1.0", + "react": "18.2.0", + "react-chartjs-2": "4.2.0", + "react-dom": "18.2.0", + "react-fast-compare": "3.2.0", + "react-i18next": "12.0.0", + "react-router-dom": "6.4.2", + "reaptcha": "1.12.1", + "sockette": "2.0.6", + "styled-components": "5.3.6", + "styled-components-breakpoint": "3.0.0-preview.20", + "swr": "1.3.0", + "tailwindcss": "3.2.2", + "xterm": "5.0.0", + "xterm-addon-fit": "0.6.0", + "xterm-addon-search": "0.10.0", + "xterm-addon-search-bar": "0.2.0", + "xterm-addon-web-links": "0.7.0", + "yup": "0.32.11" + }, + "devDependencies": { + "@testing-library/dom": "8.19.0", + "@testing-library/react": "13.4.0", + "@testing-library/user-event": "14.4.3", + "@types/codemirror": "0.0.109", + "@types/debounce": "1.2.1", + "@types/events": "3.0.0", + "@types/node": "18.11.9", + "@types/react": "18.0.24", + "@types/react-dom": "18.0.8", + "@types/styled-components": "5.1.26", + "@typescript-eslint/eslint-plugin": "5.41.0", + "@typescript-eslint/parser": "5.41.0", + "@vitejs/plugin-react": "2.2.0", + "autoprefixer": "10.4.12", + "babel-plugin-styled-components": "2.0.7", + "babel-plugin-twin": "1.0.2", + "cross-env": "7.0.3", + "eslint": "8.18.0", + "eslint-config-prettier": "8.5.0", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-react": "7.31.10", + "eslint-plugin-react-hooks": "4.6.0", + "happy-dom": "7.6.6", + "laravel-vite-plugin": "0.7.0", + "pathe": "0.3.9", + "postcss": "8.4.18", + "postcss-import": "15.0.0", + "postcss-nesting": "10.2.0", + "postcss-preset-env": "7.8.2", + "prettier": "2.7.1", + "rimraf": "3.0.2", + "ts-essentials": "9.3.0", + "twin.macro": "2.8.2", + "typescript": "4.8.4", + "vite": "3.2.2", + "vitest": "0.24.5" }, "browserslist": [ "> 0.5%", @@ -146,6 +132,7 @@ ], "babelMacros": { "twin": { + "config": "tailwind.config.js", "preset": "styled-components" }, "styledComponents": { diff --git a/postcss.config.js b/postcss.config.cjs similarity index 100% rename from postcss.config.js rename to postcss.config.cjs diff --git a/resources/scripts/TransitionRouter.tsx b/resources/scripts/TransitionRouter.tsx deleted file mode 100644 index 040097eaa..000000000 --- a/resources/scripts/TransitionRouter.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Route } from 'react-router'; -import { SwitchTransition } from 'react-transition-group'; -import Fade from '@/components/elements/Fade'; -import styled from 'styled-components/macro'; -import tw from 'twin.macro'; - -const StyledSwitchTransition = styled(SwitchTransition)` - ${tw`relative`}; - - & section { - ${tw`absolute w-full top-0 left-0`}; - } -`; - -const TransitionRouter: React.FC = ({ children }) => { - return ( - ( - - -
{children}
-
-
- )} - /> - ); -}; - -export default TransitionRouter; diff --git a/resources/scripts/__mocks__/file.ts b/resources/scripts/__mocks__/file.ts deleted file mode 100644 index 86059f362..000000000 --- a/resources/scripts/__mocks__/file.ts +++ /dev/null @@ -1 +0,0 @@ -module.exports = 'test-file-stub'; diff --git a/resources/scripts/api/account/activity.ts b/resources/scripts/api/account/activity.ts index eef215696..cbbec66f3 100644 --- a/resources/scripts/api/account/activity.ts +++ b/resources/scripts/api/account/activity.ts @@ -1,8 +1,10 @@ -import useSWR, { ConfigInterface, responseInterface } from 'swr'; -import { ActivityLog, Transformers } from '@definitions/user'; -import { AxiosError } from 'axios'; +import type { AxiosError } from 'axios'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + import http, { PaginatedResult, QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; import { toPaginatedSet } from '@definitions/helpers'; +import { ActivityLog, Transformers } from '@definitions/user'; import useFilteredObject from '@/plugins/useFilteredObject'; import { useUserSWRKey } from '@/plugins/useSWRKey'; @@ -10,8 +12,8 @@ export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'> const useActivityLogs = ( filters?: ActivityLogFilters, - config?: ConfigInterface, AxiosError> -): responseInterface, AxiosError> => { + config?: SWRConfiguration, AxiosError>, +) => { const key = useUserSWRKey(['account', 'activity', JSON.stringify(useFilteredObject(filters || {}))]); return useSWR>( @@ -26,7 +28,7 @@ const useActivityLogs = ( return toPaginatedSet(data, Transformers.toActivityLog); }, - { revalidateOnMount: false, ...(config || {}) } + { revalidateOnMount: false, ...(config || {}) }, ); }; diff --git a/resources/scripts/api/account/createApiKey.ts b/resources/scripts/api/account/createApiKey.ts index b20760e62..f2cf27b3a 100644 --- a/resources/scripts/api/account/createApiKey.ts +++ b/resources/scripts/api/account/createApiKey.ts @@ -12,7 +12,7 @@ export default (description: string, allowedIps: string): Promise) => { +const useSSHKeys = (config?: SWRConfiguration) => { const key = useUserSWRKey(['account', 'ssh-keys']); return useSWR( @@ -16,7 +18,7 @@ const useSSHKeys = (config?: ConfigInterface) => { return Transformers.toSSHKey(datum.attributes); }); }, - { revalidateOnMount: false, ...(config || {}) } + { revalidateOnMount: false, ...(config || {}) }, ); }; diff --git a/resources/scripts/api/auth/login.ts b/resources/scripts/api/auth/login.ts index ff0de7ac6..e8d6cd75e 100644 --- a/resources/scripts/api/auth/login.ts +++ b/resources/scripts/api/auth/login.ts @@ -20,9 +20,9 @@ export default ({ username, password, recaptchaData }: LoginData): Promise { + .then(response => { if (!(response.data instanceof Object)) { return reject(new Error('An error occurred while processing the login request.')); } diff --git a/resources/scripts/api/auth/loginCheckpoint.ts b/resources/scripts/api/auth/loginCheckpoint.ts index 73ffb2111..8e6c0c070 100644 --- a/resources/scripts/api/auth/loginCheckpoint.ts +++ b/resources/scripts/api/auth/loginCheckpoint.ts @@ -8,11 +8,11 @@ export default (token: string, code: string, recoveryToken?: string): Promise 0 ? recoveryToken : undefined, }) - .then((response) => + .then(response => resolve({ complete: response.data.data.complete, intended: response.data.data.intended || undefined, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/auth/performPasswordReset.ts b/resources/scripts/api/auth/performPasswordReset.ts index 0dd12f470..fb45dc4bc 100644 --- a/resources/scripts/api/auth/performPasswordReset.ts +++ b/resources/scripts/api/auth/performPasswordReset.ts @@ -19,11 +19,11 @@ export default (email: string, data: Data): Promise => { password: data.password, password_confirmation: data.passwordConfirmation, }) - .then((response) => + .then(response => resolve({ redirectTo: response.data.redirect_to, sendToLogin: response.data.send_to_login, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/auth/requestPasswordResetEmail.ts b/resources/scripts/api/auth/requestPasswordResetEmail.ts index d68fa4447..2168160c2 100644 --- a/resources/scripts/api/auth/requestPasswordResetEmail.ts +++ b/resources/scripts/api/auth/requestPasswordResetEmail.ts @@ -3,7 +3,7 @@ import http from '@/api/http'; export default (email: string, recaptchaData?: string): Promise => { return new Promise((resolve, reject) => { http.post('/auth/password', { email, 'g-recaptcha-response': recaptchaData }) - .then((response) => resolve(response.data.status || '')) + .then(response => resolve(response.data.status || '')) .catch(reject); }); }; diff --git a/resources/scripts/api/definitions/helpers.ts b/resources/scripts/api/definitions/helpers.ts index eeb933f5a..895360830 100644 --- a/resources/scripts/api/definitions/helpers.ts +++ b/resources/scripts/api/definitions/helpers.ts @@ -15,17 +15,17 @@ function transform(data: null | undefined, transformer: TransformerFunc function transform( data: FractalResponseData | null | undefined, transformer: TransformerFunc, - missing?: M + missing?: M, ): T | M; function transform( data: FractalResponseList | FractalPaginatedResponse | null | undefined, transformer: TransformerFunc, - missing?: M + missing?: M, ): T[] | M; function transform( data: FractalResponseData | FractalResponseList | FractalPaginatedResponse | null | undefined, transformer: TransformerFunc, - missing = undefined + missing = undefined, ) { if (data === undefined || data === null) { return missing; @@ -44,7 +44,7 @@ function transform( function toPaginatedSet>( response: FractalPaginatedResponse, - transformer: T + transformer: T, ): PaginatedResult> { return { items: transform(response, transformer) as ReturnType[], diff --git a/resources/scripts/api/getServers.ts b/resources/scripts/api/getServers.ts index 4e29f1532..77a057f0d 100644 --- a/resources/scripts/api/getServers.ts +++ b/resources/scripts/api/getServers.ts @@ -19,7 +19,7 @@ export default ({ query, ...params }: QueryParams): Promise rawDataToServerObject(datum)), pagination: getPaginationSet(data.meta.pagination), - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index 382c1119a..b2a537262 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -11,7 +11,7 @@ const http: AxiosInstance = axios.create({ }, }); -http.interceptors.request.use((req) => { +http.interceptors.request.use(req => { if (!req.url?.endsWith('/resources')) { store.getActions().progress.startContinuous(); } @@ -20,18 +20,18 @@ http.interceptors.request.use((req) => { }); http.interceptors.response.use( - (resp) => { + resp => { if (!resp.request?.url?.endsWith('/resources')) { store.getActions().progress.setComplete(); } return resp; }, - (error) => { + error => { store.getActions().progress.setComplete(); throw error; - } + }, ); export default http; diff --git a/resources/scripts/api/interceptors.ts b/resources/scripts/api/interceptors.ts index 7b8ac87da..c2380bc80 100644 --- a/resources/scripts/api/interceptors.ts +++ b/resources/scripts/api/interceptors.ts @@ -1,21 +1,22 @@ -import http from '@/api/http'; -import { AxiosError } from 'axios'; -import { History } from 'history'; +import type { AxiosError } from 'axios'; +import type { NavigateFunction } from 'react-router-dom'; -export const setupInterceptors = (history: History) => { +import http from '@/api/http'; + +export const setupInterceptors = (navigate: NavigateFunction) => { http.interceptors.response.use( - (resp) => resp, + resp => resp, (error: AxiosError) => { if (error.response?.status === 400) { if ( (error.response?.data as Record).errors?.[0].code === 'TwoFactorAuthRequiredException' ) { if (!window.location.pathname.startsWith('/account')) { - history.replace('/account', { twoFactorRedirect: true }); + navigate('/account', { state: { twoFactorRedirect: true } }); } } } throw error; - } + }, ); }; diff --git a/resources/scripts/api/server/activity.ts b/resources/scripts/api/server/activity.ts index a7fa8d31b..5476a582c 100644 --- a/resources/scripts/api/server/activity.ts +++ b/resources/scripts/api/server/activity.ts @@ -1,8 +1,12 @@ -import useSWR, { ConfigInterface, responseInterface } from 'swr'; -import { ActivityLog, Transformers } from '@definitions/user'; -import { AxiosError } from 'axios'; -import http, { PaginatedResult, QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import type { AxiosError } from 'axios'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + +import type { PaginatedResult, QueryBuilderParams } from '@/api/http'; +import http, { withQueryBuilderParams } from '@/api/http'; import { toPaginatedSet } from '@definitions/helpers'; +import type { ActivityLog } from '@definitions/user'; +import { Transformers } from '@definitions/user'; import useFilteredObject from '@/plugins/useFilteredObject'; import { useServerSWRKey } from '@/plugins/useSWRKey'; import { ServerContext } from '@/state/server'; @@ -11,9 +15,9 @@ export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'> const useActivityLogs = ( filters?: ActivityLogFilters, - config?: ConfigInterface, AxiosError> -): responseInterface, AxiosError> => { - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); + config?: SWRConfiguration, AxiosError>, +) => { + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); const key = useServerSWRKey(['activity', useFilteredObject(filters || {})]); return useSWR>( @@ -28,7 +32,7 @@ const useActivityLogs = ( return toPaginatedSet(data, Transformers.toActivityLog); }, - { revalidateOnMount: false, ...(config || {}) } + { revalidateOnMount: false, ...(config || {}) }, ); }; diff --git a/resources/scripts/api/server/databases/createServerDatabase.ts b/resources/scripts/api/server/databases/createServerDatabase.ts index cb0c25b9e..8d5f5c442 100644 --- a/resources/scripts/api/server/databases/createServerDatabase.ts +++ b/resources/scripts/api/server/databases/createServerDatabase.ts @@ -11,9 +11,9 @@ export default (uuid: string, data: { connectionsFrom: string; databaseName: str }, { params: { include: 'password' }, - } + }, ) - .then((response) => resolve(rawDataToServerDatabase(response.data.attributes))) + .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/databases/getServerDatabases.ts b/resources/scripts/api/server/databases/getServerDatabases.ts index a03eb30a9..32437d063 100644 --- a/resources/scripts/api/server/databases/getServerDatabases.ts +++ b/resources/scripts/api/server/databases/getServerDatabases.ts @@ -23,8 +23,8 @@ export default (uuid: string, includePassword = true): Promise http.get(`/api/client/servers/${uuid}/databases`, { params: includePassword ? { include: 'password' } : undefined, }) - .then((response) => - resolve((response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes))) + .then(response => + resolve((response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes))), ) .catch(reject); }); diff --git a/resources/scripts/api/server/databases/rotateDatabasePassword.ts b/resources/scripts/api/server/databases/rotateDatabasePassword.ts index 0e0619a84..a7d299c02 100644 --- a/resources/scripts/api/server/databases/rotateDatabasePassword.ts +++ b/resources/scripts/api/server/databases/rotateDatabasePassword.ts @@ -4,7 +4,7 @@ import http from '@/api/http'; export default (uuid: string, database: string): Promise => { return new Promise((resolve, reject) => { http.post(`/api/client/servers/${uuid}/databases/${database}/rotate-password`) - .then((response) => resolve(rawDataToServerDatabase(response.data.attributes))) + .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/files/compressFiles.ts b/resources/scripts/api/server/files/compressFiles.ts index b4c0a251e..0f7882652 100644 --- a/resources/scripts/api/server/files/compressFiles.ts +++ b/resources/scripts/api/server/files/compressFiles.ts @@ -10,7 +10,7 @@ export default async (uuid: string, directory: string, files: string[]): Promise timeout: 60000, timeoutErrorMessage: 'It looks like this archive is taking a long time to generate. It will appear once completed.', - } + }, ); return rawDataToFileObject(data); diff --git a/resources/scripts/api/server/files/decompressFiles.ts b/resources/scripts/api/server/files/decompressFiles.ts index 37557a671..d3a3e45c2 100644 --- a/resources/scripts/api/server/files/decompressFiles.ts +++ b/resources/scripts/api/server/files/decompressFiles.ts @@ -8,6 +8,6 @@ export default async (uuid: string, directory: string, file: string): Promise => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${server}/files/contents`, { params: { file }, - transformResponse: (res) => res, + transformResponse: res => res, responseType: 'text', }) .then(({ data }) => resolve(data)) diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index d2aa2e054..6c1034071 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -25,7 +25,7 @@ export interface Server { }; invocation: string; dockerImage: string; - description: string; + description: string | null; limits: { memory: number; swap: number; @@ -65,10 +65,10 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData) featureLimits: { ...data.feature_limits }, isTransferring: data.is_transferring, variables: ((data.relationships?.variables as FractalResponseList | undefined)?.data || []).map( - rawDataToServerEggVariable + rawDataToServerEggVariable, ), allocations: ((data.relationships?.allocations as FractalResponseList | undefined)?.data || []).map( - rawDataToServerAllocation + rawDataToServerAllocation, ), }); @@ -80,7 +80,7 @@ export default (uuid: string): Promise<[Server, string[]]> => { rawDataToServerObject(data), // eslint-disable-next-line camelcase data.meta?.is_server_owner ? ['*'] : data.meta?.user_permissions || [], - ]) + ]), ) .catch(reject); }); diff --git a/resources/scripts/api/server/getServerResourceUsage.ts b/resources/scripts/api/server/getServerResourceUsage.ts index 200ceb017..dd1ad4307 100644 --- a/resources/scripts/api/server/getServerResourceUsage.ts +++ b/resources/scripts/api/server/getServerResourceUsage.ts @@ -26,7 +26,7 @@ export default (server: string): Promise => { networkRxInBytes: attributes.resources.network_rx_bytes, networkTxInBytes: attributes.resources.network_tx_bytes, uptime: attributes.resources.uptime, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/server/getWebsocketToken.ts b/resources/scripts/api/server/getWebsocketToken.ts index 4769e188b..5b2994b22 100644 --- a/resources/scripts/api/server/getWebsocketToken.ts +++ b/resources/scripts/api/server/getWebsocketToken.ts @@ -12,7 +12,7 @@ export default (server: string): Promise => { resolve({ token: data.data.token, socket: data.data.socket, - }) + }), ) .catch(reject); }); diff --git a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts index 388d8d6d5..e0df70436 100644 --- a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts +++ b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts @@ -16,7 +16,7 @@ export default async (uuid: string, schedule: number, task: number | undefined, payload: data.payload, continue_on_failure: data.continueOnFailure, time_offset: data.timeOffset, - } + }, ); return rawDataToServerTask(response.attributes); diff --git a/resources/scripts/api/server/users/createOrUpdateSubuser.ts b/resources/scripts/api/server/users/createOrUpdateSubuser.ts index 019d35c7a..93303a2db 100644 --- a/resources/scripts/api/server/users/createOrUpdateSubuser.ts +++ b/resources/scripts/api/server/users/createOrUpdateSubuser.ts @@ -12,7 +12,7 @@ export default (uuid: string, params: Params, subuser?: Subuser): Promise resolve(rawDataToServerSubuser(data.data))) + .then(data => resolve(rawDataToServerSubuser(data.data))) .catch(reject); }); }; diff --git a/resources/scripts/api/server/users/getServerSubusers.ts b/resources/scripts/api/server/users/getServerSubusers.ts index dae2ce580..177bde815 100644 --- a/resources/scripts/api/server/users/getServerSubusers.ts +++ b/resources/scripts/api/server/users/getServerSubusers.ts @@ -9,7 +9,7 @@ export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({ twoFactorEnabled: data.attributes['2fa_enabled'], createdAt: new Date(data.attributes.created_at), permissions: data.attributes.permissions || [], - can: (permission) => (data.attributes.permissions || []).indexOf(permission) >= 0, + can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0, }); export default (uuid: string): Promise => { diff --git a/resources/scripts/api/swr/getServerAllocations.ts b/resources/scripts/api/swr/getServerAllocations.ts index b254b6505..0936550ee 100644 --- a/resources/scripts/api/swr/getServerAllocations.ts +++ b/resources/scripts/api/swr/getServerAllocations.ts @@ -1,11 +1,12 @@ -import { ServerContext } from '@/state/server'; import useSWR from 'swr'; + import http from '@/api/http'; -import { rawDataToServerAllocation } from '@/api/transformers'; import { Allocation } from '@/api/server/getServer'; +import { rawDataToServerAllocation } from '@/api/transformers'; +import { ServerContext } from '@/state/server'; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); return useSWR( ['server:allocations', uuid], @@ -14,6 +15,6 @@ export default () => { return (data.data || []).map(rawDataToServerAllocation); }, - { revalidateOnFocus: false, revalidateOnMount: false } + { revalidateOnFocus: false, revalidateOnMount: false }, ); }; diff --git a/resources/scripts/api/swr/getServerBackups.ts b/resources/scripts/api/swr/getServerBackups.ts index dbc7f5d98..596105816 100644 --- a/resources/scripts/api/swr/getServerBackups.ts +++ b/resources/scripts/api/swr/getServerBackups.ts @@ -1,9 +1,11 @@ +import { createContext, useContext } from 'react'; import useSWR from 'swr'; -import http, { getPaginationSet, PaginatedResult } from '@/api/http'; -import { ServerBackup } from '@/api/server/types'; + +import type { PaginatedResult } from '@/api/http'; +import http, { getPaginationSet } from '@/api/http'; +import type { ServerBackup } from '@/api/server/types'; import { rawDataToServerBackup } from '@/api/transformers'; import { ServerContext } from '@/state/server'; -import { createContext, useContext } from 'react'; interface ctx { page: number; @@ -16,7 +18,7 @@ type BackupResponse = PaginatedResult & { backupCount: number }; export default () => { const { page } = useContext(Context); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); return useSWR(['server:backups', uuid, page], async () => { const { data } = await http.get(`/api/client/servers/${uuid}/backups`, { params: { page } }); diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts index efecefe08..df05bdb3f 100644 --- a/resources/scripts/api/swr/getServerStartup.ts +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -1,7 +1,10 @@ -import useSWR, { ConfigInterface } from 'swr'; +import type { AxiosError } from 'axios'; +import type { SWRConfiguration } from 'swr'; +import useSWR from 'swr'; + import http, { FractalResponseList } from '@/api/http'; +import type { ServerEggVariable } from '@/api/server/types'; import { rawDataToServerEggVariable } from '@/api/transformers'; -import { ServerEggVariable } from '@/api/server/types'; interface Response { invocation: string; @@ -9,7 +12,7 @@ interface Response { dockerImages: Record; } -export default (uuid: string, initialData?: Response | null, config?: ConfigInterface) => +export default (uuid: string, fallbackData?: Response, config?: SWRConfiguration) => useSWR( [uuid, '/startup'], async (): Promise => { @@ -23,5 +26,5 @@ export default (uuid: string, initialData?: Response | null, config?: ConfigInte dockerImages: data.meta.docker_images || {}, }; }, - { initialData: initialData || undefined, errorRetryCount: 3, ...(config || {}) } + { fallbackData, errorRetryCount: 3, ...(config ?? {}) }, ); diff --git a/resources/scripts/api/transformers.ts b/resources/scripts/api/transformers.ts index 4ed9a5df5..34a2611d0 100644 --- a/resources/scripts/api/transformers.ts +++ b/resources/scripts/api/transformers.ts @@ -49,7 +49,7 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ const matches = ['application/jar', 'application/octet-stream', 'inode/directory', /^image\//]; - return matches.every((m) => !this.mimetype.match(m)); + return matches.every(m => !this.mimetype.match(m)); }, }); diff --git a/resources/scripts/assets/css/GlobalStylesheet.ts b/resources/scripts/assets/css/GlobalStylesheet.ts index 7f520be45..4ae89abc4 100644 --- a/resources/scripts/assets/css/GlobalStylesheet.ts +++ b/resources/scripts/assets/css/GlobalStylesheet.ts @@ -1,5 +1,5 @@ import tw from 'twin.macro'; -import { createGlobalStyle } from 'styled-components/macro'; +import { createGlobalStyle } from 'styled-components'; export default createGlobalStyle` body { diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx index 935400030..c72ce426f 100644 --- a/resources/scripts/components/App.tsx +++ b/resources/scripts/components/App.tsx @@ -1,23 +1,20 @@ -import React, { lazy } from 'react'; -import { hot } from 'react-hot-loader/root'; -import { Route, Router, Switch } from 'react-router-dom'; import { StoreProvider } from 'easy-peasy'; -import { store } from '@/state'; -import { SiteSettings } from '@/state/settings'; +import { lazy } from 'react'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; + +import '@/assets/tailwind.css'; +import GlobalStylesheet from '@/assets/css/GlobalStylesheet'; +import AuthenticatedRoute from '@/components/elements/AuthenticatedRoute'; import ProgressBar from '@/components/elements/ProgressBar'; import { NotFound } from '@/components/elements/ScreenBlock'; -import tw from 'twin.macro'; -import GlobalStylesheet from '@/assets/css/GlobalStylesheet'; -import { history } from '@/components/history'; -import { setupInterceptors } from '@/api/interceptors'; -import AuthenticatedRoute from '@/components/elements/AuthenticatedRoute'; -import { ServerContext } from '@/state/server'; -import '@/assets/tailwind.css'; import Spinner from '@/components/elements/Spinner'; +import { store } from '@/state'; +import { ServerContext } from '@/state/server'; +import { SiteSettings } from '@/state/settings'; -const DashboardRouter = lazy(() => import(/* webpackChunkName: "dashboard" */ '@/routers/DashboardRouter')); -const ServerRouter = lazy(() => import(/* webpackChunkName: "server" */ '@/routers/ServerRouter')); -const AuthenticationRouter = lazy(() => import(/* webpackChunkName: "auth" */ '@/routers/AuthenticationRouter')); +const DashboardRouter = lazy(() => import('@/routers/DashboardRouter')); +const ServerRouter = lazy(() => import('@/routers/ServerRouter')); +const AuthenticationRouter = lazy(() => import('@/routers/AuthenticationRouter')); interface ExtendedWindow extends Window { SiteConfiguration?: SiteSettings; @@ -35,9 +32,9 @@ interface ExtendedWindow extends Window { }; } -setupInterceptors(history); +// setupInterceptors(history); -const App = () => { +function App() { const { PterodactylUser, SiteConfiguration } = window as ExtendedWindow; if (PterodactylUser && !store.getState().user.data) { store.getActions().user.setUserData({ @@ -58,38 +55,55 @@ const App = () => { return ( <> + {/* @ts-expect-error go away */} + -
- - - - - - - - - - - - - - - - - - - - - - - - + +
+ + + + + + } + /> + + + + + + + + + } + /> + + + + + + + } + /> + + } /> + +
); -}; +} -export default hot(App); +export { App }; diff --git a/resources/scripts/components/Avatar.tsx b/resources/scripts/components/Avatar.tsx index 4d9d254db..5ba0489a7 100644 --- a/resources/scripts/components/Avatar.tsx +++ b/resources/scripts/components/Avatar.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import BoringAvatar, { AvatarProps } from 'boring-avatars'; import { useStoreState } from '@/state/hooks'; @@ -11,7 +10,7 @@ const _Avatar = ({ variant = 'beam', ...props }: AvatarProps) => ( ); const _UserAvatar = ({ variant = 'beam', ...props }: Omit) => { - const uuid = useStoreState((state) => state.user.data?.uuid); + const uuid = useStoreState(state => state.user.data?.uuid); return ; }; diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx index 8d0b43f2b..e7cf7726d 100644 --- a/resources/scripts/components/FlashMessageRender.tsx +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { Fragment } from 'react'; import MessageBox from '@/components/MessageBox'; import { useStoreState } from 'easy-peasy'; import tw from 'twin.macro'; @@ -9,19 +9,17 @@ type Props = Readonly<{ }>; const FlashMessageRender = ({ byKey, className }: Props) => { - const flashes = useStoreState((state) => - state.flashes.items.filter((flash) => (byKey ? flash.key === byKey : true)) - ); + const flashes = useStoreState(state => state.flashes.items.filter(flash => (byKey ? flash.key === byKey : true))); return flashes.length ? (
{flashes.map((flash, index) => ( - + {index > 0 &&
} {flash.message} -
+ ))}
) : null; diff --git a/resources/scripts/components/MessageBox.tsx b/resources/scripts/components/MessageBox.tsx index 57a5cacb5..482ee392b 100644 --- a/resources/scripts/components/MessageBox.tsx +++ b/resources/scripts/components/MessageBox.tsx @@ -1,6 +1,5 @@ -import * as React from 'react'; import tw, { TwStyle } from 'twin.macro'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; export type FlashMessageType = 'success' | 'info' | 'warning' | 'error'; @@ -42,7 +41,7 @@ const getBackground = (type?: FlashMessageType): TwStyle | string => { const Container = styled.div<{ $type?: FlashMessageType }>` ${tw`p-2 border items-center leading-normal rounded flex w-full text-sm text-white`}; - ${(props) => styling(props.$type)}; + ${props => styling(props.$type)}; `; Container.displayName = 'MessageBox.Container'; diff --git a/resources/scripts/components/NavigationBar.tsx b/resources/scripts/components/NavigationBar.tsx index 89180b14b..954d6d4cd 100644 --- a/resources/scripts/components/NavigationBar.tsx +++ b/resources/scripts/components/NavigationBar.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import { useState } from 'react'; import { Link, NavLink } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -7,7 +6,7 @@ import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import SearchContainer from '@/components/dashboard/search/SearchContainer'; import tw, { theme } from 'twin.macro'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import http from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import Tooltip from '@/components/elements/tooltip/Tooltip'; @@ -39,6 +38,7 @@ export default () => { const onTriggerLogout = () => { setIsLoggingOut(true); + http.post('/auth/logout').finally(() => { // @ts-expect-error this is valid window.location = '/'; @@ -46,41 +46,43 @@ export default () => { }; return ( -
+
-
-
+
+ - + - - + + + + {rootAdmin && ( - - + + )} - - - + + + + - + + diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index 76ddf1992..694cfce78 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -1,28 +1,29 @@ -import * as React from 'react'; +import { useStoreState } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Formik } from 'formik'; import { useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; +import Reaptcha from 'reaptcha'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import { useStoreState } from 'easy-peasy'; -import Field from '@/components/elements/Field'; -import { Formik, FormikHelpers } from 'formik'; -import { object, string } from 'yup'; -import tw from 'twin.macro'; import Button from '@/components/elements/Button'; -import Reaptcha from 'reaptcha'; +import Field from '@/components/elements/Field'; import useFlash from '@/plugins/useFlash'; interface Values { email: string; } -export default () => { +function ForgotPasswordContainer() { const ref = useRef(null); const [token, setToken] = useState(''); const { clearFlashes, addFlash } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); + const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); useEffect(() => { clearFlashes(); @@ -34,7 +35,7 @@ export default () => { // If there is no token in the state yet, request the token and then abort this submit request // since it will be re-submitted when the recaptcha data is returned by the component. if (recaptchaEnabled && !token) { - ref.current!.execute().catch((error) => { + ref.current!.execute().catch(error => { console.error(error); setSubmitting(false); @@ -45,17 +46,19 @@ export default () => { } requestPasswordResetEmail(email, token) - .then((response) => { + .then(response => { resetForm(); addFlash({ type: 'success', title: 'Success', message: response }); }) - .catch((error) => { + .catch(error => { console.error(error); addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); }) .then(() => { setToken(''); - if (ref.current) ref.current.reset(); + if (ref.current !== null) { + void ref.current.reset(); + } setSubmitting(false); }); @@ -92,9 +95,9 @@ export default () => { ref={ref} size={'invisible'} sitekey={siteKey || '_invalid_key'} - onVerify={(response) => { + onVerify={response => { setToken(response); - submitForm(); + void submitForm(); }} onExpire={() => { setSubmitting(false); @@ -114,4 +117,6 @@ export default () => { )} ); -}; +} + +export default ForgotPasswordContainer; diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index 66a6fb8f4..a5c59ee4b 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -1,28 +1,29 @@ -import React, { useState } from 'react'; -import { Link, RouteComponentProps } from 'react-router-dom'; +import type { ActionCreator } from 'easy-peasy'; +import { useFormikContext, withFormik } from 'formik'; +import { useState } from 'react'; +import type { Location, RouteProps } from 'react-router-dom'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + import loginCheckpoint from '@/api/auth/loginCheckpoint'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import { ActionCreator } from 'easy-peasy'; -import { StaticContext } from 'react-router'; -import { useFormikContext, withFormik } from 'formik'; -import useFlash from '@/plugins/useFlash'; -import { FlashStore } from '@/state/flashes'; -import Field from '@/components/elements/Field'; -import tw from 'twin.macro'; import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import useFlash from '@/plugins/useFlash'; +import type { FlashStore } from '@/state/flashes'; interface Values { code: string; recoveryCode: ''; } -type OwnProps = RouteComponentProps, StaticContext, { token?: string }>; +type OwnProps = RouteProps; type Props = OwnProps & { clearAndAddHttpError: ActionCreator; }; -const LoginCheckpointContainer = () => { +function LoginCheckpointContainer() { const { isSubmitting, setFieldValue } = useFormikContext(); const [isMissingDevice, setIsMissingDevice] = useState(false); @@ -53,7 +54,7 @@ const LoginCheckpointContainer = () => { onClick={() => { setFieldValue('code', ''); setFieldValue('recoveryCode', ''); - setIsMissingDevice((s) => !s); + setIsMissingDevice(s => !s); }} css={tw`cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`} > @@ -70,12 +71,12 @@ const LoginCheckpointContainer = () => {
); -}; +} -const EnhancedForm = withFormik({ +const EnhancedForm = withFormik({ handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { clearAndAddHttpError, location } }) => { loginCheckpoint(location.state?.token || '', code, recoveryCode) - .then((response) => { + .then(response => { if (response.complete) { // @ts-expect-error this is valid window.location = response.intended || '/'; @@ -84,7 +85,7 @@ const EnhancedForm = withFormik({ setSubmitting(false); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); clearAndAddHttpError({ error }); @@ -97,16 +98,17 @@ const EnhancedForm = withFormik({ }), })(LoginCheckpointContainer); -export default ({ history, location, ...props }: OwnProps) => { +export default ({ ...props }: OwnProps) => { const { clearAndAddHttpError } = useFlash(); + const location = useLocation(); + const navigate = useNavigate(); + if (!location.state?.token) { - history.replace('/auth/login'); + navigate('/auth/login'); return null; } - return ( - - ); + return ; }; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 453a27ecc..0cb5aae16 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -1,14 +1,16 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Link, RouteComponentProps } from 'react-router-dom'; +import { useStoreState } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Formik } from 'formik'; +import { useEffect, useRef, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import Reaptcha from 'reaptcha'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + import login from '@/api/auth/login'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import { useStoreState } from 'easy-peasy'; -import { Formik, FormikHelpers } from 'formik'; -import { object, string } from 'yup'; import Field from '@/components/elements/Field'; -import tw from 'twin.macro'; import Button from '@/components/elements/Button'; -import Reaptcha from 'reaptcha'; import useFlash from '@/plugins/useFlash'; interface Values { @@ -16,12 +18,14 @@ interface Values { password: string; } -const LoginContainer = ({ history }: RouteComponentProps) => { +function LoginContainer() { const ref = useRef(null); const [token, setToken] = useState(''); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha); + const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); + + const navigate = useNavigate(); useEffect(() => { clearFlashes(); @@ -33,7 +37,7 @@ const LoginContainer = ({ history }: RouteComponentProps) => { // If there is no token in the state yet, request the token and then abort this submit request // since it will be re-submitted when the recaptcha data is returned by the component. if (recaptchaEnabled && !token) { - ref.current!.execute().catch((error) => { + ref.current!.execute().catch(error => { console.error(error); setSubmitting(false); @@ -44,16 +48,16 @@ const LoginContainer = ({ history }: RouteComponentProps) => { } login({ ...values, recaptchaData: token }) - .then((response) => { + .then(response => { if (response.complete) { // @ts-expect-error this is valid window.location = response.intended || '/'; return; } - history.replace('/auth/login/checkpoint', { token: response.confirmationToken }); + navigate('/auth/login/checkpoint', { state: { token: response.confirmationToken } }); }) - .catch((error) => { + .catch(error => { console.error(error); setToken(''); @@ -89,7 +93,7 @@ const LoginContainer = ({ history }: RouteComponentProps) => { ref={ref} size={'invisible'} sitekey={siteKey || '_invalid_key'} - onVerify={(response) => { + onVerify={response => { setToken(response); submitForm(); }} @@ -111,6 +115,6 @@ const LoginContainer = ({ history }: RouteComponentProps) => { )} ); -}; +} export default LoginContainer; diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index 62749815b..17060572b 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -1,6 +1,7 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import { Form } from 'formik'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { breakpoint } from '@/theme'; import FlashMessageRender from '@/components/FlashMessageRender'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index 86f95abf1..15a3b2090 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -1,6 +1,5 @@ -import React, { useState } from 'react'; -import { RouteComponentProps } from 'react-router'; -import { Link } from 'react-router-dom'; +import { useState } from 'react'; +import { Link, useParams } from 'react-router-dom'; import performPasswordReset from '@/api/auth/performPasswordReset'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; @@ -18,7 +17,7 @@ interface Values { passwordConfirmation: string; } -export default ({ match, location }: RouteComponentProps<{ token: string }>) => { +function ResetPasswordContainer() { const [email, setEmail] = useState(''); const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); @@ -28,14 +27,16 @@ export default ({ match, location }: RouteComponentProps<{ token: string }>) => setEmail(parsed.get('email') || ''); } + const params = useParams<'token'>(); + const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); - performPasswordReset(email, { token: match.params.token, password, passwordConfirmation }) + performPasswordReset(email, { token: params.token ?? '', password, passwordConfirmation }) .then(() => { // @ts-expect-error this is valid window.location = '/'; }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); @@ -56,7 +57,6 @@ export default ({ match, location }: RouteComponentProps<{ token: string }>) => .min(8, 'Your new password should be at least 8 characters in length.'), passwordConfirmation: string() .required('Your new password does not match.') - // @ts-expect-error this is valid .oneOf([ref('password'), null], 'Your new password does not match.'), })} > @@ -95,4 +95,6 @@ export default ({ match, location }: RouteComponentProps<{ token: string }>) => )} ); -}; +} + +export default ResetPasswordContainer; diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx index 282f19092..645c79d6d 100644 --- a/resources/scripts/components/dashboard/AccountApiContainer.tsx +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import ContentBox from '@/components/elements/ContentBox'; import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm'; import getApiKeys, { ApiKey } from '@/api/account/getApiKeys'; @@ -23,9 +23,9 @@ export default () => { useEffect(() => { getApiKeys() - .then((keys) => setKeys(keys)) + .then(keys => setKeys(keys)) .then(() => setLoading(false)) - .catch((error) => clearAndAddHttpError(error)); + .catch(error => clearAndAddHttpError(error)); }, []); const doDeletion = (identifier: string) => { @@ -33,8 +33,8 @@ export default () => { clearAndAddHttpError(); deleteApiKey(identifier) - .then(() => setKeys((s) => [...(s || []).filter((key) => key.identifier !== identifier)])) - .catch((error) => clearAndAddHttpError(error)) + .then(() => setKeys(s => [...(s || []).filter(key => key.identifier !== identifier)])) + .catch(error => clearAndAddHttpError(error)) .then(() => { setLoading(false); setDeleteIdentifier(''); @@ -46,7 +46,7 @@ export default () => {
- setKeys((s) => [...s!, key])} /> + setKeys(s => [...s!, key])} /> diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx index 4f64aec48..2357c4847 100644 --- a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import ContentBox from '@/components/elements/ContentBox'; import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm'; import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm'; @@ -6,7 +5,7 @@ import ConfigureTwoFactorForm from '@/components/dashboard/forms/ConfigureTwoFac import PageContentBlock from '@/components/elements/PageContentBlock'; import tw from 'twin.macro'; import { breakpoint } from '@/theme'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import MessageBox from '@/components/MessageBox'; import { useLocation } from 'react-router-dom'; @@ -27,24 +26,26 @@ const Container = styled.div` `; export default () => { - const { state } = useLocation(); + const { state } = useLocation(); return ( - + {state?.twoFactorRedirect && ( - + Your account must have two-factor authentication enabled in order to continue. )} - + - + + - + + diff --git a/resources/scripts/components/dashboard/ApiKeyModal.tsx b/resources/scripts/components/dashboard/ApiKeyModal.tsx index 6d01029e2..f9d5b65a8 100644 --- a/resources/scripts/components/dashboard/ApiKeyModal.tsx +++ b/resources/scripts/components/dashboard/ApiKeyModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import { useContext } from 'react'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import asModal from '@/hoc/asModal'; diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 25e0e4d28..7ca63c9f2 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Server } from '@/api/server/getServer'; import getServers from '@/api/getServers'; import ServerRow from '@/components/dashboard/ServerRow'; @@ -20,13 +20,13 @@ export default () => { const [page, setPage] = useState(!isNaN(defaultPage) && defaultPage > 0 ? defaultPage : 1); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const uuid = useStoreState((state) => state.user.data!.uuid); - const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const uuid = useStoreState(state => state.user.data!.uuid); + const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const [showOnlyAdmin, setShowOnlyAdmin] = usePersistedState(`${uuid}:show_all_servers`, false); const { data: servers, error } = useSWR>( ['/api/client/servers', showOnlyAdmin && rootAdmin, page], - () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }) + () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }), ); useEffect(() => { @@ -58,7 +58,7 @@ export default () => { setShowOnlyAdmin((s) => !s)} + onChange={() => setShowOnlyAdmin(s => !s)} />
)} diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index 08637bfc2..08bfc0033 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -1,4 +1,5 @@ -import React, { memo, useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; import { Link } from 'react-router-dom'; @@ -8,7 +9,7 @@ import { bytesToString, ip, mbToBytes } from '@/lib/formatters'; import tw from 'twin.macro'; import GreyRowBox from '@/components/elements/GreyRowBox'; import Spinner from '@/components/elements/Spinner'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import isEqual from 'react-fast-compare'; // Determines if the current value is in an alarm threshold so we can show it in red rather @@ -17,14 +18,14 @@ const isAlarmState = (current: number, limit: number): boolean => limit > 0 && c const Icon = memo( styled(FontAwesomeIcon)<{ $alarm: boolean }>` - ${(props) => (props.$alarm ? tw`text-red-400` : tw`text-neutral-500`)}; + ${props => (props.$alarm ? tw`text-red-400` : tw`text-neutral-500`)}; `, - isEqual + isEqual, ); const IconDescription = styled.p<{ $alarm: boolean }>` ${tw`text-sm ml-2`}; - ${(props) => (props.$alarm ? tw`text-white` : tw`text-neutral-400`)}; + ${props => (props.$alarm ? tw`text-white` : tw`text-neutral-400`)}; `; const StatusIndicatorBox = styled(GreyRowBox)<{ $status: ServerPowerState | undefined }>` @@ -56,8 +57,8 @@ export default ({ server, className }: { server: Server; className?: string }) = const getStats = () => getServerResourceUsage(server.uuid) - .then((data) => setStats(data)) - .catch((error) => console.error(error)); + .then(data => setStats(data)) + .catch(error => console.error(error)); useEffect(() => { setIsSuspended(stats?.isSuspended || server.status === 'suspended'); @@ -106,8 +107,8 @@ export default ({ server, className }: { server: Server; className?: string }) =

{server.allocations - .filter((alloc) => alloc.isDefault) - .map((allocation) => ( + .filter(alloc => alloc.isDefault) + .map(allocation => ( {allocation.alias || ip(allocation.ip)}:{allocation.port} diff --git a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx index f10701f81..aaf8ccd64 100644 --- a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx +++ b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ActivityLogFilters, useActivityLogs } from '@/api/account/activity'; import { useFlashKey } from '@/plugins/useFlash'; import PageContentBlock from '@/components/elements/PageContentBlock'; @@ -23,7 +23,7 @@ export default () => { }); useEffect(() => { - setFilters((value) => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); + setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); }, [hash]); useEffect(() => { @@ -38,7 +38,7 @@ export default () => { setFilters((value) => ({ ...value, filters: {} }))} + onClick={() => setFilters(value => ({ ...value, filters: {} }))} > Clear Filters @@ -48,7 +48,7 @@ export default () => { ) : (

- {data?.items.map((activity) => ( + {data?.items.map(activity => ( {typeof activity.properties.useragent === 'string' && ( @@ -64,7 +64,7 @@ export default () => { {data && ( setFilters((value) => ({ ...value, page }))} + onPageSelect={page => setFilters(value => ({ ...value, page }))} /> )} diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx index a59977f5f..059d1474e 100644 --- a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx index 9c273044c..3e53b0235 100644 --- a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx +++ b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Field, Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; @@ -11,7 +11,7 @@ import { ApiKey } from '@/api/account/getApiKeys'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import Input, { Textarea } from '@/components/elements/Input'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import ApiKeyModal from '@/components/dashboard/ApiKeyModal'; interface Values { @@ -36,7 +36,7 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { setApiKey(`${key.identifier}${secretToken}`); onKeyCreated(key); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'account', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx index 125376eb5..fe5039b79 100644 --- a/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx +++ b/resources/scripts/components/dashboard/forms/DisableTOTPDialog.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; +import * as React from 'react'; import asDialog from '@/hoc/asDialog'; import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; @@ -14,10 +15,10 @@ const DisableTOTPDialog = () => { const [password, setPassword] = useState(''); const { clearAndAddHttpError } = useFlashKey('account:two-step'); const { close, setProps } = useContext(DialogWrapperContext); - const updateUserData = useStoreActions((actions) => actions.user.updateUserData); + const updateUserData = useStoreActions(actions => actions.user.updateUserData); useEffect(() => { - setProps((state) => ({ ...state, preventExternalClose: submitting })); + setProps(state => ({ ...state, preventExternalClose: submitting })); }, [submitting]); const submit = (e: React.FormEvent) => { @@ -48,7 +49,7 @@ const DisableTOTPDialog = () => { type={'password'} variant={Input.Text.Variants.Loose} value={password} - onChange={(e) => setPassword(e.currentTarget.value)} + onChange={e => setPassword(e.currentTarget.value)} /> Cancel diff --git a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx index 3e2d0ba20..cddd5d55a 100644 --- a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx +++ b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Dialog, DialogProps } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; import CopyOnClick from '@/components/elements/CopyOnClick'; @@ -30,7 +29,7 @@ export default ({ tokens, open, onClose }: RecoveryTokenDialogProps) => {
-                    {grouped.map((value) => (
+                    {grouped.map(value => (
                         
                             {value[0]}
                              
diff --git a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx
index 836b2ef87..f10b1036d 100644
--- a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx
+++ b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx
@@ -1,4 +1,5 @@
-import React, { useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
+import * as React from 'react';
 import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
 import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
 import { useFlashKey } from '@/plugins/useFlash';
@@ -32,11 +33,11 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
     useEffect(() => {
         getTwoFactorTokenData()
             .then(setToken)
-            .catch((error) => clearAndAddHttpError(error));
+            .catch(error => clearAndAddHttpError(error));
     }, []);
 
     useEffect(() => {
-        setProps((state) => ({ ...state, preventExternalClose: submitting }));
+        setProps(state => ({ ...state, preventExternalClose: submitting }));
     }, [submitting]);
 
     const submit = (e: React.FormEvent) => {
@@ -48,11 +49,11 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
         setSubmitting(true);
         clearAndAddHttpError();
         enableAccountTwoFactor(value, password)
-            .then((tokens) => {
+            .then(tokens => {
                 updateUserData({ useTotp: true });
                 onTokens(tokens);
             })
-            .catch((error) => {
+            .catch(error => {
                 clearAndAddHttpError(error);
                 setSubmitting(false);
             });
@@ -81,7 +82,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
                 aria-labelledby={'totp-code-description'}
                 variant={Input.Text.Variants.Loose}
                 value={value}
-                onChange={(e) => setValue(e.currentTarget.value)}
+                onChange={e => setValue(e.currentTarget.value)}
                 className={'mt-3'}
                 placeholder={'000000'}
                 type={'text'}
@@ -97,7 +98,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
                 className={'mt-1'}
                 type={'password'}
                 value={password}
-                onChange={(e) => setPassword(e.currentTarget.value)}
+                onChange={e => setPassword(e.currentTarget.value)}
             />
             
                 Cancel
diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx
index 462a37f1a..56632e1f4 100644
--- a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx
+++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import { Fragment } from 'react';
 import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
 import { Form, Formik, FormikHelpers } from 'formik';
 import * as Yup from 'yup';
@@ -34,15 +34,15 @@ export default () => {
                     type: 'success',
                     key: 'account:email',
                     message: 'Your primary email has been updated.',
-                })
+                }),
             )
-            .catch((error) =>
+            .catch(error =>
                 addFlash({
                     type: 'error',
                     key: 'account:email',
                     title: 'Error',
                     message: httpErrorToHuman(error),
-                })
+                }),
             )
             .then(() => {
                 resetForm();
@@ -53,7 +53,7 @@ export default () => {
     return (
         
             {({ isSubmitting, isValid }) => (
-                
+                
                     
                     
@@ -69,7 +69,7 @@ export default () => {
- + )} ); diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx index 707ee72d7..34dcd64e6 100644 --- a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { Fragment } from 'react'; import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; @@ -24,7 +24,7 @@ const schema = Yup.object().shape({ 'Password confirmation does not match the password you entered.', function (value) { return value === this.parent.password; - } + }, ), }); @@ -43,26 +43,26 @@ export default () => { // @ts-expect-error this is valid window.location = '/auth/login'; }) - .catch((error) => + .catch(error => addFlash({ key: 'account:password', type: 'error', title: 'Error', message: httpErrorToHuman(error), - }) + }), ) .then(() => setSubmitting(false)); }; return ( - + {({ isSubmitting, isValid }) => ( - +
{
- + )} - + ); }; diff --git a/resources/scripts/components/dashboard/search/SearchContainer.tsx b/resources/scripts/components/dashboard/search/SearchContainer.tsx index 4da0f3a3f..a9c0cc03f 100644 --- a/resources/scripts/components/dashboard/search/SearchContainer.tsx +++ b/resources/scripts/components/dashboard/search/SearchContainer.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch } from '@fortawesome/free-solid-svg-icons'; import useEventListener from '@/plugins/useEventListener'; @@ -18,7 +18,8 @@ export default () => { return ( <> - {visible && setVisible(false)} />} + setVisible(false)} /> +
setVisible(true)}> diff --git a/resources/scripts/components/dashboard/search/SearchModal.tsx b/resources/scripts/components/dashboard/search/SearchModal.tsx index 1335cff43..dc4de0b3f 100644 --- a/resources/scripts/components/dashboard/search/SearchModal.tsx +++ b/resources/scripts/components/dashboard/search/SearchModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; @@ -10,7 +10,7 @@ import getServers from '@/api/getServers'; import { Server } from '@/api/server/getServer'; import { ApplicationStore } from '@/state'; import { Link } from 'react-router-dom'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw from 'twin.macro'; import Input from '@/components/elements/Input'; import { ip } from '@/lib/formatters'; @@ -47,10 +47,10 @@ const SearchWatcher = () => { export default ({ ...props }: Props) => { const ref = useRef(null); - const isAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); const [servers, setServers] = useState([]); const { clearAndAddHttpError, clearFlashes } = useStoreActions( - (actions: Actions) => actions.flashes + (actions: Actions) => actions.flashes, ); const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers) => { @@ -58,8 +58,8 @@ export default ({ ...props }: Props) => { // if (ref.current) ref.current.focus(); getServers({ query: term, type: isAdmin ? 'admin-all' : undefined }) - .then((servers) => setServers(servers.items.filter((_, index) => index < 5))) - .catch((error) => { + .then(servers => setServers(servers.items.filter((_, index) => index < 5))) + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'search', error }); }) @@ -100,7 +100,7 @@ export default ({ ...props }: Props) => { {servers.length > 0 && (
- {servers.map((server) => ( + {servers.map(server => ( {

{server.name}

{server.allocations - .filter((alloc) => alloc.isDefault) - .map((allocation) => ( + .filter(alloc => alloc.isDefault) + .map(allocation => ( {allocation.alias || ip(allocation.ip)}:{allocation.port} diff --git a/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx index 0308b3911..9bbf30f1c 100644 --- a/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx +++ b/resources/scripts/components/dashboard/ssh/AccountSSHContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import ContentBox from '@/components/elements/ContentBox'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import FlashMessageRender from '@/components/FlashMessageRender'; diff --git a/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx index 4b4f39cbd..f4c01c295 100644 --- a/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx +++ b/resources/scripts/components/dashboard/ssh/CreateSSHKeyForm.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Field, Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; @@ -6,7 +5,7 @@ import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import Input, { Textarea } from '@/components/elements/Input'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { useFlashKey } from '@/plugins/useFlash'; import { createSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; @@ -27,11 +26,11 @@ export default () => { clearAndAddHttpError(); createSSHKey(values.name, values.publicKey) - .then((key) => { + .then(key => { resetForm(); - mutate((data) => (data || []).concat(key)); + mutate(data => (data || []).concat(key)); }) - .catch((error) => clearAndAddHttpError(error)) + .catch(error => clearAndAddHttpError(error)) .then(() => setSubmitting(false)); }; diff --git a/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx index bd9aaf0d5..96994fc5e 100644 --- a/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx +++ b/resources/scripts/components/dashboard/ssh/DeleteSSHKeyButton.tsx @@ -1,7 +1,7 @@ import tw from 'twin.macro'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useFlashKey } from '@/plugins/useFlash'; import { deleteSSHKey, useSSHKeys } from '@/api/account/ssh-keys'; import { Dialog } from '@/components/elements/dialog'; @@ -16,9 +16,9 @@ export default ({ name, fingerprint }: { name: string; fingerprint: string }) => clearAndAddHttpError(); Promise.all([ - mutate((data) => data?.filter((value) => value.fingerprint !== fingerprint), false), + mutate(data => data?.filter(value => value.fingerprint !== fingerprint), false), deleteSSHKey(fingerprint), - ]).catch((error) => { + ]).catch(error => { mutate(undefined, true).catch(console.error); clearAndAddHttpError(error); }); diff --git a/resources/scripts/components/elements/AuthenticatedRoute.tsx b/resources/scripts/components/elements/AuthenticatedRoute.tsx index 2d3b6a6b9..093427d56 100644 --- a/resources/scripts/components/elements/AuthenticatedRoute.tsx +++ b/resources/scripts/components/elements/AuthenticatedRoute.tsx @@ -1,16 +1,18 @@ -import React from 'react'; -import { Redirect, Route, RouteProps } from 'react-router'; +import type { ReactNode } from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; + import { useStoreState } from '@/state/hooks'; -export default ({ children, ...props }: Omit) => { - const isAuthenticated = useStoreState((state) => !!state.user.data?.uuid); +function AuthenticatedRoute({ children }: { children?: ReactNode }): JSX.Element { + const isAuthenticated = useStoreState(state => !!state.user.data?.uuid); - return ( - - isAuthenticated ? children : - } - /> - ); -}; + const location = useLocation(); + + if (isAuthenticated) { + return <>{children}; + } + + return ; +} + +export default AuthenticatedRoute; diff --git a/resources/scripts/components/elements/Button.tsx b/resources/scripts/components/elements/Button.tsx index e02f00ff1..f25c8b296 100644 --- a/resources/scripts/components/elements/Button.tsx +++ b/resources/scripts/components/elements/Button.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import styled, { css } from 'styled-components/macro'; +import * as React from 'react'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; import Spinner from '@/components/elements/Spinner'; @@ -13,17 +13,17 @@ interface Props { const ButtonStyle = styled.button>` ${tw`relative inline-block rounded p-2 uppercase tracking-wide text-sm transition-all duration-150 border`}; - ${(props) => + ${props => ((!props.isSecondary && !props.color) || props.color === 'primary') && css` - ${(props) => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`}; + ${props => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`}; &:hover:not(:disabled) { ${tw`bg-primary-600 border-primary-700`}; } `}; - ${(props) => + ${props => props.color === 'grey' && css` ${tw`border-neutral-600 bg-neutral-500 text-neutral-50`}; @@ -33,7 +33,7 @@ const ButtonStyle = styled.button>` } `}; - ${(props) => + ${props => props.color === 'green' && css` ${tw`border-green-600 bg-green-500 text-green-50`}; @@ -42,7 +42,7 @@ const ButtonStyle = styled.button>` ${tw`bg-green-600 border-green-700`}; } - ${(props) => + ${props => props.isSecondary && css` &:active:not(:disabled) { @@ -51,7 +51,7 @@ const ButtonStyle = styled.button>` `}; `}; - ${(props) => + ${props => props.color === 'red' && css` ${tw`border-red-600 bg-red-500 text-red-50`}; @@ -60,7 +60,7 @@ const ButtonStyle = styled.button>` ${tw`bg-red-600 border-red-700`}; } - ${(props) => + ${props => props.isSecondary && css` &:active:not(:disabled) { @@ -69,21 +69,21 @@ const ButtonStyle = styled.button>` `}; `}; - ${(props) => props.size === 'xsmall' && tw`px-2 py-1 text-xs`}; - ${(props) => (!props.size || props.size === 'small') && tw`px-4 py-2`}; - ${(props) => props.size === 'large' && tw`p-4 text-sm`}; - ${(props) => props.size === 'xlarge' && tw`p-4 w-full`}; + ${props => props.size === 'xsmall' && tw`px-2 py-1 text-xs`}; + ${props => (!props.size || props.size === 'small') && tw`px-4 py-2`}; + ${props => props.size === 'large' && tw`p-4 text-sm`}; + ${props => props.size === 'xlarge' && tw`p-4 w-full`}; - ${(props) => + ${props => props.isSecondary && css` ${tw`border-neutral-600 bg-transparent text-neutral-200`}; &:hover:not(:disabled) { ${tw`border-neutral-500 text-neutral-100`}; - ${(props) => props.color === 'red' && tw`bg-red-500 border-red-600 text-red-50`}; - ${(props) => props.color === 'primary' && tw`bg-primary-500 border-primary-600 text-primary-50`}; - ${(props) => props.color === 'green' && tw`bg-green-500 border-green-600 text-green-50`}; + ${props => props.color === 'red' && tw`bg-red-500 border-red-600 text-red-50`}; + ${props => props.color === 'primary' && tw`bg-primary-500 border-primary-600 text-primary-50`}; + ${props => props.color === 'green' && tw`bg-green-500 border-green-600 text-green-50`}; } `}; @@ -108,7 +108,7 @@ const Button: React.FC = ({ children, isLoading, ...props }) => type LinkProps = Omit & Props; -const LinkButton: React.FC = (props) => ; +const LinkButton: React.FC = props => ; export { LinkButton, ButtonStyle }; export default Button; diff --git a/resources/scripts/components/elements/Can.tsx b/resources/scripts/components/elements/Can.tsx index 824051936..70b3fa847 100644 --- a/resources/scripts/components/elements/Can.tsx +++ b/resources/scripts/components/elements/Can.tsx @@ -1,24 +1,25 @@ -import React, { memo } from 'react'; -import { usePermissions } from '@/plugins/usePermissions'; +import type { ReactNode } from 'react'; +import { memo } from 'react'; import isEqual from 'react-fast-compare'; +import { usePermissions } from '@/plugins/usePermissions'; interface Props { action: string | string[]; matchAny?: boolean; - renderOnError?: React.ReactNode | null; - children: React.ReactNode; + renderOnError?: ReactNode | null; + children: ReactNode; } -const Can = ({ action, matchAny = false, renderOnError, children }: Props) => { +function Can({ action, matchAny = false, renderOnError, children }: Props) { const can = usePermissions(action); return ( <> - {(matchAny && can.filter((p) => p).length > 0) || (!matchAny && can.every((p) => p)) - ? children - : renderOnError} + {(matchAny && can.filter(p => p).length > 0) || (!matchAny && can.every(p => p)) ? children : renderOnError} ); -}; +} -export default memo(Can, isEqual); +const MemoizedCan = memo(Can, isEqual); + +export default MemoizedCan; diff --git a/resources/scripts/components/elements/Checkbox.tsx b/resources/scripts/components/elements/Checkbox.tsx index 731fd24de..1432fe1e3 100644 --- a/resources/scripts/components/elements/Checkbox.tsx +++ b/resources/scripts/components/elements/Checkbox.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Field, FieldProps } from 'formik'; import Input from '@/components/elements/Input'; @@ -29,7 +28,7 @@ const Checkbox = ({ name, value, className, ...props }: Props & InputProps) => ( type={'checkbox'} checked={(field.value || []).includes(value)} onClick={() => form.setFieldTouched(field.name, true)} - onChange={(e) => { + onChange={e => { const set = new Set(field.value); set.has(value) ? set.delete(value) : set.add(value); diff --git a/resources/scripts/components/elements/Code.tsx b/resources/scripts/components/elements/Code.tsx index 30eac0d86..02640d699 100644 --- a/resources/scripts/components/elements/Code.tsx +++ b/resources/scripts/components/elements/Code.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import classNames from 'classnames'; interface CodeProps { diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx index 99a7b5e85..48948ad74 100644 --- a/resources/scripts/components/elements/CodemirrorEditor.tsx +++ b/resources/scripts/components/elements/CodemirrorEditor.tsx @@ -1,7 +1,9 @@ -import React, { useCallback, useEffect, useState } from 'react'; import CodeMirror from 'codemirror'; -import styled from 'styled-components/macro'; +import type { CSSProperties } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; import tw from 'twin.macro'; + import modes from '@/modes'; require('codemirror/lib/codemirror.css'); @@ -106,7 +108,7 @@ const EditorContainer = styled.div` `; export interface Props { - style?: React.CSSProperties; + style?: CSSProperties; initialContent?: string; mode: string; filename?: string; @@ -119,7 +121,7 @@ const findModeByFilename = (filename: string) => { for (let i = 0; i < modes.length; i++) { const info = modes[i]; - if (info.file && info.file.test(filename)) { + if (info?.file !== undefined && info.file.test(filename)) { return info; } } @@ -130,7 +132,7 @@ const findModeByFilename = (filename: string) => { if (ext) { for (let i = 0; i < modes.length; i++) { const info = modes[i]; - if (info.ext) { + if (info?.ext !== undefined) { for (let j = 0; j < info.ext.length; j++) { if (info.ext[j] === ext) { return info; @@ -146,10 +148,12 @@ const findModeByFilename = (filename: string) => { export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { const [editor, setEditor] = useState(); - const ref = useCallback((node) => { - if (!node) return; + const ref = useCallback<(_?: unknown) => void>(node => { + if (node === undefined) { + return; + } - const e = CodeMirror.fromTextArea(node, { + const e = CodeMirror.fromTextArea(node as HTMLTextAreaElement, { mode: 'text/plain', theme: 'ayu-mirage', indentUnit: 4, @@ -158,7 +162,6 @@ export default ({ style, initialContent, filename, mode, fetchContent, onContent indentWithTabs: false, lineWrapping: true, lineNumbers: true, - foldGutter: true, fixedGutter: true, scrollbarStyle: 'overlay', coverGutterNextToScrollbar: false, diff --git a/resources/scripts/components/elements/ConfirmationModal.tsx b/resources/scripts/components/elements/ConfirmationModal.tsx index 52f9f9e3c..ffb85a13f 100644 --- a/resources/scripts/components/elements/ConfirmationModal.tsx +++ b/resources/scripts/components/elements/ConfirmationModal.tsx @@ -1,23 +1,28 @@ -import React, { useContext } from 'react'; +import type { ReactNode } from 'react'; +import { useContext } from 'react'; import tw from 'twin.macro'; -import Button from '@/components/elements/Button'; -import asModal from '@/hoc/asModal'; -import ModalContext from '@/context/ModalContext'; -type Props = { +import Button from '@/components/elements/Button'; +import ModalContext from '@/context/ModalContext'; +import asModal from '@/hoc/asModal'; + +interface Props { + children: ReactNode; + title: string; buttonText: string; onConfirmed: () => void; showSpinnerOverlay?: boolean; -}; +} -const ConfirmationModal: React.FC = ({ title, children, buttonText, onConfirmed }) => { +function ConfirmationModal({ title, children, buttonText, onConfirmed }: Props) { const { dismiss } = useContext(ModalContext); return ( <>

{title}

{children}
+
); -}; +} -ConfirmationModal.displayName = 'ConfirmationModal'; - -export default asModal((props) => ({ +export default asModal(props => ({ showSpinnerOverlay: props.showSpinnerOverlay, }))(ConfirmationModal); diff --git a/resources/scripts/components/elements/ContentBox.tsx b/resources/scripts/components/elements/ContentBox.tsx index b829088c8..fb31f9e94 100644 --- a/resources/scripts/components/elements/ContentBox.tsx +++ b/resources/scripts/components/elements/ContentBox.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import FlashMessageRender from '@/components/FlashMessageRender'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/ContentContainer.tsx b/resources/scripts/components/elements/ContentContainer.tsx index 799f512d2..0ce40c416 100644 --- a/resources/scripts/components/elements/ContentContainer.tsx +++ b/resources/scripts/components/elements/ContentContainer.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { breakpoint } from '@/theme'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/CopyOnClick.tsx b/resources/scripts/components/elements/CopyOnClick.tsx index 431e9c8d6..80c01277c 100644 --- a/resources/scripts/components/elements/CopyOnClick.tsx +++ b/resources/scripts/components/elements/CopyOnClick.tsx @@ -1,13 +1,15 @@ -import React, { useEffect, useState } from 'react'; -import Fade from '@/components/elements/Fade'; -import Portal from '@/components/elements/Portal'; -import copy from 'copy-to-clipboard'; import classNames from 'classnames'; +import copy from 'copy-to-clipboard'; +import type { MouseEvent, ReactNode } from 'react'; +import { Children, cloneElement, isValidElement, useEffect, useState } from 'react'; + +import Portal from '@/components/elements/Portal'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; interface CopyOnClickProps { text: string | number | null | undefined; showInNotification?: boolean; - children: React.ReactNode; + children: ReactNode; } const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickProps) => { @@ -25,15 +27,16 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP }; }, [copied]); - if (!React.isValidElement(children)) { + if (!isValidElement(children)) { throw new Error('Component passed to must be a valid React element.'); } const child = !text - ? React.Children.only(children) - : React.cloneElement(React.Children.only(children), { + ? Children.only(children) + : cloneElement(Children.only(children), { + // @ts-expect-error I don't know className: classNames(children.props.className || '', 'cursor-pointer'), - onClick: (e: React.MouseEvent) => { + onClick: (e: MouseEvent) => { copy(String(text)); setCopied(true); if (typeof children.props.onClick === 'function') { @@ -46,9 +49,9 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP <> {copied && ( - -
-
+ +
+

{showInNotification ? `Copied "${String(text)}" to clipboard.` @@ -56,7 +59,7 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP

- +
)} {child} diff --git a/resources/scripts/components/elements/DropdownMenu.tsx b/resources/scripts/components/elements/DropdownMenu.tsx index 0c5361a8c..ea8208369 100644 --- a/resources/scripts/components/elements/DropdownMenu.tsx +++ b/resources/scripts/components/elements/DropdownMenu.tsx @@ -1,11 +1,13 @@ -import React, { createRef } from 'react'; -import styled from 'styled-components/macro'; +import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'; +import { createRef, PureComponent } from 'react'; +import styled from 'styled-components'; import tw from 'twin.macro'; -import Fade from '@/components/elements/Fade'; + +import FadeTransition from '@/components/elements/transitions/FadeTransition'; interface Props { - children: React.ReactNode; - renderToggle: (onClick: (e: React.MouseEvent) => void) => React.ReactChild; + children: ReactNode; + renderToggle: (onClick: (e: ReactMouseEvent) => void) => any; } export const DropdownButtonRow = styled.button<{ danger?: boolean }>` @@ -13,7 +15,7 @@ export const DropdownButtonRow = styled.button<{ danger?: boolean }>` transition: 150ms all ease; &:hover { - ${(props) => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)}; + ${props => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)}; } `; @@ -22,19 +24,19 @@ interface State { visible: boolean; } -class DropdownMenu extends React.PureComponent { +class DropdownMenu extends PureComponent { menu = createRef(); - state: State = { + override state: State = { posX: 0, visible: false, }; - componentWillUnmount() { + override componentWillUnmount() { this.removeListeners(); } - componentDidUpdate(prevProps: Readonly, prevState: Readonly) { + override componentDidUpdate(_prevProps: Readonly, prevState: Readonly) { const menu = this.menu.current; if (this.state.visible && !prevState.visible && menu) { @@ -48,19 +50,21 @@ class DropdownMenu extends React.PureComponent { } } - removeListeners = () => { + removeListeners() { document.removeEventListener('click', this.windowListener); document.removeEventListener('contextmenu', this.contextMenuListener); - }; + } - onClickHandler = (e: React.MouseEvent) => { + onClickHandler(e: ReactMouseEvent) { e.preventDefault(); this.triggerMenu(e.clientX); - }; + } - contextMenuListener = () => this.setState({ visible: false }); + contextMenuListener() { + this.setState({ visible: false }); + } - windowListener = (e: MouseEvent) => { + windowListener(e: MouseEvent): any { const menu = this.menu.current; if (e.button === 2 || !this.state.visible || !menu) { @@ -74,22 +78,24 @@ class DropdownMenu extends React.PureComponent { if (e.target !== menu && !menu.contains(e.target as Node)) { this.setState({ visible: false }); } - }; + } - triggerMenu = (posX: number) => - this.setState((s) => ({ + triggerMenu(posX: number) { + this.setState(s => ({ posX: !s.visible ? posX : s.posX, visible: !s.visible, })); + } - render() { + override render() { return (
{this.props.renderToggle(this.onClickHandler)} - + +
{ + onClick={e => { e.stopPropagation(); this.setState({ visible: false }); }} @@ -98,7 +104,7 @@ class DropdownMenu extends React.PureComponent { > {this.props.children}
-
+
); } diff --git a/resources/scripts/components/elements/ErrorBoundary.tsx b/resources/scripts/components/elements/ErrorBoundary.tsx index c92d952b5..95819a02f 100644 --- a/resources/scripts/components/elements/ErrorBoundary.tsx +++ b/resources/scripts/components/elements/ErrorBoundary.tsx @@ -1,15 +1,20 @@ -import React from 'react'; -import tw from 'twin.macro'; -import Icon from '@/components/elements/Icon'; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; +import type { ReactNode } from 'react'; +import { Component } from 'react'; +import tw from 'twin.macro'; + +import Icon from '@/components/elements/Icon'; + +interface Props { + children?: ReactNode; +} interface State { hasError: boolean; } -// eslint-disable-next-line @typescript-eslint/ban-types -class ErrorBoundary extends React.Component<{}, State> { - state: State = { +class ErrorBoundary extends Component { + override state: State = { hasError: false, }; @@ -17,15 +22,16 @@ class ErrorBoundary extends React.Component<{}, State> { return { hasError: true }; } - componentDidCatch(error: Error) { + override componentDidCatch(error: Error) { console.error(error); } - render() { + override render() { return this.state.hasError ? (
+

An error was encountered by the application while rendering this view. Try refreshing the page.

diff --git a/resources/scripts/components/elements/Fade.tsx b/resources/scripts/components/elements/Fade.tsx deleted file mode 100644 index 41bcf0be7..000000000 --- a/resources/scripts/components/elements/Fade.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import tw from 'twin.macro'; -import styled from 'styled-components/macro'; -import CSSTransition, { CSSTransitionProps } from 'react-transition-group/CSSTransition'; - -interface Props extends Omit { - timeout: number; -} - -const Container = styled.div<{ timeout: number }>` - .fade-enter, - .fade-exit, - .fade-appear { - will-change: opacity; - } - - .fade-enter, - .fade-appear { - ${tw`opacity-0`}; - - &.fade-enter-active, - &.fade-appear-active { - ${tw`opacity-100 transition-opacity ease-in`}; - transition-duration: ${(props) => props.timeout}ms; - } - } - - .fade-exit { - ${tw`opacity-100`}; - - &.fade-exit-active { - ${tw`opacity-0 transition-opacity ease-in`}; - transition-duration: ${(props) => props.timeout}ms; - } - } -`; - -const Fade: React.FC = ({ timeout, children, ...props }) => ( - - - {children} - - -); -Fade.displayName = 'Fade'; - -export default Fade; diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index 6ed5023c3..3ce78349a 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import { Field as FormikField, FieldProps } from 'formik'; import Input from '@/components/elements/Input'; import Label from '@/components/elements/Label'; @@ -41,7 +42,7 @@ const Field = forwardRef(
)} - ) + ), ); Field.displayName = 'Field'; diff --git a/resources/scripts/components/elements/FormikFieldWrapper.tsx b/resources/scripts/components/elements/FormikFieldWrapper.tsx index 95a645a85..91db2ff43 100644 --- a/resources/scripts/components/elements/FormikFieldWrapper.tsx +++ b/resources/scripts/components/elements/FormikFieldWrapper.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { Field, FieldProps } from 'formik'; import InputError from '@/components/elements/InputError'; import Label from '@/components/elements/Label'; diff --git a/resources/scripts/components/elements/FormikSwitch.tsx b/resources/scripts/components/elements/FormikSwitch.tsx index 4736e0794..52faf056b 100644 --- a/resources/scripts/components/elements/FormikSwitch.tsx +++ b/resources/scripts/components/elements/FormikSwitch.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; import { Field, FieldProps } from 'formik'; import Switch, { SwitchProps } from '@/components/elements/Switch'; diff --git a/resources/scripts/components/elements/GreyRowBox.tsx b/resources/scripts/components/elements/GreyRowBox.tsx index e5a7f9685..99e3fadf9 100644 --- a/resources/scripts/components/elements/GreyRowBox.tsx +++ b/resources/scripts/components/elements/GreyRowBox.tsx @@ -1,10 +1,10 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw from 'twin.macro'; export default styled.div<{ $hoverable?: boolean }>` ${tw`flex rounded no-underline text-neutral-200 items-center bg-neutral-700 p-4 border border-transparent transition-colors duration-150 overflow-hidden`}; - ${(props) => props.$hoverable !== false && tw`hover:border-neutral-500`}; + ${props => props.$hoverable !== false && tw`hover:border-neutral-500`}; & .icon { ${tw`rounded-full w-16 flex items-center justify-center bg-neutral-500 p-3`}; diff --git a/resources/scripts/components/elements/Icon.tsx b/resources/scripts/components/elements/Icon.tsx index 465d5a52d..d80ca96c0 100644 --- a/resources/scripts/components/elements/Icon.tsx +++ b/resources/scripts/components/elements/Icon.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties } from 'react'; +import { CSSProperties } from 'react'; import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/Input.tsx b/resources/scripts/components/elements/Input.tsx index 677a5014d..c9283e055 100644 --- a/resources/scripts/components/elements/Input.tsx +++ b/resources/scripts/components/elements/Input.tsx @@ -1,4 +1,4 @@ -import styled, { css } from 'styled-components/macro'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; export interface Props { @@ -45,7 +45,7 @@ const inputStyle = css` & + .input-help { ${tw`mt-1 text-xs`}; - ${(props) => (props.hasError ? tw`text-red-200` : tw`text-neutral-200`)}; + ${props => (props.hasError ? tw`text-red-200` : tw`text-neutral-200`)}; } &:required, @@ -55,15 +55,15 @@ const inputStyle = css` &:not(:disabled):not(:read-only):focus { ${tw`shadow-md border-primary-300 ring-2 ring-primary-400 ring-opacity-50`}; - ${(props) => props.hasError && tw`border-red-300 ring-red-200`}; + ${props => props.hasError && tw`border-red-300 ring-red-200`}; } &:disabled { ${tw`opacity-75`}; } - ${(props) => props.isLight && light}; - ${(props) => props.hasError && tw`text-red-100 border-red-400 hover:border-red-300`}; + ${props => props.isLight && light}; + ${props => props.hasError && tw`text-red-100 border-red-400 hover:border-red-300`}; `; const Input = styled.input` diff --git a/resources/scripts/components/elements/InputError.tsx b/resources/scripts/components/elements/InputError.tsx index dfabf72d9..d39735a3a 100644 --- a/resources/scripts/components/elements/InputError.tsx +++ b/resources/scripts/components/elements/InputError.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { FormikErrors, FormikTouched } from 'formik'; import tw from 'twin.macro'; import { capitalize } from '@/lib/strings'; @@ -15,7 +14,7 @@ const InputError = ({ errors, touched, name, children }: Props) =>

{typeof errors[name] === 'string' ? capitalize(errors[name] as string) - : capitalize((errors[name] as unknown as string[])[0])} + : capitalize((errors[name] as unknown as string[])[0] ?? '')}

) : ( <>{children ?

{children}

: null} diff --git a/resources/scripts/components/elements/InputSpinner.tsx b/resources/scripts/components/elements/InputSpinner.tsx index 2ed77b433..583214a02 100644 --- a/resources/scripts/components/elements/InputSpinner.tsx +++ b/resources/scripts/components/elements/InputSpinner.tsx @@ -1,14 +1,15 @@ -import React from 'react'; -import Spinner from '@/components/elements/Spinner'; -import Fade from '@/components/elements/Fade'; +import type { ReactNode } from 'react'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; -import styled, { css } from 'styled-components/macro'; + import Select from '@/components/elements/Select'; +import Spinner from '@/components/elements/Spinner'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; const Container = styled.div<{ visible?: boolean }>` ${tw`relative`}; - ${(props) => + ${props => props.visible && css` & ${Select} { @@ -17,15 +18,18 @@ const Container = styled.div<{ visible?: boolean }>` `}; `; -const InputSpinner = ({ visible, children }: { visible: boolean; children: React.ReactNode }) => ( - - -
- -
-
- {children} -
-); +function InputSpinner({ visible, children }: { visible: boolean; children: ReactNode }) { + return ( + + +
+ +
+
+ + {children} +
+ ); +} export default InputSpinner; diff --git a/resources/scripts/components/elements/Label.tsx b/resources/scripts/components/elements/Label.tsx index 704e8324b..c97e99c65 100644 --- a/resources/scripts/components/elements/Label.tsx +++ b/resources/scripts/components/elements/Label.tsx @@ -1,9 +1,9 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw from 'twin.macro'; const Label = styled.label<{ isLight?: boolean }>` ${tw`block text-xs uppercase text-neutral-200 mb-1 sm:mb-2`}; - ${(props) => props.isLight && tw`text-neutral-700`}; + ${props => props.isLight && tw`text-neutral-700`}; `; export default Label; diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index d4f6dfece..89de26562 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -1,12 +1,16 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import Spinner from '@/components/elements/Spinner'; -import tw from 'twin.macro'; -import styled, { css } from 'styled-components/macro'; -import { breakpoint } from '@/theme'; -import Fade from '@/components/elements/Fade'; +import type { ReactNode } from 'react'; +import { Fragment, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; +import styled, { css } from 'styled-components'; +import tw from 'twin.macro'; + +import Spinner from '@/components/elements/Spinner'; +import { breakpoint } from '@/theme'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; export interface RequiredModalProps { + children?: ReactNode; + visible: boolean; onDismissed: () => void; appear?: boolean; @@ -32,7 +36,7 @@ const ModalContainer = styled.div<{ alignTop?: boolean }>` ${breakpoint('lg')`max-width: 50%`}; ${tw`relative flex flex-col w-full m-auto`}; - ${(props) => + ${props => props.alignTop && css` margin-top: 20%; @@ -55,7 +59,7 @@ const ModalContainer = styled.div<{ alignTop?: boolean }>` } `; -const Modal: React.FC = ({ +function Modal({ visible, appear, dismissable, @@ -65,7 +69,7 @@ const Modal: React.FC = ({ closeOnEscape = true, onDismissed, children, -}) => { +}: ModalProps) { const [render, setRender] = useState(visible); const isDismissable = useMemo(() => { @@ -85,14 +89,20 @@ const Modal: React.FC = ({ }; }, [isDismissable, closeOnEscape, render]); - useEffect(() => setRender(visible), [visible]); + useEffect(() => { + setRender(visible); + + if (!visible) { + onDismissed(); + } + }, [visible]); return ( - onDismissed()}> + e.stopPropagation()} - onContextMenu={(e) => e.stopPropagation()} - onMouseDown={(e) => { + onClick={e => e.stopPropagation()} + onContextMenu={e => e.stopPropagation()} + onMouseDown={e => { if (isDismissable && closeOnBackground) { e.stopPropagation(); if (e.target === e.currentTarget) { @@ -119,16 +129,16 @@ const Modal: React.FC = ({
)} - {showSpinnerOverlay && ( - -
- -
-
- )} + + +
+ +
+
+
@@ -136,14 +146,14 @@ const Modal: React.FC = ({
- + ); -}; +} -const PortaledModal: React.FC = ({ children, ...props }) => { +function PortaledModal({ children, ...props }: ModalProps): JSX.Element { const element = useRef(document.getElementById('modal-portal')); return createPortal({children}, element.current!); -}; +} export default PortaledModal; diff --git a/resources/scripts/components/elements/PageContentBlock.tsx b/resources/scripts/components/elements/PageContentBlock.tsx index a4df19567..9def94e52 100644 --- a/resources/scripts/components/elements/PageContentBlock.tsx +++ b/resources/scripts/components/elements/PageContentBlock.tsx @@ -1,16 +1,19 @@ -import React, { useEffect } from 'react'; -import ContentContainer from '@/components/elements/ContentContainer'; -import { CSSTransition } from 'react-transition-group'; +import type { ReactNode } from 'react'; +import { useEffect } from 'react'; import tw from 'twin.macro'; + +import ContentContainer from '@/components/elements/ContentContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; export interface PageContentBlockProps { + children?: ReactNode; + title?: string; className?: string; showFlashKey?: string; } -const PageContentBlock: React.FC = ({ title, showFlashKey, className, children }) => { +function PageContentBlock({ title, showFlashKey, className, children }: PageContentBlockProps) { useEffect(() => { if (title) { document.title = title; @@ -18,28 +21,27 @@ const PageContentBlock: React.FC = ({ title, showFlashKey }, [title]); return ( - - <> - - {showFlashKey && } - {children} - - -

- - Pterodactyl® - -  © 2015 - {new Date().getFullYear()} -

-
- -
+ <> + + {showFlashKey && } + {children} + + + +

+ + Pterodactyl® + +  © 2015 - {new Date().getFullYear()} +

+
+ ); -}; +} export default PageContentBlock; diff --git a/resources/scripts/components/elements/Pagination.tsx b/resources/scripts/components/elements/Pagination.tsx index 7a2951f77..0bb395ea8 100644 --- a/resources/scripts/components/elements/Pagination.tsx +++ b/resources/scripts/components/elements/Pagination.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import * as React from 'react'; import { PaginatedResult } from '@/api/http'; import tw from 'twin.macro'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import Button from '@/components/elements/Button'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faAngleDoubleLeft, faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons'; @@ -48,12 +48,12 @@ function Pagination({ data: { items, pagination }, onPageSelect, children }: {children({ items, isFirstPage, isLastPage })} {pages.length > 1 && (
- {pages[0] > 1 && !isFirstPage && ( + {(pages?.[0] ?? 0) > 1 && !isFirstPage && ( onPageSelect(1)}> )} - {pages.map((i) => ( + {pages.map(i => ( ({ data: { items, pagination }, onPageSelect, children }: {i} ))} - {pages[4] < pagination.totalPages && !isLastPage && ( + {(pages?.[4] ?? 0) < pagination.totalPages && !isLastPage && ( onPageSelect(pagination.totalPages)}> diff --git a/resources/scripts/components/elements/PermissionRoute.tsx b/resources/scripts/components/elements/PermissionRoute.tsx index e7a46e570..ed50db852 100644 --- a/resources/scripts/components/elements/PermissionRoute.tsx +++ b/resources/scripts/components/elements/PermissionRoute.tsx @@ -1,28 +1,26 @@ -import React from 'react'; -import { Route } from 'react-router-dom'; -import { RouteProps } from 'react-router'; -import Can from '@/components/elements/Can'; -import { ServerError } from '@/components/elements/ScreenBlock'; +import type { ReactNode } from 'react'; -interface Props extends Omit { - path: string; - permission: string | string[] | null; +import { ServerError } from '@/components/elements/ScreenBlock'; +import { usePermissions } from '@/plugins/usePermissions'; + +interface Props { + children?: ReactNode; + + permission?: string | string[]; } -export default ({ permission, children, ...props }: Props) => ( - - {!permission ? ( - children - ) : ( - - } - > - {children} - - )} - -); +function PermissionRoute({ children, permission }: Props): JSX.Element { + if (permission === undefined) { + return <>{children}; + } + + const can = usePermissions(permission); + + if (can.filter(p => p).length > 0) { + return <>{children}; + } + + return ; +} + +export default PermissionRoute; diff --git a/resources/scripts/components/elements/Portal.tsx b/resources/scripts/components/elements/Portal.tsx index e0be75778..1bbc03e4c 100644 --- a/resources/scripts/components/elements/Portal.tsx +++ b/resources/scripts/components/elements/Portal.tsx @@ -1,4 +1,5 @@ -import React, { useRef } from 'react'; +import { useRef } from 'react'; +import * as React from 'react'; import { createPortal } from 'react-dom'; export default ({ children }: { children: React.ReactNode }) => { diff --git a/resources/scripts/components/elements/ProgressBar.tsx b/resources/scripts/components/elements/ProgressBar.tsx index c94ae80f3..9ad271972 100644 --- a/resources/scripts/components/elements/ProgressBar.tsx +++ b/resources/scripts/components/elements/ProgressBar.tsx @@ -1,25 +1,19 @@ -import React, { useEffect, useRef, useState } from 'react'; -import styled from 'styled-components/macro'; +import { Transition } from '@headlessui/react'; import { useStoreActions, useStoreState } from 'easy-peasy'; -import { randomInt } from '@/helpers'; -import { CSSTransition } from 'react-transition-group'; -import tw from 'twin.macro'; +import { Fragment, useEffect, useRef, useState } from 'react'; -const BarFill = styled.div` - ${tw`h-full bg-cyan-400`}; - transition: 250ms ease-in-out; - box-shadow: 0 -2px 10px 2px hsl(178, 78%, 57%); -`; +import { randomInt } from '@/helpers'; type Timer = ReturnType; -export default () => { - const interval = useRef(null) as React.MutableRefObject; - const timeout = useRef(null) as React.MutableRefObject; +function ProgressBar() { + const interval = useRef(); + const timeout = useRef(); const [visible, setVisible] = useState(false); - const progress = useStoreState((state) => state.progress.progress); - const continuous = useStoreState((state) => state.progress.continuous); - const setProgress = useStoreActions((actions) => actions.progress.setProgress); + + const continuous = useStoreState(state => state.progress.continuous); + const progress = useStoreState(state => state.progress.progress); + const setProgress = useStoreActions(actions => actions.progress.setProgress); useEffect(() => { return () => { @@ -59,10 +53,26 @@ export default () => { }, [progress, continuous]); return ( -
- - - +
+ +
+
); -}; +} + +export default ProgressBar; diff --git a/resources/scripts/components/elements/ScreenBlock.tsx b/resources/scripts/components/elements/ScreenBlock.tsx index 0b57e92c6..26e8ec5ac 100644 --- a/resources/scripts/components/elements/ScreenBlock.tsx +++ b/resources/scripts/components/elements/ScreenBlock.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import PageContentBlock from '@/components/elements/PageContentBlock'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faArrowLeft, faSyncAlt } from '@fortawesome/free-solid-svg-icons'; -import styled, { keyframes } from 'styled-components/macro'; +import styled, { keyframes } from 'styled-components'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import NotFoundSvg from '@/assets/images/not_found.svg'; diff --git a/resources/scripts/components/elements/Select.tsx b/resources/scripts/components/elements/Select.tsx index fc50f252d..de74a8aef 100644 --- a/resources/scripts/components/elements/Select.tsx +++ b/resources/scripts/components/elements/Select.tsx @@ -1,4 +1,4 @@ -import styled, { css } from 'styled-components/macro'; +import styled, { css } from 'styled-components'; import tw from 'twin.macro'; interface Props { @@ -25,7 +25,7 @@ const Select = styled.select` display: none; } - ${(props) => + ${props => !props.hideDropdownArrow && css` ${tw`bg-neutral-600 border-neutral-500 text-neutral-200`}; diff --git a/resources/scripts/components/elements/ServerContentBlock.tsx b/resources/scripts/components/elements/ServerContentBlock.tsx index cda208c86..6905843f9 100644 --- a/resources/scripts/components/elements/ServerContentBlock.tsx +++ b/resources/scripts/components/elements/ServerContentBlock.tsx @@ -1,5 +1,5 @@ import PageContentBlock, { PageContentBlockProps } from '@/components/elements/PageContentBlock'; -import React from 'react'; +import * as React from 'react'; import { ServerContext } from '@/state/server'; interface Props extends PageContentBlockProps { @@ -7,7 +7,7 @@ interface Props extends PageContentBlockProps { } const ServerContentBlock: React.FC = ({ title, children, ...props }) => { - const name = ServerContext.useStoreState((state) => state.server.data!.name); + const name = ServerContext.useStoreState(state => state.server.data!.name); return ( diff --git a/resources/scripts/components/elements/Spinner.tsx b/resources/scripts/components/elements/Spinner.tsx index 48de135d3..fe5ae6401 100644 --- a/resources/scripts/components/elements/Spinner.tsx +++ b/resources/scripts/components/elements/Spinner.tsx @@ -1,19 +1,23 @@ -import React, { Suspense } from 'react'; -import styled, { css, keyframes } from 'styled-components/macro'; +import type { FC, ReactNode } from 'react'; +import { Suspense } from 'react'; +import styled, { css, keyframes } from 'styled-components'; import tw from 'twin.macro'; + import ErrorBoundary from '@/components/elements/ErrorBoundary'; export type SpinnerSize = 'small' | 'base' | 'large'; interface Props { + children?: ReactNode; + size?: SpinnerSize; centered?: boolean; isBlue?: boolean; } -interface Spinner extends React.FC { +interface Spinner extends FC { Size: Record<'SMALL' | 'BASE' | 'LARGE', SpinnerSize>; - Suspense: React.FC; + Suspense: FC; } const spin = keyframes` @@ -27,7 +31,7 @@ const SpinnerComponent = styled.div` border-radius: 50%; animation: ${spin} 1s cubic-bezier(0.55, 0.25, 0.25, 0.7) infinite; - ${(props) => + ${props => props.size === 'small' ? tw`w-4 h-4 border-2` : props.size === 'large' @@ -37,8 +41,8 @@ const SpinnerComponent = styled.div` ` : null}; - border-color: ${(props) => (!props.isBlue ? 'rgba(255, 255, 255, 0.2)' : 'hsla(212, 92%, 43%, 0.2)')}; - border-top-color: ${(props) => (!props.isBlue ? 'rgb(255, 255, 255)' : 'hsl(212, 92%, 43%)')}; + border-color: ${props => (!props.isBlue ? 'rgba(255, 255, 255, 0.2)' : 'hsla(212, 92%, 43%, 0.2)')}; + border-top-color: ${props => (!props.isBlue ? 'rgb(255, 255, 255)' : 'hsl(212, 92%, 43%)')}; `; const Spinner: Spinner = ({ centered, ...props }) => diff --git a/resources/scripts/components/elements/SpinnerOverlay.tsx b/resources/scripts/components/elements/SpinnerOverlay.tsx index d2052f3be..0775a71f7 100644 --- a/resources/scripts/components/elements/SpinnerOverlay.tsx +++ b/resources/scripts/components/elements/SpinnerOverlay.tsx @@ -1,17 +1,23 @@ -import React from 'react'; -import Spinner, { SpinnerSize } from '@/components/elements/Spinner'; -import Fade from '@/components/elements/Fade'; +import type { ReactNode } from 'react'; import tw from 'twin.macro'; +import Spinner, { SpinnerSize } from '@/components/elements/Spinner'; + interface Props { + children?: ReactNode; + visible: boolean; fixed?: boolean; size?: SpinnerSize; backgroundOpacity?: number; } -const SpinnerOverlay: React.FC = ({ size, fixed, visible, backgroundOpacity, children }) => ( - +function SpinnerOverlay({ size, fixed, visible, backgroundOpacity, children }: Props) { + if (!visible) { + return null; + } + + return (
= ({ size, fixed, visible, backgroundOpaci {children && (typeof children === 'string' ?

{children}

: children)}
-
-); + ); +} export default SpinnerOverlay; diff --git a/resources/scripts/components/elements/SubNavigation.tsx b/resources/scripts/components/elements/SubNavigation.tsx index 9a25b80b0..c28ae7ba3 100644 --- a/resources/scripts/components/elements/SubNavigation.tsx +++ b/resources/scripts/components/elements/SubNavigation.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import tw, { theme } from 'twin.macro'; const SubNavigation = styled.div` diff --git a/resources/scripts/components/elements/Switch.tsx b/resources/scripts/components/elements/Switch.tsx index 45c738474..03927fd63 100644 --- a/resources/scripts/components/elements/Switch.tsx +++ b/resources/scripts/components/elements/Switch.tsx @@ -1,6 +1,7 @@ -import React, { useMemo } from 'react'; -import styled from 'styled-components/macro'; -import { v4 } from 'uuid'; +import type { ChangeEvent, ReactNode } from 'react'; +import { useMemo } from 'react'; +import { nanoid } from 'nanoid'; +import styled from 'styled-components'; import tw from 'twin.macro'; import Label from '@/components/elements/Label'; import Input from '@/components/elements/Input'; @@ -42,12 +43,12 @@ export interface SwitchProps { description?: string; defaultChecked?: boolean; readOnly?: boolean; - onChange?: (e: React.ChangeEvent) => void; - children?: React.ReactNode; + onChange?: (e: ChangeEvent) => void; + children?: ReactNode; } const Switch = ({ name, label, description, defaultChecked, readOnly, onChange, children }: SwitchProps) => { - const uuid = useMemo(() => v4(), []); + const uuid = useMemo(() => nanoid(), []); return (
@@ -57,7 +58,7 @@ const Switch = ({ name, label, description, defaultChecked, readOnly, onChange, id={uuid} name={name} type={'checkbox'} - onChange={(e) => onChange && onChange(e)} + onChange={e => onChange && onChange(e)} defaultChecked={defaultChecked} disabled={readOnly} /> diff --git a/resources/scripts/components/elements/TitledGreyBox.tsx b/resources/scripts/components/elements/TitledGreyBox.tsx index e135549b1..9ccabcc71 100644 --- a/resources/scripts/components/elements/TitledGreyBox.tsx +++ b/resources/scripts/components/elements/TitledGreyBox.tsx @@ -1,4 +1,5 @@ -import React, { memo } from 'react'; +import { memo } from 'react'; +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/elements/Translate.tsx b/resources/scripts/components/elements/Translate.tsx index 88dd1eab6..fac25eca8 100644 --- a/resources/scripts/components/elements/Translate.tsx +++ b/resources/scripts/components/elements/Translate.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { Trans, TransProps, useTranslation } from 'react-i18next'; +import type { TransProps } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; -type Props = Omit; +type Props = Omit, 't'>; -export default ({ ns, children, ...props }: Props) => { +function Translate({ ns, children, ...props }: Props) { const { t } = useTranslation(ns); return ( @@ -11,4 +11,6 @@ export default ({ ns, children, ...props }: Props) => { {children} ); -}; +} + +export default Translate; diff --git a/resources/scripts/components/elements/activity/ActivityLogEntry.tsx b/resources/scripts/components/elements/activity/ActivityLogEntry.tsx index 183b2ab3f..8f9794a81 100644 --- a/resources/scripts/components/elements/activity/ActivityLogEntry.tsx +++ b/resources/scripts/components/elements/activity/ActivityLogEntry.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { Link } from 'react-router-dom'; import Tooltip from '@/components/elements/tooltip/Tooltip'; import Translate from '@/components/elements/Translate'; diff --git a/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx b/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx index f1460ac7c..aebe3e9f8 100644 --- a/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx +++ b/resources/scripts/components/elements/activity/ActivityLogMetaButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { ClipboardListIcon } from '@heroicons/react/outline'; import { Dialog } from '@/components/elements/dialog'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/elements/alert/Alert.tsx b/resources/scripts/components/elements/alert/Alert.tsx index e99d6d57e..2252e71a0 100644 --- a/resources/scripts/components/elements/alert/Alert.tsx +++ b/resources/scripts/components/elements/alert/Alert.tsx @@ -1,5 +1,5 @@ import { ExclamationIcon, ShieldExclamationIcon } from '@heroicons/react/outline'; -import React from 'react'; +import * as React from 'react'; import classNames from 'classnames'; interface AlertProps { @@ -17,7 +17,7 @@ export default ({ type, className, children }: AlertProps) => { ['border-red-500 bg-red-500/25']: type === 'danger', ['border-yellow-500 bg-yellow-500/25']: type === 'warning', }, - className + className, )} > {type === 'danger' ? ( diff --git a/resources/scripts/components/elements/button/Button.tsx b/resources/scripts/components/elements/button/Button.tsx index 61ee96311..6b140ddf1 100644 --- a/resources/scripts/components/elements/button/Button.tsx +++ b/resources/scripts/components/elements/button/Button.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; import classNames from 'classnames'; import { ButtonProps, Options } from '@/components/elements/button/types'; import styles from './style.module.css'; @@ -17,14 +17,14 @@ const Button = forwardRef( [styles.small]: size === Options.Size.Small, [styles.large]: size === Options.Size.Large, }, - className + className, )} {...rest} > {children} ); - } + }, ); const TextButton = forwardRef(({ className, ...props }, ref) => ( diff --git a/resources/scripts/components/elements/button/types.ts b/resources/scripts/components/elements/button/types.ts index 273ac3b67..e86ec5e26 100644 --- a/resources/scripts/components/elements/button/types.ts +++ b/resources/scripts/components/elements/button/types.ts @@ -1,15 +1,15 @@ -enum Shape { +export enum Shape { Default, IconSquare, } -enum Size { +export enum Size { Default, Small, Large, } -enum Variant { +export enum Variant { Primary, Secondary, } diff --git a/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx b/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx index bedfe4ee5..3f2cd7217 100644 --- a/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx +++ b/resources/scripts/components/elements/dialog/ConfirmationDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { Dialog, RenderDialogProps } from './'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/elements/dialog/Dialog.tsx b/resources/scripts/components/elements/dialog/Dialog.tsx index bb35fe8fb..7d347063e 100644 --- a/resources/scripts/components/elements/dialog/Dialog.tsx +++ b/resources/scripts/components/elements/dialog/Dialog.tsx @@ -1,4 +1,5 @@ -import React, { useRef, useState } from 'react'; +import { useRef, useState } from 'react'; +import * as React from 'react'; import { Dialog as HDialog } from '@headlessui/react'; import { Button } from '@/components/elements/button/index'; import { XIcon } from '@heroicons/react/solid'; diff --git a/resources/scripts/components/elements/dialog/DialogFooter.tsx b/resources/scripts/components/elements/dialog/DialogFooter.tsx index 37209fce8..de0a32f91 100644 --- a/resources/scripts/components/elements/dialog/DialogFooter.tsx +++ b/resources/scripts/components/elements/dialog/DialogFooter.tsx @@ -1,4 +1,5 @@ -import React, { useContext } from 'react'; +import { useContext } from 'react'; +import * as React from 'react'; import { DialogContext } from './'; import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect'; @@ -7,7 +8,7 @@ export default ({ children }: { children: React.ReactNode }) => { useDeepCompareEffect(() => { setFooter( -
{children}
+
{children}
, ); }, [children]); diff --git a/resources/scripts/components/elements/dialog/DialogIcon.tsx b/resources/scripts/components/elements/dialog/DialogIcon.tsx index e2ed6b207..d075e4781 100644 --- a/resources/scripts/components/elements/dialog/DialogIcon.tsx +++ b/resources/scripts/components/elements/dialog/DialogIcon.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { CheckIcon, ExclamationIcon, InformationCircleIcon, ShieldExclamationIcon } from '@heroicons/react/outline'; import classNames from 'classnames'; import { DialogContext, DialogIconProps, styles } from './'; @@ -19,7 +19,7 @@ export default ({ type, position, className }: DialogIconProps) => { setIcon(
-
+
, ); }, [type, className]); diff --git a/resources/scripts/components/elements/dialog/context.ts b/resources/scripts/components/elements/dialog/context.ts index b9a0f0980..dd270e149 100644 --- a/resources/scripts/components/elements/dialog/context.ts +++ b/resources/scripts/components/elements/dialog/context.ts @@ -1,13 +1,13 @@ -import React from 'react'; +import { createContext } from 'react'; import { DialogContextType, DialogWrapperContextType } from './types'; -export const DialogContext = React.createContext({ +export const DialogContext = createContext({ setIcon: () => null, setFooter: () => null, setIconPosition: () => null, }); -export const DialogWrapperContext = React.createContext({ +export const DialogWrapperContext = createContext({ props: {}, setProps: () => null, close: () => null, diff --git a/resources/scripts/components/elements/dialog/types.d.ts b/resources/scripts/components/elements/dialog/types.d.ts index 2701b1dda..bb5808949 100644 --- a/resources/scripts/components/elements/dialog/types.d.ts +++ b/resources/scripts/components/elements/dialog/types.d.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { IconPosition } from '@/components/elements/dialog/DialogIcon'; type Callback = ((value: T) => void) | React.Dispatch>; diff --git a/resources/scripts/components/elements/dropdown/Dropdown.tsx b/resources/scripts/components/elements/dropdown/Dropdown.tsx index ef378efe5..df77921df 100644 --- a/resources/scripts/components/elements/dropdown/Dropdown.tsx +++ b/resources/scripts/components/elements/dropdown/Dropdown.tsx @@ -1,4 +1,5 @@ -import React, { ElementType, forwardRef, useMemo } from 'react'; +import { ElementType, forwardRef, useMemo } from 'react'; +import * as React from 'react'; import { Menu, Transition } from '@headlessui/react'; import styles from './style.module.css'; import classNames from 'classnames'; @@ -23,8 +24,8 @@ const Dropdown = forwardRef(({ as, children }, ref) => { const list = React.Children.toArray(children) as unknown as TypedChild[]; return [ - list.filter((child) => child.type === DropdownButton), - list.filter((child) => child.type !== DropdownButton), + list.filter(child => child.type === DropdownButton), + list.filter(child => child.type !== DropdownButton), ]; }, [children]); diff --git a/resources/scripts/components/elements/dropdown/DropdownButton.tsx b/resources/scripts/components/elements/dropdown/DropdownButton.tsx index d026e2995..13e3e38fc 100644 --- a/resources/scripts/components/elements/dropdown/DropdownButton.tsx +++ b/resources/scripts/components/elements/dropdown/DropdownButton.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import styles from '@/components/elements/dropdown/style.module.css'; import { ChevronDownIcon } from '@heroicons/react/solid'; import { Menu } from '@headlessui/react'; -import React from 'react'; +import * as React from 'react'; interface Props { className?: string; diff --git a/resources/scripts/components/elements/dropdown/DropdownItem.tsx b/resources/scripts/components/elements/dropdown/DropdownItem.tsx index 1c012e15a..d9f0cf68f 100644 --- a/resources/scripts/components/elements/dropdown/DropdownItem.tsx +++ b/resources/scripts/components/elements/dropdown/DropdownItem.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import { Menu } from '@headlessui/react'; import styles from './style.module.css'; import classNames from 'classnames'; @@ -26,7 +27,7 @@ const DropdownItem = forwardRef( [styles.danger]: danger, [styles.disabled]: disabled, }, - className + className, )} onClick={onClick} > @@ -36,7 +37,7 @@ const DropdownItem = forwardRef( )} ); - } + }, ); export default DropdownItem; diff --git a/resources/scripts/components/elements/editor/Editor.tsx b/resources/scripts/components/elements/editor/Editor.tsx new file mode 100644 index 000000000..bc395d601 --- /dev/null +++ b/resources/scripts/components/elements/editor/Editor.tsx @@ -0,0 +1,206 @@ +import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'; +import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'; +import { + defaultHighlightStyle, + syntaxHighlighting, + indentOnInput, + bracketMatching, + foldGutter, + foldKeymap, + LanguageDescription, + indentUnit, +} from '@codemirror/language'; +import { languages } from '@codemirror/language-data'; +import { lintKeymap } from '@codemirror/lint'; +import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; +import type { Extension } from '@codemirror/state'; +import { EditorState, Compartment } from '@codemirror/state'; +import { + keymap, + highlightSpecialChars, + drawSelection, + highlightActiveLine, + dropCursor, + rectangularSelection, + crosshairCursor, + lineNumbers, + highlightActiveLineGutter, + EditorView, +} from '@codemirror/view'; +import type { CSSProperties } from 'react'; +import { useEffect, useRef, useState } from 'react'; + +import { ayuMirageHighlightStyle, ayuMirageTheme } from './theme'; + +function findLanguageByFilename(filename: string): LanguageDescription | undefined { + const language = LanguageDescription.matchFilename(languages, filename); + if (language !== null) { + return language; + } + + return undefined; +} + +const defaultExtensions: Extension = [ + // Ayu Mirage + ayuMirageTheme, + syntaxHighlighting(ayuMirageHighlightStyle), + + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + dropCursor(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + crosshairCursor(), + highlightActiveLine(), + highlightSelectionMatches(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + ...completionKeymap, + ...lintKeymap, + indentWithTab, + ]), + EditorState.tabSize.of(4), + indentUnit.of('\t'), +]; + +export interface EditorProps { + // DOM + className?: string; + style?: CSSProperties; + + // CodeMirror Config + extensions?: Extension[]; + language?: LanguageDescription; + + // Options + filename?: string; + initialContent?: string; + + // ? + fetchContent?: (callback: () => Promise) => void; + + // Events + onContentSaved?: () => void; + onLanguageChanged?: (language: LanguageDescription | undefined) => void; +} + +export function Editor(props: EditorProps) { + const ref = useRef(null); + + const [view, setView] = useState(); + + // eslint-disable-next-line react/hook-use-state + const [languageConfig] = useState(new Compartment()); + // eslint-disable-next-line react/hook-use-state + const [keybindings] = useState(new Compartment()); + + const createEditorState = () => + EditorState.create({ + doc: props.initialContent, + extensions: [ + defaultExtensions, + props.extensions === undefined ? [] : props.extensions, + languageConfig.of([]), + keybindings.of([]), + ], + }); + + useEffect(() => { + if (ref.current === null) { + return; + } + + setView( + new EditorView({ + state: createEditorState(), + parent: ref.current, + }), + ); + + return () => { + if (view === undefined) { + return; + } + + view.destroy(); + setView(undefined); + }; + }, [ref]); + + useEffect(() => { + if (view === undefined) { + return; + } + + view.setState(createEditorState()); + }, [props.initialContent]); + + useEffect(() => { + if (view === undefined) { + return; + } + + const language = props.language ?? findLanguageByFilename(props.filename ?? ''); + if (language === undefined) { + return; + } + + void language.load().then(language => { + view.dispatch({ + effects: languageConfig.reconfigure(language), + }); + }); + + if (props.onLanguageChanged !== undefined) { + props.onLanguageChanged(language); + } + }, [view, props.filename, props.language]); + + useEffect(() => { + if (props.fetchContent === undefined) { + return; + } + + if (!view) { + props.fetchContent(async () => { + throw new Error('no editor session has been configured'); + }); + return; + } + + const { onContentSaved } = props; + if (onContentSaved !== undefined) { + view.dispatch({ + effects: keybindings.reconfigure( + keymap.of([ + { + key: 'Mod-s', + run() { + onContentSaved(); + return true; + }, + }, + ]), + ), + }); + } + + props.fetchContent(async () => view.state.doc.toJSON().join('\n')); + }, [view, props.fetchContent, props.onContentSaved]); + + return
; +} diff --git a/resources/scripts/components/elements/editor/index.ts b/resources/scripts/components/elements/editor/index.ts new file mode 100644 index 000000000..5ccde0efc --- /dev/null +++ b/resources/scripts/components/elements/editor/index.ts @@ -0,0 +1 @@ +export { Editor } from './Editor'; diff --git a/resources/scripts/components/elements/editor/theme.ts b/resources/scripts/components/elements/editor/theme.ts new file mode 100644 index 000000000..7e212fca1 --- /dev/null +++ b/resources/scripts/components/elements/editor/theme.ts @@ -0,0 +1,148 @@ +import { HighlightStyle } from '@codemirror/language'; +import type { Extension } from '@codemirror/state'; +import { EditorView } from '@codemirror/view'; +import { tags as t } from '@lezer/highlight'; + +const highlightBackground = 'transparent'; +const background = '#1F2430'; +const selection = '#34455A'; +const cursor = '#FFCC66'; + +export const ayuMirageTheme: Extension = EditorView.theme( + { + '&': { + color: '#CBCCC6', + backgroundColor: background, + }, + + '.cm-content': { + caretColor: cursor, + }, + + '&.cm-focused .cm-cursor': { borderLeftColor: cursor }, + '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection': { + backgroundColor: selection, + }, + + '.cm-panels': { backgroundColor: '#232834', color: '#CBCCC6' }, + '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, + '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, + + '.cm-searchMatch': { + backgroundColor: '#72a1ff59', + outline: '1px solid #457dff', + }, + '.cm-searchMatch.cm-searchMatch-selected': { + backgroundColor: '#6199ff2f', + }, + + '.cm-activeLine': { backgroundColor: highlightBackground }, + '.cm-selectionMatch': { backgroundColor: '#aafe661a' }, + + '.cm-matchingBracket, .cm-nonmatchingBracket': { + backgroundColor: '#bad0f847', + outline: '1px solid #515a6b', + }, + + '.cm-gutters': { + backgroundColor: 'transparent', + color: '#FF3333', + border: 'none', + }, + + '.cm-gutterElement': { + color: 'rgba(61, 66, 77, 99)', + }, + + '.cm-activeLineGutter': { + backgroundColor: highlightBackground, + }, + + '.cm-foldPlaceholder': { + backgroundColor: 'transparent', + border: 'none', + color: '#ddd', + }, + + '.cm-tooltip': { + border: '1px solid #181a1f', + backgroundColor: '#232834', + }, + '.cm-tooltip-autocomplete': { + '& > ul > li[aria-selected]': { + backgroundColor: highlightBackground, + color: '#CBCCC6', + }, + }, + }, + { dark: true }, +); + +export const ayuMirageHighlightStyle = HighlightStyle.define([ + { + tag: t.keyword, + color: '#FFA759', + }, + { + tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], + color: '#5CCFE6', + }, + { + tag: [t.function(t.variableName), t.labelName], + color: '#CBCCC6', + }, + { + tag: [t.color, t.constant(t.name), t.standard(t.name)], + color: '#F29E74', + }, + { + tag: [t.definition(t.name), t.separator], + color: '#CBCCC6B3', + }, + { + tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], + color: '#FFCC66', + }, + { + tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], + color: '#5CCFE6', + }, + { + tag: [t.meta, t.comment], + color: '#5C6773', + }, + { + tag: t.strong, + fontWeight: 'bold', + }, + { + tag: t.emphasis, + fontStyle: 'italic', + }, + { + tag: t.strikethrough, + textDecoration: 'line-through', + }, + { + tag: t.link, + color: '#FF3333', + textDecoration: 'underline', + }, + { + tag: t.heading, + fontWeight: 'bold', + color: '#BAE67E', + }, + { + tag: [t.atom, t.bool, t.special(t.variableName)], + color: '#5CCFE6', + }, + { + tag: [t.processingInstruction, t.string, t.inserted], + color: '#BAE67E', + }, + { + tag: t.invalid, + color: '#FF3333', + }, +]); diff --git a/resources/scripts/components/elements/inputs/Checkbox.tsx b/resources/scripts/components/elements/inputs/Checkbox.tsx index 82233b15f..e9c043b61 100644 --- a/resources/scripts/components/elements/inputs/Checkbox.tsx +++ b/resources/scripts/components/elements/inputs/Checkbox.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import classNames from 'classnames'; import styles from './styles.module.css'; diff --git a/resources/scripts/components/elements/inputs/InputField.tsx b/resources/scripts/components/elements/inputs/InputField.tsx index c3dfc3ccd..ac92e17f1 100644 --- a/resources/scripts/components/elements/inputs/InputField.tsx +++ b/resources/scripts/components/elements/inputs/InputField.tsx @@ -1,4 +1,5 @@ -import React, { forwardRef } from 'react'; +import { forwardRef } from 'react'; +import * as React from 'react'; import classNames from 'classnames'; import styles from './styles.module.css'; @@ -19,7 +20,7 @@ const Component = forwardRef(({ className, va 'form-input', styles.text_input, { [styles.loose]: variant === Variant.Loose }, - className + className, )} {...props} /> diff --git a/resources/scripts/components/elements/inputs/index.ts b/resources/scripts/components/elements/inputs/index.ts index b961b0764..3b7f15d77 100644 --- a/resources/scripts/components/elements/inputs/index.ts +++ b/resources/scripts/components/elements/inputs/index.ts @@ -6,7 +6,7 @@ const Input = Object.assign( { Text: InputField, Checkbox: Checkbox, - } + }, ); export { Input }; diff --git a/resources/scripts/components/elements/table/PaginationFooter.tsx b/resources/scripts/components/elements/table/PaginationFooter.tsx index 0484065ed..d578ae4d9 100644 --- a/resources/scripts/components/elements/table/PaginationFooter.tsx +++ b/resources/scripts/components/elements/table/PaginationFooter.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { PaginationDataSet } from '@/api/http'; import classNames from 'classnames'; import { Button } from '@/components/elements/button/index'; @@ -53,7 +52,7 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => { - {pages.previous.reverse().map((value) => ( + {pages.previous.reverse().map(value => ( {value} @@ -61,7 +60,7 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => { - {pages.next.map((value) => ( + {pages.next.map(value => ( {value} diff --git a/resources/scripts/components/elements/tooltip/Tooltip.tsx b/resources/scripts/components/elements/tooltip/Tooltip.tsx index ce488dc4f..df24d38cc 100644 --- a/resources/scripts/components/elements/tooltip/Tooltip.tsx +++ b/resources/scripts/components/elements/tooltip/Tooltip.tsx @@ -1,4 +1,5 @@ -import React, { cloneElement, useRef, useState } from 'react'; +import { cloneElement, useRef, useState } from 'react'; +import * as React from 'react'; import { arrow, autoUpdate, @@ -104,7 +105,7 @@ export default ({ children, ...props }: Props) => { ref={arrowEl} style={{ transform: `translate(${Math.round(ax || 0)}px, ${Math.round( - ay || 0 + ay || 0, )}px) rotate(45deg)`, }} className={classNames('absolute bg-gray-900 w-3 h-3', side)} diff --git a/resources/scripts/components/elements/transitions/FadeTransition.tsx b/resources/scripts/components/elements/transitions/FadeTransition.tsx index e4287cc18..be0dd6425 100644 --- a/resources/scripts/components/elements/transitions/FadeTransition.tsx +++ b/resources/scripts/components/elements/transitions/FadeTransition.tsx @@ -1,16 +1,18 @@ -import React from 'react'; import { Transition } from '@headlessui/react'; +import type { ElementType, ReactNode } from 'react'; type Duration = `duration-${number}`; interface Props { - as?: React.ElementType; + as?: ElementType; duration?: Duration | [Duration, Duration]; + appear?: boolean; + unmount?: boolean; show: boolean; - children: React.ReactNode; + children: ReactNode; } -export default ({ children, duration, ...props }: Props) => { +function FadeTransition({ children, duration, ...props }: Props) { const [enterDuration, exitDuration] = Array.isArray(duration) ? duration : !duration @@ -30,4 +32,6 @@ export default ({ children, duration, ...props }: Props) => { {children} ); -}; +} + +export default FadeTransition; diff --git a/resources/scripts/components/history.ts b/resources/scripts/components/history.ts deleted file mode 100644 index 5f339d963..000000000 --- a/resources/scripts/components/history.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createBrowserHistory } from 'history'; - -export const history = createBrowserHistory({ basename: '/' }); diff --git a/resources/scripts/components/server/ConflictStateRenderer.tsx b/resources/scripts/components/server/ConflictStateRenderer.tsx index 95e70bbaa..8bba31ca7 100644 --- a/resources/scripts/components/server/ConflictStateRenderer.tsx +++ b/resources/scripts/components/server/ConflictStateRenderer.tsx @@ -1,15 +1,14 @@ -import React from 'react'; -import { ServerContext } from '@/state/server'; -import ScreenBlock from '@/components/elements/ScreenBlock'; import ServerInstallSvg from '@/assets/images/server_installing.svg'; import ServerErrorSvg from '@/assets/images/server_error.svg'; import ServerRestoreSvg from '@/assets/images/server_restore.svg'; +import ScreenBlock from '@/components/elements/ScreenBlock'; +import { ServerContext } from '@/state/server'; export default () => { - const status = ServerContext.useStoreState((state) => state.server.data?.status || null); - const isTransferring = ServerContext.useStoreState((state) => state.server.data?.isTransferring || false); + const status = ServerContext.useStoreState(state => state.server.data?.status || null); + const isTransferring = ServerContext.useStoreState(state => state.server.data?.isTransferring || false); const isNodeUnderMaintenance = ServerContext.useStoreState( - (state) => state.server.data?.isNodeUnderMaintenance || false + state => state.server.data?.isNodeUnderMaintenance || false, ); return status === 'installing' || status === 'install_failed' ? ( @@ -36,7 +35,7 @@ export default () => { image={ServerRestoreSvg} message={ isTransferring - ? 'Your server is being transfered to a new node, please check back later.' + ? 'Your server is being transferred to a new node, please check back later.' : 'Your server is currently being restored from a backup, please check back in a few minutes.' } /> diff --git a/resources/scripts/components/server/InstallListener.tsx b/resources/scripts/components/server/InstallListener.tsx index b4e3a0e3e..fbf377a2c 100644 --- a/resources/scripts/components/server/InstallListener.tsx +++ b/resources/scripts/components/server/InstallListener.tsx @@ -5,26 +5,26 @@ import { mutate } from 'swr'; import { getDirectorySwrKey } from '@/plugins/useFileManagerSwr'; const InstallListener = () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); useWebsocketEvent(SocketEvent.BACKUP_RESTORE_COMPLETED, () => { mutate(getDirectorySwrKey(uuid, '/'), undefined); - setServerFromState((s) => ({ ...s, status: null })); + setServerFromState(s => ({ ...s, status: null })); }); // Listen for the installation completion event and then fire off a request to fetch the updated // server information. This allows the server to automatically become available to the user if they // just sit on the page. useWebsocketEvent(SocketEvent.INSTALL_COMPLETED, () => { - getServer(uuid).catch((error) => console.error(error)); + getServer(uuid).catch(error => console.error(error)); }); // When we see the install started event immediately update the state to indicate such so that the // screens automatically update. useWebsocketEvent(SocketEvent.INSTALL_STARTED, () => { - setServerFromState((s) => ({ ...s, status: 'installing' })); + setServerFromState(s => ({ ...s, status: 'installing' })); }); return null; diff --git a/resources/scripts/components/server/ServerActivityLogContainer.tsx b/resources/scripts/components/server/ServerActivityLogContainer.tsx index 531945486..a617d1d2d 100644 --- a/resources/scripts/components/server/ServerActivityLogContainer.tsx +++ b/resources/scripts/components/server/ServerActivityLogContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useActivityLogs } from '@/api/server/activity'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; import { useFlashKey } from '@/plugins/useFlash'; @@ -24,7 +24,7 @@ export default () => { }); useEffect(() => { - setFilters((value) => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); + setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } })); }, [hash]); useEffect(() => { @@ -39,7 +39,7 @@ export default () => { setFilters((value) => ({ ...value, filters: {} }))} + onClick={() => setFilters(value => ({ ...value, filters: {} }))} > Clear Filters @@ -51,7 +51,7 @@ export default () => {

No activity logs available for this server.

) : (
- {data?.items.map((activity) => ( + {data?.items.map(activity => ( @@ -61,7 +61,7 @@ export default () => { {data && ( setFilters((value) => ({ ...value, page }))} + onPageSelect={page => setFilters(value => ({ ...value, page }))} /> )} diff --git a/resources/scripts/components/server/TransferListener.tsx b/resources/scripts/components/server/TransferListener.tsx index 4d9421745..10544f547 100644 --- a/resources/scripts/components/server/TransferListener.tsx +++ b/resources/scripts/components/server/TransferListener.tsx @@ -3,19 +3,19 @@ import { ServerContext } from '@/state/server'; import { SocketEvent } from '@/components/server/events'; const TransferListener = () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); // Listen for the transfer status event, so we can update the state of the server. useWebsocketEvent(SocketEvent.TRANSFER_STATUS, (status: string) => { if (status === 'pending' || status === 'processing') { - setServerFromState((s) => ({ ...s, isTransferring: true })); + setServerFromState(s => ({ ...s, isTransferring: true })); return; } if (status === 'failed') { - setServerFromState((s) => ({ ...s, isTransferring: false })); + setServerFromState(s => ({ ...s, isTransferring: false })); return; } @@ -24,7 +24,7 @@ const TransferListener = () => { } // Refresh the server's information as it's node and allocations were just updated. - getServer(uuid).catch((error) => console.error(error)); + getServer(uuid).catch(error => console.error(error)); }); return null; diff --git a/resources/scripts/components/server/UptimeDuration.tsx b/resources/scripts/components/server/UptimeDuration.tsx index 6623de0b8..e87700a61 100644 --- a/resources/scripts/components/server/UptimeDuration.tsx +++ b/resources/scripts/components/server/UptimeDuration.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - export default ({ uptime }: { uptime: number }) => { const days = Math.floor(uptime / (24 * 60 * 60)); const hours = Math.floor((Math.floor(uptime) / 60 / 60) % 24); diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 2772013df..954acda2c 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -1,29 +1,32 @@ -import React, { useEffect, useState } from 'react'; -import { Websocket } from '@/plugins/Websocket'; -import { ServerContext } from '@/state/server'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + import getWebsocketToken from '@/api/server/getWebsocketToken'; import ContentContainer from '@/components/elements/ContentContainer'; -import { CSSTransition } from 'react-transition-group'; import Spinner from '@/components/elements/Spinner'; -import tw from 'twin.macro'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; +import { Websocket } from '@/plugins/Websocket'; +import { ServerContext } from '@/state/server'; const reconnectErrors = ['jwt: exp claim is invalid', 'jwt: created too far in past (denylist)']; -export default () => { +function WebsocketHandler() { let updatingToken = false; const [error, setError] = useState<'connecting' | string>(''); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); - const setServerStatus = ServerContext.useStoreActions((actions) => actions.status.setServerStatus); - const { setInstance, setConnectionState } = ServerContext.useStoreActions((actions) => actions.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); + const setServerStatus = ServerContext.useStoreActions(actions => actions.status.setServerStatus); + const { setInstance, setConnectionState } = ServerContext.useStoreActions(actions => actions.socket); const updateToken = (uuid: string, socket: Websocket) => { - if (updatingToken) return; + if (updatingToken) { + return; + } updatingToken = true; getWebsocketToken(uuid) - .then((data) => socket.setToken(data.token, true)) - .catch((error) => console.error(error)) + .then(data => socket.setToken(data.token, true)) + .catch(error => console.error(error)) .then(() => { updatingToken = false; }); @@ -38,9 +41,9 @@ export default () => { setError('connecting'); setConnectionState(false); }); - socket.on('status', (status) => setServerStatus(status)); + socket.on('status', status => setServerStatus(status)); - socket.on('daemon error', (message) => { + socket.on('daemon error', message => { console.warn('Got error message from daemon socket:', message); }); @@ -50,11 +53,11 @@ export default () => { setConnectionState(false); console.warn('JWT validation error from wings:', error); - if (reconnectErrors.find((v) => error.toLowerCase().indexOf(v) >= 0)) { + if (reconnectErrors.find(v => error.toLowerCase().indexOf(v) >= 0)) { updateToken(uuid, socket); } else { setError( - 'There was an error validating the credentials provided for the websocket. Please refresh the page.' + 'There was an error validating the credentials provided for the websocket. Please refresh the page.', ); } }); @@ -74,14 +77,14 @@ export default () => { }); getWebsocketToken(uuid) - .then((data) => { + .then(data => { // Connect and then set the authentication token. socket.setToken(data.token).connect(data.socket); // Once that is done, set the instance. setInstance(socket); }) - .catch((error) => console.error(error)); + .catch(error => console.error(error)); }; useEffect(() => { @@ -105,7 +108,7 @@ export default () => { }, [uuid]); return error ? ( - +
{error === 'connecting' ? ( @@ -120,6 +123,8 @@ export default () => { )}
-
+ ) : null; -}; +} + +export default WebsocketHandler; diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 0134142bd..a980920c0 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import Spinner from '@/components/elements/Spinner'; import useFlash from '@/plugins/useFlash'; import Can from '@/components/elements/Can'; @@ -16,7 +16,7 @@ const BackupContainer = () => { const { clearFlashes, clearAndAddHttpError } = useFlash(); const { data: backups, error, isValidating } = getServerBackups(); - const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups); + const backupLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.backups); useEffect(() => { if (!error) { diff --git a/resources/scripts/components/server/backups/BackupContextMenu.tsx b/resources/scripts/components/server/backups/BackupContextMenu.tsx index 43c8b3575..4e371f2bd 100644 --- a/resources/scripts/components/server/backups/BackupContextMenu.tsx +++ b/resources/scripts/components/server/backups/BackupContextMenu.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { faBoxOpen, faCloudDownloadAlt, @@ -28,8 +28,8 @@ interface Props { } export default ({ backup }: Props) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const [modal, setModal] = useState(''); const [loading, setLoading] = useState(false); const [truncate, setTruncate] = useState(false); @@ -40,11 +40,11 @@ export default ({ backup }: Props) => { setLoading(true); clearFlashes('backups'); getBackupDownloadUrl(uuid, backup.uuid) - .then((url) => { + .then(url => { // @ts-expect-error this is valid window.location = url; }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'backups', error }); }) @@ -55,17 +55,18 @@ export default ({ backup }: Props) => { setLoading(true); clearFlashes('backups'); deleteBackup(uuid, backup.uuid) - .then(() => - mutate( - (data) => ({ - ...data, - items: data.items.filter((b) => b.uuid !== backup.uuid), - backupCount: data.backupCount - 1, - }), - false - ) + .then( + async () => + await mutate( + data => ({ + ...data!, + items: data!.items.filter(b => b.uuid !== backup.uuid), + backupCount: data!.backupCount - 1, + }), + false, + ), ) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'backups', error }); setLoading(false); @@ -78,12 +79,12 @@ export default ({ backup }: Props) => { clearFlashes('backups'); restoreServerBackup(uuid, backup.uuid, truncate) .then(() => - setServerFromState((s) => ({ + setServerFromState(s => ({ ...s, status: 'restoring_backup', - })) + })), ) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'backups', error }); }) @@ -97,23 +98,24 @@ export default ({ backup }: Props) => { } http.post(`/api/client/servers/${uuid}/backups/${backup.uuid}/lock`) - .then(() => - mutate( - (data) => ({ - ...data, - items: data.items.map((b) => - b.uuid !== backup.uuid - ? b - : { - ...b, - isLocked: !b.isLocked, - } - ), - }), - false - ) + .then( + async () => + await mutate( + data => ({ + ...data!, + items: data!.items.map(b => + b.uuid !== backup.uuid + ? b + : { + ...b, + isLocked: !b.isLocked, + }, + ), + }), + false, + ), ) - .catch((error) => alert(httpErrorToHuman(error))) + .catch(error => alert(httpErrorToHuman(error))) .then(() => setModal('')); }; @@ -146,7 +148,7 @@ export default ({ backup }: Props) => { id={'restore_truncate'} value={'true'} checked={truncate} - onChange={() => setTruncate((s) => !s)} + onChange={() => setTruncate(s => !s)} /> Delete all files before restoring backup. @@ -164,7 +166,7 @@ export default ({ backup }: Props) => { {backup.isSuccessful ? ( ( + renderToggle={onClick => (
); -}; +} + +export default StatBlock; diff --git a/resources/scripts/components/server/console/StatGraphs.tsx b/resources/scripts/components/server/console/StatGraphs.tsx index 9af405ba6..a7ab28f45 100644 --- a/resources/scripts/components/server/console/StatGraphs.tsx +++ b/resources/scripts/components/server/console/StatGraphs.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { ServerContext } from '@/state/server'; import { SocketEvent } from '@/components/server/events'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; @@ -12,8 +12,8 @@ import ChartBlock from '@/components/server/console/ChartBlock'; import Tooltip from '@/components/elements/tooltip/Tooltip'; export default () => { - const status = ServerContext.useStoreState((state) => state.status.value); - const limits = ServerContext.useStoreState((state) => state.server.data!.limits); + const status = ServerContext.useStoreState(state => state.status.value); + const limits = ServerContext.useStoreState(state => state.server.data!.limits); const previous = useRef>({ tx: -1, rx: -1 }); const cpu = useChartTickLabel('CPU', limits.cpu, '%', 2); diff --git a/resources/scripts/components/server/console/chart.ts b/resources/scripts/components/server/console/chart.ts index 65f919aa1..d495cbbfc 100644 --- a/resources/scripts/components/server/console/chart.ts +++ b/resources/scripts/components/server/console/chart.ts @@ -71,13 +71,14 @@ const options: ChartOptions<'line'> = { }; function getOptions(opts?: DeepPartial> | undefined): ChartOptions<'line'> { - return deepmerge(options, opts || {}); + // @ts-expect-error go away + return deepmerge(options, opts ?? {}); } type ChartDatasetCallback = (value: ChartDataset<'line'>, index: number) => ChartDataset<'line'>; function getEmptyData(label: string, sets = 1, callback?: ChartDatasetCallback | undefined): ChartData<'line'> { - const next = callback || ((value) => value); + const next = callback || (value => value); return { labels: Array(20) @@ -94,8 +95,8 @@ function getEmptyData(label: string, sets = 1, callback?: ChartDatasetCallback | borderColor: theme('colors.cyan.400'), backgroundColor: hexToRgba(theme('colors.cyan.700'), 0.5), }, - index - ) + index, + ), ), }; } @@ -110,30 +111,31 @@ interface UseChartOptions { function useChart(label: string, opts?: UseChartOptions) { const options = getOptions( - typeof opts?.options === 'number' ? { scales: { y: { min: 0, suggestedMax: opts.options } } } : opts?.options + typeof opts?.options === 'number' ? { scales: { y: { min: 0, suggestedMax: opts.options } } } : opts?.options, ); const [data, setData] = useState(getEmptyData(label, opts?.sets || 1, opts?.callback)); const push = (items: number | null | (number | null)[]) => - setData((state) => + setData(state => merge(state, { datasets: (Array.isArray(items) ? items : [items]).map((item, index) => ({ ...state.datasets[index], - data: state.datasets[index].data - .slice(1) - .concat(typeof item === 'number' ? Number(item.toFixed(2)) : item), + data: + state.datasets[index]?.data + ?.slice(1) + ?.concat(typeof item === 'number' ? Number(item.toFixed(2)) : item) ?? [], })), - }) + }), ); const clear = () => - setData((state) => + setData(state => merge(state, { - datasets: state.datasets.map((value) => ({ + datasets: state.datasets.map(value => ({ ...value, data: Array(20).fill(-5), })), - }) + }), ); return { props: { data, options }, push, clear }; diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index 24c19e040..a3c85e288 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import Modal from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; @@ -23,17 +23,17 @@ const schema = object().shape({ .max(48, 'Database name must not exceed 48 characters.') .matches( /^[\w\-.]{3,48}$/, - 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.' + 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.', ), connectionsFrom: string().matches(/^[\w\-/.%:]+$/, 'A valid host address must be provided.'), }); export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { addError, clearFlashes } = useFlash(); const [visible, setVisible] = useState(false); - const appendDatabase = ServerContext.useStoreActions((actions) => actions.databases.appendDatabase); + const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('database:create'); @@ -41,11 +41,11 @@ export default () => { databaseName: values.databaseName, connectionsFrom: values.connectionsFrom || '%', }) - .then((database) => { + .then(database => { appendDatabase(database); setVisible(false); }) - .catch((error) => { + .catch(error => { addError({ key: 'database:create', message: httpErrorToHuman(error) }); setSubmitting(false); }); diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 9d2c05a73..32c9f6659 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faDatabase, faEye, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import Modal from '@/components/elements/Modal'; @@ -26,13 +26,13 @@ interface Props { } export default ({ database, className }: Props) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { addError, clearFlashes } = useFlash(); const [visible, setVisible] = useState(false); const [connectionVisible, setConnectionVisible] = useState(false); - const appendDatabase = ServerContext.useStoreActions((actions) => actions.databases.appendDatabase); - const removeDatabase = ServerContext.useStoreActions((actions) => actions.databases.removeDatabase); + const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase); + const removeDatabase = ServerContext.useStoreActions(actions => actions.databases.removeDatabase); const jdbcConnectionString = `jdbc:mysql://${database.username}${ database.password ? `:${encodeURIComponent(database.password)}` : '' @@ -44,14 +44,14 @@ export default ({ database, className }: Props) => { .oneOf([database.name.split('_', 2)[1], database.name], 'The database name must be provided.'), }); - const submit = (values: { confirm: string }, { setSubmitting }: FormikHelpers<{ confirm: string }>) => { + const submit = (_: { confirm: string }, { setSubmitting }: FormikHelpers<{ confirm: string }>) => { clearFlashes(); deleteServerDatabase(uuid, database.id) .then(() => { setVisible(false); setTimeout(() => removeDatabase(database.id), 150); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); addError({ key: 'database:delete', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 16b690014..6d74bb965 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import getServerDatabases from '@/api/server/databases/getServerDatabases'; import { ServerContext } from '@/state/server'; import { httpErrorToHuman } from '@/api/http'; @@ -9,27 +9,27 @@ import CreateDatabaseButton from '@/components/server/databases/CreateDatabaseBu import Can from '@/components/elements/Can'; import useFlash from '@/plugins/useFlash'; import tw from 'twin.macro'; -import Fade from '@/components/elements/Fade'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; import { useDeepMemoize } from '@/plugins/useDeepMemoize'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const databaseLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.databases); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const databaseLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.databases); const { addError, clearFlashes } = useFlash(); const [loading, setLoading] = useState(true); - const databases = useDeepMemoize(ServerContext.useStoreState((state) => state.databases.data)); - const setDatabases = ServerContext.useStoreActions((state) => state.databases.setDatabases); + const databases = useDeepMemoize(ServerContext.useStoreState(state => state.databases.data)); + const setDatabases = ServerContext.useStoreActions(state => state.databases.setDatabases); useEffect(() => { setLoading(!databases.length); clearFlashes('databases'); getServerDatabases(uuid) - .then((databases) => setDatabases(databases)) - .catch((error) => { + .then(databases => setDatabases(databases)) + .catch(error => { console.error(error); addError({ key: 'databases', message: httpErrorToHuman(error) }); }) @@ -42,7 +42,7 @@ export default () => { {!databases.length && loading ? ( ) : ( - + <> {databases.length > 0 ? ( databases.map((database, index) => ( @@ -73,7 +73,7 @@ export default () => {
- + )} ); diff --git a/resources/scripts/components/server/databases/RotatePasswordButton.tsx b/resources/scripts/components/server/databases/RotatePasswordButton.tsx index 0c687d776..1159dd87b 100644 --- a/resources/scripts/components/server/databases/RotatePasswordButton.tsx +++ b/resources/scripts/components/server/databases/RotatePasswordButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import rotateDatabasePassword from '@/api/server/databases/rotateDatabasePassword'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -11,7 +11,7 @@ import tw from 'twin.macro'; export default ({ databaseId, onUpdate }: { databaseId: string; onUpdate: (database: ServerDatabase) => void }) => { const [loading, setLoading] = useState(false); const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); - const server = ServerContext.useStoreState((state) => state.server.data!); + const server = ServerContext.useStoreState(state => state.server.data!); if (!databaseId) { return null; @@ -22,8 +22,8 @@ export default ({ databaseId, onUpdate }: { databaseId: string; onUpdate: (datab clearFlashes(); rotateDatabasePassword(server.uuid, databaseId) - .then((database) => onUpdate(database)) - .catch((error) => { + .then(database => onUpdate(database)) + .catch(error => { console.error(error); addFlash({ type: 'error', diff --git a/resources/scripts/components/server/features/Features.tsx b/resources/scripts/components/server/features/Features.tsx index 571c7afc6..720b18853 100644 --- a/resources/scripts/components/server/features/Features.tsx +++ b/resources/scripts/components/server/features/Features.tsx @@ -1,21 +1,23 @@ -import React, { useMemo } from 'react'; +import type { ComponentType } from 'react'; +import { Suspense, useMemo } from 'react'; + import features from './index'; import { getObjectKeys } from '@/lib/objects'; -type ListItems = [string, React.ComponentType][]; +type ListItems = [string, ComponentType][]; export default ({ enabled }: { enabled: string[] }) => { const mapped: ListItems = useMemo(() => { return getObjectKeys(features) - .filter((key) => enabled.map((v) => v.toLowerCase()).includes(key.toLowerCase())) - .reduce((arr, key) => [...arr, [key, features[key]]], [] as ListItems); + .filter(key => enabled.map(v => v.toLowerCase()).includes(key.toLowerCase())) + .reduce((arr, key) => [...arr, [key, features[key]]] as ListItems, [] as ListItems); }, [enabled]); return ( - + {mapped.map(([key, Component]) => ( ))} - + ); }; diff --git a/resources/scripts/components/server/features/GSLTokenModalFeature.tsx b/resources/scripts/components/server/features/GSLTokenModalFeature.tsx index f56ae7cca..ec2eead4a 100644 --- a/resources/scripts/components/server/features/GSLTokenModalFeature.tsx +++ b/resources/scripts/components/server/features/GSLTokenModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -18,10 +18,10 @@ const GSLTokenModalFeature = () => { const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const status = ServerContext.useStoreState((state) => state.status.value); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -29,7 +29,7 @@ const GSLTokenModalFeature = () => { const errors = ['(gsl token expired)', '(account not found)']; const listener = (line: string) => { - if (errors.some((p) => line.toLowerCase().includes(p))) { + if (errors.some(p => line.toLowerCase().includes(p))) { setVisible(true); } }; @@ -54,7 +54,7 @@ const GSLTokenModalFeature = () => { setLoading(false); setVisible(false); }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'feature:gslToken', error }); }) diff --git a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx index 91902f0aa..80d8cc287 100644 --- a/resources/scripts/components/server/features/JavaVersionModalFeature.tsx +++ b/resources/scripts/components/server/features/JavaVersionModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -26,25 +26,25 @@ const JavaVersionModalFeature = () => { const [loading, setLoading] = useState(false); const [selectedVersion, setSelectedVersion] = useState(''); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const status = ServerContext.useStoreState((state) => state.status.value); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { instance } = ServerContext.useStoreState((state) => state.socket); + const { instance } = ServerContext.useStoreState(state => state.socket); - const { data, isValidating, mutate } = getServerStartup(uuid, null, { revalidateOnMount: false }); + const { data, isValidating, mutate } = getServerStartup(uuid, undefined, { revalidateOnMount: false }); useEffect(() => { if (!visible) return; - mutate().then((value) => { + mutate().then(value => { setSelectedVersion(Object.values(value?.dockerImages || [])[0] || ''); }); }, [visible]); - useWebsocketEvent(SocketEvent.CONSOLE_OUTPUT, (data) => { + useWebsocketEvent(SocketEvent.CONSOLE_OUTPUT, data => { if (status === 'running') return; - if (MATCH_ERRORS.some((p) => data.toLowerCase().includes(p.toLowerCase()))) { + if (MATCH_ERRORS.some(p => data.toLowerCase().includes(p.toLowerCase()))) { setVisible(true); } }); @@ -60,7 +60,7 @@ const JavaVersionModalFeature = () => { } setVisible(false); }) - .catch((error) => clearAndAddHttpError({ key: 'feature:javaVersion', error })) + .catch(error => clearAndAddHttpError({ key: 'feature:javaVersion', error })) .then(() => setLoading(false)); }; @@ -86,11 +86,11 @@ const JavaVersionModalFeature = () => {
- setSelectedVersion(e.target.value)}> {!data ? ( diff --git a/resources/scripts/components/server/features/PIDLimitModalFeature.tsx b/resources/scripts/components/server/features/PIDLimitModalFeature.tsx index f4061bc6d..054f6aaf2 100644 --- a/resources/scripts/components/server/features/PIDLimitModalFeature.tsx +++ b/resources/scripts/components/server/features/PIDLimitModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -14,10 +14,10 @@ const PIDLimitModalFeature = () => { const [visible, setVisible] = useState(false); const [loading] = useState(false); - const status = ServerContext.useStoreState((state) => state.status.value); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); - const isAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -32,7 +32,7 @@ const PIDLimitModalFeature = () => { ]; const listener = (line: string) => { - if (errors.some((p) => line.toLowerCase().includes(p))) { + if (errors.some(p => line.toLowerCase().includes(p))) { setVisible(true); } }; diff --git a/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx b/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx index 81acba579..36aa74563 100644 --- a/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx +++ b/resources/scripts/components/server/features/SteamDiskSpaceFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -12,10 +12,10 @@ const SteamDiskSpaceFeature = () => { const [visible, setVisible] = useState(false); const [loading] = useState(false); - const status = ServerContext.useStoreState((state) => state.status.value); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); - const isAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -23,7 +23,7 @@ const SteamDiskSpaceFeature = () => { const errors = ['steamcmd needs 250mb of free disk space to update', '0x202 after update job']; const listener = (line: string) => { - if (errors.some((p) => line.toLowerCase().includes(p))) { + if (errors.some(p => line.toLowerCase().includes(p))) { setVisible(true); } }; diff --git a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx index fd7afe1d4..6fa3ae6ce 100644 --- a/resources/scripts/components/server/features/eula/EulaModalFeature.tsx +++ b/resources/scripts/components/server/features/eula/EulaModalFeature.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import Modal from '@/components/elements/Modal'; import tw from 'twin.macro'; @@ -12,10 +12,10 @@ const EulaModalFeature = () => { const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const status = ServerContext.useStoreState((state) => state.status.value); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const status = ServerContext.useStoreState(state => state.status.value); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); useEffect(() => { if (!connected || !instance || status === 'running') return; @@ -46,7 +46,7 @@ const EulaModalFeature = () => { setLoading(false); setVisible(false); }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'feature:eula', error }); }) @@ -72,7 +72,7 @@ const EulaModalFeature = () => { target={'_blank'} css={tw`text-primary-300 underline transition-colors duration-150 hover:text-primary-400`} rel={'noreferrer noopener'} - href='https://account.mojang.com/documents/minecraft_eula' + href="https://account.mojang.com/documents/minecraft_eula" > Minecraft® EULA diff --git a/resources/scripts/components/server/files/ChmodFileModal.tsx b/resources/scripts/components/server/files/ChmodFileModal.tsx index 27a474d93..7a0e2fa42 100644 --- a/resources/scripts/components/server/files/ChmodFileModal.tsx +++ b/resources/scripts/components/server/files/ChmodFileModal.tsx @@ -1,6 +1,5 @@ import { fileBitsToString } from '@/helpers'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; -import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; @@ -22,29 +21,29 @@ interface File { type OwnProps = RequiredModalProps & { files: File[] }; const ChmodFileModal = ({ files, ...props }: OwnProps) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const directory = ServerContext.useStoreState((state) => state.files.directory); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); + const directory = ServerContext.useStoreState(state => state.files.directory); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); - const submit = ({ mode }: FormikValues, { setSubmitting }: FormikHelpers) => { + const submit = async ({ mode }: FormikValues, { setSubmitting }: FormikHelpers) => { clearFlashes('files'); - mutate( - (data) => - data.map((f) => - f.name === files[0].file ? { ...f, mode: fileBitsToString(mode, !f.isFile), modeBits: mode } : f + await mutate( + data => + data!.map(f => + f.name === files[0]?.file ? { ...f, mode: fileBitsToString(mode, !f.isFile), modeBits: mode } : f, ), - false + false, ); - const data = files.map((f) => ({ file: f.file, mode: mode })); + const data = files.map(f => ({ file: f.file, mode: mode })); chmodFiles(uuid, directory, data) .then((): Promise => (files.length > 0 ? mutate() : Promise.resolve())) .then(() => setSelectedFiles([])) - .catch((error) => { + .catch(error => { mutate(); setSubmitting(false); clearAndAddHttpError({ key: 'files', error }); @@ -53,7 +52,7 @@ const ChmodFileModal = ({ files, ...props }: OwnProps) => { }; return ( - 1 ? '' : files[0].mode || '' }}> + 1 ? '' : files[0]?.mode ?? '' }}> {({ isSubmitting }) => (
diff --git a/resources/scripts/components/server/files/FileDropdownMenu.tsx b/resources/scripts/components/server/files/FileDropdownMenu.tsx index fd892951d..41c446919 100644 --- a/resources/scripts/components/server/files/FileDropdownMenu.tsx +++ b/resources/scripts/components/server/files/FileDropdownMenu.tsx @@ -1,4 +1,5 @@ -import React, { memo, useRef, useState } from 'react'; +import { memo, useRef, useState } from 'react'; +import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBoxOpen, @@ -14,7 +15,7 @@ import { } from '@fortawesome/free-solid-svg-icons'; import RenameFileModal from '@/components/server/files/RenameFileModal'; import { ServerContext } from '@/state/server'; -import { join } from 'path'; +import { join } from 'pathe'; import deleteFiles from '@/api/server/files/deleteFiles'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import copyFile from '@/api/server/files/copyFile'; @@ -25,7 +26,7 @@ import tw from 'twin.macro'; import { FileObject } from '@/api/server/files/loadDirectory'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import DropdownMenu from '@/components/elements/DropdownMenu'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import useEventListener from '@/plugins/useEventListener'; import compressFiles from '@/api/server/files/compressFiles'; import decompressFiles from '@/api/server/files/decompressFiles'; @@ -37,7 +38,7 @@ type ModalType = 'rename' | 'move' | 'chmod'; const StyledRow = styled.div<{ $danger?: boolean }>` ${tw`p-2 flex items-center rounded`}; - ${(props) => + ${props => props.$danger ? tw`hover:bg-red-100 hover:text-red-700` : tw`hover:bg-neutral-100 hover:text-neutral-700`}; `; @@ -60,10 +61,10 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { const [modal, setModal] = useState(null); const [showConfirmation, setShowConfirmation] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearAndAddHttpError, clearFlashes } = useFlash(); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const directory = ServerContext.useStoreState(state => state.files.directory); useEventListener(`pterodactyl:files:ctx:${file.key}`, (e: CustomEvent) => { if (onClickRef.current) { @@ -71,14 +72,14 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { } }); - const doDeletion = () => { + const doDeletion = async () => { clearFlashes('files'); // For UI speed, immediately remove the file from the listing before calling the deletion function. // If the delete actually fails, we'll fetch the current directory contents again automatically. - mutate((files) => files.filter((f) => f.key !== file.key), false); + await mutate(files => files!.filter(f => f.key !== file.key), false); - deleteFiles(uuid, directory, [file.name]).catch((error) => { + deleteFiles(uuid, directory, [file.name]).catch(error => { mutate(); clearAndAddHttpError({ key: 'files', error }); }); @@ -90,7 +91,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { copyFile(uuid, join(directory, file.name)) .then(() => mutate()) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -99,11 +100,11 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { clearFlashes('files'); getFileDownloadUrl(uuid, join(directory, file.name)) - .then((url) => { + .then(url => { // @ts-expect-error this is valid window.location = url; }) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -113,7 +114,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { compressFiles(uuid, directory, [file.name]) .then(() => mutate()) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -123,7 +124,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { decompressFiles(uuid, directory, file.name) .then(() => mutate()) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; @@ -141,7 +142,7 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => { ( + renderToggle={onClick => (
{modal ? ( diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 28ba91d25..623c130c9 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -1,25 +1,27 @@ -import React, { useEffect, useState } from 'react'; -import getFileContents from '@/api/server/files/getFileContents'; +import type { LanguageDescription } from '@codemirror/language'; +import { dirname } from 'pathe'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + import { httpErrorToHuman } from '@/api/http'; -import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import getFileContents from '@/api/server/files/getFileContents'; import saveFileContents from '@/api/server/files/saveFileContents'; -import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; -import { useHistory, useLocation, useParams } from 'react-router'; -import FileNameModal from '@/components/server/files/FileNameModal'; -import Can from '@/components/elements/Can'; import FlashMessageRender from '@/components/FlashMessageRender'; +import Button from '@/components/elements/Button'; +import Can from '@/components/elements/Can'; +import Select from '@/components/elements/Select'; import PageContentBlock from '@/components/elements/PageContentBlock'; import { ServerError } from '@/components/elements/ScreenBlock'; -import tw from 'twin.macro'; -import Button from '@/components/elements/Button'; -import Select from '@/components/elements/Select'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; +import FileNameModal from '@/components/server/files/FileNameModal'; +import ErrorBoundary from '@/components/elements/ErrorBoundary'; +import { Editor } from '@/components/elements/editor'; import modes from '@/modes'; import useFlash from '@/plugins/useFlash'; import { ServerContext } from '@/state/server'; -import ErrorBoundary from '@/components/elements/ErrorBoundary'; import { encodePathSegments, hashToPath } from '@/helpers'; -import { dirname } from 'path'; -import CodemirrorEditor from '@/components/elements/CodemirrorEditor'; export default () => { const [error, setError] = useState(''); @@ -28,13 +30,14 @@ export default () => { const [content, setContent] = useState(''); const [modalVisible, setModalVisible] = useState(false); const [mode, setMode] = useState('text/plain'); + const [language, setLanguage] = useState(); - const history = useHistory(); const { hash } = useLocation(); + const navigate = useNavigate(); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const setDirectory = ServerContext.useStoreActions((actions) => actions.files.setDirectory); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory); const { addError, clearFlashes } = useFlash(); let fetchFileContent: null | (() => Promise) = null; @@ -48,7 +51,7 @@ export default () => { setDirectory(dirname(path)); getFileContents(uuid, path) .then(setContent) - .catch((error) => { + .catch(error => { console.error(error); setError(httpErrorToHuman(error)); }) @@ -63,16 +66,16 @@ export default () => { setLoading(true); clearFlashes('files:view'); fetchFileContent() - .then((content) => saveFileContents(uuid, name || hashToPath(hash), content)) + .then(content => saveFileContents(uuid, name || hashToPath(hash), content)) .then(() => { if (name) { - history.push(`/server/${id}/files/edit#/${encodePathSegments(name)}`); + navigate(`/server/${id}/files/edit#/${encodePathSegments(name)}`); return; } return Promise.resolve(); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ message: httpErrorToHuman(error), key: 'files:view' }); }) @@ -80,17 +83,20 @@ export default () => { }; if (error) { - return history.goBack()} />; + // TODO: onBack + return ; } return ( +
+ {hash.replace(/^#/, '').endsWith('.pteroignore') && (

@@ -102,22 +108,24 @@ export default () => {

)} + setModalVisible(false)} - onFileNamed={(name) => { + onFileNamed={name => { setModalVisible(false); save(name); }} /> +
- { + language={language} + onLanguageChanged={setLanguage} + fetchContent={value => { fetchFileContent = value; }} onContentSaved={() => { @@ -129,16 +137,18 @@ export default () => { }} />
+
- setMode(e.currentTarget.value)}> + {modes.map(mode => ( ))}
+ {action === 'edit' ? (
); diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 3bebd2062..d699cd783 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -1,6 +1,8 @@ -import React, { useEffect } from 'react'; +import type { ChangeEvent } from 'react'; +import { useEffect } from 'react'; +import tw from 'twin.macro'; + import { httpErrorToHuman } from '@/api/http'; -import { CSSTransition } from 'react-transition-group'; import Spinner from '@/components/elements/Spinner'; import FileObjectRow from '@/components/server/files/FileObjectRow'; import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; @@ -9,7 +11,6 @@ import NewDirectoryButton from '@/components/server/files/NewDirectoryButton'; import { NavLink, useLocation } from 'react-router-dom'; import Can from '@/components/elements/Can'; import { ServerError } from '@/components/elements/ScreenBlock'; -import tw from 'twin.macro'; import { Button } from '@/components/elements/button/index'; import { ServerContext } from '@/state/server'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; @@ -22,24 +23,25 @@ import ErrorBoundary from '@/components/elements/ErrorBoundary'; import { FileActionCheckbox } from '@/components/server/files/SelectFileCheckbox'; import { hashToPath } from '@/helpers'; import style from './style.module.css'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; const sortFiles = (files: FileObject[]): FileObject[] => { const sortedFiles: FileObject[] = files .sort((a, b) => a.name.localeCompare(b.name)) .sort((a, b) => (a.isFile === b.isFile ? 0 : a.isFile ? 1 : -1)); - return sortedFiles.filter((file, index) => index === 0 || file.name !== sortedFiles[index - 1].name); + return sortedFiles.filter((file, index) => index === 0 || file.name !== sortedFiles[index - 1]?.name); }; export default () => { - const id = ServerContext.useStoreState((state) => state.server.data!.id); + const id = ServerContext.useStoreState(state => state.server.data!.id); const { hash } = useLocation(); const { data: files, error, mutate } = useFileManagerSwr(); - const directory = ServerContext.useStoreState((state) => state.files.directory); - const clearFlashes = useStoreActions((actions) => actions.flashes.clearFlashes); - const setDirectory = ServerContext.useStoreActions((actions) => actions.files.setDirectory); + const directory = ServerContext.useStoreState(state => state.files.directory); + const clearFlashes = useStoreActions(actions => actions.flashes.clearFlashes); + const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); - const selectedFilesLength = ServerContext.useStoreState((state) => state.files.selectedFiles.length); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); + const selectedFilesLength = ServerContext.useStoreState(state => state.files.selectedFiles.length); useEffect(() => { clearFlashes('files'); @@ -48,11 +50,11 @@ export default () => { }, [hash]); useEffect(() => { - mutate(); + void mutate(); }, [directory]); - const onSelectAllClick = (e: React.ChangeEvent) => { - setSelectedFiles(e.currentTarget.checked ? files?.map((file) => file.name) || [] : []); + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedFiles(e.currentTarget.checked ? files?.map(file => file.name) || [] : []); }; if (error) { @@ -92,7 +94,7 @@ export default () => { {!files.length ? (

This directory seems to be empty.

) : ( - +
{files.length > 250 && (
@@ -102,12 +104,12 @@ export default () => {

)} - {sortFiles(files.slice(0, 250)).map((file) => ( + {sortFiles(files.slice(0, 250)).map(file => ( ))}
-
+ )} )} diff --git a/resources/scripts/components/server/files/FileManagerStatus.tsx b/resources/scripts/components/server/files/FileManagerStatus.tsx index 620e067c1..2e1534b41 100644 --- a/resources/scripts/components/server/files/FileManagerStatus.tsx +++ b/resources/scripts/components/server/files/FileManagerStatus.tsx @@ -1,12 +1,13 @@ -import React, { useContext, useEffect } from 'react'; -import { ServerContext } from '@/state/server'; import { CloudUploadIcon, XIcon } from '@heroicons/react/solid'; -import asDialog from '@/hoc/asDialog'; -import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; +import { useSignal } from '@preact/signals-react'; +import { useContext, useEffect } from 'react'; + import { Button } from '@/components/elements/button/index'; +import { Dialog, DialogWrapperContext } from '@/components/elements/dialog'; import Tooltip from '@/components/elements/tooltip/Tooltip'; import Code from '@/components/elements/Code'; -import { useSignal } from '@preact/signals-react'; +import asDialog from '@/hoc/asDialog'; +import { ServerContext } from '@/state/server'; const svgProps = { cx: 16, @@ -32,10 +33,10 @@ const Spinner = ({ progress, className }: { progress: number; className?: string const FileUploadList = () => { const { close } = useContext(DialogWrapperContext); - const removeFileUpload = ServerContext.useStoreActions((actions) => actions.files.removeFileUpload); - const clearFileUploads = ServerContext.useStoreActions((actions) => actions.files.clearFileUploads); - const uploads = ServerContext.useStoreState((state) => - Object.entries(state.files.uploads).sort(([a], [b]) => a.localeCompare(b)) + const removeFileUpload = ServerContext.useStoreActions(actions => actions.files.removeFileUpload); + const clearFileUploads = ServerContext.useStoreActions(actions => actions.files.clearFileUploads); + const uploads = ServerContext.useStoreState(state => + Object.entries(state.files.uploads).sort(([a], [b]) => a.localeCompare(b)), ); return ( @@ -74,8 +75,8 @@ const FileUploadListDialog = asDialog({ export default () => { const open = useSignal(false); - const count = ServerContext.useStoreState((state) => Object.keys(state.files.uploads).length); - const progress = ServerContext.useStoreState((state) => ({ + const count = ServerContext.useStoreState(state => Object.keys(state.files.uploads).length); + const progress = ServerContext.useStoreState(state => ({ uploaded: Object.values(state.files.uploads).reduce((count, file) => count + file.loaded, 0), total: Object.values(state.files.uploads).reduce((count, file) => count + file.total, 0), })); diff --git a/resources/scripts/components/server/files/FileNameModal.tsx b/resources/scripts/components/server/files/FileNameModal.tsx index 04781babe..adb6a7555 100644 --- a/resources/scripts/components/server/files/FileNameModal.tsx +++ b/resources/scripts/components/server/files/FileNameModal.tsx @@ -1,10 +1,9 @@ -import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; import Field from '@/components/elements/Field'; import { ServerContext } from '@/state/server'; -import { join } from 'path'; +import { join } from 'pathe'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; @@ -17,7 +16,7 @@ interface Values { } export default ({ onFileNamed, onDismissed, ...props }: Props) => { - const directory = ServerContext.useStoreState((state) => state.files.directory); + const directory = ServerContext.useStoreState(state => state.files.directory); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { onFileNamed(join(directory, values.fileName)); diff --git a/resources/scripts/components/server/files/FileObjectRow.tsx b/resources/scripts/components/server/files/FileObjectRow.tsx index 8032dab1e..d945c2a3a 100644 --- a/resources/scripts/components/server/files/FileObjectRow.tsx +++ b/resources/scripts/components/server/files/FileObjectRow.tsx @@ -1,69 +1,74 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faFileAlt, faFileArchive, faFileImport, faFolder } from '@fortawesome/free-solid-svg-icons'; -import { encodePathSegments } from '@/helpers'; import { differenceInHours, format, formatDistanceToNow } from 'date-fns'; -import React, { memo } from 'react'; +import type { ReactNode } from 'react'; +import { memo } from 'react'; +import isEqual from 'react-fast-compare'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; +import { join } from 'pathe'; + +import { encodePathSegments } from '@/helpers'; import { FileObject } from '@/api/server/files/loadDirectory'; import FileDropdownMenu from '@/components/server/files/FileDropdownMenu'; -import { ServerContext } from '@/state/server'; -import { NavLink, useRouteMatch } from 'react-router-dom'; -import tw from 'twin.macro'; -import isEqual from 'react-fast-compare'; import SelectFileCheckbox from '@/components/server/files/SelectFileCheckbox'; -import { usePermissions } from '@/plugins/usePermissions'; -import { join } from 'path'; import { bytesToString } from '@/lib/formatters'; +import { usePermissions } from '@/plugins/usePermissions'; +import { ServerContext } from '@/state/server'; import styles from './style.module.css'; -const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => { +function Clickable({ file, children }: { file: FileObject; children: ReactNode }) { const [canReadContents] = usePermissions(['file.read-content']); - const directory = ServerContext.useStoreState((state) => state.files.directory); - - const match = useRouteMatch(); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const directory = ServerContext.useStoreState(state => state.files.directory); return !canReadContents || (file.isFile && !file.isEditable()) ? (
{children}
) : ( {children} ); -}, isEqual); +} -const FileObjectRow = ({ file }: { file: FileObject }) => ( -
{ - e.preventDefault(); - window.dispatchEvent(new CustomEvent(`pterodactyl:files:ctx:${file.key}`, { detail: e.clientX })); - }} - > - - -
- {file.isFile ? ( - - ) : ( - - )} -
-
{file.name}
- {file.isFile && } - -
- -
-); +const MemoizedClickable = memo(Clickable, isEqual); + +function FileObjectRow({ file }: { file: FileObject }) { + return ( +
{ + e.preventDefault(); + window.dispatchEvent(new CustomEvent(`pterodactyl:files:ctx:${file.key}`, { detail: e.clientX })); + }} + > + + +
+ {file.isFile ? ( + + ) : ( + + )} +
+
{file.name}
+ {file.isFile && } + +
+ +
+ ); +} export default memo(FileObjectRow, (prevProps, nextProps) => { /* eslint-disable @typescript-eslint/no-unused-vars */ diff --git a/resources/scripts/components/server/files/MassActionsBar.tsx b/resources/scripts/components/server/files/MassActionsBar.tsx index 44230f214..ff7c8eb6f 100644 --- a/resources/scripts/components/server/files/MassActionsBar.tsx +++ b/resources/scripts/components/server/files/MassActionsBar.tsx @@ -1,19 +1,19 @@ -import React, { useEffect, useState } from 'react'; -import tw from 'twin.macro'; -import { Button } from '@/components/elements/button/index'; -import Fade from '@/components/elements/Fade'; +import { useEffect, useState } from 'react'; + +import compressFiles from '@/api/server/files/compressFiles'; +import deleteFiles from '@/api/server/files/deleteFiles'; +import { Button } from '@/components/elements/button'; +import { Dialog } from '@/components/elements/dialog'; +import Portal from '@/components/elements/Portal'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import RenameFileModal from '@/components/server/files/RenameFileModal'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import useFlash from '@/plugins/useFlash'; -import compressFiles from '@/api/server/files/compressFiles'; import { ServerContext } from '@/state/server'; -import deleteFiles from '@/api/server/files/deleteFiles'; -import RenameFileModal from '@/components/server/files/RenameFileModal'; -import Portal from '@/components/elements/Portal'; -import { Dialog } from '@/components/elements/dialog'; +import FadeTransition from '@/components/elements/transitions/FadeTransition'; const MassActionsBar = () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearFlashes, clearAndAddHttpError } = useFlash(); @@ -21,10 +21,10 @@ const MassActionsBar = () => { const [loadingMessage, setLoadingMessage] = useState(''); const [showConfirm, setShowConfirm] = useState(false); const [showMove, setShowMove] = useState(false); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const directory = ServerContext.useStoreState(state => state.files.directory); - const selectedFiles = ServerContext.useStoreState((state) => state.files.selectedFiles); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); + const selectedFiles = ServerContext.useStoreState(state => state.files.selectedFiles); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); useEffect(() => { if (!loading) setLoadingMessage(''); @@ -38,7 +38,7 @@ const MassActionsBar = () => { compressFiles(uuid, directory, selectedFiles) .then(() => mutate()) .then(() => setSelectedFiles([])) - .catch((error) => clearAndAddHttpError({ key: 'files', error })) + .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setLoading(false)); }; @@ -49,12 +49,12 @@ const MassActionsBar = () => { setLoadingMessage('Deleting files...'); deleteFiles(uuid, directory, selectedFiles) - .then(() => { - mutate((files) => files.filter((f) => selectedFiles.indexOf(f.name) < 0), false); + .then(async () => { + await mutate(files => files!.filter(f => selectedFiles.indexOf(f.name) < 0), false); setSelectedFiles([]); }) - .catch((error) => { - mutate(); + .catch(async error => { + await mutate(); clearAndAddHttpError({ key: 'files', error }); }) .then(() => setLoading(false)); @@ -62,7 +62,7 @@ const MassActionsBar = () => { return ( <> -
+
{loadingMessage} @@ -73,12 +73,12 @@ const MassActionsBar = () => { onClose={() => setShowConfirm(false)} onConfirmed={onClickConfirmDeletion} > -

+

Are you sure you want to delete  - {selectedFiles.length} files? This is a + {selectedFiles.length} files? This is a permanent action and the files cannot be recovered.

- {selectedFiles.slice(0, 15).map((file) => ( + {selectedFiles.slice(0, 15).map(file => (
  • {file}
  • ))} {selectedFiles.length > 15 &&
  • and {selectedFiles.length - 15} others
  • } @@ -93,16 +93,16 @@ const MassActionsBar = () => { /> )} -
    - 0} unmountOnExit> -
    +
    + 0} appear unmount> +
    setShowConfirm(true)}> Delete
    - +
    diff --git a/resources/scripts/components/server/files/NewDirectoryButton.tsx b/resources/scripts/components/server/files/NewDirectoryButton.tsx index ff74357cf..f92c6f602 100644 --- a/resources/scripts/components/server/files/NewDirectoryButton.tsx +++ b/resources/scripts/components/server/files/NewDirectoryButton.tsx @@ -1,8 +1,8 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; -import { join } from 'path'; +import { join } from 'pathe'; import { object, string } from 'yup'; import createDirectory from '@/api/server/files/createDirectory'; import tw from 'twin.macro'; @@ -42,8 +42,8 @@ const generateDirectoryData = (name: string): FileObject => ({ const NewDirectoryDialog = asDialog({ title: 'Create Directory', })(() => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const directory = ServerContext.useStoreState(state => state.files.directory); const { mutate } = useFileManagerSwr(); const { close } = useContext(DialogWrapperContext); @@ -57,9 +57,9 @@ const NewDirectoryDialog = asDialog({ const submit = ({ directoryName }: Values, { setSubmitting }: FormikHelpers) => { createDirectory(uuid, directory, directoryName) - .then(() => mutate((data) => [...data, generateDirectoryData(directoryName)], false)) + .then(() => mutate(data => [...data!, generateDirectoryData(directoryName)], false)) .then(() => close()) - .catch((error) => { + .catch(error => { setSubmitting(false); clearAndAddHttpError(error); }); diff --git a/resources/scripts/components/server/files/RenameFileModal.tsx b/resources/scripts/components/server/files/RenameFileModal.tsx index 4f675338e..7b72811e9 100644 --- a/resources/scripts/components/server/files/RenameFileModal.tsx +++ b/resources/scripts/components/server/files/RenameFileModal.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import Field from '@/components/elements/Field'; -import { join } from 'path'; +import { join } from 'pathe'; import renameFiles from '@/api/server/files/renameFiles'; import { ServerContext } from '@/state/server'; import tw from 'twin.macro'; @@ -17,11 +16,11 @@ interface FormikValues { type OwnProps = RequiredModalProps & { files: string[]; useMoveTerminology?: boolean }; const RenameFileModal = ({ files, useMoveTerminology, ...props }: OwnProps) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const directory = ServerContext.useStoreState((state) => state.files.directory); - const setSelectedFiles = ServerContext.useStoreActions((actions) => actions.files.setSelectedFiles); + const directory = ServerContext.useStoreState(state => state.files.directory); + const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles); const submit = ({ name }: FormikValues, { setSubmitting }: FormikHelpers) => { clearFlashes('files'); @@ -30,24 +29,24 @@ const RenameFileModal = ({ files, useMoveTerminology, ...props }: OwnProps) => { if (files.length === 1) { if (!useMoveTerminology && len === 1) { // Rename the file within this directory. - mutate((data) => data.map((f) => (f.name === files[0] ? { ...f, name } : f)), false); + mutate(data => data!.map(f => (f.name === files[0] ? { ...f, name } : f)), false); } else if (useMoveTerminology || len > 1) { // Remove the file from this directory since they moved it elsewhere. - mutate((data) => data.filter((f) => f.name !== files[0]), false); + mutate(data => data!.filter(f => f.name !== files[0]), false); } } let data; if (useMoveTerminology && files.length > 1) { - data = files.map((f) => ({ from: f, to: join(name, f) })); + data = files.map(f => ({ from: f, to: join(name, f) })); } else { - data = files.map((f) => ({ from: f, to: name })); + data = files.map(f => ({ from: f, to: name })); } renameFiles(uuid, directory, data) .then((): Promise => (files.length > 0 ? mutate() : Promise.resolve())) .then(() => setSelectedFiles([])) - .catch((error) => { + .catch(error => { mutate(); setSubmitting(false); clearAndAddHttpError({ key: 'files', error }); diff --git a/resources/scripts/components/server/files/SelectFileCheckbox.tsx b/resources/scripts/components/server/files/SelectFileCheckbox.tsx index 1708fd413..cc9807c2d 100644 --- a/resources/scripts/components/server/files/SelectFileCheckbox.tsx +++ b/resources/scripts/components/server/files/SelectFileCheckbox.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import * as React from 'react'; import tw from 'twin.macro'; import { ServerContext } from '@/state/server'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import Input from '@/components/elements/Input'; export const FileActionCheckbox = styled(Input)` @@ -15,9 +15,9 @@ export const FileActionCheckbox = styled(Input)` `; export default ({ name }: { name: string }) => { - const isChecked = ServerContext.useStoreState((state) => state.files.selectedFiles.indexOf(name) >= 0); - const appendSelectedFile = ServerContext.useStoreActions((actions) => actions.files.appendSelectedFile); - const removeSelectedFile = ServerContext.useStoreActions((actions) => actions.files.removeSelectedFile); + const isChecked = ServerContext.useStoreState(state => state.files.selectedFiles.indexOf(name) >= 0); + const appendSelectedFile = ServerContext.useStoreActions(actions => actions.files.appendSelectedFile); + const removeSelectedFile = ServerContext.useStoreActions(actions => actions.files.removeSelectedFile); return (
    - + { + onChange={e => { if (!e.currentTarget.files) return; onFileSubmission(e.currentTarget.files); diff --git a/resources/scripts/components/server/network/AllocationRow.tsx b/resources/scripts/components/server/network/AllocationRow.tsx index e68bc3359..079c4d350 100644 --- a/resources/scripts/components/server/network/AllocationRow.tsx +++ b/resources/scripts/components/server/network/AllocationRow.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; import isEqual from 'react-fast-compare'; import tw from 'twin.macro'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -9,7 +9,7 @@ import Can from '@/components/elements/Can'; import { Button } from '@/components/elements/button/index'; import GreyRowBox from '@/components/elements/GreyRowBox'; import { Allocation } from '@/api/server/getServer'; -import styled from 'styled-components/macro'; +import styled from 'styled-components'; import { debounce } from 'debounce'; import setServerAllocationNotes from '@/api/server/network/setServerAllocationNotes'; import { useFlashKey } from '@/plugins/useFlash'; @@ -32,11 +32,11 @@ interface Props { const AllocationRow = ({ allocation }: Props) => { const [loading, setLoading] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network'); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = getServerAllocations(); const onNotesChanged = useCallback((id: number, notes: string) => { - mutate((data) => data?.map((a) => (a.id === id ? { ...a, notes } : a)), false); + mutate(data => data?.map(a => (a.id === id ? { ...a, notes } : a)), false); }, []); const setAllocationNotes = debounce((notes: string) => { @@ -45,15 +45,15 @@ const AllocationRow = ({ allocation }: Props) => { setServerAllocationNotes(uuid, allocation.id, notes) .then(() => onNotesChanged(allocation.id, notes)) - .catch((error) => clearAndAddHttpError(error)) + .catch(error => clearAndAddHttpError(error)) .then(() => setLoading(false)); }, 750); const setPrimaryAllocation = () => { clearFlashes(); - mutate((data) => data?.map((a) => ({ ...a, isDefault: a.id === allocation.id })), false); + mutate(data => data?.map(a => ({ ...a, isDefault: a.id === allocation.id })), false); - setPrimaryServerAllocation(uuid, allocation.id).catch((error) => { + setPrimaryServerAllocation(uuid, allocation.id).catch(error => { clearAndAddHttpError(error); mutate(); }); @@ -90,7 +90,7 @@ const AllocationRow = ({ allocation }: Props) => { className={'bg-neutral-800 hover:border-neutral-600 border-transparent'} placeholder={'Notes'} defaultValue={allocation.notes || undefined} - onChange={(e) => setAllocationNotes(e.currentTarget.value)} + onChange={e => setAllocationNotes(e.currentTarget.value)} />
    diff --git a/resources/scripts/components/server/network/DeleteAllocationButton.tsx b/resources/scripts/components/server/network/DeleteAllocationButton.tsx index c6804484e..5dd5b7c65 100644 --- a/resources/scripts/components/server/network/DeleteAllocationButton.tsx +++ b/resources/scripts/components/server/network/DeleteAllocationButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import tw from 'twin.macro'; import Icon from '@/components/elements/Icon'; @@ -16,8 +16,8 @@ interface Props { const DeleteAllocationButton = ({ allocation }: Props) => { const [confirm, setConfirm] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const { mutate } = getServerAllocations(); const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network'); @@ -25,10 +25,10 @@ const DeleteAllocationButton = ({ allocation }: Props) => { const deleteAllocation = () => { clearFlashes(); - mutate((data) => data?.filter((a) => a.id !== allocation), false); - setServerFromState((s) => ({ ...s, allocations: s.allocations.filter((a) => a.id !== allocation) })); + mutate(data => data?.filter(a => a.id !== allocation), false); + setServerFromState(s => ({ ...s, allocations: s.allocations.filter(a => a.id !== allocation) })); - deleteServerAllocation(uuid, allocation).catch((error) => { + deleteServerAllocation(uuid, allocation).catch(error => { clearAndAddHttpError(error); mutate(); }); diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index 182beaab8..5f4acce24 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import Spinner from '@/components/elements/Spinner'; import { useFlashKey } from '@/plugins/useFlash'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; @@ -15,10 +15,10 @@ import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect'; const NetworkContainer = () => { const [loading, setLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const allocationLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.allocations); - const allocations = ServerContext.useStoreState((state) => state.server.data!.allocations, isEqual); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const allocationLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.allocations); + const allocations = ServerContext.useStoreState(state => state.server.data!.allocations, isEqual); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const { clearFlashes, clearAndAddHttpError } = useFlashKey('server:network'); const { data, error, mutate } = getServerAllocations(); @@ -34,7 +34,7 @@ const NetworkContainer = () => { useDeepCompareEffect(() => { if (!data) return; - setServerFromState((state) => ({ ...state, allocations: data })); + setServerFromState(state => ({ ...state, allocations: data })); }, [data]); const onCreateAllocation = () => { @@ -42,11 +42,11 @@ const NetworkContainer = () => { setLoading(true); createServerAllocation(uuid) - .then((allocation) => { - setServerFromState((s) => ({ ...s, allocations: s.allocations.concat(allocation) })); + .then(allocation => { + setServerFromState(s => ({ ...s, allocations: s.allocations.concat(allocation) })); return mutate(data?.concat(allocation), false); }) - .catch((error) => clearAndAddHttpError(error)) + .catch(error => clearAndAddHttpError(error)) .then(() => setLoading(false)); }; @@ -56,7 +56,7 @@ const NetworkContainer = () => { ) : ( <> - {data.map((allocation) => ( + {data.map(allocation => ( ))} {allocationLimit > 0 && ( diff --git a/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx b/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx index 1710ad0a8..bdd4b4bfe 100644 --- a/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx +++ b/resources/scripts/components/server/schedules/DeleteScheduleButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import deleteSchedule from '@/api/server/schedules/deleteSchedule'; import { ServerContext } from '@/state/server'; import { Actions, useStoreActions } from 'easy-peasy'; @@ -16,7 +16,7 @@ interface Props { export default ({ scheduleId, onDeleted }: Props) => { const [visible, setVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const onDelete = () => { @@ -27,7 +27,7 @@ export default ({ scheduleId, onDeleted }: Props) => { setIsLoading(false); onDeleted(); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'schedules', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/server/schedules/EditScheduleModal.tsx b/resources/scripts/components/server/schedules/EditScheduleModal.tsx index 6a89cb34c..8ab853656 100644 --- a/resources/scripts/components/server/schedules/EditScheduleModal.tsx +++ b/resources/scripts/components/server/schedules/EditScheduleModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import Field from '@/components/elements/Field'; import { Form, Formik, FormikHelpers } from 'formik'; @@ -34,8 +34,8 @@ const EditScheduleModal = ({ schedule }: Props) => { const { addError, clearFlashes } = useFlash(); const { dismiss } = useContext(ModalContext); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const [showCheatsheet, setShowCheetsheet] = useState(false); useEffect(() => { @@ -59,12 +59,12 @@ const EditScheduleModal = ({ schedule }: Props) => { onlyWhenOnline: values.onlyWhenOnline, isActive: values.enabled, }) - .then((schedule) => { + .then(schedule => { setSubmitting(false); appendSchedule(schedule); dismiss(); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); @@ -114,7 +114,7 @@ const EditScheduleModal = ({ schedule }: Props) => { description={'Show the cron cheatsheet for some examples.'} label={'Show Cheatsheet'} defaultChecked={showCheatsheet} - onChange={() => setShowCheetsheet((s) => !s)} + onChange={() => setShowCheetsheet(s => !s)} /> {showCheatsheet && (
    diff --git a/resources/scripts/components/server/schedules/NewTaskButton.tsx b/resources/scripts/components/server/schedules/NewTaskButton.tsx index 7b480163f..186b0a2ff 100644 --- a/resources/scripts/components/server/schedules/NewTaskButton.tsx +++ b/resources/scripts/components/server/schedules/NewTaskButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/server/schedules/RunScheduleButton.tsx b/resources/scripts/components/server/schedules/RunScheduleButton.tsx index 750b5440b..7c1b4a69b 100644 --- a/resources/scripts/components/server/schedules/RunScheduleButton.tsx +++ b/resources/scripts/components/server/schedules/RunScheduleButton.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import { useCallback, useState } from 'react'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { Button } from '@/components/elements/button/index'; import triggerScheduleExecution from '@/api/server/schedules/triggerScheduleExecution'; @@ -10,8 +10,8 @@ const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => { const [loading, setLoading] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const onTriggerExecute = useCallback(() => { clearFlashes('schedule'); @@ -21,7 +21,7 @@ const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => { setLoading(false); appendSchedule({ ...schedule, isProcessing: true }); }) - .catch((error) => { + .catch(error => { console.error(error); clearAndAddHttpError({ error, key: 'schedules' }); }) diff --git a/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx b/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx index 0eeed1527..5a2edd4c2 100644 --- a/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx +++ b/resources/scripts/components/server/schedules/ScheduleCheatsheetCards.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import tw from 'twin.macro'; export default () => { diff --git a/resources/scripts/components/server/schedules/ScheduleContainer.tsx b/resources/scripts/components/server/schedules/ScheduleContainer.tsx index 1722314fb..f644653e0 100644 --- a/resources/scripts/components/server/schedules/ScheduleContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleContainer.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import getServerSchedules from '@/api/server/schedules/getServerSchedules'; import { ServerContext } from '@/state/server'; import Spinner from '@/components/elements/Spinner'; -import { useHistory, useRouteMatch } from 'react-router-dom'; import FlashMessageRender from '@/components/FlashMessageRender'; import ScheduleRow from '@/components/server/schedules/ScheduleRow'; import { httpErrorToHuman } from '@/api/http'; @@ -13,24 +12,23 @@ import tw from 'twin.macro'; import GreyRowBox from '@/components/elements/GreyRowBox'; import { Button } from '@/components/elements/button/index'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; +import { Link } from 'react-router-dom'; -export default () => { - const match = useRouteMatch(); - const history = useHistory(); - - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); +function ScheduleContainer() { + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { clearFlashes, addError } = useFlash(); const [loading, setLoading] = useState(true); const [visible, setVisible] = useState(false); - const schedules = ServerContext.useStoreState((state) => state.schedules.data); - const setSchedules = ServerContext.useStoreActions((actions) => actions.schedules.setSchedules); + const schedules = ServerContext.useStoreState(state => state.schedules.data); + const setSchedules = ServerContext.useStoreActions(actions => actions.schedules.setSchedules); useEffect(() => { clearFlashes('schedules'); + getServerSchedules(uuid) - .then((schedules) => setSchedules(schedules)) - .catch((error) => { + .then(schedules => setSchedules(schedules)) + .catch(error => { addError({ message: httpErrorToHuman(error), key: 'schedules' }); console.error(error); }) @@ -49,16 +47,13 @@ export default () => { There are no schedules configured for this server.

    ) : ( - schedules.map((schedule) => ( + schedules.map(schedule => ( + // @ts-expect-error go away { - e.preventDefault(); - history.push(`${match.url}/${schedule.id}`); - }} > @@ -76,4 +71,6 @@ export default () => { )} ); -}; +} + +export default ScheduleContainer; diff --git a/resources/scripts/components/server/schedules/ScheduleCronRow.tsx b/resources/scripts/components/server/schedules/ScheduleCronRow.tsx index 187478f55..282c05038 100644 --- a/resources/scripts/components/server/schedules/ScheduleCronRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleCronRow.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import classNames from 'classnames'; diff --git a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx index bacef24ef..cca2cce98 100644 --- a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx @@ -1,5 +1,5 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; import getServerSchedule from '@/api/server/schedules/getServerSchedule'; import Spinner from '@/components/elements/Spinner'; import FlashMessageRender from '@/components/FlashMessageRender'; @@ -18,10 +18,6 @@ import { format } from 'date-fns'; import ScheduleCronRow from '@/components/server/schedules/ScheduleCronRow'; import RunScheduleButton from '@/components/server/schedules/RunScheduleButton'; -interface Params { - id: string; -} - const CronBox = ({ title, value }: { title: string; value: string }) => (

    {title}

    @@ -41,21 +37,21 @@ const ActivePill = ({ active }: { active: boolean }) => ( ); export default () => { - const history = useHistory(); - const { id: scheduleId } = useParams(); + const { id: scheduleId } = useParams<'id'>(); + const navigate = useNavigate(); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { clearFlashes, clearAndAddHttpError } = useFlash(); const [isLoading, setIsLoading] = useState(true); const [showEditModal, setShowEditModal] = useState(false); const schedule = ServerContext.useStoreState( - (st) => st.schedules.data.find((s) => s.id === Number(scheduleId)), - isEqual + st => st.schedules.data.find(s => s.id === Number(scheduleId)), + isEqual, ); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); useEffect(() => { if (schedule?.id === Number(scheduleId)) { @@ -65,8 +61,8 @@ export default () => { clearFlashes('schedules'); getServerSchedule(uuid, Number(scheduleId)) - .then((schedule) => appendSchedule(schedule)) - .catch((error) => { + .then(schedule => appendSchedule(schedule)) + .catch(error => { console.error(error); clearAndAddHttpError({ error, key: 'schedules' }); }) @@ -74,7 +70,7 @@ export default () => { }, [scheduleId]); const toggleEditModal = useCallback(() => { - setShowEditModal((s) => !s); + setShowEditModal(s => !s); }, []); return ( @@ -140,9 +136,9 @@ export default () => { {schedule.tasks.length > 0 ? schedule.tasks .sort((a, b) => - a.sequenceId === b.sequenceId ? 0 : a.sequenceId > b.sequenceId ? 1 : -1 + a.sequenceId === b.sequenceId ? 0 : a.sequenceId > b.sequenceId ? 1 : -1, ) - .map((task) => ( + .map(task => ( { history.push(`/server/${id}/schedules`)} + onDeleted={() => navigate(`/server/${id}/schedules`)} /> {schedule.tasks.length > 0 && ( diff --git a/resources/scripts/components/server/schedules/ScheduleRow.tsx b/resources/scripts/components/server/schedules/ScheduleRow.tsx index 1a7da64c0..030947cd7 100644 --- a/resources/scripts/components/server/schedules/ScheduleRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleRow.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCalendarAlt } from '@fortawesome/free-solid-svg-icons'; diff --git a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx index f950b94f8..008c99f02 100644 --- a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Schedule, Task } from '@/api/server/schedules/getServerSchedules'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { @@ -40,12 +40,12 @@ const getActionDetails = (action: string): [string, any] => { }; export default ({ schedule, task }: Props) => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { clearFlashes, addError } = useFlash(); const [visible, setVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isEditing, setIsEditing] = useState(false); - const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const onConfirmDeletion = () => { setIsLoading(true); @@ -54,10 +54,10 @@ export default ({ schedule, task }: Props) => { .then(() => appendSchedule({ ...schedule, - tasks: schedule.tasks.filter((t) => t.id !== task.id), - }) + tasks: schedule.tasks.filter(t => t.id !== task.id), + }), ) - .catch((error) => { + .catch(error => { console.error(error); setIsLoading(false); addError({ message: httpErrorToHuman(error), key: 'schedules' }); diff --git a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx index f54c0a258..a52e59a33 100644 --- a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx +++ b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { Schedule, Task } from '@/api/server/schedules/getServerSchedules'; import { Field as FormikField, Form, Formik, FormikHelpers, useField } from 'formik'; import { ServerContext } from '@/state/server'; @@ -35,7 +35,7 @@ interface Values { const schema = object().shape({ action: string().required().oneOf(['command', 'power', 'backup']), payload: string().when('action', { - is: (v) => v !== 'backup', + is: (v: string) => v !== 'backup', then: string().required('A task payload must be provided.'), otherwise: string(), }), @@ -68,9 +68,9 @@ const TaskDetailsModal = ({ schedule, task }: Props) => { const { dismiss } = useContext(ModalContext); const { clearFlashes, addError } = useFlash(); - 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 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); useEffect(() => { return () => { @@ -88,16 +88,16 @@ const TaskDetailsModal = ({ schedule, task }: Props) => { }); } else { createOrUpdateScheduleTask(uuid, schedule.id, task?.id, values) - .then((task) => { - let tasks = schedule.tasks.map((t) => (t.id === task.id ? task : t)); - if (!schedule.tasks.find((t) => t.id === task.id)) { + .then(task => { + let tasks = schedule.tasks.map(t => (t.id === task.id ? task : t)); + if (!schedule.tasks.find(t => t.id === task.id)) { tasks = [...tasks, task]; } appendSchedule({ ...schedule, tasks }); dismiss(); }) - .catch((error) => { + .catch(error => { console.error(error); setSubmitting(false); addError({ message: httpErrorToHuman(error), key: 'schedule:task' }); diff --git a/resources/scripts/components/server/settings/ReinstallServerBox.tsx b/resources/scripts/components/server/settings/ReinstallServerBox.tsx index 3a5a8dad5..0a5a15723 100644 --- a/resources/scripts/components/server/settings/ReinstallServerBox.tsx +++ b/resources/scripts/components/server/settings/ReinstallServerBox.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import reinstallServer from '@/api/server/reinstallServer'; @@ -10,7 +10,7 @@ import { Button } from '@/components/elements/button/index'; import { Dialog } from '@/components/elements/dialog'; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const [modalVisible, setModalVisible] = useState(false); const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); @@ -24,7 +24,7 @@ export default () => { message: 'Your server has begun the reinstallation process.', }); }) - .catch((error) => { + .catch(error => { console.error(error); addFlash({ key: 'settings', type: 'error', message: httpErrorToHuman(error) }); diff --git a/resources/scripts/components/server/settings/RenameServerBox.tsx b/resources/scripts/components/server/settings/RenameServerBox.tsx index 9cb290a1a..34d050879 100644 --- a/resources/scripts/components/server/settings/RenameServerBox.tsx +++ b/resources/scripts/components/server/settings/RenameServerBox.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { ServerContext } from '@/state/server'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; @@ -43,15 +42,15 @@ const RenameServerBox = () => { }; export default () => { - const server = ServerContext.useStoreState((state) => state.server.data!); - const setServer = ServerContext.useStoreActions((actions) => actions.server.setServer); + const server = ServerContext.useStoreState(state => state.server.data!); + const setServer = ServerContext.useStoreActions(actions => actions.server.setServer); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('settings'); renameServer(server.uuid, name, description) .then(() => setServer({ ...server, name, description })) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'settings', message: httpErrorToHuman(error) }); }) @@ -63,7 +62,7 @@ export default () => { onSubmit={submit} initialValues={{ name: server.name, - description: server.description, + description: server.description ?? '', }} validationSchema={object().shape({ name: string().required().min(1), diff --git a/resources/scripts/components/server/settings/SettingsContainer.tsx b/resources/scripts/components/server/settings/SettingsContainer.tsx index bcdc80b8e..e86aa07d9 100644 --- a/resources/scripts/components/server/settings/SettingsContainer.tsx +++ b/resources/scripts/components/server/settings/SettingsContainer.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { ServerContext } from '@/state/server'; import { useStoreState } from 'easy-peasy'; @@ -16,11 +15,11 @@ import { ip } from '@/lib/formatters'; import { Button } from '@/components/elements/button/index'; export default () => { - const username = useStoreState((state) => state.user.data!.username); - const id = ServerContext.useStoreState((state) => state.server.data!.id); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const node = ServerContext.useStoreState((state) => state.server.data!.node); - const sftp = ServerContext.useStoreState((state) => state.server.data!.sftpDetails, isEqual); + const username = useStoreState(state => state.user.data!.username); + const id = ServerContext.useStoreState(state => state.server.data!.id); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const node = ServerContext.useStoreState(state => state.server.data!.node); + const sftp = ServerContext.useStoreState(state => state.server.data!.sftpDetails, isEqual); return ( diff --git a/resources/scripts/components/server/startup/StartupContainer.tsx b/resources/scripts/components/server/startup/StartupContainer.tsx index 35bb9a63c..ff8afa129 100644 --- a/resources/scripts/components/server/startup/StartupContainer.tsx +++ b/resources/scripts/components/server/startup/StartupContainer.tsx @@ -1,4 +1,5 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import * as React from 'react'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import tw from 'twin.macro'; import VariableBox from '@/components/server/startup/VariableBox'; @@ -20,14 +21,14 @@ const StartupContainer = () => { const [loading, setLoading] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const variables = ServerContext.useStoreState( ({ server }) => ({ variables: server.data!.variables, invocation: server.data!.invocation, dockerImage: server.data!.dockerImage, }), - isEqual + isEqual, ); const { data, error, isValidating, mutate } = getServerStartup(uuid, { @@ -35,11 +36,11 @@ const StartupContainer = () => { dockerImages: { [variables.dockerImage]: variables.dockerImage }, }); - const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState); + const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const isCustomImage = data && !Object.values(data.dockerImages) - .map((v) => v.toLowerCase()) + .map(v => v.toLowerCase()) .includes(variables.dockerImage.toLowerCase()); useEffect(() => { @@ -52,7 +53,7 @@ const StartupContainer = () => { useDeepCompareEffect(() => { if (!data) return; - setServerFromState((s) => ({ + setServerFromState(s => ({ ...s, invocation: data.invocation, variables: data.variables, @@ -66,14 +67,14 @@ const StartupContainer = () => { const image = v.currentTarget.value; setSelectedDockerImage(uuid, image) - .then(() => setServerFromState((s) => ({ ...s, dockerImage: image }))) - .catch((error) => { + .then(() => setServerFromState(s => ({ ...s, dockerImage: image }))) + .catch(error => { console.error(error); clearAndAddHttpError({ key: 'startup:image', error }); }) .then(() => setLoading(false)); }, - [uuid] + [uuid], ); return !data ? ( @@ -99,7 +100,7 @@ const StartupContainer = () => { onChange={updateSelectedDockerImage} defaultValue={variables.dockerImage} > - {Object.keys(data.dockerImages).map((key) => ( + {Object.keys(data.dockerImages).map(key => ( @@ -126,7 +127,7 @@ const StartupContainer = () => {

    Variables

    - {data.variables.map((variable) => ( + {data.variables.map(variable => ( ))}
    diff --git a/resources/scripts/components/server/startup/VariableBox.tsx b/resources/scripts/components/server/startup/VariableBox.tsx index ad73615c6..3e38b435b 100644 --- a/resources/scripts/components/server/startup/VariableBox.tsx +++ b/resources/scripts/components/server/startup/VariableBox.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState } from 'react'; +import { memo, useState } from 'react'; import { ServerEggVariable } from '@/api/server/types'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { usePermissions } from '@/plugins/usePermissions'; @@ -22,7 +22,7 @@ interface Props { const VariableBox = ({ variable }: Props) => { const FLASH_KEY = `server:startup:${variable.envVariable}`; - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const [loading, setLoading] = useState(false); const [canEdit] = usePermissions(['startup.update']); const { clearFlashes, clearAndAddHttpError } = useFlash(); @@ -35,28 +35,28 @@ const VariableBox = ({ variable }: Props) => { updateStartupVariable(uuid, variable.envVariable, value) .then(([response, invocation]) => mutate( - (data) => ({ - ...data, + data => ({ + ...data!, invocation, - variables: (data.variables || []).map((v) => - v.envVariable === response.envVariable ? response : v + variables: (data!.variables || []).map(v => + v.envVariable === response.envVariable ? response : v, ), }), - false - ) + false, + ), ) - .catch((error) => { + .catch(error => { console.error(error); - clearAndAddHttpError({ error, key: FLASH_KEY }); + clearAndAddHttpError({ key: FLASH_KEY, error }); }) .then(() => setLoading(false)); }, 500); const useSwitch = variable.rules.some( - (v) => v === 'boolean' || v === 'in:0,1' || v === 'in:1,0' || v === 'in:true,false' || v === 'in:false,true' + v => v === 'boolean' || v === 'in:0,1' || v === 'in:1,0' || v === 'in:true,false' || v === 'in:false,true', ); - const isStringSwitch = variable.rules.some((v) => v === 'string'); - const selectValues = variable.rules.find((v) => v.startsWith('in:'))?.split(',') || []; + const isStringSwitch = variable.rules.some(v => v === 'string'); + const selectValues = variable.rules.find(v => v.startsWith('in:'))?.split(',') || []; return ( { {selectValues.length > 0 ? ( <> { + onKeyUp={e => { if (canEdit && variable.isEditable) { setVariableValue(e.currentTarget.value); } diff --git a/resources/scripts/components/server/users/AddSubuserButton.tsx b/resources/scripts/components/server/users/AddSubuserButton.tsx index d4847ce81..42bfa6a4f 100644 --- a/resources/scripts/components/server/users/AddSubuserButton.tsx +++ b/resources/scripts/components/server/users/AddSubuserButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import EditSubuserModal from '@/components/server/users/EditSubuserModal'; import { Button } from '@/components/elements/button/index'; diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx index c28d56f6c..86adca9cf 100644 --- a/resources/scripts/components/server/users/EditSubuserModal.tsx +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useRef } from 'react'; +import { useContext, useEffect, useRef } from 'react'; import { Subuser } from '@/state/server/subusers'; import { Form, Formik } from 'formik'; import { array, object, string } from 'yup'; @@ -29,24 +29,24 @@ interface Values { const EditSubuserModal = ({ subuser }: Props) => { const ref = useRef(null); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const appendSubuser = ServerContext.useStoreActions((actions) => actions.subusers.appendSubuser); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser); const { clearFlashes, clearAndAddHttpError } = useStoreActions( - (actions: Actions) => actions.flashes + (actions: Actions) => actions.flashes, ); const { dismiss, setPropOverrides } = useContext(ModalContext); - const isRootAdmin = useStoreState((state) => state.user.data!.rootAdmin); - const permissions = useStoreState((state) => state.permissions.data); + const isRootAdmin = useStoreState(state => state.user.data!.rootAdmin); + const permissions = useStoreState(state => state.permissions.data); // The currently logged in user's permissions. We're going to filter out any permissions // that they should not need. - const loggedInPermissions = ServerContext.useStoreState((state) => state.server.permissions); + const loggedInPermissions = ServerContext.useStoreState(state => state.server.permissions); const [canEditUser] = usePermissions(subuser ? ['user.update'] : ['user.create']); // The permissions that can be modified by this user. const editablePermissions = useDeepCompareMemo(() => { - const cleaned = Object.keys(permissions).map((key) => - Object.keys(permissions[key].keys).map((pkey) => `${key}.${pkey}`) + const cleaned = Object.keys(permissions).map(key => + Object.keys(permissions[key]?.keys ?? {}).map(pkey => `${key}.${pkey}`), ); const list: string[] = ([] as string[]).concat.apply([], Object.values(cleaned)); @@ -55,7 +55,7 @@ const EditSubuserModal = ({ subuser }: Props) => { return list; } - return list.filter((key) => loggedInPermissions.indexOf(key) >= 0); + return list.filter(key => loggedInPermissions.indexOf(key) >= 0); }, [isRootAdmin, permissions, loggedInPermissions]); const submit = (values: Values) => { @@ -63,11 +63,11 @@ const EditSubuserModal = ({ subuser }: Props) => { clearFlashes('user:edit'); createOrUpdateSubuser(uuid, values, subuser) - .then((subuser) => { + .then(subuser => { appendSubuser(subuser); dismiss(); }) - .catch((error) => { + .catch(error => { console.error(error); setPropOverrides(null); clearAndAddHttpError({ key: 'user:edit', error }); @@ -82,7 +82,7 @@ const EditSubuserModal = ({ subuser }: Props) => { () => () => { clearFlashes('user:edit'); }, - [] + [], ); return ( @@ -137,17 +137,17 @@ const EditSubuserModal = ({ subuser }: Props) => { )}
    {Object.keys(permissions) - .filter((key) => key !== 'websocket') + .filter(key => key !== 'websocket') .map((key, index) => ( `${key}.${pkey}`)} + permissions={Object.keys(permissions[key]?.keys ?? {}).map(pkey => `${key}.${pkey}`)} css={index > 0 ? tw`mt-4` : undefined} > -

    {permissions[key].description}

    - {Object.keys(permissions[key].keys).map((pkey) => ( +

    {permissions[key]?.description}

    + {Object.keys(permissions[key]?.keys ?? {}).map(pkey => ( { - const [key, pkey] = permission.split('.', 2); - const permissions = useStoreState((state) => state.permissions.data); + const [key = '', pkey = ''] = permission.split('.', 2); + const permissions = useStoreState(state => state.permissions.data); return ( @@ -54,8 +53,8 @@ const PermissionRow = ({ permission, disabled }: Props) => { - {permissions[key].keys[pkey].length > 0 && ( -

    {permissions[key].keys[pkey]}

    + {(permissions[key]?.keys?.[pkey]?.length ?? 0) > 0 && ( +

    {permissions[key]?.keys?.[pkey] ?? ''}

    )}
    diff --git a/resources/scripts/components/server/users/PermissionTitleBox.tsx b/resources/scripts/components/server/users/PermissionTitleBox.tsx index 1d678f221..b8f8fd7b3 100644 --- a/resources/scripts/components/server/users/PermissionTitleBox.tsx +++ b/resources/scripts/components/server/users/PermissionTitleBox.tsx @@ -1,29 +1,33 @@ -import React, { memo, useCallback } from 'react'; import { useField } from 'formik'; -import TitledGreyBox from '@/components/elements/TitledGreyBox'; -import tw from 'twin.macro'; -import Input from '@/components/elements/Input'; +import type { ReactNode } from 'react'; +import { memo, useCallback } from 'react'; import isEqual from 'react-fast-compare'; +import tw from 'twin.macro'; + +import TitledGreyBox from '@/components/elements/TitledGreyBox'; +import Input from '@/components/elements/Input'; interface Props { - isEditable: boolean; + children?: ReactNode; + className?: string; + + isEditable?: boolean; title: string; permissions: string[]; - className?: string; } -const PermissionTitleBox: React.FC = memo(({ isEditable, title, permissions, className, children }) => { +function PermissionTitleBox({ isEditable, title, permissions, className, children }: Props) { const [{ value }, , { setValue }] = useField('permissions'); const onCheckboxClicked = useCallback( (e: React.ChangeEvent) => { if (e.currentTarget.checked) { - setValue([...value, ...permissions.filter((p) => !value.includes(p))]); + setValue([...value, ...permissions.filter(p => !value.includes(p))]); } else { - setValue(value.filter((p) => !permissions.includes(p))); + setValue(value.filter(p => !permissions.includes(p))); } }, - [permissions, value] + [permissions, value], ); return ( @@ -34,7 +38,7 @@ const PermissionTitleBox: React.FC = memo(({ isEditable, title, permissio {isEditable && ( value.includes(p))} + checked={permissions.every(p => value.includes(p))} onChange={onCheckboxClicked} /> )} @@ -45,6 +49,8 @@ const PermissionTitleBox: React.FC = memo(({ isEditable, title, permissio {children}
    ); -}, isEqual); +} -export default PermissionTitleBox; +const MemoizedPermissionTitleBox = memo(PermissionTitleBox, isEqual); + +export default MemoizedPermissionTitleBox; diff --git a/resources/scripts/components/server/users/RemoveSubuserButton.tsx b/resources/scripts/components/server/users/RemoveSubuserButton.tsx index defc6b47f..eb4294d9b 100644 --- a/resources/scripts/components/server/users/RemoveSubuserButton.tsx +++ b/resources/scripts/components/server/users/RemoveSubuserButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import ConfirmationModal from '@/components/elements/ConfirmationModal'; import { ServerContext } from '@/state/server'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -14,8 +14,8 @@ export default ({ subuser }: { subuser: Subuser }) => { const [loading, setLoading] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const removeSubuser = ServerContext.useStoreActions((actions) => actions.subusers.removeSubuser); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const removeSubuser = ServerContext.useStoreActions(actions => actions.subusers.removeSubuser); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const doDeletion = () => { @@ -26,7 +26,7 @@ export default ({ subuser }: { subuser: Subuser }) => { setLoading(false); removeSubuser(subuser.uuid); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'users', message: httpErrorToHuman(error) }); setShowConfirmation(false); diff --git a/resources/scripts/components/server/users/UserRow.tsx b/resources/scripts/components/server/users/UserRow.tsx index 45693c5d4..0265daa2f 100644 --- a/resources/scripts/components/server/users/UserRow.tsx +++ b/resources/scripts/components/server/users/UserRow.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Subuser } from '@/state/server/subusers'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPencilAlt, faUnlockAlt, faUserLock } from '@fortawesome/free-solid-svg-icons'; @@ -14,7 +14,7 @@ interface Props { } export default ({ subuser }: Props) => { - const uuid = useStoreState((state) => state.user!.data!.uuid); + const uuid = useStoreState(state => state.user!.data!.uuid); const [visible, setVisible] = useState(false); return ( @@ -40,7 +40,7 @@ export default ({ subuser }: Props) => {
    diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index a5d505fe1..01128e668 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -15,9 +15,9 @@ import tw from 'twin.macro'; export default () => { const [loading, setLoading] = useState(true); - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const subusers = ServerContext.useStoreState((state) => state.subusers.data); - const setSubusers = ServerContext.useStoreActions((actions) => actions.subusers.setSubusers); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const subusers = ServerContext.useStoreState(state => state.subusers.data); + const setSubusers = ServerContext.useStoreActions(actions => actions.subusers.setSubusers); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); const getPermissions = useStoreActions((actions: Actions) => actions.permissions.getPermissions); @@ -26,18 +26,18 @@ export default () => { useEffect(() => { clearFlashes('users'); getServerSubusers(uuid) - .then((subusers) => { + .then(subusers => { setSubusers(subusers); setLoading(false); }) - .catch((error) => { + .catch(error => { console.error(error); addError({ key: 'users', message: httpErrorToHuman(error) }); }); }, []); useEffect(() => { - getPermissions().catch((error) => { + getPermissions().catch(error => { addError({ key: 'users', message: httpErrorToHuman(error) }); console.error(error); }); @@ -53,7 +53,7 @@ export default () => { {!subusers.length ? (

    It looks like you don't have any subusers.

    ) : ( - subusers.map((subuser) => ) + subusers.map(subuser => ) )}
    diff --git a/resources/scripts/context/ModalContext.ts b/resources/scripts/context/ModalContext.ts index cfda13de1..270cdb610 100644 --- a/resources/scripts/context/ModalContext.ts +++ b/resources/scripts/context/ModalContext.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import { createContext } from 'react'; import { SettableModalProps } from '@/hoc/asModal'; export interface ModalContextValues { @@ -7,11 +7,11 @@ export interface ModalContextValues { value: | ((current: Readonly>) => Partial) | Partial - | null + | null, ) => void; } -const ModalContext = React.createContext({ +const ModalContext = createContext({ dismiss: () => null, setPropOverrides: () => null, }); diff --git a/resources/scripts/helpers.ts b/resources/scripts/helpers.ts index 210ce040e..a09a15fe8 100644 --- a/resources/scripts/helpers.ts +++ b/resources/scripts/helpers.ts @@ -40,7 +40,7 @@ export function fileBitsToString(mode: string, directory: boolean): string { export function encodePathSegments(path: string): string { return path .split('/') - .map((s) => encodeURIComponent(s)) + .map(s => encodeURIComponent(s)) .join('/'); } diff --git a/resources/scripts/hoc/RequireServerPermission.tsx b/resources/scripts/hoc/RequireServerPermission.tsx index cb7da2b31..9b112f430 100644 --- a/resources/scripts/hoc/RequireServerPermission.tsx +++ b/resources/scripts/hoc/RequireServerPermission.tsx @@ -1,12 +1,15 @@ -import React from 'react'; +import type { ReactNode } from 'react'; + import Can from '@/components/elements/Can'; import { ServerError } from '@/components/elements/ScreenBlock'; export interface RequireServerPermissionProps { + children?: ReactNode; + permissions: string | string[]; } -const RequireServerPermission: React.FC = ({ children, permissions }) => { +function RequireServerPermission({ children, permissions }: RequireServerPermissionProps) { return ( = ({ child {children} ); -}; +} export default RequireServerPermission; diff --git a/resources/scripts/hoc/asDialog.tsx b/resources/scripts/hoc/asDialog.tsx index 0be5dbe65..3ea5d4dc6 100644 --- a/resources/scripts/hoc/asDialog.tsx +++ b/resources/scripts/hoc/asDialog.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; +import type { ComponentProps, ComponentType, FunctionComponent } from 'react'; +import { useState } from 'react'; import { Dialog, DialogProps, DialogWrapperContext, WrapperProps } from '@/components/elements/dialog'; function asDialog( - initialProps?: WrapperProps + initialProps?: WrapperProps, // eslint-disable-next-line @typescript-eslint/ban-types -):

    (C: React.ComponentType

    ) => React.FunctionComponent

    { +):

    (C: ComponentType

    ) => FunctionComponent

    { return function (Component) { return function ({ open, onClose, ...rest }) { const [props, setProps] = useState(initialProps || {}); @@ -12,7 +13,7 @@ function asDialog( return (

    - )} /> + )} /> ); diff --git a/resources/scripts/hoc/asModal.tsx b/resources/scripts/hoc/asModal.tsx index 142ead1d5..ae0f166c7 100644 --- a/resources/scripts/hoc/asModal.tsx +++ b/resources/scripts/hoc/asModal.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import { PureComponent } from 'react'; +import isEqual from 'react-fast-compare'; + import PortaledModal, { ModalProps } from '@/components/elements/Modal'; import ModalContext, { ModalContextValues } from '@/context/ModalContext'; -import isEqual from 'react-fast-compare'; export interface AsModalProps { visible: boolean; @@ -16,14 +17,12 @@ interface State { propOverrides: Partial; } -type ExtendedComponentType = (C: React.ComponentType) => React.ComponentType; - // eslint-disable-next-line @typescript-eslint/ban-types function asModal

    ( - modalProps?: SettableModalProps | ((props: P) => SettableModalProps) -): ExtendedComponentType

    { + modalProps?: SettableModalProps | ((props: P) => SettableModalProps), +): (Component: any) => any { return function (Component) { - return class extends React.PureComponent

    { + return class extends PureComponent

    { static displayName = `asModal(${Component.displayName})`; constructor(props: P & AsModalProps) { @@ -47,7 +46,7 @@ function asModal

    ( /** * @this {React.PureComponent

    } */ - componentDidUpdate(prevProps: Readonly

    , prevState: Readonly) { + override componentDidUpdate(prevProps: Readonly

    , prevState: Readonly) { if (prevProps.visible && !this.props.visible) { this.setState({ visible: false, propOverrides: {} }); } else if (!prevProps.visible && this.props.visible) { @@ -60,15 +59,15 @@ function asModal

    ( dismiss = () => this.setState({ visible: false }); - setPropOverrides: ModalContextValues['setPropOverrides'] = (value) => - this.setState((state) => ({ + setPropOverrides: ModalContextValues['setPropOverrides'] = value => + this.setState(state => ({ propOverrides: !value ? {} : typeof value === 'function' ? value(state.propOverrides) : value, })); /** * @this {React.PureComponent

    } */ - render() { + override render() { if (!this.state.render) return null; return ( diff --git a/resources/scripts/i18n.ts b/resources/scripts/i18n.ts index c36879eab..2cb656558 100644 --- a/resources/scripts/i18n.ts +++ b/resources/scripts/i18n.ts @@ -6,7 +6,7 @@ import I18NextMultiloadBackendAdapter from 'i18next-multiload-backend-adapter'; // If we're using HMR use a unique hash per page reload so that we're always // doing cache busting. Otherwise just use the builder provided hash value in // the URL to allow cache busting to occur whenever the front-end is rebuilt. -const hash = module.hot ? Date.now().toString(16) : process.env.WEBPACK_BUILD_HASH; +const hash = Date.now().toString(16); i18n.use(I18NextMultiloadBackendAdapter) .use(initReactI18next) diff --git a/resources/scripts/index.tsx b/resources/scripts/index.tsx index 2aade2475..99fd50d37 100644 --- a/resources/scripts/index.tsx +++ b/resources/scripts/index.tsx @@ -1,16 +1,7 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from '@/components/App'; -import { setConfig } from 'react-hot-loader'; +import { createRoot } from 'react-dom/client'; +import { App } from '@/components/App'; // Enable language support. import './i18n'; -// Prevents page reloads while making component changes which -// also avoids triggering constant loading indicators all over -// the place in development. -// -// @see https://github.com/gaearon/react-hot-loader#hook-support -setConfig({ reloadHooks: false }); - -ReactDOM.render(, document.getElementById('app')); +createRoot(document.getElementById('app')!).render(); diff --git a/resources/scripts/lib/formatters.spec.ts b/resources/scripts/lib/formatters.spec.ts index c03008971..c786f5869 100644 --- a/resources/scripts/lib/formatters.spec.ts +++ b/resources/scripts/lib/formatters.spec.ts @@ -1,17 +1,19 @@ +import { describe, expect, it } from 'vitest'; + import { bytesToString, ip, mbToBytes } from '@/lib/formatters'; -describe('@/lib/formatters.ts', function () { - describe('mbToBytes()', function () { - it('should convert from MB to Bytes', function () { - expect(mbToBytes(1)).toBe(1_048_576); - expect(mbToBytes(0)).toBe(0); - expect(mbToBytes(0.1)).toBe(104_857); - expect(mbToBytes(0.001)).toBe(1_048); - expect(mbToBytes(1024)).toBe(1_073_741_824); +describe('@/lib/formatters.ts', () => { + describe('mbToBytes()', () => { + it('should convert from MB to Bytes', () => { + expect(mbToBytes(1)).equals(1_048_576); + expect(mbToBytes(0)).equals(0); + expect(mbToBytes(0.1)).equals(104_857); + expect(mbToBytes(0.001)).equals(1_048); + expect(mbToBytes(1024)).equals(1_073_741_824); }); }); - describe('bytesToString()', function () { + describe('bytesToString()', () => { it.each([ [0, '0 Bytes'], [0.5, '0 Bytes'], @@ -38,24 +40,24 @@ describe('@/lib/formatters.ts', function () { [1_000_000_000_000, '931.32 GiB'], [1_099_511_627_776, '1 TiB'], ])('should format %d bytes as "%s"', function (input, output) { - expect(bytesToString(input)).toBe(output); + expect(bytesToString(input)).equals(output); }); }); - describe('ip()', function () { - it('should format an IPv4 address', function () { - expect(ip('127.0.0.1')).toBe('127.0.0.1'); + describe('ip()', () => { + it('should format an IPv4 address', () => { + expect(ip('127.0.0.1')).equals('127.0.0.1'); }); - it('should format an IPv6 address', function () { - expect(ip(':::1')).toBe('[:::1]'); - expect(ip('2001:db8::')).toBe('[2001:db8::]'); + it('should format an IPv6 address', () => { + expect(ip(':::1')).equals('[:::1]'); + expect(ip('2001:db8::')).equals('[2001:db8::]'); }); - it('should handle random inputs', function () { - expect(ip('1')).toBe('1'); - expect(ip('foobar')).toBe('foobar'); - expect(ip('127.0.0.1:25565')).toBe('[127.0.0.1:25565]'); + it('should handle random inputs', () => { + expect(ip('1')).equals('1'); + expect(ip('foobar')).equals('foobar'); + expect(ip('127.0.0.1:25565')).equals('[127.0.0.1:25565]'); }); }); }); diff --git a/resources/scripts/lib/helpers.spec.ts b/resources/scripts/lib/helpers.spec.ts index 6d18e7e3d..b22f60f98 100644 --- a/resources/scripts/lib/helpers.spec.ts +++ b/resources/scripts/lib/helpers.spec.ts @@ -1,29 +1,31 @@ +import { describe, expect, it } from 'vitest'; + import { hexToRgba } from '@/lib/helpers'; -describe('@/lib/helpers.ts', function () { - describe('hexToRgba()', function () { - it('should return the expected rgba', function () { - expect(hexToRgba('#ffffff')).toBe('rgba(255, 255, 255, 1)'); - expect(hexToRgba('#00aabb')).toBe('rgba(0, 170, 187, 1)'); - expect(hexToRgba('#efefef')).toBe('rgba(239, 239, 239, 1)'); +describe('@/lib/helpers.ts', () => { + describe('hexToRgba()', () => { + it('should return the expected rgba', () => { + expect(hexToRgba('#ffffff')).equals('rgba(255, 255, 255, 1)'); + expect(hexToRgba('#00aabb')).equals('rgba(0, 170, 187, 1)'); + expect(hexToRgba('#efefef')).equals('rgba(239, 239, 239, 1)'); }); - it('should ignore case', function () { - expect(hexToRgba('#FF00A3')).toBe('rgba(255, 0, 163, 1)'); + it('should ignore case', () => { + expect(hexToRgba('#FF00A3')).equals('rgba(255, 0, 163, 1)'); }); - it('should allow alpha channel changes', function () { - expect(hexToRgba('#ece5a8', 0.5)).toBe('rgba(236, 229, 168, 0.5)'); - expect(hexToRgba('#ece5a8', 0.1)).toBe('rgba(236, 229, 168, 0.1)'); - expect(hexToRgba('#000000', 0)).toBe('rgba(0, 0, 0, 0)'); + it('should allow alpha channel changes', () => { + expect(hexToRgba('#ece5a8', 0.5)).equals('rgba(236, 229, 168, 0.5)'); + expect(hexToRgba('#ece5a8', 0.1)).equals('rgba(236, 229, 168, 0.1)'); + expect(hexToRgba('#000000', 0)).equals('rgba(0, 0, 0, 0)'); }); - it('should handle invalid strings', function () { - expect(hexToRgba('')).toBe(''); - expect(hexToRgba('foobar')).toBe('foobar'); - expect(hexToRgba('#fff')).toBe('#fff'); - expect(hexToRgba('#')).toBe('#'); - expect(hexToRgba('#fffffy')).toBe('#fffffy'); + it('should handle invalid strings', () => { + expect(hexToRgba('')).equals(''); + expect(hexToRgba('foobar')).equals('foobar'); + expect(hexToRgba('#fff')).equals('#fff'); + expect(hexToRgba('#')).equals('#'); + expect(hexToRgba('#fffffy')).equals('#fffffy'); }); }); }); diff --git a/resources/scripts/lib/helpers.ts b/resources/scripts/lib/helpers.ts index ee293a2bb..4d5fb581e 100644 --- a/resources/scripts/lib/helpers.ts +++ b/resources/scripts/lib/helpers.ts @@ -9,7 +9,7 @@ function hexToRgba(hex: string, alpha = 1): string { } // noinspection RegExpSimplifiable - const [r, g, b] = hex.match(/[a-fA-F0-9]{2}/g)!.map((v) => parseInt(v, 16)); + const [r, g, b] = hex.match(/[a-fA-F0-9]{2}/g)!.map(v => parseInt(v, 16)); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } diff --git a/resources/scripts/lib/objects.spec.ts b/resources/scripts/lib/objects.spec.ts index 330b6e954..03a6fbdd1 100644 --- a/resources/scripts/lib/objects.spec.ts +++ b/resources/scripts/lib/objects.spec.ts @@ -1,22 +1,24 @@ +import { describe, expect, it } from 'vitest'; + import { isObject } from '@/lib/objects'; -describe('@/lib/objects.ts', function () { - describe('isObject()', function () { - it('should return true for objects', function () { - expect(isObject({})).toBe(true); - expect(isObject({ foo: 123 })).toBe(true); - expect(isObject(Object.freeze({}))).toBe(true); +describe('@/lib/objects.ts', () => { + describe('isObject()', () => { + it('should return true for objects', () => { + expect(isObject({})).equals(true); + expect(isObject({ foo: 123 })).equals(true); + expect(isObject(Object.freeze({}))).equals(true); }); - it('should return false for null', function () { - expect(isObject(null)).toBe(false); + it('should return false for null', () => { + expect(isObject(null)).equals(false); }); it.each([undefined, 123, 'foobar', () => ({}), Function, String(123), isObject, () => null, [], [1, 2, 3]])( 'should return false for %p', - function (value) { - expect(isObject(value)).toBe(false); - } + value => { + expect(isObject(value)).equals(false); + }, ); }); }); diff --git a/resources/scripts/lib/strings.spec.ts b/resources/scripts/lib/strings.spec.ts index 8875ce217..2c39ca2ef 100644 --- a/resources/scripts/lib/strings.spec.ts +++ b/resources/scripts/lib/strings.spec.ts @@ -1,14 +1,16 @@ +import { describe, expect, it } from 'vitest'; + import { capitalize } from '@/lib/strings'; -describe('@/lib/strings.ts', function () { - describe('capitalize()', function () { - it('should capitalize a string', function () { - expect(capitalize('foo bar')).toBe('Foo bar'); - expect(capitalize('FOOBAR')).toBe('Foobar'); +describe('@/lib/strings.ts', () => { + describe('capitalize()', () => { + it('should capitalize a string', () => { + expect(capitalize('foo bar')).equals('Foo bar'); + expect(capitalize('FOOBAR')).equals('Foobar'); }); - it('should handle empty strings', function () { - expect(capitalize('')).toBe(''); + it('should handle empty strings', () => { + expect(capitalize('')).equals(''); }); }); }); diff --git a/resources/scripts/macros.d.ts b/resources/scripts/macros.d.ts index a71078482..410e3bcb9 100644 --- a/resources/scripts/macros.d.ts +++ b/resources/scripts/macros.d.ts @@ -15,10 +15,10 @@ declare module 'styled-components' { T extends object, // eslint-disable-next-line @typescript-eslint/ban-types O extends object = {}, - A extends keyof any = never + A extends keyof any = never, > extends ForwardRefExoticBase> { ( - props: StyledComponentProps & { as?: Element | string; forwardedAs?: never | undefined } + props: StyledComponentProps & { as?: Element | string; forwardedAs?: never | undefined }, ): ReactElement>; } } diff --git a/resources/scripts/plugins/Websocket.ts b/resources/scripts/plugins/Websocket.ts index 48d8dc7e7..8308afa5d 100644 --- a/resources/scripts/plugins/Websocket.ts +++ b/resources/scripts/plugins/Websocket.ts @@ -26,7 +26,7 @@ export class Websocket extends EventEmitter { this.url = url; this.socket = new Sockette(`${this.url}`, { - onmessage: (e) => { + onmessage: e => { try { const { event, args } = JSON.parse(e.data); args ? this.emit(event, ...args) : this.emit(event); @@ -47,7 +47,7 @@ export class Websocket extends EventEmitter { this.authenticate(); }, onclose: () => this.emit('SOCKET_CLOSE'), - onerror: (error) => this.emit('SOCKET_ERROR', error), + onerror: error => this.emit('SOCKET_ERROR', error), }); this.timer = setTimeout(() => { @@ -100,7 +100,7 @@ export class Websocket extends EventEmitter { JSON.stringify({ event, args: Array.isArray(payload) ? payload : [payload], - }) + }), ); } } diff --git a/resources/scripts/plugins/useEventListener.ts b/resources/scripts/plugins/useEventListener.ts index 3fef05c4c..b552bee19 100644 --- a/resources/scripts/plugins/useEventListener.ts +++ b/resources/scripts/plugins/useEventListener.ts @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'; export default ( eventName: string, handler: (e: Event | CustomEvent | UIEvent | any) => void, - options?: boolean | EventListenerOptions + options?: boolean | EventListenerOptions, ) => { const savedHandler = useRef(null); diff --git a/resources/scripts/plugins/useFileManagerSwr.ts b/resources/scripts/plugins/useFileManagerSwr.ts index a03ccaefb..e134cb402 100644 --- a/resources/scripts/plugins/useFileManagerSwr.ts +++ b/resources/scripts/plugins/useFileManagerSwr.ts @@ -6,8 +6,8 @@ import { ServerContext } from '@/state/server'; export const getDirectorySwrKey = (uuid: string, directory: string): string => `${uuid}:files:${directory}`; export default () => { - const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); - const directory = ServerContext.useStoreState((state) => state.files.directory); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const directory = ServerContext.useStoreState(state => state.files.directory); return useSWR( getDirectorySwrKey(uuid, directory), @@ -17,6 +17,6 @@ export default () => { revalidateOnMount: false, refreshInterval: 0, errorRetryCount: 2, - } + }, ); }; diff --git a/resources/scripts/plugins/useFlash.ts b/resources/scripts/plugins/useFlash.ts index 947975528..2d570592c 100644 --- a/resources/scripts/plugins/useFlash.ts +++ b/resources/scripts/plugins/useFlash.ts @@ -18,7 +18,7 @@ const useFlashKey = (key: string): KeyedFlashStore => { return { addError: (message, title) => addFlash({ key, message, title, type: 'error' }), clearFlashes: () => clearFlashes(key), - clearAndAddHttpError: (error) => clearAndAddHttpError({ key, error }), + clearAndAddHttpError: error => clearAndAddHttpError({ key, error }), }; }; diff --git a/resources/scripts/plugins/useLocationHash.ts b/resources/scripts/plugins/useLocationHash.ts index 6137e897c..e6e377139 100644 --- a/resources/scripts/plugins/useLocationHash.ts +++ b/resources/scripts/plugins/useLocationHash.ts @@ -9,7 +9,7 @@ export default () => { .substring(1) .split('&') .reduce((obj, str) => { - const [key, value = ''] = str.split('='); + const [key = '', value = ''] = str.split('='); return !str.trim() ? obj : { ...obj, [key]: value }; }, {}); @@ -18,11 +18,11 @@ export default () => { const current = getHashObject(location.hash); for (const key in params) { - current[key] = params[key]; + current[key] = params[key] ?? ''; } return Object.keys(current) - .map((key) => `${key}=${current[key]}`) + .map(key => `${key}=${current[key]}`) .join('&'); }; diff --git a/resources/scripts/plugins/usePermissions.ts b/resources/scripts/plugins/usePermissions.ts index 50550ad9f..f3bb660f9 100644 --- a/resources/scripts/plugins/usePermissions.ts +++ b/resources/scripts/plugins/usePermissions.ts @@ -2,7 +2,7 @@ import { ServerContext } from '@/state/server'; import { useDeepCompareMemo } from '@/plugins/useDeepCompareMemo'; export const usePermissions = (action: string | string[]): boolean[] => { - const userPermissions = ServerContext.useStoreState((state) => state.server.permissions); + const userPermissions = ServerContext.useStoreState(state => state.server.permissions); return useDeepCompareMemo(() => { if (userPermissions[0] === '*') { @@ -10,13 +10,13 @@ export const usePermissions = (action: string | string[]): boolean[] => { } return (Array.isArray(action) ? action : [action]).map( - (permission) => + permission => // Allows checking for any permission matching a name, for example files.* // will return if the user has any permission under the file.XYZ namespace. (permission.endsWith('.*') && - userPermissions.filter((p) => p.startsWith(permission.split('.')[0])).length > 0) || + userPermissions.filter(p => p.startsWith(permission.split('.')?.[0] ?? '')).length > 0) || // Otherwise just check if the entire permission exists in the array or not. - userPermissions.indexOf(permission) >= 0 + userPermissions.indexOf(permission) >= 0, ); }, [action, userPermissions]); }; diff --git a/resources/scripts/plugins/usePersistedState.ts b/resources/scripts/plugins/usePersistedState.ts index f5d247677..bffcfb1f0 100644 --- a/resources/scripts/plugins/usePersistedState.ts +++ b/resources/scripts/plugins/usePersistedState.ts @@ -2,7 +2,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from 'react'; export function usePersistedState( key: string, - defaultValue: S + defaultValue: S, ): [S | undefined, Dispatch>] { const [state, setState] = useState(() => { try { diff --git a/resources/scripts/plugins/useSWRKey.ts b/resources/scripts/plugins/useSWRKey.ts index 78e84fd77..fe69ff4eb 100644 --- a/resources/scripts/plugins/useSWRKey.ts +++ b/resources/scripts/plugins/useSWRKey.ts @@ -7,7 +7,7 @@ type Context = string | string[] | (string | number | null | {})[]; function useSWRKey(context: Context, prefix: string | null = null): string { const key = useDeepCompareMemo((): string => { - return (Array.isArray(context) ? context : [context]).map((value) => JSON.stringify(value)).join(':'); + return (Array.isArray(context) ? context : [context]).map(value => JSON.stringify(value)).join(':'); }, [context]); if (!key.trim().length) { @@ -18,13 +18,13 @@ function useSWRKey(context: Context, prefix: string | null = null): string { } function useServerSWRKey(context: Context): string { - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); return useSWRKey(context, `server:${uuid}`); } function useUserSWRKey(context: Context): string { - const uuid = useStoreState((state) => state.user.data?.uuid); + const uuid = useStoreState(state => state.user.data?.uuid); return useSWRKey(context, `user:${uuid}`); } diff --git a/resources/scripts/plugins/useWebsocketEvent.ts b/resources/scripts/plugins/useWebsocketEvent.ts index b746e3889..7d55c076c 100644 --- a/resources/scripts/plugins/useWebsocketEvent.ts +++ b/resources/scripts/plugins/useWebsocketEvent.ts @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'; import { SocketEvent } from '@/components/server/events'; const useWebsocketEvent = (event: SocketEvent, callback: (data: string) => void) => { - const { connected, instance } = ServerContext.useStoreState((state) => state.socket); + const { connected, instance } = ServerContext.useStoreState(state => state.socket); const savedCallback = useRef(null); useEffect(() => { diff --git a/resources/scripts/routers/AuthenticationRouter.tsx b/resources/scripts/routers/AuthenticationRouter.tsx index 27d13f11b..580d7fbb0 100644 --- a/resources/scripts/routers/AuthenticationRouter.tsx +++ b/resources/scripts/routers/AuthenticationRouter.tsx @@ -1,29 +1,23 @@ -import React from 'react'; -import { Route, Switch, useRouteMatch } from 'react-router-dom'; +import { Route, Routes, useNavigate } from 'react-router-dom'; + import LoginContainer from '@/components/auth/LoginContainer'; import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer'; import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer'; import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer'; import { NotFound } from '@/components/elements/ScreenBlock'; -import { useHistory, useLocation } from 'react-router'; export default () => { - const history = useHistory(); - const location = useLocation(); - const { path } = useRouteMatch(); + const navigate = useNavigate(); return ( -

    - - - - - - - - history.push('/auth/login')} /> - - +
    + + } /> + } /> + } /> + } /> + navigate('/auth/login')} />} /> +
    ); }; diff --git a/resources/scripts/routers/DashboardRouter.tsx b/resources/scripts/routers/DashboardRouter.tsx index 4ad69c652..f874b20e3 100644 --- a/resources/scripts/routers/DashboardRouter.tsx +++ b/resources/scripts/routers/DashboardRouter.tsx @@ -1,50 +1,47 @@ -import React from 'react'; -import { NavLink, Route, Switch } from 'react-router-dom'; +import { Suspense } from 'react'; +import { NavLink, Route, Routes, useLocation } from 'react-router-dom'; + import NavigationBar from '@/components/NavigationBar'; import DashboardContainer from '@/components/dashboard/DashboardContainer'; import { NotFound } from '@/components/elements/ScreenBlock'; -import TransitionRouter from '@/TransitionRouter'; -import SubNavigation from '@/components/elements/SubNavigation'; -import { useLocation } from 'react-router'; import Spinner from '@/components/elements/Spinner'; +import SubNavigation from '@/components/elements/SubNavigation'; import routes from '@/routers/routes'; -export default () => { +function DashboardRouter() { const location = useLocation(); return ( <> + {location.pathname.startsWith('/account') && (
    {routes.account - .filter((route) => !!route.name) - .map(({ path, name, exact = false }) => ( - + .filter(route => route.path !== undefined) + .map(({ path, name, end = false }) => ( + {name} ))}
    )} - - }> - - - - - {routes.account.map(({ path, component: Component }) => ( - - - - ))} - - - - - - + + }> + + } /> + + {routes.account.map(({ route, component: Component }) => ( + } /> + ))} + + } /> + + ); -}; +} + +export default DashboardRouter; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 2976cf789..fa97dba9c 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -1,11 +1,9 @@ import TransferListener from '@/components/server/TransferListener'; -import React, { useEffect, useState } from 'react'; -import { NavLink, Route, Switch, useRouteMatch } from 'react-router-dom'; +import { Fragment, useEffect, useState } from 'react'; +import { NavLink, Route, Routes, useParams } from 'react-router-dom'; import NavigationBar from '@/components/NavigationBar'; -import TransitionRouter from '@/TransitionRouter'; import WebsocketHandler from '@/components/server/WebsocketHandler'; import { ServerContext } from '@/state/server'; -import { CSSTransition } from 'react-transition-group'; import Can from '@/components/elements/Can'; import Spinner from '@/components/elements/Spinner'; import { NotFound, ServerError } from '@/components/elements/ScreenBlock'; @@ -21,38 +19,35 @@ import ConflictStateRenderer from '@/components/server/ConflictStateRenderer'; import PermissionRoute from '@/components/elements/PermissionRoute'; import routes from '@/routers/routes'; -export default () => { - const match = useRouteMatch<{ id: string }>(); +function ServerRouter() { + const params = useParams<'id'>(); const location = useLocation(); - const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const [error, setError] = useState(''); - const id = ServerContext.useStoreState((state) => state.server.data?.id); - const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid); - const inConflictState = ServerContext.useStoreState((state) => state.server.inConflictState); - const serverId = ServerContext.useStoreState((state) => state.server.data?.internalId); - const getServer = ServerContext.useStoreActions((actions) => actions.server.getServer); - const clearServerState = ServerContext.useStoreActions((actions) => actions.clearServerState); - - const to = (value: string, url = false) => { - if (value === '/') { - return url ? match.url : match.path; - } - return `${(url ? match.url : match.path).replace(/\/*$/, '')}/${value.replace(/^\/+/, '')}`; - }; + const id = ServerContext.useStoreState(state => state.server.data?.id); + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); + const inConflictState = ServerContext.useStoreState(state => state.server.inConflictState); + const serverId = ServerContext.useStoreState(state => state.server.data?.internalId); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState); useEffect( () => () => { clearServerState(); }, - [] + [], ); useEffect(() => { setError(''); - getServer(match.params.id).catch((error) => { + if (params.id === undefined) { + return; + } + + getServer(params.id).catch(error => { console.error(error); setError(httpErrorToHuman(error)); }); @@ -60,10 +55,10 @@ export default () => { return () => { clearServerState(); }; - }, [match.params.id]); + }, [params.id]); return ( - + {!uuid || !id ? ( error ? ( @@ -73,56 +68,61 @@ export default () => { ) ) : ( <> - - -
    - {routes.server - .filter((route) => !!route.name) - .map((route) => - route.permission ? ( - - - {route.name} - - - ) : ( - + +
    + {routes.server + .filter(route => route.path !== undefined) + .map(route => + route.permission ? ( + + {route.name} - ) - )} - {rootAdmin && ( - // eslint-disable-next-line react/jsx-no-target-blank - - - + + ) : ( + + {route.name} + + ), )} -
    -
    - + {rootAdmin && ( + // eslint-disable-next-line react/jsx-no-target-blank + + + + )} +
    +
    - {inConflictState && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${id}`))) ? ( + {inConflictState && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${id}/`))) ? ( ) : ( - - - {routes.server.map(({ path, permission, component: Component }) => ( - - - - - - ))} - - - + + {routes.server.map(({ route, permission, component: Component }) => ( + + + + + + } + /> + ))} + + } /> + )} )} -
    + ); -}; +} + +export default ServerRouter; diff --git a/resources/scripts/routers/routes.ts b/resources/scripts/routers/routes.ts index 8fba64221..5f1da1fe7 100644 --- a/resources/scripts/routers/routes.ts +++ b/resources/scripts/routers/routes.ts @@ -1,4 +1,6 @@ -import React, { lazy } from 'react'; +import type { ComponentType } from 'react'; +import { lazy } from 'react'; + import ServerConsole from '@/components/server/console/ServerConsoleContainer'; import DatabasesContainer from '@/components/server/databases/DatabasesContainer'; import ScheduleContainer from '@/components/server/schedules/ScheduleContainer'; @@ -15,7 +17,7 @@ import ActivityLogContainer from '@/components/dashboard/activity/ActivityLogCon import ServerActivityLogContainer from '@/components/server/ServerActivityLogContainer'; // Each of the router files is already code split out appropriately — so -// all of the items above will only be loaded in when that router is loaded. +// all the items above will only be loaded in when that router is loaded. // // These specific lazy loaded routes are to avoid loading in heavy screens // for the server dashboard when they're only needed for specific instances. @@ -23,119 +25,147 @@ const FileEditContainer = lazy(() => import('@/components/server/files/FileEditC const ScheduleEditContainer = lazy(() => import('@/components/server/schedules/ScheduleEditContainer')); interface RouteDefinition { - path: string; + /** + * Route is the path that will be matched against, this field supports wildcards. + */ + route: string; + /** + * Path is the path that will be used for any navbars or links, do not use wildcards or fancy + * matchers here. If this field is left undefined, this route will not have a navigation element, + */ + path?: string; // If undefined is passed this route is still rendered into the router itself // but no navigation link is displayed in the sub-navigation menu. name: string | undefined; - component: React.ComponentType; - exact?: boolean; + component: ComponentType; + end?: boolean; } interface ServerRouteDefinition extends RouteDefinition { - permission: string | string[] | null; + permission?: string | string[]; } interface Routes { - // All of the routes available under "/account" + // All the routes available under "/account" account: RouteDefinition[]; - // All of the routes available under "/server/:id" + // All the routes available under "/server/:id" server: ServerRouteDefinition[]; } export default { account: [ { - path: '/', + route: '', + path: '', name: 'Account', component: AccountOverviewContainer, - exact: true, + end: true, }, { - path: '/api', + route: 'api', + path: 'api', name: 'API Credentials', component: AccountApiContainer, }, { - path: '/ssh', + route: 'ssh', + path: 'ssh', name: 'SSH Keys', component: AccountSSHContainer, }, { - path: '/activity', + route: 'activity', + path: 'activity', name: 'Activity', component: ActivityLogContainer, }, ], server: [ { - path: '/', + route: '', + path: '', permission: null, name: 'Console', component: ServerConsole, - exact: true, + end: true, }, { - path: '/files', + route: 'files/*', + path: 'files', permission: 'file.*', name: 'Files', component: FileManagerContainer, }, { - path: '/files/:action(edit|new)', + route: 'files/edit/*', permission: 'file.*', name: undefined, component: FileEditContainer, }, { - path: '/databases', + route: 'files/new/*', + permission: 'file.*', + name: undefined, + component: FileEditContainer, + }, + { + route: 'databases/*', + path: 'databases', permission: 'database.*', name: 'Databases', component: DatabasesContainer, }, { - path: '/schedules', + route: 'schedules/*', + path: 'schedules', permission: 'schedule.*', name: 'Schedules', component: ScheduleContainer, }, { - path: '/schedules/:id', + route: 'schedules/:id/*', permission: 'schedule.*', name: undefined, component: ScheduleEditContainer, }, { - path: '/users', + route: 'users/*', + path: 'users', permission: 'user.*', name: 'Users', component: UsersContainer, }, { - path: '/backups', + route: 'backups/*', + path: 'backups', permission: 'backup.*', name: 'Backups', component: BackupContainer, }, { - path: '/network', + route: 'network/*', + path: 'network', permission: 'allocation.*', name: 'Network', component: NetworkContainer, }, { - path: '/startup', + route: 'startup/*', + path: 'startup', permission: 'startup.*', name: 'Startup', component: StartupContainer, }, { - path: '/settings', + route: 'settings/*', + path: 'settings', permission: ['settings.*', 'file.sftp'], name: 'Settings', component: SettingsContainer, }, { - path: '/activity', + route: 'activity/*', + path: 'activity', permission: 'activity.*', name: 'Activity', component: ServerActivityLogContainer, diff --git a/resources/scripts/setup-tests.ts b/resources/scripts/setup-tests.ts deleted file mode 100644 index 7b0828bfa..000000000 --- a/resources/scripts/setup-tests.ts +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom'; diff --git a/resources/scripts/state/flashes.ts b/resources/scripts/state/flashes.ts index 088aca2f0..9585bb084 100644 --- a/resources/scripts/state/flashes.ts +++ b/resources/scripts/state/flashes.ts @@ -47,7 +47,7 @@ const flashes: FlashStore = { }), clearFlashes: action((state, payload) => { - state.items = payload ? state.items.filter((flashes) => flashes.key !== payload) : []; + state.items = payload ? state.items.filter(flashes => flashes.key !== payload) : []; }), }; diff --git a/resources/scripts/state/permissions.ts b/resources/scripts/state/permissions.ts index 2e1fc0144..41579b586 100644 --- a/resources/scripts/state/permissions.ts +++ b/resources/scripts/state/permissions.ts @@ -21,7 +21,7 @@ const permissions: GloablPermissionsStore = { state.data = payload; }), - getPermissions: thunk(async (actions) => { + getPermissions: thunk(async actions => { const permissions = await getSystemPermissions(); actions.setPermissions(permissions); diff --git a/resources/scripts/state/progress.ts b/resources/scripts/state/progress.ts index 530083d0f..98f7d1de2 100644 --- a/resources/scripts/state/progress.ts +++ b/resources/scripts/state/progress.ts @@ -13,7 +13,7 @@ const progress: ProgressStore = { continuous: false, progress: undefined, - startContinuous: action((state) => { + startContinuous: action(state => { state.continuous = true; }), @@ -21,7 +21,7 @@ const progress: ProgressStore = { state.progress = payload; }), - setComplete: action((state) => { + setComplete: action(state => { if (state.progress) { state.progress = 100; } diff --git a/resources/scripts/state/server/databases.ts b/resources/scripts/state/server/databases.ts index 46f37ea76..211275fe9 100644 --- a/resources/scripts/state/server/databases.ts +++ b/resources/scripts/state/server/databases.ts @@ -16,15 +16,15 @@ const databases: ServerDatabaseStore = { }), appendDatabase: action((state, payload) => { - if (state.data.find((database) => database.id === payload.id)) { - state.data = state.data.map((database) => (database.id === payload.id ? payload : database)); + if (state.data.find(database => database.id === payload.id)) { + state.data = state.data.map(database => (database.id === payload.id ? payload : database)); } else { state.data = [...state.data, payload]; } }), removeDatabase: action((state, payload) => { - state.data = [...state.data.filter((database) => database.id !== payload)]; + state.data = [...state.data.filter(database => database.id !== payload)]; }), }; diff --git a/resources/scripts/state/server/files.ts b/resources/scripts/state/server/files.ts index 7e4786ae4..2ca338638 100644 --- a/resources/scripts/state/server/files.ts +++ b/resources/scripts/state/server/files.ts @@ -1,13 +1,15 @@ -import { action, Action } from 'easy-peasy'; +import type { Action } from 'easy-peasy'; +import { action } from 'easy-peasy'; + import { cleanDirectoryPath } from '@/helpers'; -export interface FileUploadData { +interface FileUploadData { loaded: number; readonly abort: AbortController; readonly total: number; } -export interface ServerFileStore { +interface ServerFileStore { directory: string; selectedFiles: string[]; uploads: Record; @@ -37,15 +39,15 @@ const files: ServerFileStore = { }), appendSelectedFile: action((state, payload) => { - state.selectedFiles = state.selectedFiles.filter((f) => f !== payload).concat(payload); + state.selectedFiles = state.selectedFiles.filter(f => f !== payload).concat(payload); }), removeSelectedFile: action((state, payload) => { - state.selectedFiles = state.selectedFiles.filter((f) => f !== payload); + state.selectedFiles = state.selectedFiles.filter(f => f !== payload); }), - clearFileUploads: action((state) => { - Object.values(state.uploads).forEach((upload) => upload.abort.abort()); + clearFileUploads: action(state => { + Object.values(state.uploads).forEach(upload => upload.abort.abort()); state.uploads = {}; }), @@ -55,20 +57,27 @@ const files: ServerFileStore = { }), setUploadProgress: action((state, { name, loaded }) => { - if (state.uploads[name]) { - state.uploads[name].loaded = loaded; + const upload = state.uploads[name]; + if (upload === undefined) { + return; } + + upload.loaded = loaded; }), removeFileUpload: action((state, payload) => { - if (state.uploads[payload]) { - // Abort the request if it is still in flight. If it already completed this is - // a no-op. - state.uploads[payload].abort.abort(); - - delete state.uploads[payload]; + const upload = state.uploads[payload]; + if (upload === undefined) { + return; } + + // Abort the request if it is still in flight. If it already completed this is + // a no-op. + upload.abort.abort(); + + delete state.uploads[payload]; }), }; +export type { FileUploadData, ServerFileStore }; export default files; diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index f9806b649..87d2088b0 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -1,13 +1,20 @@ -import getServer, { Server } from '@/api/server/getServer'; -import { action, Action, computed, Computed, createContextStore, thunk, Thunk } from 'easy-peasy'; -import socket, { SocketStore } from './socket'; -import files, { ServerFileStore } from '@/state/server/files'; -import subusers, { ServerSubuserStore } from '@/state/server/subusers'; -import { composeWithDevTools } from 'redux-devtools-extension'; -import schedules, { ServerScheduleStore } from '@/state/server/schedules'; -import databases, { ServerDatabaseStore } from '@/state/server/databases'; +import type { Action, Computed, Thunk } from 'easy-peasy'; +import { action, computed, createContextStore, thunk } from 'easy-peasy'; import isEqual from 'react-fast-compare'; +import type { Server } from '@/api/server/getServer'; +import getServer from '@/api/server/getServer'; +import type { ServerDatabaseStore } from '@/state/server/databases'; +import databases from '@/state/server/databases'; +import type { ServerFileStore } from '@/state/server/files'; +import files from '@/state/server/files'; +import type { ServerScheduleStore } from '@/state/server/schedules'; +import schedules from '@/state/server/schedules'; +import type { SocketStore } from '@/state/server/socket'; +import socket from '@/state/server/socket'; +import type { ServerSubuserStore } from '@/state/server/subusers'; +import subusers from '@/state/server/subusers'; + export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running' | null; interface ServerDataStore { @@ -25,7 +32,7 @@ interface ServerDataStore { const server: ServerDataStore = { permissions: [], - inConflictState: computed((state) => { + inConflictState: computed(state => { if (!state.data) { return false; } @@ -33,7 +40,7 @@ const server: ServerDataStore = { return state.data.status !== null || state.data.isTransferring || state.data.isNodeUnderMaintenance; }), - isInstalling: computed((state) => { + isInstalling: computed(state => { return state.data?.status === 'installing' || state.data?.status === 'install_failed'; }), @@ -87,37 +94,29 @@ export interface ServerStore { clearServerState: Action; } -export const ServerContext = createContextStore( - { - server, - socket, - status, - databases, - files, - subusers, - schedules, - clearServerState: action((state) => { - state.server.data = undefined; - state.server.permissions = []; - state.databases.data = []; - state.subusers.data = []; - state.files.directory = '/'; - state.files.selectedFiles = []; - state.schedules.data = []; +export const ServerContext = createContextStore({ + server, + socket, + status, + databases, + files, + subusers, + schedules, + clearServerState: action(state => { + state.server.data = undefined; + state.server.permissions = []; + state.databases.data = []; + state.subusers.data = []; + state.files.directory = '/'; + state.files.selectedFiles = []; + state.schedules.data = []; - if (state.socket.instance) { - state.socket.instance.removeAllListeners(); - state.socket.instance.close(); - } + if (state.socket.instance) { + state.socket.instance.removeAllListeners(); + state.socket.instance.close(); + } - state.socket.instance = null; - state.socket.connected = false; - }), - }, - { - compose: composeWithDevTools({ - name: 'ServerStore', - trace: true, - }), - } -); + state.socket.instance = null; + state.socket.connected = false; + }), +}); diff --git a/resources/scripts/state/server/schedules.ts b/resources/scripts/state/server/schedules.ts index 416cc8a49..f4e73ac75 100644 --- a/resources/scripts/state/server/schedules.ts +++ b/resources/scripts/state/server/schedules.ts @@ -16,15 +16,15 @@ const schedules: ServerScheduleStore = { }), appendSchedule: action((state, payload) => { - if (state.data.find((schedule) => schedule.id === payload.id)) { - state.data = state.data.map((schedule) => (schedule.id === payload.id ? payload : schedule)); + if (state.data.find(schedule => schedule.id === payload.id)) { + state.data = state.data.map(schedule => (schedule.id === payload.id ? payload : schedule)); } else { state.data = [...state.data, payload]; } }), removeSchedule: action((state, payload) => { - state.data = [...state.data.filter((schedule) => schedule.id !== payload)]; + state.data = [...state.data.filter(schedule => schedule.id !== payload)]; }), }; diff --git a/resources/scripts/state/server/subusers.ts b/resources/scripts/state/server/subusers.ts index e727211f4..64359b06e 100644 --- a/resources/scripts/state/server/subusers.ts +++ b/resources/scripts/state/server/subusers.ts @@ -60,7 +60,7 @@ const subusers: ServerSubuserStore = { let matched = false; state.data = [ ...state.data - .map((user) => { + .map(user => { if (user.uuid === payload.uuid) { matched = true; @@ -74,7 +74,7 @@ const subusers: ServerSubuserStore = { }), removeSubuser: action((state, payload) => { - state.data = [...state.data.filter((user) => user.uuid !== payload)]; + state.data = [...state.data.filter(user => user.uuid !== payload)]; }), }; diff --git a/resources/scripts/theme.ts b/resources/scripts/theme.ts index 8679ebaa7..737d5225c 100644 --- a/resources/scripts/theme.ts +++ b/resources/scripts/theme.ts @@ -1,4 +1,5 @@ -import { BreakpointFunction, createBreakpoint } from 'styled-components-breakpoint'; +import type { BreakpointFunction } from 'styled-components-breakpoint'; +import { createBreakpoint } from 'styled-components-breakpoint'; type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; export const breakpoint: BreakpointFunction = createBreakpoint({ diff --git a/resources/views/templates/wrapper.blade.php b/resources/views/templates/wrapper.blade.php index 1105a0724..8624d2e85 100644 --- a/resources/views/templates/wrapper.blade.php +++ b/resources/views/templates/wrapper.blade.php @@ -1,5 +1,5 @@ - + {{ config('app.name', 'Pterodactyl') }} @@ -39,6 +39,9 @@ @yield('assets') @include('layouts.scripts') + + @viteReactRefresh + @vite('resources/scripts/index.tsx') @section('content') @@ -46,8 +49,5 @@ @yield('container') @yield('below-container') @show - @section('scripts') - {!! $asset->js('main.js') !!} - @show diff --git a/shell.nix b/shell.nix index afe21af5e..682c88f42 100644 --- a/shell.nix +++ b/shell.nix @@ -1,17 +1,14 @@ -{pkgs ? import {}}: +{ + pkgs ? import {}, + php81WithExtensions, +}: with pkgs; mkShell rec { buildInputs = [ alejandra - (php81.buildEnv { - extensions = ({ enabled, all }: enabled ++ (with all; [ - redis - xdebug - ])); - extraConfig = '' - xdebug.mode=debug - ''; - }) - php81Packages.composer + nodejs-18_x + nodePackages.yarn + php81WithExtensions + (php81Packages.composer.override {php = php81WithExtensions;}) ]; } diff --git a/tailwind.config.js b/tailwind.config.js index 7c814bd61..dd5e17530 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -14,9 +14,7 @@ const gray = { }; module.exports = { - content: [ - './resources/scripts/**/*.{js,ts,tsx}', - ], + content: ['./resources/scripts/**/*.{js,ts,tsx}'], theme: { extend: { fontFamily: { @@ -47,5 +45,5 @@ module.exports = { require('@tailwindcss/forms')({ strategy: 'class', }), - ] + ], }; diff --git a/tsconfig.json b/tsconfig.json index 578a73e31..4e3d49923 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,47 +1,55 @@ { - "compilerOptions": { - "target": "es2015", - "module": "es2020", - "jsx": "react", - "moduleResolution": "node", - "lib": [ - "es2015", - "dom" - ], - "strict": true, - "noEmit": true, - "sourceMap": true, - "noImplicitReturns": true, - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "importsNotUsedAsValues": "preserve", - "paths": { - "@/*": [ - "./resources/scripts/*" - ], - "@definitions/*": [ - "./resources/scripts/api/definitions/*" - ], - "@feature/*": [ - "./resources/scripts/components/server/features/*" - ] + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "jsx": "react-jsx", + "module": "ESNext", + "target": "ES2019", + "paths": { + "@/*": [ + "./resources/scripts/*" + ], + "@definitions/*": [ + "./resources/scripts/api/definitions/*" + ], + "@feature/*": [ + "./resources/scripts/components/server/features/*" + ] + }, + "plugins": [ + { + "name": "typescript-plugin-tw-template" + } + ], + + "allowJs": false, + "allowSyntheticDefaultImports": true, + "composite": false, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "moduleResolution": "Node", + "newLine": "lf", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "stripInternal": true, + "useDefineForClassFields": true }, - "plugins": [ - { - "name": "typescript-plugin-tw-template" - } - ], - "typeRoots": [ - "node_modules/@types" - ] - }, - "include": [ - "./resources/scripts/**/*" - ], - "exclude": [ - "/node_modules/" - ] + "include": ["./resources/scripts/**/*", "vite.config.ts"], + "exclude": ["node_modules"] } diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 000000000..cd326f6e5 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,62 @@ +/// +import react from '@vitejs/plugin-react'; +import laravel from 'laravel-vite-plugin'; +import { dirname, resolve } from 'pathe'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; + +const plugins = [ + react({ + babel: { + plugins: ['babel-plugin-macros', 'babel-plugin-styled-components'], + }, + }), +]; + +if (process.env.VITEST === undefined) { + plugins.push( + laravel({ + input: 'resources/scripts/index.tsx', + }), + ); +} + +export default defineConfig({ + define: + process.env.VITEST === undefined + ? { + 'process.env': {}, + 'process.platform': null, + 'process.version': null, + 'process.versions': null, + } + : undefined, + + plugins, + + resolve: { + alias: { + '@': resolve(dirname(fileURLToPath(import.meta.url)), 'resources', 'scripts'), + '@definitions': resolve( + dirname(fileURLToPath(import.meta.url)), + 'resources', + 'scripts', + 'api', + 'definitions', + ), + '@feature': resolve( + dirname(fileURLToPath(import.meta.url)), + 'resources', + 'scripts', + 'components', + 'server', + 'features', + ), + }, + }, + + test: { + environment: 'happy-dom', + include: ['resources/scripts/**/*.{spec,test}.{ts,tsx}'], + }, +}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index aa240fbd6..000000000 --- a/webpack.config.js +++ /dev/null @@ -1,156 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const AssetsManifestPlugin = require('webpack-assets-manifest'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - -const isProduction = process.env.NODE_ENV === 'production'; - -module.exports = { - cache: true, - target: 'web', - mode: process.env.NODE_ENV, - devtool: isProduction ? false : (process.env.DEVTOOL || 'eval-source-map'), - performance: { - hints: false, - }, - entry: ['react-hot-loader/patch', './resources/scripts/index.tsx'], - output: { - path: path.join(__dirname, '/public/assets'), - filename: isProduction ? 'bundle.[chunkhash:8].js' : 'bundle.[hash:8].js', - chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].[hash:8].js', - publicPath: (process.env.WEBPACK_PUBLIC_PATH || '/assets/'), - crossOriginLoading: 'anonymous', - }, - module: { - rules: [ - { - test: /\.tsx?$/, - exclude: /node_modules|\.spec\.tsx?$/, - loader: 'babel-loader', - }, - { - test: /\.mjs$/, - include: /node_modules/, - type: 'javascript/auto', - }, - { - test: /\.css$/, - use: [ - { loader: 'style-loader' }, - { - loader: 'css-loader', - options: { - modules: { - auto: true, - localIdentName: isProduction ? '[name]_[hash:base64:8]' : '[path][name]__[local]', - localIdentContext: path.join(__dirname, 'resources/scripts/components'), - }, - sourceMap: !isProduction, - importLoaders: 1, - }, - }, - { - loader: 'postcss-loader', - options: { sourceMap: !isProduction }, - }, - ], - }, - { - test: /\.(png|jp(e?)g|gif)$/, - loader: 'file-loader', - options: { - name: 'images/[name].[hash:8].[ext]', - }, - }, - { - test: /\.svg$/, - loader: 'svg-url-loader', - }, - { - test: /\.js$/, - enforce: 'pre', - loader: 'source-map-loader', - } - ], - }, - stats: { - // Ignore warnings emitted by "source-map-loader" when trying to parse source maps from - // JS plugins we use, namely brace editor. - warningsFilter: [/Failed to parse source map/], - }, - resolve: { - extensions: ['.ts', '.tsx', '.js', '.json'], - alias: { - '@': path.join(__dirname, '/resources/scripts'), - '@definitions': path.join(__dirname, '/resources/scripts/api/definitions'), - '@feature': path.join(__dirname, '/resources/scripts/components/server/features'), - }, - symlinks: false, - }, - externals: { - // Mark moment as an external to exclude it from the Chart.js build since we don't need to use - // it for anything. - moment: 'moment', - }, - plugins: [ - new webpack.EnvironmentPlugin({ - NODE_ENV: 'development', - DEBUG: process.env.NODE_ENV !== 'production', - WEBPACK_BUILD_HASH: Date.now().toString(16), - }), - new AssetsManifestPlugin({ writeToDisk: true, publicPath: true, integrity: true, integrityHashes: ['sha384'] }), - new ForkTsCheckerWebpackPlugin({ - typescript: { - mode: 'write-references', - diagnosticOptions: { - semantic: true, - syntactic: true, - }, - }, - eslint: isProduction ? undefined : { - files: `${path.join(__dirname, '/resources/scripts')}/**/*.{ts,tsx}`, - } - }), - process.env.ANALYZE_BUNDLE ? new BundleAnalyzerPlugin({ - analyzerHost: '0.0.0.0', - analyzerPort: 8081, - }) : null - ].filter(p => p), - optimization: { - usedExports: true, - sideEffects: false, - runtimeChunk: false, - removeEmptyChunks: true, - minimize: isProduction, - minimizer: [ - new TerserPlugin({ - cache: isProduction, - parallel: true, - extractComments: false, - terserOptions: { - mangle: true, - output: { - comments: false, - }, - }, - }), - ], - }, - watchOptions: { - poll: 1000, - ignored: /node_modules/, - }, - devServer: { - compress: true, - contentBase: path.join(__dirname, '/public'), - publicPath: process.env.WEBPACK_PUBLIC_PATH || '/assets/', - allowedHosts: [ - '.pterodactyl.test', - ], - headers: { - 'Access-Control-Allow-Origin': '*', - }, - }, -}; diff --git a/yarn.lock b/yarn.lock index 3142ac755..8c7463126 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,47 +10,47 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: "@babel/highlight" "^7.18.6" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": +"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: "@babel/highlight" "^7.16.7" -"@babel/compat-data@^7.12.1", "@babel/compat-data@^7.17.10": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" - integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== +"@babel/compat-data@^7.19.3": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" + integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== -"@babel/core@^7.11.6", "@babel/core@^7.12.1", "@babel/core@^7.12.3": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" - integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== +"@babel/core@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" + integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== dependencies: "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-compilation-targets" "^7.18.2" - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helpers" "^7.18.2" - "@babel/parser" "^7.18.5" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.5" - "@babel/types" "^7.18.4" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helpers" "^7.19.4" + "@babel/parser" "^7.19.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.18.2", "@babel/generator@^7.7.2": +"@babel/generator@^7.18.2": version "7.18.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== @@ -59,90 +59,50 @@ "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.16.0": +"@babel/generator@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" + integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== + dependencies: + "@babel/types" "^7.19.4" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.16.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" - integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== dependencies: - "@babel/helper-explode-assignable-expression" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/types" "^7.18.6" -"@babel/helper-builder-react-jsx-experimental@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.1.tgz#1f1ad4c95f1d059856d2fdbc0763489d020cd02d" - integrity sha512-82to8lR7TofZWbTd3IEZT1xNHfeU/Ef4rDm/GLXddzqDh+yQ19QuGSzqww51aNxVH8rwfRIzL0EUQsvODVhtyw== +"@babel/helper-compilation-targets@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" + integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-module-imports" "^7.12.1" - "@babel/types" "^7.12.1" - -"@babel/helper-builder-react-jsx@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz#8095cddbff858e6fa9c326daee54a2f2732c1d5d" - integrity sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-compilation-targets@^7.12.1", "@babel/helper-compilation-targets@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" - integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== - dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.20.2" + "@babel/compat-data" "^7.19.3" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz#3c45998f431edd4a9214c5f1d3ad1448a6137f6e" - integrity sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-member-expression-to-functions" "^7.12.1" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/helper-replace-supers" "^7.12.1" - "@babel/helper-split-export-declaration" "^7.10.4" - -"@babel/helper-create-regexp-features-plugin@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz#18b1302d4677f9dc4740fe8c9ed96680e29d37e8" - integrity sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-regex" "^7.10.4" - regexpu-core "^4.7.1" - -"@babel/helper-define-map@^7.10.4": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" - integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/types" "^7.10.5" - lodash "^4.17.19" - -"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": +"@babel/helper-environment-visitor@^7.18.2": version "7.18.2" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== -"@babel/helper-explode-assignable-expression@^7.10.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz#8006a466695c4ad86a2a5f2fb15b5f2c31ad5633" - integrity sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA== - dependencies: - "@babel/types" "^7.12.1" +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-function-name@^7.10.4", "@babel/helper-function-name@^7.17.9": +"@babel/helper-function-name@^7.17.9": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== @@ -150,133 +110,110 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.17.0" -"@babel/helper-hoist-variables@^7.10.4", "@babel/helper-hoist-variables@^7.16.7": +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" - integrity sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ== +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== dependencies: - "@babel/types" "^7.12.1" + "@babel/types" "^7.18.6" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" - integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.0" - "@babel/types" "^7.18.0" + "@babel/types" "^7.18.6" -"@babel/helper-optimise-call-expression@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" - integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== +"@babel/helper-module-transforms@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" + integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== dependencies: - "@babel/types" "^7.10.4" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" - integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== +"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== -"@babel/helper-regex@^7.10.4": - version "7.10.5" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" - integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== +"@babel/helper-simple-access@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" + integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== dependencies: - lodash "^4.17.19" + "@babel/types" "^7.19.4" -"@babel/helper-remap-async-to-generator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz#8c4dbbf916314f6047dc05e6a2217074238347fd" - integrity sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-wrap-function" "^7.10.4" - "@babel/types" "^7.12.1" - -"@babel/helper-replace-supers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz#f15c9cc897439281891e11d5ce12562ac0cf3fa9" - integrity sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.12.1" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/traverse" "^7.12.1" - "@babel/types" "^7.12.1" - -"@babel/helper-simple-access@^7.17.7", "@babel/helper-simple-access@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" - integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== - dependencies: - "@babel/types" "^7.18.2" - -"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" - integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== - dependencies: - "@babel/types" "^7.12.1" - -"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.16.7": +"@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-validator-identifier@^7.10.4": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== "@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== -"@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" - integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== +"@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-wrap-function@^7.10.4": - version "7.12.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz#3332339fc4d1fbbf1c27d7958c27d34708e990d9" - integrity sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helpers@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" - integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== +"@babel/helpers@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" + integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.2" - "@babel/types" "^7.18.2" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.4" + "@babel/types" "^7.19.4" "@babel/highlight@^7.16.7", "@babel/highlight@^7.18.6": version "7.18.6" @@ -287,658 +224,79 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.5", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": +"@babel/parser@^7.12.5", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": version "7.18.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== -"@babel/plugin-proposal-async-generator-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e" - integrity sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.12.1" - "@babel/plugin-syntax-async-generators" "^7.8.0" - -"@babel/plugin-proposal-class-properties@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de" - integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-proposal-dynamic-import@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz#43eb5c2a3487ecd98c5c8ea8b5fdb69a2749b2dc" - integrity sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - -"@babel/plugin-proposal-export-namespace-from@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz#8b9b8f376b2d88f5dd774e4d24a5cc2e3679b6d4" - integrity sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz#d45423b517714eedd5621a9dfdc03fa9f4eb241c" - integrity sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.0" - -"@babel/plugin-proposal-logical-assignment-operators@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz#f2c490d36e1b3c9659241034a5d2cd50263a2751" - integrity sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz#3ed4fff31c015e7f3f1467f190dbe545cd7b046c" - integrity sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - -"@babel/plugin-proposal-numeric-separator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz#0e2c6774c4ce48be412119b4d693ac777f7685a6" - integrity sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" - integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.12.1" - -"@babel/plugin-proposal-optional-catch-binding@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz#ccc2421af64d3aae50b558a71cede929a5ab2942" - integrity sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - -"@babel/plugin-proposal-optional-chaining@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz#cce122203fc8a32794296fc377c6dedaf4363797" - integrity sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - -"@babel/plugin-proposal-private-methods@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz#86814f6e7a21374c980c10d38b4493e703f4a389" - integrity sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-proposal-unicode-property-regex@^7.12.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz#2a183958d417765b9eae334f47758e5d6a82e072" - integrity sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.1", "@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" - integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.12.1", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" - integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-transform-arrow-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz#8083ffc86ac8e777fbe24b5967c4b2521f3cb2b3" - integrity sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-async-to-generator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz#3849a49cc2a22e9743cbd6b52926d30337229af1" - integrity sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A== - dependencies: - "@babel/helper-module-imports" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.12.1" - -"@babel/plugin-transform-block-scoped-functions@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz#f2a1a365bde2b7112e0a6ded9067fdd7c07905d9" - integrity sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-block-scoping@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz#f0ee727874b42a208a48a586b84c3d222c2bbef1" - integrity sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-classes@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz#65e650fcaddd3d88ddce67c0f834a3d436a32db6" - integrity sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-define-map" "^7.10.4" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.12.1" - "@babel/helper-split-export-declaration" "^7.10.4" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz#d68cf6c9b7f838a8a4144badbe97541ea0904852" - integrity sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-destructuring@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz#b9a570fe0d0a8d460116413cb4f97e8e08b2f847" - integrity sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-dotall-regex@^7.12.1", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz#a1d16c14862817b6409c0a678d6f9373ca9cd975" - integrity sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-duplicate-keys@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz#745661baba295ac06e686822797a69fbaa2ca228" - integrity sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-exponentiation-operator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz#b0f2ed356ba1be1428ecaf128ff8a24f02830ae0" - integrity sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-for-of@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz#07640f28867ed16f9511c99c888291f560921cfa" - integrity sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-function-name@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz#2ec76258c70fe08c6d7da154003a480620eba667" - integrity sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz#d73b803a26b37017ddf9d3bb8f4dc58bfb806f57" - integrity sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-member-expression-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz#496038602daf1514a64d43d8e17cbb2755e0c3ad" - integrity sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-modules-amd@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz#3154300b026185666eebb0c0ed7f8415fefcf6f9" - integrity sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ== - dependencies: - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-commonjs@^7.12.1", "@babel/plugin-transform-modules-commonjs@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz#1aa8efa2e2a6e818b6a7f2235fceaf09bdb31e9e" - integrity sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ== - dependencies: - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-simple-access" "^7.18.2" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-systemjs@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz#663fea620d593c93f214a464cd399bf6dc683086" - integrity sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q== - dependencies: - "@babel/helper-hoist-variables" "^7.10.4" - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-validator-identifier" "^7.10.4" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-umd@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz#eb5a218d6b1c68f3d6217b8fa2cc82fec6547902" - integrity sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q== - dependencies: - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz#b407f5c96be0d9f5f88467497fa82b30ac3e8753" - integrity sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" - -"@babel/plugin-transform-new-target@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz#80073f02ee1bb2d365c3416490e085c95759dec0" - integrity sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-object-super@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz#4ea08696b8d2e65841d0c7706482b048bed1066e" - integrity sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.12.1" - -"@babel/plugin-transform-parameters@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz#d2e963b038771650c922eff593799c96d853255d" - integrity sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-property-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz#41bc81200d730abb4456ab8b3fbd5537b59adecd" - integrity sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-react-display-name@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz#1cbcd0c3b1d6648c55374a22fc9b6b7e5341c00d" - integrity sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-react-jsx-development@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.1.tgz#0b8f8cd531dcf7991f1e5f2c10a2a4f1cfc78e36" - integrity sha512-IilcGWdN1yNgEGOrB96jbTplRh+V2Pz1EoEwsKsHfX1a/L40cUYuD71Zepa7C+ujv7kJIxnDftWeZbKNEqZjCQ== - dependencies: - "@babel/helper-builder-react-jsx-experimental" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.12.1" - -"@babel/plugin-transform-react-jsx-self@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz#ef43cbca2a14f1bd17807dbe4376ff89d714cf28" - integrity sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-react-jsx-source@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz#d07de6863f468da0809edcf79a1aa8ce2a82a26b" - integrity sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-react-jsx@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.1.tgz#c2d96c77c2b0e4362cc4e77a43ce7c2539d478cb" - integrity sha512-RmKejwnT0T0QzQUzcbP5p1VWlpnP8QHtdhEtLG55ZDQnJNalbF3eeDyu3dnGKvGzFIQiBzFhBYTwvv435p9Xpw== - dependencies: - "@babel/helper-builder-react-jsx" "^7.10.4" - "@babel/helper-builder-react-jsx-experimental" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx" "^7.12.1" - -"@babel/plugin-transform-react-pure-annotations@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz#05d46f0ab4d1339ac59adf20a1462c91b37a1a42" - integrity sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-regenerator@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz#5f0a28d842f6462281f06a964e88ba8d7ab49753" - integrity sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng== - dependencies: - regenerator-transform "^0.14.2" - -"@babel/plugin-transform-reserved-words@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz#6fdfc8cc7edcc42b36a7c12188c6787c873adcd8" - integrity sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-runtime@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz#04b792057eb460389ff6a4198e377614ea1e7ba5" - integrity sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg== - dependencies: - "@babel/helper-module-imports" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - resolve "^1.8.1" - semver "^5.5.1" - -"@babel/plugin-transform-shorthand-properties@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz#0bf9cac5550fce0cfdf043420f661d645fdc75e3" - integrity sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-spread@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz#527f9f311be4ec7fdc2b79bb89f7bf884b3e1e1e" - integrity sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" - -"@babel/plugin-transform-sticky-regex@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz#5c24cf50de396d30e99afc8d1c700e8bce0f5caf" - integrity sha512-CiUgKQ3AGVk7kveIaPEET1jNDhZZEl1RPMWdTBE1799bdz++SwqDHStmxfCtDfBhQgCl38YRiSnrMuUMZIWSUQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-regex" "^7.10.4" - -"@babel/plugin-transform-template-literals@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz#b43ece6ed9a79c0c71119f576d299ef09d942843" - integrity sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-typeof-symbol@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz#9ca6be343d42512fbc2e68236a82ae64bc7af78a" - integrity sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-typescript@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz#d92cc0af504d510e26a754a7dbc2e5c8cd9c7ab4" - integrity sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-typescript" "^7.12.1" - -"@babel/plugin-transform-unicode-escapes@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz#5232b9f81ccb07070b7c3c36c67a1b78f1845709" - integrity sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" +"@babel/parser@^7.18.10", "@babel/parser@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" + integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== -"@babel/plugin-transform-unicode-regex@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz#cc9661f61390db5c65e3febaccefd5c6ac3faecb" - integrity sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg== +"@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.1.tgz#9c7e5ca82a19efc865384bb4989148d2ee5d7ac2" - integrity sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg== +"@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== dependencies: - "@babel/compat-data" "^7.12.1" - "@babel/helper-compilation-targets" "^7.12.1" - "@babel/helper-module-imports" "^7.12.1" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-validator-option" "^7.12.1" - "@babel/plugin-proposal-async-generator-functions" "^7.12.1" - "@babel/plugin-proposal-class-properties" "^7.12.1" - "@babel/plugin-proposal-dynamic-import" "^7.12.1" - "@babel/plugin-proposal-export-namespace-from" "^7.12.1" - "@babel/plugin-proposal-json-strings" "^7.12.1" - "@babel/plugin-proposal-logical-assignment-operators" "^7.12.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" - "@babel/plugin-proposal-numeric-separator" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread" "^7.12.1" - "@babel/plugin-proposal-optional-catch-binding" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.12.1" - "@babel/plugin-proposal-private-methods" "^7.12.1" - "@babel/plugin-proposal-unicode-property-regex" "^7.12.1" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.12.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.12.1" - "@babel/plugin-transform-arrow-functions" "^7.12.1" - "@babel/plugin-transform-async-to-generator" "^7.12.1" - "@babel/plugin-transform-block-scoped-functions" "^7.12.1" - "@babel/plugin-transform-block-scoping" "^7.12.1" - "@babel/plugin-transform-classes" "^7.12.1" - "@babel/plugin-transform-computed-properties" "^7.12.1" - "@babel/plugin-transform-destructuring" "^7.12.1" - "@babel/plugin-transform-dotall-regex" "^7.12.1" - "@babel/plugin-transform-duplicate-keys" "^7.12.1" - "@babel/plugin-transform-exponentiation-operator" "^7.12.1" - "@babel/plugin-transform-for-of" "^7.12.1" - "@babel/plugin-transform-function-name" "^7.12.1" - "@babel/plugin-transform-literals" "^7.12.1" - "@babel/plugin-transform-member-expression-literals" "^7.12.1" - "@babel/plugin-transform-modules-amd" "^7.12.1" - "@babel/plugin-transform-modules-commonjs" "^7.12.1" - "@babel/plugin-transform-modules-systemjs" "^7.12.1" - "@babel/plugin-transform-modules-umd" "^7.12.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.1" - "@babel/plugin-transform-new-target" "^7.12.1" - "@babel/plugin-transform-object-super" "^7.12.1" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-transform-property-literals" "^7.12.1" - "@babel/plugin-transform-regenerator" "^7.12.1" - "@babel/plugin-transform-reserved-words" "^7.12.1" - "@babel/plugin-transform-shorthand-properties" "^7.12.1" - "@babel/plugin-transform-spread" "^7.12.1" - "@babel/plugin-transform-sticky-regex" "^7.12.1" - "@babel/plugin-transform-template-literals" "^7.12.1" - "@babel/plugin-transform-typeof-symbol" "^7.12.1" - "@babel/plugin-transform-unicode-escapes" "^7.12.1" - "@babel/plugin-transform-unicode-regex" "^7.12.1" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.12.1" - core-js-compat "^3.6.2" - semver "^5.5.0" + "@babel/plugin-transform-react-jsx" "^7.18.6" -"@babel/preset-modules@^0.1.3": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" - integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== +"@babel/plugin-transform-react-jsx-self@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7" + integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-react@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.12.1.tgz#7f022b13f55b6dd82f00f16d1c599ae62985358c" - integrity sha512-euCExymHCi0qB9u5fKw7rvlw7AZSjw/NaB9h7EkdTt5+yHRrXdiRTh7fkG3uBPpJg82CqLfp1LHLqWGSCrab+g== +"@babel/plugin-transform-react-jsx-source@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" + integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-transform-react-display-name" "^7.12.1" - "@babel/plugin-transform-react-jsx" "^7.12.1" - "@babel/plugin-transform-react-jsx-development" "^7.12.1" - "@babel/plugin-transform-react-jsx-self" "^7.12.1" - "@babel/plugin-transform-react-jsx-source" "^7.12.1" - "@babel/plugin-transform-react-pure-annotations" "^7.12.1" + "@babel/helper-plugin-utils" "^7.19.0" -"@babel/preset-typescript@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.12.1.tgz#86480b483bb97f75036e8864fe404cc782cc311b" - integrity sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw== +"@babel/plugin-transform-react-jsx@^7.18.6", "@babel/plugin-transform-react-jsx@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-transform-typescript" "^7.12.1" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.19.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.9.2": version "7.18.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4", "@babel/template@^7.14.5", "@babel/template@^7.16.7", "@babel/template@^7.3.3": +"@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.12.13", "@babel/template@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/template@^7.14.5", "@babel/template@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== @@ -947,7 +305,23 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.19.4", "@babel/traverse@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.6.tgz#7b4c865611df6d99cb131eec2e8ac71656a490dc" + integrity sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.6" + "@babel/types" "^7.19.4" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.4.5": version "7.18.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== @@ -963,7 +337,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.12.1", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4": version "7.18.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== @@ -971,68 +345,312 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@csstools/postcss-cascade-layers@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.0.3.tgz#71ee4a3f00f947788097f8d67310b2e4a336aa51" - integrity sha512-fvXP0+dcllGtRKAjA5n5tBr57xWQalKky09hSiXAZ9qqjHn0sDuQV2Jz0Y5zHRQ6iGrAjJZOf2+xQj3yuXfLwA== +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== dependencies: - "@csstools/selector-specificity" "^2.0.0" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@codemirror/autocomplete@^6.0.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.3.0.tgz#217e16bb6ce63374ec7b9d2a01d007ba53ff0aff" + integrity sha512-4jEvh3AjJZTDKazd10J6ZsCIqaYxDMCeua5ouQxY8hlFIml+nr7le0SgBhT3SIytFBmdzPK3AUhXGuW3T79nVg== + dependencies: + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + +"@codemirror/commands@^6.0.0": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.1.2.tgz#84fb7d170047c3aeb7b0047ace59510bb19208de" + integrity sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg== + dependencies: + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + +"@codemirror/lang-cpp@^6.0.0": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz#076c98340c3beabde016d7d83e08eebe17254ef9" + integrity sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg== + dependencies: + "@codemirror/language" "^6.0.0" + "@lezer/cpp" "^1.0.0" + +"@codemirror/lang-css@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-6.0.1.tgz#470fff614e4cfbbe796ec43103420d59c797dd7a" + integrity sha512-rlLq1Dt0WJl+2epLQeAsfqIsx3lGu4HStHCJu95nGGuz2P2fNugbU3dQYafr2VRjM4eMC9HviI6jvS98CNtG5w== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/css" "^1.0.0" + +"@codemirror/lang-html@^6.0.0": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.1.3.tgz#b13f542e50c210f8f8ada2fb08f12836593e07fe" + integrity sha512-LmtIElopGK6bBfddAyjBitS6hz8nFr/PVUtvqmfomXlHB4m+Op2d5eGk/X9/CSby6Y8NqXXkGa3yDd9lfJ6Qlg== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/lang-css" "^6.0.0" + "@codemirror/lang-javascript" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.2.2" + "@lezer/common" "^1.0.0" + "@lezer/html" "^1.0.1" + +"@codemirror/lang-java@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-java/-/lang-java-6.0.1.tgz#03bd06334da7c8feb9dff6db01ac6d85bd2e48bb" + integrity sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg== + dependencies: + "@codemirror/language" "^6.0.0" + "@lezer/java" "^1.0.0" + +"@codemirror/lang-javascript@^6.0.0": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.1.1.tgz#f920192db30531927a02b8a1af9cf3c3d895101c" + integrity sha512-F4+kiuC5d5dUSJmff96tJQwpEXs/tX/4bapMRnZWW6bHKK1Fx6MunTzopkCUWRa9bF87GPmb9m7Qtg7Yv8f3uQ== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/lint" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/javascript" "^1.0.0" + +"@codemirror/lang-json@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz#0a0be701a5619c4b0f8991f9b5e95fe33f462330" + integrity sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ== + dependencies: + "@codemirror/language" "^6.0.0" + "@lezer/json" "^1.0.0" + +"@codemirror/lang-lezer@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-lezer/-/lang-lezer-6.0.1.tgz#16a5909ab8ab4a23e9b214476413dc92a3191780" + integrity sha512-WHwjI7OqKFBEfkunohweqA5B/jIlxaZso6Nl3weVckz8EafYbPZldQEKSDb4QQ9H9BUkle4PVELP4sftKoA0uQ== + dependencies: + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/lezer" "^1.0.0" + +"@codemirror/lang-markdown@^6.0.0": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@codemirror/lang-markdown/-/lang-markdown-6.0.4.tgz#184eebadb232a5fb60cfcb66163da838ba646983" + integrity sha512-w50etMCYnm4btsVwOkREVc73sHk2+ZXA0q0nb7hNhjQ/NeEix9jRa63l/FUgrsfG2jjuRqsXTNjGdmmcorkTBQ== + dependencies: + "@codemirror/lang-html" "^6.0.0" + "@codemirror/language" "^6.3.0" + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/markdown" "^1.0.0" + +"@codemirror/lang-php@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-php/-/lang-php-6.0.1.tgz#fa34cc75562178325861a5731f79bd621f57ffaa" + integrity sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA== + dependencies: + "@codemirror/lang-html" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/php" "^1.0.0" + +"@codemirror/lang-python@^6.0.0": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@codemirror/lang-python/-/lang-python-6.0.4.tgz#81dc262e57cce6cba72302420b22a63c2d2a5892" + integrity sha512-CuC7V6MVw4HshQuFaB1SMXHOSbKLnBnBXMzm9Zjb+uvkggyY8fXp79T9eYFzMn7fuadoPJcXyTcT/q/SRT7lvQ== + dependencies: + "@codemirror/language" "^6.0.0" + "@lezer/python" "^1.0.0" + +"@codemirror/lang-rust@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz#d6829fc7baa39a15bcd174a41a9e0a1bf7cf6ba8" + integrity sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ== + dependencies: + "@codemirror/language" "^6.0.0" + "@lezer/rust" "^1.0.0" + +"@codemirror/lang-sql@^6.0.0": + version "6.3.2" + resolved "https://registry.yarnpkg.com/@codemirror/lang-sql/-/lang-sql-6.3.2.tgz#478bbe741daa5b2f53b647ec6d1b5ca19f1ed297" + integrity sha512-lbk2jBVvVK6NkIEn6HU3RwLh368qEcGP5bknwv6kiLGffFZHNoXj/J/F/YNXSynsgswapBofb3J6yVwsjXYQPw== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@codemirror/lang-wast@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-wast/-/lang-wast-6.0.1.tgz#c15bec84548a5e9b0a43fa69fb63631d087d6047" + integrity sha512-sQLsqhRjl2MWG3rxZysX+2XAyed48KhLBHLgq9xcKxIJu3npH/G+BIXW5NM5mHeDUjG0jcGh9BcjP0NfMStuzA== + dependencies: + "@codemirror/language" "^6.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@codemirror/lang-xml@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@codemirror/lang-xml/-/lang-xml-6.0.1.tgz#ac2dd701d26683163543248b5abc56829ba7fcc6" + integrity sha512-0tvycUTElajCcRKgsszhKjWX+uuOogdu5+enpfqYA+j0gnP8ek7LRxujh2/XMPRdXt/hwOML4slJLE7r2eX3yQ== + dependencies: + "@codemirror/autocomplete" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/state" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/xml" "^1.0.0" + +"@codemirror/language-data@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@codemirror/language-data/-/language-data-6.1.0.tgz#479eff66289a6453493f7c8213d7b2ceb95c89f6" + integrity sha512-g9V23fuLRI9AEbpM6bDy1oquqgpFlIDHTihUhL21NPmxp+x67ZJbsKk+V71W7/Bj8SCqEO1PtqQA/tDGgt1nfw== + dependencies: + "@codemirror/lang-cpp" "^6.0.0" + "@codemirror/lang-css" "^6.0.0" + "@codemirror/lang-html" "^6.0.0" + "@codemirror/lang-java" "^6.0.0" + "@codemirror/lang-javascript" "^6.0.0" + "@codemirror/lang-json" "^6.0.0" + "@codemirror/lang-markdown" "^6.0.0" + "@codemirror/lang-php" "^6.0.0" + "@codemirror/lang-python" "^6.0.0" + "@codemirror/lang-rust" "^6.0.0" + "@codemirror/lang-sql" "^6.0.0" + "@codemirror/lang-wast" "^6.0.0" + "@codemirror/lang-xml" "^6.0.0" + "@codemirror/language" "^6.0.0" + "@codemirror/legacy-modes" "^6.1.0" + +"@codemirror/language@^6.0.0", "@codemirror/language@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.3.0.tgz#141c715e1fce5f6dcca3b1b984ed8f03f583dd5c" + integrity sha512-6jOE5DEt6sKD46SXhn3xPbBehn+l48ACcA6Uxs2k+E2YNH9XGF5WdGMTYr2DlggfK4h0QZBK6zEb5S7lkTriWA== + dependencies: + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + "@lezer/common" "^1.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + style-mod "^4.0.0" + +"@codemirror/legacy-modes@^6.0.0", "@codemirror/legacy-modes@^6.1.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@codemirror/legacy-modes/-/legacy-modes-6.2.0.tgz#473016ccafba0990f23c981b678843194be5e131" + integrity sha512-RtZfwALTSswzKsnU3zo5FytDBA+/6J85Z8qNO7hnJ3Lo+jbdKUHreiw5m9yAT1DoB5WFhSZODsSijksdSbnGqA== + dependencies: + "@codemirror/language" "^6.0.0" + +"@codemirror/lint@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.0.0.tgz#a249b021ac9933b94fe312d994d220f0ef11a157" + integrity sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA== + dependencies: + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + crelt "^1.0.5" + +"@codemirror/search@^6.0.0": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.2.2.tgz#278ac204bd19a038271595ce060ad32c13eb70a6" + integrity sha512-2pWY599zXk+lSoJ2iv9EuTO4gB7lhgBPLPwFb/zTbimFH4NmZSaKzJSV51okjABZ7/Rj0DYy5klWbIgaJh2LoQ== + dependencies: + "@codemirror/state" "^6.0.0" + "@codemirror/view" "^6.0.0" + crelt "^1.0.5" + +"@codemirror/state@^6.0.0": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.1.2.tgz#182d46eabcc17c95508984d6add5a5a641dcd517" + integrity sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA== + +"@codemirror/view@^6.0.0", "@codemirror/view@^6.2.2": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.4.0.tgz#f8c213ab6dfec56048b372d2c378213428e2b4e5" + integrity sha512-Kv32b6Tn7QVwFbj/EDswTLSocjk5kgggF6zzBFAL4o4hZ/vmtFD155+EjH1pVlbfoDyVC2M6SedPsMrwYscgNg== + dependencies: + "@codemirror/state" "^6.0.0" + style-mod "^4.0.0" + w3c-keyname "^2.2.4" + +"@csstools/postcss-cascade-layers@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz#8a997edf97d34071dd2e37ea6022447dd9e795ad" + integrity sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA== + dependencies: + "@csstools/selector-specificity" "^2.0.2" postcss-selector-parser "^6.0.10" -"@csstools/postcss-color-function@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.0.tgz#229966327747f58fbe586de35daa139db3ce1e5d" - integrity sha512-5D5ND/mZWcQoSfYnSPsXtuiFxhzmhxt6pcjrFLJyldj+p0ZN2vvRpYNX+lahFTtMhAYOa2WmkdGINr0yP0CvGA== +"@csstools/postcss-color-function@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz#2bd36ab34f82d0497cfacdc9b18d34b5e6f64b6b" + integrity sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-font-format-keywords@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.0.tgz#7e7df948a83a0dfb7eb150a96e2390ac642356a1" - integrity sha512-oO0cZt8do8FdVBX8INftvIA4lUrKUSCcWUf9IwH9IPWOgKT22oAZFXeHLoDK7nhB2SmkNycp5brxfNMRLIhd6Q== - dependencies: - postcss-value-parser "^4.2.0" - -"@csstools/postcss-hwb-function@^1.0.1": +"@csstools/postcss-font-format-keywords@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.1.tgz#5224db711ed09a965f85c80c18144ac1c2702fce" - integrity sha512-AMZwWyHbbNLBsDADWmoXT9A5yl5dsGEBeJSJRUJt8Y9n8Ziu7Wstt4MC8jtPW7xjcLecyfJwtnUTNSmOzcnWeg== + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz#677b34e9e88ae997a67283311657973150e8b16a" + integrity sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-ic-unit@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.0.tgz#f484db59fc94f35a21b6d680d23b0ec69b286b7f" - integrity sha512-i4yps1mBp2ijrx7E96RXrQXQQHm6F4ym1TOD0D69/sjDjZvQ22tqiEvaNw7pFZTUO5b9vWRHzbHzP9+UKuw+bA== +"@csstools/postcss-hwb-function@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz#ab54a9fce0ac102c754854769962f2422ae8aa8b" + integrity sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-ic-unit@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz#28237d812a124d1a16a5acc5c3832b040b303e58" + integrity sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-is-pseudo-class@^2.0.4": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.5.tgz#60fea78776fc3916ad66d568064aa31029b9f772" - integrity sha512-Ek+UFI4UP2hB9u0N1cJd6KgSF1rL0J3PT4is0oSStuus8+WzbGGPyJNMOKQ0w/tyPjxiCnOI4RdSMZt3nks64g== +"@csstools/postcss-is-pseudo-class@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz#846ae6c0d5a1eaa878fce352c544f9c295509cd1" + integrity sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA== dependencies: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" -"@csstools/postcss-normalize-display-values@^1.0.0": +"@csstools/postcss-nested-calc@^1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.0.tgz#ce698f688c28517447aedf15a9037987e3d2dc97" - integrity sha512-bX+nx5V8XTJEmGtpWTO6kywdS725t71YSLlxWt78XoHUbELWgoCXeOFymRJmL3SU1TLlKSIi7v52EWqe60vJTQ== + resolved "https://registry.yarnpkg.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz#d7e9d1d0d3d15cf5ac891b16028af2a1044d0c26" + integrity sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-oklab-function@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.0.tgz#e9a269487a292e0930760948e923e1d46b638ee6" - integrity sha512-e/Q5HopQzmnQgqimG9v3w2IG4VRABsBq3itOcn4bnm+j4enTgQZ0nWsaH/m9GV2otWGQ0nwccYL5vmLKyvP1ww== +"@csstools/postcss-normalize-display-values@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz#15da54a36e867b3ac5163ee12c1d7f82d4d612c3" + integrity sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-oklab-function@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz#88cee0fbc8d6df27079ebd2fa016ee261eecf844" + integrity sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" @@ -1044,42 +662,66 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-stepped-value-functions@^1.0.0": +"@csstools/postcss-stepped-value-functions@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz#f8772c3681cc2befed695e2b0b1d68e22f08c4f4" + integrity sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-text-decoration-shorthand@^1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.0.tgz#f8ffc05e163ba7bcbefc5fdcaf264ce9fd408c16" - integrity sha512-q8c4bs1GumAiRenmFjASBcWSLKrbzHzWl6C2HcaAxAXIiL2rUlUWbqQZUjwVG5tied0rld19j/Mm90K3qI26vw== + resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz#ea96cfbc87d921eca914d3ad29340d9bcc4c953f" + integrity sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-trigonometric-functions@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.1.tgz#e36e61f445614193dbf6d3a8408709b0cf184a6f" - integrity sha512-G78CY/+GePc6dDCTUbwI6TTFQ5fs3N9POHhI6v0QzteGpf6ylARiJUNz9HrRKi4eVYBNXjae1W2766iUEFxHlw== +"@csstools/postcss-trigonometric-functions@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz#94d3e4774c36d35dcdc88ce091336cb770d32756" + integrity sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-unset-value@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.1.tgz#2cc020785db5ec82cc9444afe4cdae2a65445f89" - integrity sha512-f1G1WGDXEU/RN1TWAxBPQgQudtLnLQPyiWdtypkPC+mVYNKFKH/HYXSxH4MVNqwF8M0eDsoiU7HumJHCg/L/jg== +"@csstools/postcss-unset-value@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz#c99bb70e2cdc7312948d1eb41df2412330b81f77" + integrity sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g== "@csstools/selector-specificity@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.0.tgz#65b12f12db55188422070e34687bf3af09870922" integrity sha512-rZ6vufeY/UjAgtyiJ4WvfF6XP6HizIyOfbZOg0RnecIwjrvH8Am3nN1BpKnnPZunYAkUcPPXDhwbxOtGop8cfQ== -"@emotion/is-prop-valid@^0.8.2", "@emotion/is-prop-valid@^0.8.8": +"@csstools/selector-specificity@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" + integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== + +"@emotion/is-prop-valid@^0.8.2": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== dependencies: "@emotion/memoize" "0.7.4" +"@emotion/is-prop-valid@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" + integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== + dependencies: + "@emotion/memoize" "^0.8.0" + "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== +"@emotion/memoize@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" + integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== + "@emotion/stylis@^0.8.4": version "0.8.5" resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" @@ -1090,6 +732,16 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@esbuild/android-arm@0.15.12": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.12.tgz#e548b10a5e55b9e10537a049ebf0bc72c453b769" + integrity sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA== + +"@esbuild/linux-loong64@0.15.12": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.12.tgz#475b33a2631a3d8ca8aa95ee127f9a61d95bf9c1" + integrity sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -1105,86 +757,76 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@floating-ui/core@^0.7.3": - version "0.7.3" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86" - integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== +"@floating-ui/core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.1.tgz#00e64d74e911602c8533957af0cce5af6b2e93c8" + integrity sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA== -"@floating-ui/dom@^0.5.3": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1" - integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg== +"@floating-ui/dom@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.0.3.tgz#b439c8a66436c2cae8d97e889f0b76cce757a6ec" + integrity sha512-6H1kwjkOZKabApNtXRiYHvMmYJToJ1DV7rQ3xc/WJpOABhQIOJJOdz2AOejj8X+gcybaFmBpisVTZxBZAM3V0w== dependencies: - "@floating-ui/core" "^0.7.3" + "@floating-ui/core" "^1.0.1" -"@floating-ui/react-dom-interactions@^0.6.6": - version "0.6.6" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.6.tgz#8542e8c4bcbee2cd0d512de676c6a493e0a2d168" - integrity sha512-qnao6UPjSZNHnXrF+u4/n92qVroQkx0Umlhy3Avk1oIebm/5ee6yvDm4xbHob0OjY7ya8WmUnV3rQlPwX3Atwg== +"@floating-ui/react-dom-interactions@0.10.2": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.10.2.tgz#1a9c86f8bb9aa36b5926ae03d96de78579d2a70d" + integrity sha512-KhF+UN+MVqUx1bG1fe0aAiBl1hbz07Uin6UW70mxwUDhaGpitM16CYvGri1EqGY4hnWK8TQknDSP8iQFOxjhsg== dependencies: - "@floating-ui/react-dom" "^0.7.2" + "@floating-ui/react-dom" "^1.0.0" aria-hidden "^1.1.3" - use-isomorphic-layout-effect "^1.1.1" -"@floating-ui/react-dom@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864" - integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg== +"@floating-ui/react-dom@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.0.0.tgz#e0975966694433f1f0abffeee5d8e6bb69b7d16e" + integrity sha512-uiOalFKPG937UCLm42RxjESTWUVpbbatvlphQAU6bsv+ence6IoVG8JOUZcy8eW81NkU+Idiwvx10WFLmR4MIg== dependencies: - "@floating-ui/dom" "^0.5.3" - use-isomorphic-layout-effect "^1.1.1" + "@floating-ui/dom" "^1.0.0" -"@fortawesome/fontawesome-common-types@^0.2.32": - version "0.2.32" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz#3436795d5684f22742989bfa08f46f50f516f259" - integrity sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w== - -"@fortawesome/fontawesome-svg-core@^1.2.32": - version "1.2.32" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz#da092bfc7266aa274be8604de610d7115f9ba6cf" - integrity sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ== +"@flyyer/use-fit-text@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@flyyer/use-fit-text/-/use-fit-text-3.0.1.tgz#99bdcc59f4fd6659c55061dec0deb134305af7aa" + integrity sha512-bE51XCTURJrRDUCkWUsRKNT8vhzl0Ivar8T2yD43MTE3Q+fzDd+iZxy77HrqycwG35ykQdjua3cRZkDhznNboA== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.32" + dequal "^2.0.2" -"@fortawesome/free-solid-svg-icons@^5.15.1": - version "5.15.1" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz#e1432676ddd43108b41197fee9f86d910ad458ef" - integrity sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg== +"@fortawesome/fontawesome-common-types@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz#76467a94aa888aeb22aafa43eb6ff889df3a5a7f" + integrity sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg== + +"@fortawesome/fontawesome-svg-core@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz#11856eaf4dd1d865c442ddea1eed8ee855186ba2" + integrity sha512-Cf2mAAeMWFMzpLC7Y9H1I4o3wEU+XovVJhTiNG8ZNgSQj53yl7OCJaS80K4YjrABWZzbAHVaoHE1dVJ27AAYXw== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.32" + "@fortawesome/fontawesome-common-types" "6.2.0" -"@fortawesome/react-fontawesome@^0.1.11": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz#c1a95a2bdb6a18fa97b355a563832e248bf6ef4a" - integrity sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg== +"@fortawesome/free-solid-svg-icons@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.0.tgz#8dcde48109354fd7a5ece8ea48d678bb91d4b5f0" + integrity sha512-UjCILHIQ4I8cN46EiQn0CZL/h8AwCGgR//1c4R96Q5viSRwuKVo0NdQEc4bm+69ZwC0dUvjbDqAHF1RR5FA3XA== dependencies: - prop-types "^15.7.2" + "@fortawesome/fontawesome-common-types" "6.2.0" -"@gar/promisify@^1.0.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@fortawesome/react-fontawesome@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4" + integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw== + dependencies: + prop-types "^15.8.1" -"@headlessui/react@^1.6.4": - version "1.6.4" - resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.4.tgz#c73084e23386bef5fb86cd16da3352c3a844bb4c" - integrity sha512-0yqz1scwbFtwljmbbKjXsSGl5ABEYNICVHZnMCWo0UtOZodo2Tpu94uOVgCRjRZ77l2WcTi2S0uidINDvG7lsA== +"@headlessui/react@1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.3.tgz#853c598ff47b37cdd192c5cbee890d9b610c3ec0" + integrity sha512-LGp06SrGv7BMaIQlTs8s2G06moqkI0cb0b8stgq7KZ3xcHdH3qMP+cRyV7qe5x4XEW/IGY48BW4fLesD6NQLng== -"@heroicons/react@^1.0.6": +"@heroicons/react@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324" integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ== -"@hot-loader/react-dom@^16.14.0": - version "16.14.0" - resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.14.0.tgz#3cfc64e40bb78fa623e59b582b8f09dcdaad648a" - integrity sha512-EN9czvcLsMYmSDo5yRKZOAq3ZGRlDpad1gPtX0NdMMomJXcPE3yFSeFzE94X/NjOaiSVimB7LuqPYpkWVaIi4Q== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" - "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" @@ -1199,215 +841,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.1.tgz#305f8ca50b6e70413839f54c0e002b60a0f2fd7d" - integrity sha512-0RiUocPVFEm3WRMOStIHbRWllG6iW6E3/gUPnf4lkrVFyXIIDeCe+vlKeYyFOMhB2EPE6FLFCNADSOOQMaqvyA== - dependencies: - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - slash "^3.0.0" - -"@jest/core@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.1.tgz#086830bec6267accf9af5ca76f794858e9f9f092" - integrity sha512-3pYsBoZZ42tXMdlcFeCc/0j9kOlK7MYuXs2B1QbvDgMoW1K9NJ4G/VYvIbMb26iqlkTfPHo7SC2JgjDOk/mxXw== - dependencies: - "@jest/console" "^28.1.1" - "@jest/reporters" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^28.0.2" - jest-config "^28.1.1" - jest-haste-map "^28.1.1" - jest-message-util "^28.1.1" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.1" - jest-resolve-dependencies "^28.1.1" - jest-runner "^28.1.1" - jest-runtime "^28.1.1" - jest-snapshot "^28.1.1" - jest-util "^28.1.1" - jest-validate "^28.1.1" - jest-watcher "^28.1.1" - micromatch "^4.0.4" - pretty-format "^28.1.1" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.1.tgz#c4cbf85283278d768f816ebd1a258ea6f9e39d4f" - integrity sha512-9auVQ2GzQ7nrU+lAr8KyY838YahElTX9HVjbQPPS2XjlxQ+na18G113OoBhyBGBtD6ZnO/SrUy5WR8EzOj1/Uw== - dependencies: - "@jest/fake-timers" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - jest-mock "^28.1.1" - -"@jest/expect-utils@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.1.tgz#d84c346025b9f6f3886d02c48a6177e2b0360587" - integrity sha512-n/ghlvdhCdMI/hTcnn4qV57kQuV9OTsZzH1TTCVARANKhl6hXJqLKUkwX69ftMGpsbpt96SsDD8n8LD2d9+FRw== - dependencies: - jest-get-type "^28.0.2" - -"@jest/expect@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.1.tgz#ea4fcc8504b45835029221c0dc357c622a761326" - integrity sha512-/+tQprrFoT6lfkMj4mW/mUIfAmmk/+iQPmg7mLDIFOf2lyf7EBHaS+x3RbeR0VZVMe55IvX7QRoT/2aK3AuUXg== - dependencies: - expect "^28.1.1" - jest-snapshot "^28.1.1" - -"@jest/fake-timers@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.1.tgz#47ce33296ab9d680c76076d51ddbe65ceb3337f1" - integrity sha512-BY/3+TyLs5+q87rGWrGUY5f8e8uC3LsVHS9Diz8+FV3ARXL4sNnkLlIB8dvDvRrp+LUCGM+DLqlsYubizGUjIA== - dependencies: - "@jest/types" "^28.1.1" - "@sinonjs/fake-timers" "^9.1.1" - "@types/node" "*" - jest-message-util "^28.1.1" - jest-mock "^28.1.1" - jest-util "^28.1.1" - -"@jest/globals@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.1.tgz#c0a7977f85e26279cc090d9adcdf82b8a34c4061" - integrity sha512-dEgl/6v7ToB4vXItdvcltJBgny0xBE6xy6IYQrPJAJggdEinGxCDMivNv7sFzPcTITGquXD6UJwYxfJ/5ZwDSg== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/expect" "^28.1.1" - "@jest/types" "^28.1.1" - -"@jest/reporters@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.1.tgz#9389f4bb3cce4d9b586f6195f83c79cd2a1c8662" - integrity sha512-597Zj4D4d88sZrzM4atEGLuO7SdA/YrOv9SRXHXRNC+/FwPCWxZhBAEzhXoiJzfRwn8zes/EjS8Lo6DouGN5Gg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@jridgewell/trace-mapping" "^0.3.7" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - jest-worker "^28.1.1" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - terminal-link "^2.0.0" - v8-to-istanbul "^9.0.0" - -"@jest/schemas@^28.0.2": - version "28.0.2" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.0.2.tgz#08c30df6a8d07eafea0aef9fb222c5e26d72e613" - integrity sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA== - dependencies: - "@sinclair/typebox" "^0.23.3" - -"@jest/source-map@^28.0.2": - version "28.0.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.0.2.tgz#914546f4410b67b1d42c262a1da7e0406b52dc90" - integrity sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.7" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.1.tgz#c6f18d1bbb01aa88925dd687872a75f8414b317a" - integrity sha512-hPmkugBktqL6rRzwWAtp1JtYT4VHwv8OQ+9lE5Gymj6dHzubI/oJHMUpPOt8NrdVWSrz9S7bHjJUmv2ggFoUNQ== - dependencies: - "@jest/console" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.1.tgz#f594ee2331df75000afe0d1ae3237630ecec732e" - integrity sha512-nuL+dNSVMcWB7OOtgb0EGH5AjO4UBCt68SLP08rwmC+iRhyuJWS9MtZ/MpipxFwKAlHFftbMsydXqWre8B0+XA== - dependencies: - "@jest/test-result" "^28.1.1" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - slash "^3.0.0" - -"@jest/transform@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.1.tgz#83541f2a3f612077c8501f49cc4e205d4e4a6b27" - integrity sha512-PkfaTUuvjUarl1EDr5ZQcCA++oXkFCP9QFUkG0yVKVmNObjhrqDy0kbMpMebfHWm3CCDHjYNem9eUSH8suVNHQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^28.1.1" - "@jridgewell/trace-mapping" "^0.3.7" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - jest-regex-util "^28.0.2" - jest-util "^28.1.1" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.1" - -"@jest/types@^28.1.1": - version "28.1.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.1.tgz#d059bbc80e6da6eda9f081f293299348bd78ee0b" - integrity sha512-vRXVqSg1VhDnB8bWcmvLzmg0Bt9CRKVgHPXqYwvWMX3TvAjeO+nRuK6+VdTKCtWOvYlmkF/HqNAL/z+N3B53Kw== - dependencies: - "@jest/schemas" "^28.0.2" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1425,6 +858,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": version "3.0.7" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" @@ -1435,20 +877,17 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.13" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.9": version "0.3.14" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== @@ -1456,6 +895,175 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@lezer/common@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.0.1.tgz#d014fda6d582c24336fadf2715e76f02f73c8908" + integrity sha512-8TR5++Q/F//tpDsLd5zkrvEX5xxeemafEaek7mUp7Y+bI8cKQXdSqhzTOBaOogETcMOVr0pT3BBPXp13477ciw== + +"@lezer/cpp@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/cpp/-/cpp-1.0.0.tgz#3293fd88aaf16a6d4f18188602b4d931be8f0915" + integrity sha512-Klk3/AIEKoptmm6cNm7xTulNXjdTKkD+hVOEcz/NeRg8tIestP5hsGHJeFDR/XtyDTxsjoPjKZRIGohht7zbKw== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/css@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@lezer/css/-/css-1.0.1.tgz#589d16d557024481f38dd8a036be3c3db1a199c2" + integrity sha512-kLGsbzXdp1ntzO2jDwFf+2w76EBlLiD4FKofx7tgkdqeFRoslFiMS2qqbNtAauXw8ihZ4cE5YpxSpfsKXSs5Sg== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/highlight@1.1.2", "@lezer/highlight@^1.0.0": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.1.2.tgz#60cd6c2a0a2cf753b8a026b04feeb0ea8df326ea" + integrity sha512-CAun1WR1glxG9ZdOokTZwXbcwB7PXkIEyZRUMFBVwSrhTcogWq634/ByNImrkUnQhjju6xsIaOBIxvcRJtplXQ== + dependencies: + "@lezer/common" "^1.0.0" + +"@lezer/html@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@lezer/html/-/html-1.0.1.tgz#5d62b98cdd37e50394e1b7097c86d8ce3ef128b8" + integrity sha512-sC00zEt3GBh3vVO6QaGX4YZCl41S9dHWN/WGBsDixy9G+sqOC7gsa4cxA/fmRVAiBvhqYkJk+5Ul4oul92CPVw== + dependencies: + "@lezer/common" "^1.0.0" + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/java@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/java/-/java-1.0.0.tgz#fe74e062350f7a4268107e7562971bfbad994f49" + integrity sha512-z2EA0JHq2WoiKfQy5uOOd4t17PJtq8guh58gPkSzOnNcQ7DNbkrU+Axak+jL8+Noinwyz2tRNOseQFj+Tg+P0A== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/javascript@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.0.2.tgz#79b5c2c77e27322a0ae516395a193574b9ad3f5e" + integrity sha512-IjOVeIRhM8IuafWNnk+UzRz7p4/JSOKBNINLYLsdSGuJS9Ju7vFdc82AlTt0jgtV5D8eBZf4g0vK4d3ttBNz7A== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/json@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/json/-/json-1.0.0.tgz#848ad9c2c3e812518eb02897edd5a7f649e9c160" + integrity sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/lezer@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lezer/lezer/-/lezer-1.1.0.tgz#6a42b969735e632dd7ae6a102cdc7e12e474f86c" + integrity sha512-XTomM3C2MzHNuZwjYbyYZ44IRV6rHIOvi++yAD1O4djlDoKAnikx3BFoREK2g/z8zUIc/kyWuZO9W9xN4/OR1g== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/lr@^1.0.0": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.2.3.tgz#f44ca844f15f6762fde4eab877d110567e34ffa1" + integrity sha512-qpB7rBzH8f6Mzjv2AVZRahcm+2Cf7nbIH++uXbvVOL1yIRvVWQ3HAM/saeBLCyz/togB7LGo76qdJYL1uKQlqA== + dependencies: + "@lezer/common" "^1.0.0" + +"@lezer/markdown@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@lezer/markdown/-/markdown-1.0.2.tgz#8c804a9f6fe1ccca4a20acd2fd9fbe0fae1ae178" + integrity sha512-8CY0OoZ6V5EzPjSPeJ4KLVbtXdLBd8V6sRCooN5kHnO28ytreEGTyrtU/zUwo/XLRzGr/e1g44KlzKi3yWGB5A== + dependencies: + "@lezer/common" "^1.0.0" + "@lezer/highlight" "^1.0.0" + +"@lezer/php@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/php/-/php-1.0.0.tgz#522d2d2d8a4eee6c598060e2a222526953c66adb" + integrity sha512-kFQu/mk/vmjpA+fjQU87d9eimqKJ9PFCa8CZCPFWGEwNnm7Ahpw32N+HYEU/YAQ0XcfmOAnW/YJCEa8WpUOMMw== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/python@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@lezer/python/-/python-1.1.1.tgz#6d688071ed93d063a589a7d31df3279b1eba607a" + integrity sha512-ArUGh9kvdaOVu6IkSaYUS9WFQeMAFVWKRuZo6vexnxoeCLnxf0Y9DCFEAMMa7W9SQBGYE55OarSpPqSkdOXSCA== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/rust@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/rust/-/rust-1.0.0.tgz#939f3e7b0376ebe13f4ac336ed7d59ca2c8adf52" + integrity sha512-IpGAxIjNxYmX9ra6GfQTSPegdCAWNeq23WNmrsMMQI7YNSvKtYxO4TX5rgZUmbhEucWn0KTBMeDEPXg99YKtTA== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@lezer/xml@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lezer/xml/-/xml-1.0.0.tgz#02817a3d421e7189b50fd31ed17430b2e1c8c0d8" + integrity sha512-73iI9UK8iqSvWtLlOEl/g+50ivwQn8Ge6foHVN66AXUS1RccFnAoc7BYU8b3c8/rP6dfCOGqAGaWLxBzhj60MA== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + +"@motionone/animation@^10.13.1": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/animation/-/animation-10.14.0.tgz#2f2a3517183bb58d82e389aac777fe0850079de6" + integrity sha512-h+1sdyBP8vbxEBW5gPFDnj+m2DCqdlAuf2g6Iafb1lcMnqjsRXWlPw1AXgvUMXmreyhqmPbJqoNfIKdytampRQ== + dependencies: + "@motionone/easing" "^10.14.0" + "@motionone/types" "^10.14.0" + "@motionone/utils" "^10.14.0" + tslib "^2.3.1" + +"@motionone/dom@10.13.1": + version "10.13.1" + resolved "https://registry.yarnpkg.com/@motionone/dom/-/dom-10.13.1.tgz#fc29ea5d12538f21b211b3168e502cfc07a24882" + integrity sha512-zjfX+AGMIt/fIqd/SL1Lj93S6AiJsEA3oc5M9VkUr+Gz+juRmYN1vfvZd6MvEkSqEjwPQgcjN7rGZHrDB9APfQ== + dependencies: + "@motionone/animation" "^10.13.1" + "@motionone/generators" "^10.13.1" + "@motionone/types" "^10.13.0" + "@motionone/utils" "^10.13.1" + hey-listen "^1.0.8" + tslib "^2.3.1" + +"@motionone/easing@^10.14.0": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/easing/-/easing-10.14.0.tgz#d8154b7f71491414f3cdee23bd3838d763fffd00" + integrity sha512-2vUBdH9uWTlRbuErhcsMmt1jvMTTqvGmn9fHq8FleFDXBlHFs5jZzHJT9iw+4kR1h6a4SZQuCf72b9ji92qNYA== + dependencies: + "@motionone/utils" "^10.14.0" + tslib "^2.3.1" + +"@motionone/generators@^10.13.1": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/generators/-/generators-10.14.0.tgz#e05d9dd56da78a4b92db99185848a0f3db62242d" + integrity sha512-6kRHezoFfIjFN7pPpaxmkdZXD36tQNcyJe3nwVqwJ+ZfC0e3rFmszR8kp9DEVFs9QL/akWjuGPSLBI1tvz+Vjg== + dependencies: + "@motionone/types" "^10.14.0" + "@motionone/utils" "^10.14.0" + tslib "^2.3.1" + +"@motionone/types@^10.13.0", "@motionone/types@^10.14.0": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/types/-/types-10.14.0.tgz#148c34f3270b175397e49c3058b33fab405c21e3" + integrity sha512-3bNWyYBHtVd27KncnJLhksMFQ5o2MSdk1cA/IZqsHtA9DnRM1SYgN01CTcJ8Iw8pCXF5Ocp34tyAjY7WRpOJJQ== + +"@motionone/utils@^10.13.1", "@motionone/utils@^10.14.0": + version "10.14.0" + resolved "https://registry.yarnpkg.com/@motionone/utils/-/utils-10.14.0.tgz#a19a3464ed35b08506747b062d035c7bc9bbe708" + integrity sha512-sLWBLPzRqkxmOTRzSaD3LFQXCPHvDzyHJ1a3VP9PRzBxyVd2pv51/gMOsdAcxQ9n+MIeGJnxzXBYplUHKj4jkw== + dependencies: + "@motionone/types" "^10.14.0" + hey-listen "^1.0.8" + tslib "^2.3.1" + "@nodelib/fs.scandir@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" @@ -1477,70 +1085,39 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@preact/signals-core@^1.2.2": +"@preact/signals-core@^1.2.1": version "1.2.2" resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.2.2.tgz#279dcc5ab249de2f2e8f6e6779b1958256ba843e" integrity sha512-z3/bCj7rRA21RJb4FeJ4guCrD1CQbaURHkCTunUWQpxUMAFOPXCD8tSFqERyGrrcSb4T3Hrmdc1OAl0LXBHwiw== -"@preact/signals-react@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@preact/signals-react/-/signals-react-1.2.1.tgz#6d5d305ebdb38c879043acebc65c0d9351e663c1" - integrity sha512-73J8sL1Eru7Ot4yBYOCPj1izEZjzCEXlembRgk6C7PkwsqoAVbCxMlDOFfCLoPFuJ6qeGatrJzRkcycXppMqVQ== +"@preact/signals-react@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@preact/signals-react/-/signals-react-1.1.1.tgz#8c77e86bc94dcd7f58143e06b6d7564ee79e9585" + integrity sha512-U5HNWBt4q5pmsZjDOuVcz3OXLQtaBMMSErnTHFohOFQClBqHlVD/hmhayEEO38I9iU71kofhw2ngeWso/GsVMw== dependencies: - "@preact/signals-core" "^1.2.2" - use-sync-external-store "^1.2.0" + "@preact/signals-core" "^1.2.1" -"@sinclair/typebox@^0.23.3": - version "0.23.5" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" - integrity sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg== +"@remix-run/router@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.2.tgz#1c17eadb2fa77f80a796ad5ea9bf108e6993ef06" + integrity sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ== -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^9.1.1": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@tailwindcss/forms@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.2.tgz#4ef45f9916dcb37838cbe7fecdcc4ba7a7c2ab59" - integrity sha512-pSrFeJB6Bg1Mrg9CdQW3+hqZXAKsBrSG9MAfFLKy1pVA4Mb4W7C0k7mEhlmS2Dfo/otxrQOET7NJiJ9RrS563w== +"@tailwindcss/forms@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.3.tgz#e4d7989686cbcaf416c53f1523df5225332a86e7" + integrity sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q== dependencies: mini-svg-data-uri "^1.2.3" -"@tailwindcss/line-clamp@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.0.tgz#03353e31e77636b785f2336e8c978502cec1de81" - integrity sha512-HQZo6gfx1D0+DU3nWlNLD5iA6Ef4JAXh0LeD8lOGrJwEDBwwJNKQza6WoXhhY1uQrxOuU8ROxV7CqiQV4CoiLw== +"@tailwindcss/line-clamp@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz#f353c5a8ab2c939c6267ac5b907f012e5ee130f9" + integrity sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw== -"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.11.1", "@testing-library/dom@^8.14.0": - version "8.14.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.14.0.tgz#c9830a21006d87b9ef6e1aae306cf49b0283e28e" - integrity sha512-m8FOdUo77iMTwVRCyzWcqxlEIk+GnopbrRI15a0EaLbpZSCinIVI4kSQzWhkShK83GogvEFJSsHF3Ws0z1vrqA== +"@testing-library/dom@8.19.0", "@testing-library/dom@^8.5.0": + version "8.19.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" + integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -1551,235 +1128,132 @@ lz-string "^1.4.4" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.4": - version "5.16.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" - integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== - dependencies: - "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" - aria-query "^5.0.0" - chalk "^3.0.0" - css "^3.0.0" - css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" - lodash "^4.17.15" - redent "^3.0.0" - -"@testing-library/react@12.1.5": - version "12.1.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" - integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== +"@testing-library/react@13.4.0": + version "13.4.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966" + integrity sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "<18.0.0" + "@testing-library/dom" "^8.5.0" + "@types/react-dom" "^18.0.0" -"@testing-library/user-event@^14.2.1": - version "14.2.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.1.tgz#8c5ff2d004544bb2220e1d864f7267fe7eb6c556" - integrity sha512-HOr1QiODrq+0j9lKU5i10y9TbhxMBMRMGimNx10asdmau9cb8Xb1Vyg0GvTwyIL2ziQyh2kAloOtAQFBQVuecA== +"@testing-library/user-event@14.4.3": + version "14.4.3" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" + integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== "@types/aria-query@^4.2.0": version "4.2.2" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== -"@types/babel__core@^7.1.14": - version "7.1.19" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== +"@types/chai-subset@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" + integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw== dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" + "@types/chai" "*" -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" +"@types/chai@*", "@types/chai@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" + integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.17.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" - integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== - dependencies: - "@babel/types" "^7.3.0" - -"@types/codemirror@^0.0.98": - version "0.0.98" - resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.98.tgz#b35c7a4ab1fc1684b08a4e3eb65240020556ebfb" - integrity sha512-cbty5LPayy2vNSeuUdjNA9tggG+go5vAxmnLDRWpiZI5a+RDBi9dlozy4/jW/7P/gletbBWbQREEa7A81YxstA== +"@types/codemirror@0.0.109": + version "0.0.109" + resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.109.tgz#89d575ff1c7b462c4c3b8654f8bb38e5622e9036" + integrity sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg== dependencies: "@types/tern" "*" -"@types/debounce@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192" - integrity sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw== +"@types/concat-stream@^1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74" + integrity sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA== + dependencies: + "@types/node" "*" + +"@types/debounce@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.1.tgz#79b65710bc8b6d44094d286aecf38e44f9627852" + integrity sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA== "@types/estree@*": version "0.0.45" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== -"@types/events@*", "@types/events@^3.0.0": +"@types/events@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== +"@types/form-data@0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8" + integrity sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw== dependencies: "@types/node" "*" -"@types/history@*": - version "4.7.2" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220" - -"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": +"@types/hoist-non-react-statics@*": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" dependencies: "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@*", "@types/jest@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.3.tgz#52f3f3e50ce59191ff5fbb1084896cc0cf30c9ce" - integrity sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw== - dependencies: - jest-matcher-utils "^28.0.0" - pretty-format "^28.0.0" - -"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" +"@types/lodash@^4.14.175": + version "4.14.186" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.186.tgz#862e5514dd7bd66ada6c70ee5fce844b06c8ee97" + integrity sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw== -"@types/node@*", "@types/node@^14.11.10": +"@types/node@*": version "14.11.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.10.tgz#8c102aba13bf5253f35146affbf8b26275069bef" integrity sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA== +"@types/node@18.11.9": + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + +"@types/node@^10.0.3": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + +"@types/node@^8.0.0": + version "8.10.66" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" + integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.1.5": - version "2.6.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" - integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== - "@types/prop-types@*": version "15.7.1" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6" -"@types/qrcode.react@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/qrcode.react/-/qrcode.react-1.0.1.tgz#0904e7a075a6274a5258f19567b4f64013c159d8" - integrity sha512-PcVCjpsiT2KFKfJibOgTQtkt0QQT/6GbQUp1Np/hMPhwUzMJ2DRUkR9j7tXN9Q8X06qukw+RbaJ8lJ22SBod+Q== +"@types/qs@^6.2.31": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/react-dom@18.0.8", "@types/react-dom@^18.0.0": + version "18.0.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.8.tgz#d2606d855186cd42cc1b11e63a71c39525441685" + integrity sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw== dependencies: "@types/react" "*" -"@types/react-copy-to-clipboard@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz#8e07becb4f11cfced4bd36038cb5bdf5c2658be5" - integrity sha512-iideNPRyroENqsOFh1i2Dv3zkviYS9r/9qD9Uh3Z9NNoAAqqa2x53i7iGndGNnJFIo20wIu7Hgh77tx1io8bgw== - dependencies: - "@types/react" "*" - -"@types/react-dom@<18.0.0": - version "17.0.17" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1" - integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg== - dependencies: - "@types/react" "^17" - -"@types/react-dom@^16.9.16": - version "16.9.16" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.16.tgz#c591f2ed1c6f32e9759dfa6eb4abfd8041f29e39" - integrity sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg== - dependencies: - "@types/react" "^16" - -"@types/react-redux@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.1.tgz#eb01e89cf71cad77df9f442b819d5db692b997cb" - dependencies: - "@types/hoist-non-react-statics" "^3.3.0" - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - redux "^4.0.0" - -"@types/react-router-dom@^5.1.3": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" - dependencies: - "@types/history" "*" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*", "@types/react-router@^5.1.3": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.3.tgz#7c7ca717399af64d8733d8cb338dd43641b96f2d" - dependencies: - "@types/history" "*" - "@types/react" "*" - -"@types/react-transition-group@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" - integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^17": +"@types/react@*": version "17.0.47" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.47.tgz#4ee71aaf4c5a9e290e03aa4d0d313c5d666b3b78" integrity sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA== @@ -1788,10 +1262,10 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@^16", "@types/react@^16.14.0": - version "16.14.26" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.26.tgz#82540a240ba7207ebe87d9579051bc19c9ef7605" - integrity sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ== +"@types/react@18.0.24": + version "18.0.24" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.24.tgz#2f79ed5b27f08d05107aab45c17919754cc44c20" + integrity sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1802,15 +1276,15 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/semver@^7.3.12": + version "7.3.12" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.12.tgz#920447fdd78d76b19de0438b7f60df3c4a80bf1c" + integrity sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A== -"@types/styled-components@^5.1.7": - version "5.1.7" - resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.7.tgz#3cd10b088c1cb1acde2e4b166b3e8275a3083710" - integrity sha512-BJzPhFygYspyefAGFZTZ/8lCEY4Tk+Iqktvnko3xmJf9LrLqs3+grxPeU3O0zLl6yjbYBopD0/VikbHgXDbJtA== +"@types/styled-components@5.1.26": + version "5.1.26" + resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af" + integrity sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw== dependencies: "@types/hoist-non-react-statics" "*" "@types/react" "*" @@ -1823,300 +1297,107 @@ dependencies: "@types/estree" "*" -"@types/testing-library__jest-dom@^5.9.1": - version "5.14.5" - resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f" - integrity sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ== +"@typescript-eslint/eslint-plugin@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz#f8eeb1c6bb2549f795f3ba71aec3b38d1ab6b1e1" + integrity sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA== dependencies: - "@types/jest" "*" - -"@types/uuid@^3.4.5": - version "3.4.5" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.5.tgz#d4dc10785b497a1474eae0ba7f0cb09c0ddfd6eb" - dependencies: - "@types/node" "*" - -"@types/webpack-env@^1.15.2": - version "1.15.2" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a" - integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ== - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.10" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" - integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== - dependencies: - "@types/yargs-parser" "*" - -"@types/yup@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.3.tgz#5a85024796bffe0eb01601bfc180fe218356dba4" - integrity sha512-XxZFKnxzTfm+DR8MMBA35UUXfUPmjPpi8HJ90VZg7q/LIbtiOhVGJ26gNnATcflcpnIyf2Qm9A+oEhswaqoDpA== - -"@typescript-eslint/eslint-plugin@^5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz#c67794d2b0fd0b4a47f50266088acdc52a08aab6" - integrity sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w== - dependencies: - "@typescript-eslint/scope-manager" "5.29.0" - "@typescript-eslint/type-utils" "5.29.0" - "@typescript-eslint/utils" "5.29.0" + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/type-utils" "5.41.0" + "@typescript-eslint/utils" "5.41.0" debug "^4.3.4" - functional-red-black-tree "^1.0.1" ignore "^5.2.0" regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.29.0.tgz#41314b195b34d44ff38220caa55f3f93cfca43cf" - integrity sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw== +"@typescript-eslint/parser@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.41.0.tgz#0414a6405007e463dc527b459af1f19430382d67" + integrity sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA== dependencies: - "@typescript-eslint/scope-manager" "5.29.0" - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/typescript-estree" "5.29.0" + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/typescript-estree" "5.41.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz#2a6a32e3416cb133e9af8dcf54bf077a916aeed3" - integrity sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA== +"@typescript-eslint/scope-manager@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz#28e3a41d626288d0628be14cf9de8d49fc30fadf" + integrity sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ== dependencies: - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/visitor-keys" "5.29.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/visitor-keys" "5.41.0" -"@typescript-eslint/type-utils@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz#241918001d164044020b37d26d5b9f4e37cc3d5d" - integrity sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg== +"@typescript-eslint/type-utils@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz#2371601171e9f26a4e6da918a7913f7266890cdf" + integrity sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA== dependencies: - "@typescript-eslint/utils" "5.29.0" + "@typescript-eslint/typescript-estree" "5.41.0" + "@typescript-eslint/utils" "5.41.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.29.0.tgz#7861d3d288c031703b2d97bc113696b4d8c19aab" - integrity sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg== +"@typescript-eslint/types@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.41.0.tgz#6800abebc4e6abaf24cdf220fb4ce28f4ab09a85" + integrity sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA== -"@typescript-eslint/typescript-estree@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz#e83d19aa7fd2e74616aab2f25dfbe4de4f0b5577" - integrity sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ== +"@typescript-eslint/typescript-estree@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz#bf5c6b3138adbdc73ba4871d060ae12c59366c61" + integrity sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg== dependencies: - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/visitor-keys" "5.29.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/visitor-keys" "5.41.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.29.0.tgz#775046effd5019667bd086bcf326acbe32cd0082" - integrity sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A== +"@typescript-eslint/utils@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.41.0.tgz#f41ae5883994a249d00b2ce69f4188f3a23fa0f9" + integrity sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.29.0" - "@typescript-eslint/types" "5.29.0" - "@typescript-eslint/typescript-estree" "5.29.0" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/typescript-estree" "5.41.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" + semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.29.0": - version "5.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz#7a4749fa7ef5160c44a451bf060ac1dc6dfb77ee" - integrity sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ== +"@typescript-eslint/visitor-keys@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz#d3510712bc07d5540160ed3c0f8f213b73e3bcd9" + integrity sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw== dependencies: - "@typescript-eslint/types" "5.29.0" + "@typescript-eslint/types" "5.41.0" eslint-visitor-keys "^3.3.0" -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== +"@vitejs/plugin-react@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz#1b9f63b8b6bc3f56258d20cd19b33f5cc761ce6e" + integrity sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA== dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== - -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== - dependencies: - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== - -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - -abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" + "@babel/core" "^7.19.6" + "@babel/plugin-transform-react-jsx" "^7.19.0" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-jsx-self" "^7.18.6" + "@babel/plugin-transform-react-jsx-source" "^7.19.6" + magic-string "^0.26.7" + react-refresh "^0.14.0" acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-node@^1.6.1: +acorn-node@^1.6.1, acorn-node@^1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== @@ -2125,44 +1406,27 @@ acorn-node@^1.6.1: acorn-walk "^7.0.0" xtend "^4.0.2" -acorn-walk@^7.0.0, acorn-walk@^7.1.1: +acorn-walk@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== - -acorn@^7.0.0, acorn@^7.1.1: +acorn@^7.0.0: version "7.3.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== -acorn@^8.5.0, acorn@^8.7.1: +acorn@^8.7.1: version "8.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" +acorn@^8.8.0: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2172,46 +1436,19 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-colors@^3.0.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -2223,14 +1460,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -2238,27 +1468,15 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3, aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - arg@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - dependencies: - sprintf-js "~1.0.2" +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== argparse@^2.0.1: version "2.0.1" @@ -2277,26 +1495,6 @@ aria-query@^5.0.0: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - array-includes@^3.1.2, array-includes@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" @@ -2308,25 +1506,11 @@ array-includes@^3.1.2, array-includes@^3.1.5: get-intrinsic "^1.1.1" is-string "^1.0.7" -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - dependencies: - array-uniq "^1.0.1" - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - array.prototype.flatmap@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" @@ -2337,54 +1521,34 @@ array.prototype.flatmap@^1.3.0: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -assert@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - dependencies: - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - -async-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - -async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" - -async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - dependencies: - lodash "^4.17.14" +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +autoprefixer@10.4.12, autoprefixer@^10.4.11: + version "10.4.12" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.12.tgz#183f30bf0b0722af54ee5ef257f7d4320bb33129" + integrity sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q== + dependencies: + browserslist "^4.21.4" + caniuse-lite "^1.0.30001407" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" -atob@^2.1.1, atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@^10.2.5, autoprefixer@^10.4.7: +autoprefixer@^10.2.5: version "10.4.7" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf" integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA== @@ -2396,7 +1560,7 @@ autoprefixer@^10.2.5, autoprefixer@^10.4.7: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -axios@^0.27.2: +axios@0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== @@ -2404,57 +1568,6 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -babel-jest@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.1.tgz#2a3a4ae50964695b2d694ccffe4bec537c5a3586" - integrity sha512-MEt0263viUdAkTq5D7upHPNxvt4n9uLUGa6pPz3WviNBMtOmStb1lIXS3QobnoqM+qnH+vr4EKlvhe8QcmxIYw== - dependencies: - "@jest/transform" "^28.1.1" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^28.1.1" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-loader@^8.2.5: - version "8.2.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e" - integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.1.tgz#5e055cdcc47894f28341f87f5e35aad2df680b11" - integrity sha512-NovGCy5Hn25uMJSAU8FaHqzs13cFoOI4lhIujiepssjCKRsAo3TA734RDWSGxuFTsUJXerYOqQQodlxgmtqbzw== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - babel-plugin-macros@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" @@ -2464,7 +1577,7 @@ babel-plugin-macros@^2.8.0: cosmiconfig "^6.0.0" resolve "^1.12.0" -"babel-plugin-styled-components@>= 1", babel-plugin-styled-components@^2.0.7: +babel-plugin-styled-components@2.0.7, "babel-plugin-styled-components@>= 1.12.0": version "2.0.7" resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz#c81ef34b713f9da2b7d3f5550df0d1e19e798086" integrity sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA== @@ -2479,31 +1592,12 @@ babel-plugin-syntax-jsx@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== +babel-plugin-twin@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-twin/-/babel-plugin-twin-1.0.2.tgz#7ed6622d2a6268fdb76cad414a1f2486df59dc96" + integrity sha512-723f0tizypoy3Fn9ZAuidTCQ+sKh6sn/jX+Cjj3Pk148ew/hUa+fRUyqAPKACIFaVNqRFnZgI1DoNhNMDL+6QA== dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.1.tgz#5b6e5e69f963eb2d70f739c607b8f723c0ee75e4" - integrity sha512-FCq9Oud0ReTeWtcneYf/48981aTfXYuB9gbU4rBNNJVBSQ6ssv7E6v/qvbBxtOWwZFXjLZwpg+W3q7J6vhH25g== - dependencies: - babel-plugin-jest-hoist "^28.1.1" - babel-preset-current-node-syntax "^1.0.0" + "@babel/template" "^7.12.13" babel-runtime@^6.26.0: version "6.26.0" @@ -2518,84 +1612,12 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - -bfj@^6.1.1: - version "6.1.2" - resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f" - integrity sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw== - dependencies: - bluebird "^3.5.5" - check-types "^8.0.3" - hoopy "^0.1.4" - tryer "^1.0.1" - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - -binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bluebird@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - -boring-avatars@^1.7.0: +boring-avatars@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/boring-avatars/-/boring-avatars-1.7.0.tgz#70ac7146bbf37d8e69a35544b24f1d75558f868a" integrity sha512-ZNHd8J7C/V0IjQMGQowLJ5rScEFU23WxePigH6rqKcT2Esf0qhYvYxw8s9i3srmlfCnCV00ddBjaoGey1eNOfA== @@ -2608,84 +1630,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" dependencies: fill-range "^7.0.1" -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c" - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - dependencies: - pako "~1.0.5" - -browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.8.5: +browserslist@^4.20.3: version "4.21.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== @@ -2695,110 +1646,25 @@ browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.8.5: node-releases "^2.0.5" update-browserslist-db "^1.0.0" -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== +browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" buffer-from@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - -buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - -bytes@3.1.0, bytes@^3.0.0: +bytes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" -cacache@^12.0.2: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^15.0.5: - version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -2817,15 +1683,6 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - camelize@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" @@ -2835,7 +1692,30 @@ caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001358: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz#a1c1cbe1c2da9e689638813618b4219acbd4925e" integrity sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw== -chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407: + version "1.0.30001425" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001425.tgz#52917791a453eb3265143d2cd08d80629e82c735" + integrity sha512-/pzFv0OmNG6W0ym80P3NtapU0QEiDS3VuYAZMGoLLqiC7f6FJFe1MjpQDREGApeenD9wloeytmVDj+JLXPC6qw== + +caseless@^0.12.0, caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +chai@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" + integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + loupe "^2.3.1" + pathval "^1.1.1" + type-detect "^4.0.5" + +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2844,14 +1724,6 @@ chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2860,40 +1732,17 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -char-regex@^1.0.2: +chart.js@3.9.1: + version "3.9.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8" + integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w== + +check-error@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== -chart.js@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.0.tgz#c6c14c457b9dc3ce7f1514a59e9b262afd6f1a94" - integrity sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg== - -check-types@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" - integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ== - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chokidar@^3.4.0, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: +chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -2908,105 +1757,21 @@ chokidar@^3.4.0, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chownr@^1.0.1, chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - dependencies: - tslib "^1.9.0" - -ci-info@^3.2.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" - integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -classnames@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" - integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +classnames@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== clean-set@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/clean-set/-/clean-set-1.1.2.tgz#76d8bf238c3e27827bfa73073ecdfdc767187070" integrity sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug== -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - -codemirror@^5.57.0: +codemirror@5.57.0: version "5.57.0" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.57.0.tgz#d26365b72f909f5d2dbb6b1209349ca1daeb2d50" integrity sha512-WGc6UL7Hqt+8a6ZAsj/f1ApQl3NPvHY/UQSzG6fB6l4BjExgVdhFaxd7mRTw1UCiYe/6q86zHP+kfvBQcZGvUg== -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -3066,143 +1831,56 @@ color@^4.0.1: color-convert "^2.0.1" color-string "^1.9.0" -combined-stream@^1.0.8: +combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -commander@^2.10.0, commander@^2.18.0, commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - commander@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - -component-emitter@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - -compressible@~2.0.16: - version "2.0.17" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" - dependencies: - mime-db ">= 1.40.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.0: +concat-stream@^1.6.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== dependencies: buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" -connect-history-api-fallback@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" - -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - -copy-to-clipboard@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" - integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== +copy-to-clipboard@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz#5b263ec2366224b100181dded7ce0579b340c107" + integrity sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg== dependencies: toggle-selection "^1.0.6" -core-js-compat@^3.6.2: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" - integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== - dependencies: - browserslist "^4.8.5" - semver "7.0.0" - core-js@^2.4.0: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cosmiconfig@^6.0.0: version "6.0.0" @@ -3215,7 +1893,7 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: +cosmiconfig@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== @@ -3226,38 +1904,15 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" +crelt@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94" + integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA== -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-env@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" - integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== +cross-env@7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== dependencies: cross-spawn "^7.0.1" @@ -3268,17 +1923,7 @@ cross-fetch@3.1.5: dependencies: node-fetch "2.6.7" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.1, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3287,22 +1932,6 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - css-blank-pseudo@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" @@ -3326,22 +1955,6 @@ css-has-pseudo@^3.0.4: dependencies: postcss-selector-parser "^6.0.9" -css-loader@^5.2.7: - version "5.2.7" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" - integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== - dependencies: - icss-utils "^5.1.0" - loader-utils "^2.0.0" - postcss "^8.2.15" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^3.0.0" - semver "^7.3.5" - css-prefers-color-scheme@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" @@ -3365,62 +1978,29 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== -css@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" - integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== - dependencies: - inherits "^2.0.4" - source-map "^0.6.1" - source-map-resolve "^0.6.0" - -cssdb@^6.6.3: - version "6.6.3" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-6.6.3.tgz#1f331a2fab30c18d9f087301e6122a878bb1e505" - integrity sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA== +cssdb@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-7.0.2.tgz#e1cadfe2be318797bd02ca929d2b3c7bac332abc" + integrity sha512-Vm4b6P/PifADu0a76H0DKRNVWq3Rq9xa/Nx6oEMUBJlwTUuZoZ3dkZxo8Gob3UEL53Cq+Ma1GBgISed6XEBs3w== cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" -csstype@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" - csstype@^3.0.2: version "3.0.5" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.5.tgz#7fdec6a28a67ae18647c51668a9ff95bb2fa7bb8" integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ== -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" +date-fns@2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== -date-fns@^2.28.0: - version "2.28.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" - integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - -debounce@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" - integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== - -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - -debug@^3.1.1, debug@^3.2.5: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - dependencies: - ms "^2.1.1" +debounce@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" @@ -3429,53 +2009,27 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== - -deep-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge-ts@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-4.2.1.tgz#104fe27c91abde4597bad1dcfd00a7a8be22d532" - integrity sha512-xzJLiUo4z1dD2nggSfaMvHo5qWLoy/JVa9rKuktC6FrQQEBI8Qnj7KwuCYZhqBoGOOpGqs6+3MR2ZhSMcTr4BA== +deepmerge-ts@4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-4.2.2.tgz#582bf34a37592dc8274b137617b539f871aaf11a" + integrity sha512-Ka3Kb21tiWjvQvS9U+1Dx+aqFAHsdTnMdYptLTmC2VAmDFMugWMY1e15aTODstipmCun8iNuqeSfcx6rsUUk0Q== deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" - define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" @@ -3484,81 +2038,19 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - -des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" +dequal@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== detective@^5.2.0: version "5.2.0" @@ -3569,24 +2061,20 @@ detective@^5.2.0: defined "^1.0.0" minimist "^1.1.1" +detective@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== + dependencies: + acorn-node "^1.8.2" + defined "^1.0.0" + minimist "^1.2.6" + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== -diff-sequences@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" - integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -3599,23 +2087,6 @@ dlv@^1.1.3: resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - dependencies: - buffer-indexof "^1.0.0" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -3629,128 +2100,38 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.9: version "0.5.14" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== -dom-helpers@^5.0.1: - version "5.1.3" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821" - dependencies: - "@babel/runtime" "^7.6.3" - csstype "^2.6.7" - -dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - dset@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/dset/-/dset-2.0.1.tgz#a15fff3d1e4d60ac0c95634625cbd5441a76deb1" integrity sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ== -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" +easy-peasy@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-5.1.0.tgz#5fbd3a2d4803ef982b42083be47badf553227341" + integrity sha512-Gd5jzTRrWrE6r59014GXTzpcq5abJ5AMGxc4fzd9sM8PCy18evzxVF9MYlkkzdTnUI/Rbvf0sQW50ev8DXyNjw== dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -easy-peasy@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-4.0.1.tgz#8b3ab1ebb43509a62dc2c37b4269a9141e33d918" - integrity sha512-aTvB48M2ej6dM/wllUm1F7CTWGnYOYh82SHBkvJtOZhJ/9L8Gmg/nIVqDPwJeojOWZe+gbLtpyi8DhN6fPNBYg== - dependencies: - immer "7.0.9" - is-plain-object "^5.0.0" - memoizerific "^1.11.3" - redux "^4.0.5" - redux-thunk "^2.3.0" - symbol-observable "^2.0.3" - ts-toolbelt "^8.0.7" - use-memo-one "^1.1.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - -ejs@^2.6.1: - version "2.7.4" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== + "@babel/runtime" "^7.17.2" + fast-deep-equal "^3.1.3" + immer "^9.0.12" + redux "^4.1.2" + redux-thunk "^2.4.1" + ts-toolbelt "^9.6.0" + use-sync-external-store "^1.2.0" electron-to-chromium@^1.4.164: version "1.4.170" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.170.tgz#0415fc489402e09bfbe1f0c99bbf4d73f31d48d4" integrity sha512-rZ8PZLhK4ORPjFqLp9aqC4/S1j4qWFsPPz13xmWdrbBkU/LlxMcok+f+6f8YnQ57MiZwKtOaW15biZZsY5Igvw== -elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" - integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - dependencies: - prr "~1.0.1" +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== error-ex@^1.3.1: version "1.3.2" @@ -3804,31 +2185,150 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild-android-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.12.tgz#5e8151d5f0a748c71a7fbea8cee844ccf008e6fc" + integrity sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q== + +esbuild-android-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.12.tgz#5ee72a6baa444bc96ffcb472a3ba4aba2cc80666" + integrity sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA== + +esbuild-darwin-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.12.tgz#70047007e093fa1b3ba7ef86f9b3fa63db51fe25" + integrity sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q== + +esbuild-darwin-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.12.tgz#41c951f23d9a70539bcca552bae6e5196696ae04" + integrity sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw== + +esbuild-freebsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.12.tgz#a761b5afd12bbedb7d56c612e9cfa4d2711f33f0" + integrity sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw== + +esbuild-freebsd-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.12.tgz#6b0839d4d58deabc6cbd96276eb8cbf94f7f335e" + integrity sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g== + +esbuild-linux-32@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.12.tgz#bd50bfe22514d434d97d5150977496e2631345b4" + integrity sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA== + +esbuild-linux-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.12.tgz#074bb2b194bf658245f8490f29c01ffcdfa8c931" + integrity sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA== + +esbuild-linux-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.12.tgz#3bf789c4396dc032875a122988efd6f3733f28f5" + integrity sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ== + +esbuild-linux-arm@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.12.tgz#b91b5a8d470053f6c2c9c8a5e67ec10a71fe4a67" + integrity sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A== + +esbuild-linux-mips64le@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.12.tgz#2fb54099ada3c950a7536dfcba46172c61e580e2" + integrity sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A== + +esbuild-linux-ppc64le@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.12.tgz#9e3b8c09825fb27886249dfb3142a750df29a1b7" + integrity sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg== + +esbuild-linux-riscv64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.12.tgz#923d0f5b6e12ee0d1fe116b08e4ae4478fe40693" + integrity sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA== + +esbuild-linux-s390x@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.12.tgz#3b1620220482b96266a0c6d9d471d451a1eab86f" + integrity sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww== + +esbuild-netbsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.12.tgz#276730f80da646859b1af5a740e7802d8cd73e42" + integrity sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w== + +esbuild-openbsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.12.tgz#bd0eea1dd2ca0722ed489d88c26714034429f8ae" + integrity sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw== + +esbuild-sunos-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.12.tgz#5e56bf9eef3b2d92360d6d29dcde7722acbecc9e" + integrity sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg== + +esbuild-windows-32@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.12.tgz#a4f1a301c1a2fa7701fcd4b91ef9d2620cf293d0" + integrity sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw== + +esbuild-windows-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.12.tgz#bc2b467541744d653be4fe64eaa9b0dbbf8e07f6" + integrity sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA== + +esbuild-windows-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.12.tgz#9a7266404334a86be800957eaee9aef94c3df328" + integrity sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA== + +esbuild@^0.15.9: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.12.tgz#6c8e22d6d3b7430d165c33848298d3fc9a1f251c" + integrity sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng== + optionalDependencies: + "@esbuild/android-arm" "0.15.12" + "@esbuild/linux-loong64" "0.15.12" + esbuild-android-64 "0.15.12" + esbuild-android-arm64 "0.15.12" + esbuild-darwin-64 "0.15.12" + esbuild-darwin-arm64 "0.15.12" + esbuild-freebsd-64 "0.15.12" + esbuild-freebsd-arm64 "0.15.12" + esbuild-linux-32 "0.15.12" + esbuild-linux-64 "0.15.12" + esbuild-linux-arm "0.15.12" + esbuild-linux-arm64 "0.15.12" + esbuild-linux-mips64le "0.15.12" + esbuild-linux-ppc64le "0.15.12" + esbuild-linux-riscv64 "0.15.12" + esbuild-linux-s390x "0.15.12" + esbuild-netbsd-64 "0.15.12" + esbuild-openbsd-64 "0.15.12" + esbuild-sunos-64 "0.15.12" + esbuild-windows-32 "0.15.12" + esbuild-windows-64 "0.15.12" + esbuild-windows-arm64 "0.15.12" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^8.5.0: +eslint-config-prettier@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== @@ -3841,16 +2341,7 @@ eslint-plugin-es@^3.0.0: eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-jest-dom@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-4.0.2.tgz#9d3e2f51055f74c74e745d89c4b1a9781e0ec7a9" - integrity sha512-Jo51Atwyo2TdcUncjmU+UQeSTKh3sc2LF/M5i/R3nTU0Djw9V65KGJisdm/RtuKhy2KH/r7eQ1n6kwYFPNdHlA== - dependencies: - "@babel/runtime" "^7.16.3" - "@testing-library/dom" "^8.11.1" - requireindex "^1.2.0" - -eslint-plugin-node@^11.1.0: +eslint-plugin-node@11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== @@ -3862,22 +2353,22 @@ eslint-plugin-node@^11.1.0: resolve "^1.10.1" semver "^6.1.0" -eslint-plugin-prettier@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" - integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== +eslint-plugin-prettier@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^4.6.0: +eslint-plugin-react-hooks@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== -eslint-plugin-react@^7.30.1: - version "7.30.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz#2be4ab23ce09b5949c6631413ba64b2810fd3e22" - integrity sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg== +eslint-plugin-react@7.31.10: + version "7.31.10" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a" + integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA== dependencies: array-includes "^3.1.5" array.prototype.flatmap "^1.3.0" @@ -3894,13 +2385,6 @@ eslint-plugin-react@^7.30.1: semver "^6.3.0" string.prototype.matchall "^4.0.7" -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -3945,7 +2429,7 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.18.0: +eslint@8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== @@ -3995,10 +2479,6 @@ espree@^9.3.2: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" -esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" - esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -4006,7 +2486,7 @@ esquery@^1.4.0: dependencies: estraverse "^5.1.0" -esrecurse@^4.1.0, esrecurse@^4.3.0: +esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== @@ -4026,156 +2506,10 @@ esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - -eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - -events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - dependencies: - original "^1.0.0" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - dependencies: - homedir-polyfill "^1.0.1" - -expect@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.1.tgz#ca6fff65f6517cf7220c2e805a49c19aea30b420" - integrity sha512-/AANEwGL0tWBwzLNOvO0yUdy2D52jVdNXppOqswC49sxMN2cPWsGCQdzuIf9tj6hHoBQzNvx75JUYuQAckPo3w== - dependencies: - "@jest/expect-utils" "^28.1.1" - jest-get-type "^28.0.2" - jest-matcher-utils "^28.1.1" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - -express@^4.16.3, express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fast-deep-equal@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" +events@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -4187,7 +2521,18 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9: +fast-glob@^3.2.12: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-glob@^3.2.7, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -4198,7 +2543,7 @@ fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -4214,29 +2559,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" - dependencies: - websocket-driver ">=0.5.1" - -fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== - dependencies: - bser "2.1.1" - -figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -4244,28 +2566,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-loader@~6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -filesize@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" - integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -4273,59 +2573,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-cache-dir@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" - integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -4339,45 +2586,19 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== -flush-write-stream@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.4" - -fn-name@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" - integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== - -follow-redirects@^1.0.0, follow-redirects@^1.14.9: +follow-redirects@^1.14.9: version "1.15.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - -fork-ts-checker-webpack-plugin@^6.2.10: - version "6.5.2" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340" - integrity sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA== +form-data@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" form-data@^4.0.0: version "4.0.0" @@ -4388,64 +2609,44 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -formik@^2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d" - integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA== +formik@2.2.9: + version "2.2.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" + integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA== dependencies: deepmerge "^2.1.1" hoist-non-react-statics "^3.3.0" - lodash "^4.17.14" - lodash-es "^4.17.14" + lodash "^4.17.21" + lodash-es "^4.17.21" react-fast-compare "^2.0.1" tiny-warning "^1.0.2" tslib "^1.10.0" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" +framer-motion@7.6.2: + version "7.6.2" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-7.6.2.tgz#7fb93ebfeda27c8c2cff1895ca7a417229e81bf7" + integrity sha512-YRr+KaC+1MlLx7iArVyjZRpc0QXI7H0XIOJrdol+dF1+WLQJwS2sP04KGq808BG+byD36UAmAt4YqObE5YFLtw== dependencies: - map-cache "^0.2.2" - -framer-motion@^6.3.10: - version "6.3.10" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.3.10.tgz#e71f8c4ee09612de8328725c4fb1a1c204ae451f" - integrity sha512-modFplFb1Fznsm0MrmRAJUC32UDA5jbGU9rDvkGzhAHksru2tnoKbU/Pa3orzdsJI0CJviG4NGBrmwGveU98Cg== - dependencies: - framesync "6.0.1" + "@motionone/dom" "10.13.1" + framesync "6.1.2" hey-listen "^1.0.8" - popmotion "11.0.3" - style-value-types "5.0.0" - tslib "^2.1.0" + popmotion "11.0.5" + style-value-types "5.1.2" + tslib "2.4.0" optionalDependencies: "@emotion/is-prop-valid" "^0.8.2" -framesync@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.0.1.tgz#5e32fc01f1c42b39c654c35b16440e07a25d6f20" - integrity sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA== +framesync@6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.1.2.tgz#755eff2fb5b8f3b4d2b266dd18121b300aefea27" + integrity sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g== dependencies: - tslib "^2.1.0" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" + tslib "2.4.0" fs-extra@^10.0.0: version "10.1.0" @@ -4456,56 +2657,12 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - dependencies: - minipass "^2.2.1" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-monkey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" - dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" - -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -4533,27 +2690,15 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" @@ -4564,21 +2709,10 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-port@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== get-symbol-description@^1.0.0: version "1.0.0" @@ -4588,17 +2722,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -4613,7 +2736,7 @@ glob-parent@^6.0.1, glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: +glob@^7.1.3, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -4625,46 +2748,6 @@ glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -global@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" - dependencies: - min-document "^2.19.0" - process "~0.5.1" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -4688,41 +2771,23 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: +graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -gud@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" - -gzip-size@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== +happy-dom@7.6.6: + version "7.6.6" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-7.6.6.tgz#f94ce99c5a32d1ea8578b75e8f584e8fcd162513" + integrity sha512-28NxRiHXjzhr+BGciLNUoQW4OaBnQPRT/LPYLufh0Fj3Iwh1j9qJaozjBm/Uqdj5Ps4cukevQ7ERieA6Ddwf1g== dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - -handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" - -harmony-reflect@^1.4.6: - version "1.6.2" - resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" - integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== + css.escape "^1.5.1" + he "^1.2.0" + node-fetch "^2.x.x" + sync-request "^6.1.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" has-bigints@^1.0.2: version "1.0.2" @@ -4758,56 +2823,16 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" dependencies: function-bind "^1.1.1" -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.0" +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== hex-color-regex@^1.1.0: version "1.1.0" @@ -4819,52 +2844,13 @@ hey-listen@^1.0.8: resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q== -history@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca" - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^2.2.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^0.4.0" - -hmac-drbg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: react-is "^16.7.0" -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - dependencies: - parse-passwd "^1.0.0" - -hoopy@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" - integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -4875,157 +2861,70 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA== -html-entities@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -html-parse-stringify2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== dependencies: - void-elements "^2.0.1" + void-elements "3.1.0" html-tags@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" +http-basic@^8.1.1: + version "8.1.3" + resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf" + integrity sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw== dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" + caseless "^0.12.0" + concat-stream "^1.6.2" + http-response-object "^3.0.1" + parse-cache-control "^1.0.1" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" +http-response-object@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" + integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA== dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" + "@types/node" "^10.0.3" -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-parser-js@>=0.4.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" - -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - -http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -i18next-http-backend@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.4.1.tgz#d8d308e7d8c5b89988446d0b83f469361e051bc0" - integrity sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA== +i18next-http-backend@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.0.0.tgz#7be736eb4c592e110b9ee54a985b737248d1c43f" + integrity sha512-6aFT5LcDOSxFyaoezruIxZDzpp6nu92j1iZc444nrz/OOaF7rsxQFNi1es19la53MQQFzG7uD2Koxi7Jav8khg== dependencies: cross-fetch "3.1.5" -i18next-multiload-backend-adapter@^1.0.0: +i18next-multiload-backend-adapter@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/i18next-multiload-backend-adapter/-/i18next-multiload-backend-adapter-1.0.0.tgz#3cc3ea102814273bb9059a317d04a3b6e4316121" integrity sha512-rZd/Qmr7KkGktVgJa78GPLXEnd51OyB2I9qmbI/mXKPm3MWbXwplIApqmZgxkPC9ce+b8Jnk227qX62W9SaLPQ== -i18next@^21.8.9: - version "21.8.9" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.8.9.tgz#c79edd5bba61e0a0d5b43a93d52e2d13a526de82" - integrity sha512-PY9a/8ADVmnju1tETeglbbVQi+nM5pcJQWm9kvKMTE3GPgHHtpDsHy5HQ/hccz2/xtW7j3vuso23JdQSH0EttA== +i18next@22.0.3: + version "22.0.3" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.0.3.tgz#084e40ec88d63c13385175ddebcc4395c89b97e3" + integrity sha512-gG6kCG5+gnPXdK8TLTJ2oiuFSjn6CYMSUwV3vnmISxwTunJHREn/z6gi1g7942c61K1dL3Gm+9a64nZCPv6mlg== dependencies: "@babel/runtime" "^7.17.2" -iconv-lite@0.4.24, iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.2: +iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -identity-obj-proxy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" - integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== - dependencies: - harmony-reflect "^1.4.6" - -ieee754@^1.1.4: - version "1.1.11" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - dependencies: - minimatch "^3.0.4" - ignore@^5.1.1, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immer@7.0.9: - version "7.0.9" - resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e" - integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A== +immer@^9.0.12: + version "9.0.16" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198" + integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ== import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" @@ -5035,35 +2934,10 @@ import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -5072,29 +2946,10 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -5104,43 +2959,6 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -interpret@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - -ipaddr.js@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" - -ipaddr.js@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5155,12 +2973,6 @@ is-bigint@^1.0.1: resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - dependencies: - binary-extensions "^1.0.0" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -5175,10 +2987,6 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.2" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" @@ -5203,79 +3011,22 @@ is-core-module@^2.2.0, is-core-module@^2.8.1: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - dependencies: - kind-of "^6.0.0" + has "^1.0.3" is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - dependencies: - is-extglob "^2.1.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -5293,54 +3044,11 @@ is-number-object@^1.0.4: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-odd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" - dependencies: - is-number "^4.0.0" - -is-path-cwd@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - dependencies: - path-is-inside "^1.0.2" - -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - dependencies: - isobject "^3.0.1" - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -5356,15 +3064,6 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -5386,456 +3085,19 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" - integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.0.2.tgz#7d7810660a5bd043af9e9cfbe4d58adb05e91531" - integrity sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA== - dependencies: - execa "^5.0.0" - throat "^6.0.1" - -jest-circus@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.1.tgz#3d27da6a974d85a466dc0cdc6ddeb58daaa57bb4" - integrity sha512-75+BBVTsL4+p2w198DQpCeyh1RdaS2lhEG87HkaFX/UG0gJExVq2skG2pT7XZEGBubNj2CytcWSPan4QEPNosw== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/expect" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^0.7.0" - is-generator-fn "^2.0.0" - jest-each "^28.1.1" - jest-matcher-utils "^28.1.1" - jest-message-util "^28.1.1" - jest-runtime "^28.1.1" - jest-snapshot "^28.1.1" - jest-util "^28.1.1" - pretty-format "^28.1.1" - slash "^3.0.0" - stack-utils "^2.0.3" - throat "^6.0.1" - -jest-cli@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.1.tgz#23ddfde8940e1818585ae4a568877b33b0e51cfe" - integrity sha512-+sUfVbJqb1OjBZ0OdBbI6OWfYM1i7bSfzYy6gze1F1w3OKWq8ZTEKkZ8a7ZQPq6G/G1qMh/uKqpdWhgl11NFQQ== - dependencies: - "@jest/core" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/types" "^28.1.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^28.1.1" - jest-util "^28.1.1" - jest-validate "^28.1.1" - prompts "^2.0.1" - yargs "^17.3.1" - -jest-config@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.1.tgz#e90b97b984f14a6c24a221859e81b258990fce2f" - integrity sha512-tASynMhS+jVV85zKvjfbJ8nUyJS/jUSYZ5KQxLUN2ZCvcQc/OmhQl2j6VEL3ezQkNofxn5pQ3SPYWPHb0unTZA== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^28.1.1" - "@jest/types" "^28.1.1" - babel-jest "^28.1.1" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^28.1.1" - jest-environment-node "^28.1.1" - jest-get-type "^28.0.2" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.1" - jest-runner "^28.1.1" - jest-util "^28.1.1" - jest-validate "^28.1.1" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^28.1.1" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.1.tgz#1a3eedfd81ae79810931c63a1d0f201b9120106c" - integrity sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg== - dependencies: - chalk "^4.0.0" - diff-sequences "^28.1.1" - jest-get-type "^28.0.2" - pretty-format "^28.1.1" - -jest-docblock@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" - integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== - dependencies: - detect-newline "^3.0.0" - -jest-each@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.1.tgz#ba5238dacf4f31d9fe23ddc2c44c01e7c23885c4" - integrity sha512-A042rqh17ZvEhRceDMi784ppoXR7MWGDEKTXEZXb4svt0eShMZvijGxzKsx+yIjeE8QYmHPrnHiTSQVhN4nqaw== - dependencies: - "@jest/types" "^28.1.1" - chalk "^4.0.0" - jest-get-type "^28.0.2" - jest-util "^28.1.1" - pretty-format "^28.1.1" - -jest-environment-node@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.1.tgz#1c86c59003a7d319fa06ea3b1bbda6c193715c67" - integrity sha512-2aV/eeY/WNgUUJrrkDJ3cFEigjC5fqT1+fCclrY6paqJ5zVPoM//sHmfgUUp7WLYxIdbPwMiVIzejpN56MxnNA== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/fake-timers" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - jest-mock "^28.1.1" - jest-util "^28.1.1" - -jest-get-type@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" - integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== - -jest-haste-map@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.1.tgz#471685f1acd365a9394745bb97c8fc16289adca3" - integrity sha512-ZrRSE2o3Ezh7sb1KmeLEZRZ4mgufbrMwolcFHNRSjKZhpLa8TdooXOOFlSwoUzlbVs1t0l7upVRW2K7RWGHzbQ== - dependencies: - "@jest/types" "^28.1.1" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^28.0.2" - jest-util "^28.1.1" - jest-worker "^28.1.1" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.1.tgz#537f37afd610a4b3f4cab15e06baf60484548efb" - integrity sha512-4jvs8V8kLbAaotE+wFR7vfUGf603cwYtFf1/PYEsyX2BAjSzj8hQSVTP6OWzseTl0xL6dyHuKs2JAks7Pfubmw== - dependencies: - jest-get-type "^28.0.2" - pretty-format "^28.1.1" - -jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz#a7c4653c2b782ec96796eb3088060720f1e29304" - integrity sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw== - dependencies: - chalk "^4.0.0" - jest-diff "^28.1.1" - jest-get-type "^28.0.2" - pretty-format "^28.1.1" - -jest-message-util@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.1.tgz#60aa0b475cfc08c8a9363ed2fb9108514dd9ab89" - integrity sha512-xoDOOT66fLfmTRiqkoLIU7v42mal/SqwDKvfmfiWAdJMSJiU+ozgluO7KbvoAgiwIrrGZsV7viETjc8GNrA/IQ== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^28.1.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^28.1.1" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.1.tgz#37903d269427fa1ef5b2447be874e1c62a39a371" - integrity sha512-bDCb0FjfsmKweAvE09dZT59IMkzgN0fYBH6t5S45NoJfd2DHkS3ySG2K+hucortryhO3fVuXdlxWcbtIuV/Skw== - dependencies: - "@jest/types" "^28.1.1" - "@types/node" "*" - -jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" - integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== - -jest-resolve-dependencies@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.1.tgz#3dffaaa56f4b41bc6b61053899d1756401763a27" - integrity sha512-p8Y150xYJth4EXhOuB8FzmS9r8IGLEioiaetgdNGb9VHka4fl0zqWlVe4v7mSkYOuEUg2uB61iE+zySDgrOmgQ== - dependencies: - jest-regex-util "^28.0.2" - jest-snapshot "^28.1.1" - -jest-resolve@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.1.tgz#bc2eaf384abdcc1aaf3ba7c50d1adf01e59095e5" - integrity sha512-/d1UbyUkf9nvsgdBildLe6LAD4DalgkgZcKd0nZ8XUGPyA/7fsnaQIlKVnDiuUXv/IeZhPEDrRJubVSulxrShA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - jest-pnp-resolver "^1.2.2" - jest-util "^28.1.1" - jest-validate "^28.1.1" - resolve "^1.20.0" - resolve.exports "^1.1.0" - slash "^3.0.0" - -jest-runner@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.1.tgz#9ecdb3f27a00059986797aa6b012ba8306aa436c" - integrity sha512-W5oFUiDBgTsCloTAj6q95wEvYDB0pxIhY6bc5F26OucnwBN+K58xGTGbliSMI4ChQal5eANDF+xvELaYkJxTmA== - dependencies: - "@jest/console" "^28.1.1" - "@jest/environment" "^28.1.1" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.10.2" - graceful-fs "^4.2.9" - jest-docblock "^28.1.1" - jest-environment-node "^28.1.1" - jest-haste-map "^28.1.1" - jest-leak-detector "^28.1.1" - jest-message-util "^28.1.1" - jest-resolve "^28.1.1" - jest-runtime "^28.1.1" - jest-util "^28.1.1" - jest-watcher "^28.1.1" - jest-worker "^28.1.1" - source-map-support "0.5.13" - throat "^6.0.1" - -jest-runtime@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.1.tgz#569e1dc3c36c6c4c0b29516c1c49b6ad580abdaf" - integrity sha512-J89qEJWW0leOsqyi0D9zHpFEYHwwafFdS9xgvhFHtIdRghbadodI0eA+DrthK/1PebBv3Px8mFSMGKrtaVnleg== - dependencies: - "@jest/environment" "^28.1.1" - "@jest/fake-timers" "^28.1.1" - "@jest/globals" "^28.1.1" - "@jest/source-map" "^28.0.2" - "@jest/test-result" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - execa "^5.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^28.1.1" - jest-message-util "^28.1.1" - jest-mock "^28.1.1" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.1" - jest-snapshot "^28.1.1" - jest-util "^28.1.1" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.1.tgz#ab825c16c8d8b5e883bd57eee6ca8748c42ab848" - integrity sha512-1KjqHJ98adRcbIdMizjF5DipwZFbvxym/kFO4g4fVZCZRxH/dqV8TiBFCa6rqic3p0karsy8RWS1y4E07b7P0A== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^28.1.1" - "@jest/transform" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^28.1.1" - graceful-fs "^4.2.9" - jest-diff "^28.1.1" - jest-get-type "^28.0.2" - jest-haste-map "^28.1.1" - jest-matcher-utils "^28.1.1" - jest-message-util "^28.1.1" - jest-util "^28.1.1" - natural-compare "^1.4.0" - pretty-format "^28.1.1" - semver "^7.3.5" - -jest-util@^28.0.0, jest-util@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.1.tgz#ff39e436a1aca397c0ab998db5a51ae2b7080d05" - integrity sha512-FktOu7ca1DZSyhPAxgxB6hfh2+9zMoJ7aEQA759Z6p45NuO8mWcqujH+UdHlCm/V6JTWwDztM2ITCzU1ijJAfw== - dependencies: - "@jest/types" "^28.1.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.1.tgz#59b7b339b3c85b5144bd0c06ad3600f503a4acc8" - integrity sha512-Kpf6gcClqFCIZ4ti5++XemYJWUPCFUW+N2gknn+KgnDf549iLul3cBuKVe1YcWRlaF8tZV8eJCap0eECOEE3Ug== - dependencies: - "@jest/types" "^28.1.1" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^28.0.2" - leven "^3.1.0" - pretty-format "^28.1.1" - -jest-watcher@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.1.tgz#533597fb3bfefd52b5cd115cd916cffd237fb60c" - integrity sha512-RQIpeZ8EIJMxbQrXpJQYIIlubBnB9imEHsxxE41f54ZwcqWLysL/A0ZcdMirf+XsMn3xfphVQVV4EW0/p7i7Ug== - dependencies: - "@jest/test-result" "^28.1.1" - "@jest/types" "^28.1.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^28.1.1" - string-length "^4.0.1" - -jest-worker@^26.5.0: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.1.tgz#3480c73247171dfd01eda77200f0063ab6a3bf28" - integrity sha512-Au7slXB08C6h+xbJPp7VIb6U0XX5Kc9uel/WFc6/rcTzGiaVCBRngBExSYuXSLFPULPSYU3cJ3ybS988lNFQhQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.1.tgz#3c39a3a09791e16e9ef283597d24ab19a0df701e" - integrity sha512-qw9YHBnjt6TCbIDMPMpJZqf9E12rh6869iZaN08/vpOGgHJSAaLLUn6H8W3IAEuy34Ls3rct064mZLETkxJ2XA== - dependencies: - "@jest/core" "^28.1.1" - "@jest/types" "^28.1.1" - import-local "^3.0.2" - jest-cli "^28.1.1" - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" -js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -5847,14 +3109,6 @@ jsesc@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -5869,17 +3123,7 @@ json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" -json3@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - dependencies: - minimist "^1.2.0" - -json5@^2.1.2, json5@^2.2.1: +json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -5901,44 +3145,12 @@ jsonfile@^6.0.1: array-includes "^3.1.2" object.assign "^4.1.2" -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" +laravel-vite-plugin@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/laravel-vite-plugin/-/laravel-vite-plugin-0.7.0.tgz#61403d3e3068fb62ea97d8d4feecf03db4a6a23f" + integrity sha512-eYViSOGqqVEjQJXkXZFX7DpI5f8vGyAZ9pSoAeM5esJGsX5NtdZmJf2UfblODYcaknXd4POo/pkg0bTRt97X4A== dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -klona@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" - integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + vite-plugin-full-reload "^1.0.1" levn@^0.4.1: version "0.4.1" @@ -5953,69 +3165,35 @@ lilconfig@^2.0.5: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== +lilconfig@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" +local-pkg@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f" + integrity sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg== -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0, loader-utils@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash-es@^4.17.11, lodash-es@^4.17.14: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== lodash.flatmap@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" integrity sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg== -lodash.get@^4.0, lodash.get@^4.4.2: +lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" -lodash.has@^4.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -6026,27 +3204,23 @@ lodash.topath@^4.5.2: resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" integrity sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg== -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loglevel@^1.6.8: - version "1.6.8" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" - integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== - -loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" +loupe@^2.3.1: + version "2.3.4" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" + integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== dependencies: - yallist "^3.0.2" + get-func-name "^2.0.0" lru-cache@^6.0.0: version "6.0.0" @@ -6060,122 +3234,19 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ== -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" +magic-string@^0.26.7: + version "0.26.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f" + integrity sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow== dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@1.x: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - -map-or-similar@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - dependencies: - object-visit "^1.0.0" - -md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - -memfs@^3.1.2: - version "3.4.7" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.7.tgz#e5252ad2242a724f938cb937e3c4f7ceb1f70e5a" - integrity sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw== - dependencies: - fs-monkey "^1.0.3" - -memoizerific@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" - dependencies: - map-or-similar "^1.5.0" - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + sourcemap-codec "^1.4.8" merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.4: +micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -6183,70 +3254,23 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.52.0, "mime-db@>= 1.40.0 < 2": +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.24: +mime-types@^2.1.12: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - -mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - dependencies: - dom-walk "^0.1.0" - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -mini-create-react-context@^0.3.0: - version "0.3.2" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" - dependencies: - "@babel/runtime" "^7.4.0" - gud "^1.0.0" - tiny-warning "^1.0.2" - mini-svg-data-uri@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.2.3.tgz#e16baa92ad55ddaa1c2c135759129f41910bc39f" integrity sha512-zd6KCAyXgmq6FV1mR10oKXYtvmA9vRoB6xPSTUJTbFApCtkefDnYueVR1gkof3KcdLZo1Y8mjF2DFmQMIxsHNQ== -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -6254,184 +3278,45 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.1: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1: - version "3.1.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" - integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== - dependencies: - yallist "^4.0.0" - -minizlib@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" - dependencies: - minipass "^2.2.1" - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== modern-normalize@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7" integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA== -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" - -nan@^2.9.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" +nanoid@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.0.tgz#6e144dee117609232c3f415c34b0e550e64999a5" + integrity sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg== nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== -nanomatch@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-odd "^2.0.0" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" -needle@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - -neo-async@^2.5.0, neo-async@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - -nice-try@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" - node-emoji@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -6439,82 +3324,22 @@ node-emoji@^1.11.0: dependencies: lodash "^4.17.21" -node-fetch@2.6.7: +node-fetch@2.6.7, node-fetch@^2.x.x: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-pre-gyp@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46" - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.0" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.1.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - node-releases@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - dependencies: - remove-trailing-separator "^1.0.1" +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -6526,55 +3351,10 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= -npm-bundled@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" - -npm-packlist@^1.1.6: - version "1.1.10" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a" - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - dependencies: - path-key "^2.0.0" - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-hash@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" @@ -6594,13 +3374,7 @@ object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0, object.assign@^4.1.2: +object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== @@ -6636,12 +3410,6 @@ object.hasown@^1.1.1: define-properties "^1.1.4" es-abstract "^1.19.5" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - dependencies: - isobject "^3.0.1" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -6651,45 +3419,13 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -opener@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" - integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA== - -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - dependencies: - is-wsl "^1.1.0" - optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -6702,95 +3438,6 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - dependencies: - url-parse "^1.4.3" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - dependencies: - retry "^0.12.0" - -p-try@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" - -pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - -parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - dependencies: - cyclist "~0.2.2" - inherits "^2.0.3" - readable-stream "^2.1.5" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -6798,17 +3445,12 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" +parse-cache-control@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" + integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg== -parse-json@^5.0.0, parse-json@^5.2.0: +parse-json@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -6818,49 +3460,12 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - -path-key@^3.0.0, path-key@^3.1.0: +path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -6870,104 +3475,49 @@ path-parse@^1.0.6, path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - -path-to-regexp@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" - dependencies: - isarray "0.0.1" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.3: - version "3.0.16" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" +pathe@0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.3.9.tgz#4baff768f37f03e3d9341502865fb93116f65191" + integrity sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0, pify@^2.3.0: +pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" +popmotion@11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.5.tgz#8e3e014421a0ffa30ecd722564fd2558954e1f7d" + integrity sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA== dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - -pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0, pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -popmotion@11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9" - integrity sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA== - dependencies: - framesync "6.0.1" + framesync "6.1.2" hey-listen "^1.0.8" - style-value-types "5.0.0" - tslib "^2.1.0" + style-value-types "5.1.2" + tslib "2.4.0" -portfinder@^1.0.26: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - -postcss-attribute-case-insensitive@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.1.tgz#86d323c77ab8896ed90500071c2c8329fba64fda" - integrity sha512-wrt2VndqSLJpyBRNz9OmJcgnhI9MaongeWgapdBuUMu2a/KNJ8SENesG4SdiTnQwGO9b1VKbTWYAfCPeokLqZQ== +postcss-attribute-case-insensitive@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz#03d761b24afc04c09e757e92ff53716ae8ea2741" + integrity sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ== dependencies: postcss-selector-parser "^6.0.10" @@ -6978,59 +3528,59 @@ postcss-clamp@^4.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-color-functional-notation@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.3.tgz#23c9d73c76113b75473edcf66f443c6f1872bd0f" - integrity sha512-5fbr6FzFzjwHXKsVnkmEYrJYG8VNNzvD1tAXaPPWR97S6rhKI5uh2yOfV5TAzhDkZoq4h+chxEplFDc8GeyFtw== +postcss-color-functional-notation@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz#21a909e8d7454d3612d1659e471ce4696f28caec" + integrity sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg== dependencies: postcss-value-parser "^4.2.0" -postcss-color-hex-alpha@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.3.tgz#61a0fd151d28b128aa6a8a21a2dad24eebb34d52" - integrity sha512-fESawWJCrBV035DcbKRPAVmy21LpoyiXdPTuHUfWJ14ZRjY7Y7PA6P4g8z6LQGYhU1WAxkTxjIjurXzoe68Glw== +postcss-color-hex-alpha@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz#c66e2980f2fbc1a63f5b079663340ce8b55f25a5" + integrity sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ== dependencies: postcss-value-parser "^4.2.0" -postcss-color-rebeccapurple@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.0.2.tgz#5d397039424a58a9ca628762eb0b88a61a66e079" - integrity sha512-SFc3MaocHaQ6k3oZaFwH8io6MdypkUtEy/eXzXEB1vEQlO3S3oDc/FSZA8AsS04Z25RirQhlDlHLh3dn7XewWw== +postcss-color-rebeccapurple@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz#63fdab91d878ebc4dd4b7c02619a0c3d6a56ced0" + integrity sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg== dependencies: postcss-value-parser "^4.2.0" -postcss-custom-media@^8.0.1: +postcss-custom-media@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz#c8f9637edf45fef761b014c024cee013f80529ea" integrity sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg== dependencies: postcss-value-parser "^4.2.0" -postcss-custom-properties@^12.1.7: - version "12.1.7" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.7.tgz#ca470fd4bbac5a87fd868636dafc084bc2a78b41" - integrity sha512-N/hYP5gSoFhaqxi2DPCmvto/ZcRDVjE3T1LiAMzc/bg53hvhcHOLpXOHb526LzBBp5ZlAUhkuot/bfpmpgStJg== +postcss-custom-properties@^12.1.9: + version "12.1.10" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz#624517179fd4cf50078a7a60f628d5782e7d4903" + integrity sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A== dependencies: postcss-value-parser "^4.2.0" -postcss-custom-selectors@^6.0.2: +postcss-custom-selectors@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz#1ab4684d65f30fed175520f82d223db0337239d9" integrity sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg== dependencies: postcss-selector-parser "^6.0.4" -postcss-dir-pseudo-class@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.4.tgz#9afe49ea631f0cb36fa0076e7c2feb4e7e3f049c" - integrity sha512-I8epwGy5ftdzNWEYok9VjW9whC4xnelAtbajGv4adql4FIF09rnrxnA9Y8xSHN47y7gqFIv10C5+ImsLeJpKBw== +postcss-dir-pseudo-class@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz#2bf31de5de76added44e0a25ecf60ae9f7c7c26c" + integrity sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA== dependencies: - postcss-selector-parser "^6.0.9" + postcss-selector-parser "^6.0.10" -postcss-double-position-gradients@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.1.tgz#a12cfdb7d11fa1a99ccecc747f0c19718fb37152" - integrity sha512-jM+CGkTs4FcG53sMPjrrGE0rIvLDdCrqMzgDC5fLI7JHDO7o6QG8C5TQBtExb13hdBdoH9C2QVbG4jo2y9lErQ== +postcss-double-position-gradients@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz#b96318fdb477be95997e86edd29c6e3557a49b91" + integrity sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" @@ -7061,18 +3611,27 @@ postcss-font-variant@^5.0.0: resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== -postcss-gap-properties@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz#6401bb2f67d9cf255d677042928a70a915e6ba60" - integrity sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ== +postcss-gap-properties@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz#f7e3cddcf73ee19e94ccf7cb77773f9560aa2fff" + integrity sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg== -postcss-image-set-function@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.6.tgz#bcff2794efae778c09441498f40e0c77374870a9" - integrity sha512-KfdC6vg53GC+vPd2+HYzsZ6obmPqOk6HY09kttU19+Gj1nC3S3XBVEXDHxkhxTohgZqzbUb94bKXvKDnYWBm/A== +postcss-image-set-function@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz#08353bd756f1cbfb3b6e93182c7829879114481f" + integrity sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw== dependencies: postcss-value-parser "^4.2.0" +postcss-import@15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.0.0.tgz#0b66c25fdd9c0d19576e63c803cf39e4bad08822" + integrity sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + postcss-import@^14.1.0: version "14.1.0" resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" @@ -7102,10 +3661,10 @@ postcss-js@^4.0.0: dependencies: camelcase-css "^2.0.1" -postcss-lab-function@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.2.0.tgz#e054e662c6480202f5760887ec1ae0d153357123" - integrity sha512-Zb1EO9DGYfa3CP8LhINHCcTTCTLI+R3t7AX2mKsDzdgVQ/GkCpHOTgOr6HBHslP7XDdVbqgHW5vvRPMdVANQ8w== +postcss-lab-function@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz#6fe4c015102ff7cd27d1bd5385582f67ebdbdc98" + integrity sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w== dependencies: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" @@ -7118,17 +3677,6 @@ postcss-load-config@^3.1.0, postcss-load-config@^3.1.4: lilconfig "^2.0.5" yaml "^1.10.2" -postcss-loader@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.3.0.tgz#2c4de9657cd4f07af5ab42bd60a673004da1b8cc" - integrity sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.4" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - semver "^7.3.4" - postcss-logical@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" @@ -7139,34 +3687,6 @@ postcss-media-minmax@^5.0.0: resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - postcss-nested@5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" @@ -7174,10 +3694,17 @@ postcss-nested@5.0.6: dependencies: postcss-selector-parser "^6.0.6" -postcss-nesting@^10.1.7, postcss-nesting@^10.1.8: - version "10.1.8" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.8.tgz#1675542cfedc3dc9621993f3abfdafa260c3a460" - integrity sha512-txdb3/idHYsBbNDFo1PFY0ExCgH5nfWi8G5lO49e6iuU42TydbODTzJgF5UuL5bhgeSlnAtDgfFTDG0Cl1zaSQ== +postcss-nested@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735" + integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w== + dependencies: + postcss-selector-parser "^6.0.10" + +postcss-nesting@10.2.0, postcss-nesting@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.2.0.tgz#0b12ce0db8edfd2d8ae0aaf86427370b898890be" + integrity sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA== dependencies: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" @@ -7187,80 +3714,84 @@ postcss-opacity-percentage@^1.1.2: resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz#bd698bb3670a0a27f6d657cc16744b3ebf3b1145" integrity sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w== -postcss-overflow-shorthand@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz#ebcfc0483a15bbf1b27fdd9b3c10125372f4cbc2" - integrity sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg== +postcss-overflow-shorthand@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz#7ed6486fec44b76f0eab15aa4866cda5d55d893e" + integrity sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A== + dependencies: + postcss-value-parser "^4.2.0" postcss-page-break@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== -postcss-place@^7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.4.tgz#eb026650b7f769ae57ca4f938c1addd6be2f62c9" - integrity sha512-MrgKeiiu5OC/TETQO45kV3npRjOFxEHthsqGtkh3I1rPbZSbXGD/lZVi9j13cYh+NA8PIAPyk6sGjT9QbRyvSg== +postcss-place@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.5.tgz#95dbf85fd9656a3a6e60e832b5809914236986c4" + integrity sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g== dependencies: postcss-value-parser "^4.2.0" -postcss-preset-env@^7.7.1: - version "7.7.1" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.7.1.tgz#ca416c15fd63fd44abe5dcd2890a34b0a664d2c8" - integrity sha512-1sx6+Nl1wMVJzaYLVaz4OAR6JodIN/Z1upmVqLwSPCLT6XyxrEoePgNMHPH08kseLe3z06i9Vfkt/32BYEKDeA== +postcss-preset-env@7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz#4c834d5cbd2e29df2abf59118947c456922b79ba" + integrity sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ== dependencies: - "@csstools/postcss-cascade-layers" "^1.0.2" - "@csstools/postcss-color-function" "^1.1.0" - "@csstools/postcss-font-format-keywords" "^1.0.0" - "@csstools/postcss-hwb-function" "^1.0.1" - "@csstools/postcss-ic-unit" "^1.0.0" - "@csstools/postcss-is-pseudo-class" "^2.0.4" - "@csstools/postcss-normalize-display-values" "^1.0.0" - "@csstools/postcss-oklab-function" "^1.1.0" + "@csstools/postcss-cascade-layers" "^1.1.0" + "@csstools/postcss-color-function" "^1.1.1" + "@csstools/postcss-font-format-keywords" "^1.0.1" + "@csstools/postcss-hwb-function" "^1.0.2" + "@csstools/postcss-ic-unit" "^1.0.1" + "@csstools/postcss-is-pseudo-class" "^2.0.7" + "@csstools/postcss-nested-calc" "^1.0.0" + "@csstools/postcss-normalize-display-values" "^1.0.1" + "@csstools/postcss-oklab-function" "^1.1.1" "@csstools/postcss-progressive-custom-properties" "^1.3.0" - "@csstools/postcss-stepped-value-functions" "^1.0.0" - "@csstools/postcss-trigonometric-functions" "^1.0.1" - "@csstools/postcss-unset-value" "^1.0.1" - autoprefixer "^10.4.7" - browserslist "^4.20.3" + "@csstools/postcss-stepped-value-functions" "^1.0.1" + "@csstools/postcss-text-decoration-shorthand" "^1.0.0" + "@csstools/postcss-trigonometric-functions" "^1.0.2" + "@csstools/postcss-unset-value" "^1.0.2" + autoprefixer "^10.4.11" + browserslist "^4.21.3" css-blank-pseudo "^3.0.3" css-has-pseudo "^3.0.4" css-prefers-color-scheme "^6.0.3" - cssdb "^6.6.3" - postcss-attribute-case-insensitive "^5.0.1" + cssdb "^7.0.1" + postcss-attribute-case-insensitive "^5.0.2" postcss-clamp "^4.1.0" - postcss-color-functional-notation "^4.2.3" - postcss-color-hex-alpha "^8.0.3" - postcss-color-rebeccapurple "^7.0.2" - postcss-custom-media "^8.0.1" - postcss-custom-properties "^12.1.7" - postcss-custom-selectors "^6.0.2" - postcss-dir-pseudo-class "^6.0.4" - postcss-double-position-gradients "^3.1.1" + postcss-color-functional-notation "^4.2.4" + postcss-color-hex-alpha "^8.0.4" + postcss-color-rebeccapurple "^7.1.1" + postcss-custom-media "^8.0.2" + postcss-custom-properties "^12.1.9" + postcss-custom-selectors "^6.0.3" + postcss-dir-pseudo-class "^6.0.5" + postcss-double-position-gradients "^3.1.2" postcss-env-function "^4.0.6" postcss-focus-visible "^6.0.4" postcss-focus-within "^5.0.4" postcss-font-variant "^5.0.0" - postcss-gap-properties "^3.0.3" - postcss-image-set-function "^4.0.6" + postcss-gap-properties "^3.0.5" + postcss-image-set-function "^4.0.7" postcss-initial "^4.0.1" - postcss-lab-function "^4.2.0" + postcss-lab-function "^4.2.1" postcss-logical "^5.0.4" postcss-media-minmax "^5.0.0" - postcss-nesting "^10.1.7" + postcss-nesting "^10.2.0" postcss-opacity-percentage "^1.1.2" - postcss-overflow-shorthand "^3.0.3" + postcss-overflow-shorthand "^3.0.4" postcss-page-break "^3.0.4" - postcss-place "^7.0.4" - postcss-pseudo-class-any-link "^7.1.4" + postcss-place "^7.0.5" + postcss-pseudo-class-any-link "^7.1.6" postcss-replace-overflow-wrap "^4.0.0" - postcss-selector-not "^6.0.0" + postcss-selector-not "^6.0.1" postcss-value-parser "^4.2.0" -postcss-pseudo-class-any-link@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.4.tgz#ac72aac4fe11fc4a0a368691f8fd5fe89e95aba4" - integrity sha512-JxRcLXm96u14N3RzFavPIE9cRPuOqLDuzKeBsqi4oRk4vt8n0A7I0plFs/VXTg7U2n7g/XkQi0OwqTO3VWBfEg== +postcss-pseudo-class-any-link@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz#2693b221902da772c278def85a4d9a64b6e617ab" + integrity sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w== dependencies: postcss-selector-parser "^6.0.10" @@ -7269,14 +3800,14 @@ postcss-replace-overflow-wrap@^4.0.0: resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== -postcss-selector-not@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.0.tgz#d100f273d345917246762300411b4d2e24905047" - integrity sha512-i/HI/VNd3V9e1WOLCwJsf9nePBRXqcGtVibcJ9FsVo0agfDEfsLSlFt94aYjY35wUNcdG0KrvdyjEr7It50wLQ== +postcss-selector-not@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz#8f0a709bf7d4b45222793fc34409be407537556d" + integrity sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ== dependencies: postcss-selector-parser "^6.0.10" -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: version "6.0.10" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== @@ -7293,7 +3824,16 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.1.6, postcss@^8.1.8, postcss@^8.2.15, postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.14: +postcss@8.4.18, postcss@^8.4.18: + version "8.4.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2" + integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.1.6, postcss@^8.1.8, postcss@^8.3.5: version "8.4.14" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== @@ -7314,7 +3854,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.7.1: +prettier@2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== @@ -7328,45 +3868,23 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^28.0.0, pretty-format@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.1.tgz#f731530394e0f7fcd95aba6b43c50e02d86b95cb" - integrity sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw== - dependencies: - "@jest/schemas" "^28.0.2" - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^18.0.0" - pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - -process@~0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== +promise@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" + integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" + asap "~2.0.6" -prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7375,61 +3893,10 @@ prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, object-assign "^4.1.1" react-is "^16.13.1" -property-expr@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330" - integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g== - -proxy-addr@~2.0.5: +property-expr@^2.0.4: version "2.0.5" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.0" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - -public-encrypt@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" + integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== punycode@^2.1.0: version "2.1.1" @@ -7446,35 +3913,17 @@ purgecss@^4.0.3: postcss "^8.3.5" postcss-selector-parser "^6.0.6" -qr.js@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" - integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8= +qrcode.react@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" + integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== -qrcode.react@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-1.0.1.tgz#2834bb50e5e275ffe5af6906eff15391fe9e38a5" - integrity sha512-8d3Tackk8IRLXTo67Y+c1rpaiXjoz/Dd2HpcMdW//62/x8J1Nbho14Kh8x974t9prsLHN6XqVgcnRiBGFptQmg== +qs@^6.4.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: - loose-envify "^1.4.0" - prop-types "^15.6.0" - qr.js "0.0.0" - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - -querystringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" + side-channel "^1.0.4" queue-microtask@^1.2.2: version "1.2.3" @@ -7486,87 +3935,37 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.1.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-chartjs-2@^4.2.0: +react-chartjs-2@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-4.2.0.tgz#bc5693a8b161f125301cf28ab0fe980d7dce54aa" integrity sha512-9Vm9Sg9XAKiR579/FnBkesofjW9goaaFLfS7XlGTzUJlWFZGSE6A/pBI6+i/bP3pobKZoFcWJdFnjShytToqXw== -"react-dom@npm:@hot-loader/react-dom": - version "16.11.0" - resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.11.0.tgz#c0b483923b289db5431516f56ee2a69448ebf9bd" +react-dom@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.17.0" + scheduler "^0.23.0" + +react-fast-compare@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== react-fast-compare@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" -react-fast-compare@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" - integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== - -react-hot-loader@^4.12.21: - version "4.12.21" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.21.tgz#332e830801fb33024b5a147d6b13417f491eb975" - integrity sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA== +react-i18next@12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.0.0.tgz#634015a2c035779c5736ae4c2e5c34c1659753b1" + integrity sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg== dependencies: - fast-levenshtein "^2.0.6" - global "^4.3.0" - hoist-non-react-statics "^3.3.0" - loader-utils "^1.1.0" - prop-types "^15.6.1" - react-lifecycles-compat "^3.0.4" - shallowequal "^1.1.0" - source-map "^0.7.3" + "@babel/runtime" "^7.14.5" + html-parse-stringify "^3.0.1" -react-i18next@^11.2.1: - version "11.2.1" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.2.1.tgz#a56d9f1f52d003eb4fa8f1c7d6752123827160f0" - dependencies: - "@babel/runtime" "^7.3.1" - html-parse-stringify2 "2.0.1" - -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -7576,60 +3975,32 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== + +react-router-dom@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.4.2.tgz#115b37d501d6d8ac870683694978c51c43e6c0d2" + integrity sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ== + dependencies: + "@remix-run/router" "1.0.2" + react-router "6.4.2" + +react-router@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.4.2.tgz#300628ee9ed81b8ef1597b5cb98b474efe9779b8" + integrity sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw== + dependencies: + "@remix-run/router" "1.0.2" + +react@18.2.0: version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - -react-router-dom@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" - dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.1.2" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" - dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.3.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-transition-group@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" - integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - -react@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" read-cache@^1.0.0: version "1.0.0" @@ -7638,9 +4009,10 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" +readable-stream@^2.2.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -7650,22 +4022,6 @@ read-cache@^1.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6: - version "3.1.1" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -7673,18 +4029,10 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -reaptcha@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/reaptcha/-/reaptcha-1.7.2.tgz#d829f54270c241f46501e92a5a7badeb1fcf372d" - integrity sha512-/RXiPeMd+fPUGByv+kAaQlCXCsSflZ9bKX5Fcwv9IYGS1oyT2nntL/8zn9IaiUFHL66T1jBtOABcb92g2+3w8w== - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" +reaptcha@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/reaptcha/-/reaptcha-1.12.1.tgz#1993d2f2ed52669188f16e3210afbf89b7221211" + integrity sha512-zoppfGKHmo8x4PBmSrIQYQOGgVp1e8wMhr6KbwAdbQ76rSky1DcDCXLWRtBg7HGXn2hw+o+0hknafMB0rnrzZQ== reduce-css-calc@^2.1.8: version "2.1.8" @@ -7694,32 +4042,17 @@ reduce-css-calc@^2.1.8: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" -redux-devtools-extension@^2.13.8: - version "2.13.8" - resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" +redux-thunk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== -redux-thunk@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" - -redux@^4.0.0, redux@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" - integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== +redux@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" + integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" - -regenerate-unicode-properties@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" - integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== - dependencies: - regenerate "^1.4.0" - -regenerate@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + "@babel/runtime" "^7.9.2" regenerator-runtime@^0.11.0: version "0.11.1" @@ -7731,20 +4064,6 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== -regenerator-transform@^0.14.2: - version "0.14.5" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" - integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== - dependencies: - "@babel/runtime" "^7.8.4" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -7759,112 +4078,12 @@ regexpp@^3.0.0, regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^4.7.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" - integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.2.0" - regjsgen "^0.5.1" - regjsparser "^0.6.4" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.2.0" - -regjsgen@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== - -regjsparser@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" - integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== - dependencies: - jsesc "~0.5.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - -requireindex@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - -resize-observer-polyfill@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - dependencies: - resolve-from "^3.0.0" - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-pathname@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== - -resolve@^1.1.7, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.8.1: +resolve@^1.1.7, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -7873,6 +4092,15 @@ resolve@^1.1.7, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.3: version "2.0.0-next.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" @@ -7881,14 +4109,6 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -7904,25 +4124,19 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - dependencies: - glob "^7.1.3" - -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" +rollup@^2.79.1: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" run-parallel@^1.1.9: version "1.2.0" @@ -7931,210 +4145,42 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - dependencies: - aproba "^1.1.1" - rxjs-compat@^6.5.4: version "6.6.3" resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.6.3.tgz#141405fcee11f48718d428b99c8f01826f594e5c" integrity sha512-y+wUqq7bS2dG+7rH2fNMoxsDiJ32RQzFxZQE/JdtpnmEZmwLQrb1tCiItyHxdXJHXjmHnnzFscn3b6PEmORGKw== -safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - -scheduler@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe" +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" +semver@^6.1.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" -schema-utils@2.7.0, schema-utils@^2.6.5: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - dependencies: - node-forge "0.9.0" - -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: +semver@^7.3.7: version "7.3.7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - -semver@^6.0.0, semver@^6.1.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" - integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -set-value@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.1" - to-object-path "^0.3.0" - -set-value@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -8142,10 +4188,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -8160,284 +4202,37 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" dependencies: is-arrayish "^0.3.1" -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sockette@^2.0.6: +sockette@2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/sockette/-/sockette-2.0.6.tgz#63b533f3cfe3b592fc84178beea6577fa18cebf3" - -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.20: - version "0.3.20" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" - integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== - dependencies: - faye-websocket "^0.10.0" - uuid "^3.4.0" - websocket-driver "0.6.5" - -source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + integrity sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q== source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-loader@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-1.1.3.tgz#7dbc2fe7ea09d3e43c51fd9fc478b7f016c1f820" - integrity sha512-6YHeF+XzDOrT/ycFJNI53cgEsp/tHTMl37hi7uVyqFAlTXW109JazaQCkbc+jjoL2637qkH1amLi+JzrIpt5lA== - dependencies: - abab "^2.0.5" - iconv-lite "^0.6.2" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - source-map "^0.6.1" - whatwg-mimetype "^2.3.0" - -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-resolve@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" - integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@~0.5.12, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - -ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== - dependencies: - escape-string-regexp "^2.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - -stream-browserify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd" - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== string-similarity@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.3.tgz#ef52d6fc59c8a0fc93b6307fbbc08cc6e18cde21" integrity sha512-QEwJzNFCqq+5AGImk5z4vbsEPTN/+gtyKfXBVLBcbPBRPNganZGfQnIuf9yJ+GiwSnD65sT8xrw/uwU1Q1WmfQ== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string.prototype.matchall@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" @@ -8470,104 +4265,61 @@ string.prototype.trimstart@^1.0.5: define-properties "^1.1.4" es-abstract "^1.19.5" -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" - dependencies: - safe-buffer "~5.1.0" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - -style-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" - integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== +strip-literal@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-0.4.2.tgz#4f9fa6c38bb157b924e9ace7155ebf8a2342cbcf" + integrity sha512-pv48ybn4iE1O9RLgCAN0iU4Xv7RlBTiit6DKmMiErbs9x1wH6vXBs45tWc0H5wUIF6TLTrKweqkmYF/iraQKNw== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" + acorn "^8.8.0" -style-value-types@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.0.0.tgz#76c35f0e579843d523187989da866729411fc8ad" - integrity sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA== +style-mod@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.0.0.tgz#97e7c2d68b592975f2ca7a63d0dd6fcacfe35a01" + integrity sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw== + +style-value-types@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.1.2.tgz#6be66b237bd546048a764883528072ed95713b62" + integrity sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q== dependencies: hey-listen "^1.0.8" - tslib "^2.1.0" + tslib "2.4.0" -styled-components-breakpoint@^3.0.0-preview.20: +styled-components-breakpoint@3.0.0-preview.20: version "3.0.0-preview.20" resolved "https://registry.yarnpkg.com/styled-components-breakpoint/-/styled-components-breakpoint-3.0.0-preview.20.tgz#877e88a00c0cf66976f610a1d347839a1a0b6d70" + integrity sha512-rZ+Upo9lJfzK4xXRZxlvAsT90jaONa5VtoNT18fXaMLd+J75vCD1MU4/pwT/Y5Jw4rzGztMtZpelVC6P+AuNeA== -styled-components@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a" - integrity sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ== +styled-components@5.3.6: + version "5.3.6" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.6.tgz#27753c8c27c650bee9358e343fc927966bfd00d1" + integrity sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/traverse" "^7.4.5" - "@emotion/is-prop-valid" "^0.8.8" + "@emotion/is-prop-valid" "^1.1.0" "@emotion/stylis" "^0.8.4" "@emotion/unitless" "^0.7.4" - babel-plugin-styled-components ">= 1" + babel-plugin-styled-components ">= 1.12.0" css-to-react-native "^3.0.0" hoist-non-react-statics "^3.0.0" shallowequal "^1.1.0" @@ -8579,67 +4331,67 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svg-url-loader@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/svg-url-loader/-/svg-url-loader-7.1.1.tgz#0cbdb30beb8679cb060c12eaf30085747fa7591f" - integrity sha512-NlsMCePODm7FQhU9aEZyGLPx5Xe1QRI1cSEUE6vTq5LJc9l9pStagvXoEIyZ9O3r00w6G3+Wbkimb+SC3DI/Aw== +swr@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" + integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== + +sync-request@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68" + integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw== dependencies: - file-loader "~6.2.0" - loader-utils "~2.0.0" + http-response-object "^3.0.1" + sync-rpc "^1.2.1" + then-request "^6.0.0" -swr@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/swr/-/swr-0.2.3.tgz#e0fb260d27f12fafa2388312083368f45127480d" - integrity sha512-JhuuD5ojqgjAQpZAhoPBd8Di0Mr1+ykByVKuRJdtKaxkUX/y8kMACWKkLgLQc8pcDOKEAnbIreNjU7HfqI9nHQ== +sync-rpc@^1.2.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.6.tgz#b2e8b2550a12ccbc71df8644810529deb68665a7" + integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw== dependencies: - fast-deep-equal "2.0.1" + get-port "^3.1.0" -symbol-observable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - -symbol-observable@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" - integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== - -synchronous-promise@^2.0.10: - version "2.0.13" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702" - integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA== +tailwindcss@3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.2.tgz#705f78cec8f4de2feb52abdb7a8a056e67f2d736" + integrity sha512-c2GtSdqg+harR4QeoTmex0Ngfg8IIHNeLQH5yr2B9uZbZR1Xt1rYbjWOWTcj3YLTZhrmZnPowoQDbSRFyZHQ5Q== + dependencies: + arg "^5.0.2" + chokidar "^3.5.3" + color-name "^1.1.4" + detective "^5.2.1" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.12" + glob-parent "^6.0.2" + is-glob "^4.0.3" + lilconfig "^2.0.6" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.18" + postcss-import "^14.1.0" + postcss-js "^4.0.0" + postcss-load-config "^3.1.4" + postcss-nested "6.0.0" + postcss-selector-parser "^6.0.10" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.1" tailwindcss@^2.2.7: version "2.2.19" @@ -8679,164 +4431,50 @@ tailwindcss@^2.2.7: resolve "^1.20.0" tmp "^0.2.1" -tailwindcss@^3.0.24: - version "3.0.24" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d" - integrity sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig== - dependencies: - arg "^5.0.1" - chokidar "^3.5.3" - color-name "^1.1.4" - detective "^5.2.0" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.2.11" - glob-parent "^6.0.2" - is-glob "^4.0.3" - lilconfig "^2.0.5" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.12" - postcss-js "^4.0.0" - postcss-load-config "^3.1.4" - postcss-nested "5.0.6" - postcss-selector-parser "^6.0.10" - postcss-value-parser "^4.2.0" - quick-lru "^5.1.1" - resolve "^1.22.0" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - -tar@^4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" - dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - -tar@^6.0.2: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - -terser-webpack-plugin@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f" - integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^3.1.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser-webpack-plugin@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" - integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== - dependencies: - cacache "^15.0.5" - find-cache-dir "^3.3.1" - jest-worker "^26.5.0" - p-limit "^3.0.2" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.3.4" - webpack-sources "^1.4.3" - -terser@^4.1.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^5.3.4: - version "5.14.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.0.tgz#eefeec9af5153f55798180ee2617f390bdd285e2" - integrity sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -throat@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" - integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== - -through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" +then-request@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.2.tgz#ec18dd8b5ca43aaee5cb92f7e4c1630e950d4f0c" + integrity sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA== dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -thunky@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" - -timers-browserify@^2.0.4: - version "2.0.10" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" - dependencies: - setimmediate "^1.0.4" + "@types/concat-stream" "^1.6.0" + "@types/form-data" "0.0.33" + "@types/node" "^8.0.0" + "@types/qs" "^6.2.31" + caseless "~0.12.0" + concat-stream "^1.6.0" + form-data "^2.2.0" + http-basic "^8.1.1" + http-response-object "^3.0.1" + promise "^8.0.0" + qs "^6.4.0" timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" -tiny-invariant@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" - -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" +tinybench@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.3.1.tgz#14f64e6b77d7ef0b1f6ab850c7a808c6760b414d" + integrity sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA== + +tinypool@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.3.0.tgz#c405d8b743509fc28ea4ca358433190be654f819" + integrity sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ== + +tinyspy@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-1.0.2.tgz#6da0b3918bfd56170fb3cd3a2b5ef832ee1dff0d" + integrity sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q== + tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -8844,32 +4482,10 @@ tmp@^0.2.1: dependencies: rimraf "^3.0.0" -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -8877,24 +4493,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - toggle-selection@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - toposort@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" @@ -8904,45 +4507,26 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -tryer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" - integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +ts-essentials@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.3.0.tgz#7e639c1a76b1805c3c60d6e1b5178da2e70aea02" + integrity sha512-XeiCboEyBG8UqXZtXl59bWEi4ZgOqRsogFDI6WDGIF1LmzbYiAkIwjkXN6zZWWl4re/lsOqMlYfe8KA0XiiEPw== -ts-essentials@^9.1.2: - version "9.1.2" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.1.2.tgz#46db6944b73b4cd603f3d959ef1123c16ba56f59" - integrity sha512-EaSmXsAhEiirrTY1Oaa7TSpei9dzuCuFPmjKRJRPamERYtfaGS8/KpOSbjergLz/Y76/aZlV9i/krgzsuWEBbg== +ts-toolbelt@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5" + integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w== -ts-jest@^28.0.5: - version "28.0.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.5.tgz#31776f768fba6dfc8c061d488840ed0c8eeac8b9" - integrity sha512-Sx9FyP9pCY7pUzQpy4FgRZf2bhHY3za576HMKJFs+OnQ9jS96Du5vNsDKkyedQkik+sEabbKAnCliv9BEsHZgQ== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^28.0.0" - json5 "^2.2.1" - lodash.memoize "4.x" - make-error "1.x" - semver "7.x" - yargs-parser "^21.0.1" - -ts-toolbelt@^8.0.7: - version "8.0.7" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-8.0.7.tgz#4dad2928831a811ee17dbdab6eb1919fc0a295bf" - integrity sha512-KICHyKxc5Nu34kyoODrEe2+zvuQQaubTJz7pnC5RQ19TH/Jged1xv+h8LBrouaSD310m75oAljYs59LNHkLDkQ== - -tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.1.0: +tslib@2.4.0, tslib@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -8950,11 +4534,7 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - -twin.macro@^2.8.2: +twin.macro@2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/twin.macro/-/twin.macro-2.8.2.tgz#7f1344b4b1c3811da93a62fa204fe08999df7a75" integrity sha512-2Vg09mp+nA70AWUedJ8WRgB2me3buq7JGbOnjHnFnNaBzomVu5k7lJ9YGpByIlre+UYr7QRhtlj7+IUKxvCrUA== @@ -8982,7 +4562,7 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@4.0.8: +type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -8992,26 +4572,15 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^4.7.3: - version "4.7.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" - integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== +typescript@4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== unbox-primitive@^1.0.2: version "1.0.2" @@ -9023,67 +4592,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -unicode-canonical-property-names-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" - -unicode-match-property-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" - dependencies: - unicode-canonical-property-names-ecmascript "^1.0.4" - unicode-property-aliases-ecmascript "^1.0.4" - -unicode-match-property-value-ecmascript@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" - integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== - -unicode-property-aliases-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" - -union-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^0.4.3" - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab" - dependencies: - imurmurhash "^0.1.4" - universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - update-browserslist-db@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" @@ -9092,6 +4605,14 @@ update-browserslist-db@^1.0.0: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -9099,312 +4620,88 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - -url-parse@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" - dependencies: - querystringify "^2.0.0" - requires-port "^1.0.0" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use-fit-text@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/use-fit-text/-/use-fit-text-2.4.0.tgz#d3d1cd72f6d29cfb2233ec0e0b38c6f25d319f67" - integrity sha512-Iy4LMrXcdxWlyZ5phntMpJMgyXGB1p3tV73y2r0QrZ6f/thPh+/QU3ie6RCXmjF8tHMs20FKMPskXeDYIla/Ww== - dependencies: - resize-observer-polyfill "^1.5.1" - -use-isomorphic-layout-effect@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-memo-one@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" - integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== - use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== -use@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" - dependencies: - kind-of "^6.0.2" - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - dependencies: - inherits "2.0.3" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - -uuid@^3.3.2, uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: +v8-compile-cache@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== -v8-to-istanbul@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" - integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== +vite-plugin-full-reload@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vite-plugin-full-reload/-/vite-plugin-full-reload-1.0.4.tgz#3fecd446f9accd5af01eb0328f6d161dca7cfc45" + integrity sha512-9WejQII6zJ++m/YE173Zvl2jq4cqa404KNrVT+JDzDnqaGRq5UvOvA48fnsSWPIMXFV7S0dq5+sZqcSB+tKBgA== dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" + picocolors "^1.0.0" + picomatch "^2.3.1" -value-equal@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - -vm-browserify@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" - -void-elements@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== +vite@3.2.2, vite@^3.0.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.2.tgz#280762bfaf47bcea1d12698427331c0009ac7c1f" + integrity sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw== dependencies: - makeerror "1.0.12" - -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== - dependencies: - chokidar "^2.1.8" - -watchpack@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== - dependencies: - graceful-fs "^4.1.2" - neo-async "^2.5.0" + esbuild "^0.15.9" + postcss "^8.4.18" + resolve "^1.22.1" + rollup "^2.79.1" optionalDependencies: - chokidar "^3.4.0" - watchpack-chokidar2 "^2.0.0" + fsevents "~2.3.2" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" +vitest@0.24.5: + version "0.24.5" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.24.5.tgz#ba23acdf4362e3299ca2a8a55afe3d2e7402b763" + integrity sha512-zw6JhPUHtLILQDe5Q39b/SzoITkG+R7hcFjuthp4xsi6zpmfQPOZcHodZ+3bqoWl4EdGK/p1fuMiEwdxgbGLOA== dependencies: - minimalistic-assert "^1.0.0" + "@types/chai" "^4.3.3" + "@types/chai-subset" "^1.3.3" + "@types/node" "*" + chai "^4.3.6" + debug "^4.3.4" + local-pkg "^0.4.2" + strip-literal "^0.4.2" + tinybench "^2.3.1" + tinypool "^0.3.0" + tinyspy "^1.0.2" + vite "^3.0.0" + +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + +w3c-keyname@^2.2.4: + version "2.2.6" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.6.tgz#8412046116bc16c5d73d4e612053ea10a189c85f" + integrity sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg== webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= -webpack-assets-manifest@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz#39bbc3bf2ee57fcd8ba07cda51c9ba4a3c6ae1de" - integrity sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ== - dependencies: - chalk "^2.0" - lodash.get "^4.0" - lodash.has "^4.0" - mkdirp "^0.5" - schema-utils "^1.0.0" - tapable "^1.0.0" - webpack-sources "^1.0.0" +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -webpack-bundle-analyzer@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.8.0.tgz#ce6b3f908daf069fd1f7266f692cbb3bded9ba16" - integrity sha512-PODQhAYVEourCcOuU+NiYI7WdR8QyELZGgPvB1y2tjbUpbmcQOt5Q7jEK+ttd5se0KSBKD9SXHCEozS++Wllmw== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - bfj "^6.1.1" - chalk "^2.4.1" - commander "^2.18.0" - ejs "^2.6.1" - express "^4.16.3" - filesize "^3.6.1" - gzip-size "^5.0.0" - lodash "^4.17.15" - mkdirp "^0.5.1" - opener "^1.5.1" - ws "^6.0.0" - -webpack-cli@^3.3.12: - version "3.3.12" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" - integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== - dependencies: - chalk "^2.4.2" - cross-spawn "^6.0.5" - enhanced-resolve "^4.1.1" - findup-sync "^3.0.0" - global-modules "^2.0.0" - import-local "^2.0.0" - interpret "^1.4.0" - loader-utils "^1.4.0" - supports-color "^6.1.0" - v8-compile-cache "^2.1.1" - yargs "^13.3.2" - -webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" - integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.7" - semver "^6.3.0" - serve-index "^1.9.1" - sockjs "0.3.20" - sockjs-client "1.4.0" - spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: +whatwg-encoding@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" + iconv-lite "0.6.3" -webpack-sources@^1.0.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^4.43.0: - version "4.43.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6" - integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.6.1" - webpack-sources "^1.4.1" - -websocket-driver@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= - dependencies: - websocket-extensions ">=0.1.1" - -websocket-driver@>=0.5.1: - version "0.7.0" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" - dependencies: - http-parser-js ">=0.4.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== whatwg-url@^5.0.0: version "5.0.0" @@ -9425,16 +4722,6 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - -which@^1.2.14, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - dependencies: - isexe "^2.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -9442,70 +4729,27 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - dependencies: - string-width "^1.0.2 || 2" - word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - dependencies: - errno "~0.1.7" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" - integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -ws@^6.0.0, ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - dependencies: - async-limiter "~1.0.0" - -xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: +xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-fit@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596" - integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ== +xterm-addon-fit@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.6.0.tgz#142e1ce181da48763668332593fc440349c88c34" + integrity sha512-9/7A+1KEjkFam0yxTaHfuk9LEvvTSBi0PZmEkzJqgafXPEXL9pCMAVV7rB09sX6ATRDXAdBpQhZkhKj7CGvYeg== -xterm-addon-search-bar@^0.2.0: +xterm-addon-search-bar@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/xterm-addon-search-bar/-/xterm-addon-search-bar-0.2.0.tgz#e03c020a5ed22f1e8d503946b26a14ade508bc91" integrity sha512-xvXmBA/ShbnzGe5CCy0kqPNNGqjkpuaRgH3Z1iW0V71vCAPRrtJ/v/hMnysZBH7WGUYhlCQr1cJZagW2fBVvSg== @@ -9513,33 +4757,20 @@ xterm-addon-search-bar@^0.2.0: babel-runtime "^6.26.0" rxjs-compat "^6.5.4" -xterm-addon-search@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0.tgz#95278ebb818cfcf882209ae75be96e0bea5d52a5" - integrity sha512-aoolI8YuHvdGw+Qjg8g2M4kst0v86GtB7WeBm4F0jNXA005/6QbWWy9eCsvnIDLJOFI5JSSrZnD6CaOkvBQYPA== +xterm-addon-search@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0.tgz#b6a5e859c0bfd83ad534233f93376640c0e0c652" + integrity sha512-l+kjDxNDQbkniU5OUo9BHknxUEPZGM0OFpVpc2sMmrb97S0FKJVJO4wAZPJvSGVJ8ZEG6KuDyzXluvnb08t71Q== -xterm-addon-web-links@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.6.0.tgz#0296cb6c99588847894670d998c9ea6a6aeb26ee" - integrity sha512-H6XzjWWZu8FBo+fnYpxdPk9w5M6drbsvwPEJZGRS38MihiQaVFpKlCMKdfRgDbKGE530tw1yH54rhpZfHgt2/A== +xterm-addon-web-links@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.7.0.tgz#dceac36170605f9db10a01d716bd83ee38f65c17" + integrity sha512-6PqoqzzPwaeSq22skzbvyboDvSnYk5teUYEoKBwMYvhbkwOQkemZccjWHT5FnNA8o1aInTc4PRYAl4jjPucCKA== -xterm@^4.19.0: - version "4.19.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" - integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== - -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" +xterm@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0.tgz#0af50509b33d0dc62fde7a4ec17750b8e453cc5c" + integrity sha512-tmVsKzZovAYNDIaUinfz+VDclraQpPUnAME+JawosgWRMphInDded/PuY0xmU5dOhyeYZsI0nz5yd8dPYsdLTA== yallist@^4.0.0: version "4.0.0" @@ -9551,70 +4782,15 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== +yup@0.32.11: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^21.0.0, yargs-parser@^21.0.1: - version "21.0.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" - integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== - -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yargs@^17.3.1: - version "17.5.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - -yarn-deduplicate@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d" - dependencies: - "@yarnpkg/lockfile" "^1.1.0" - commander "^2.10.0" - semver "^5.3.0" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -yup@^0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.1.tgz#35d25aab470a0c3950f66040ba0ff4b1b6efe0d9" - integrity sha512-U7mPIbgfQWI6M3hZCJdGFrr+U0laG28FxMAKIgNvgl7OtyYuUoc4uy9qCWYHZjh49b8T7Ug8NNDdiMIEytcXrQ== - dependencies: - "@babel/runtime" "^7.9.6" - fn-name "~3.0.0" - lodash "^4.17.15" - lodash-es "^4.17.11" - property-expr "^2.0.2" - synchronous-promise "^2.0.10" + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" toposort "^2.0.2" From 3bf5a718021fcf4a08699f83a1829b502c54a662 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Fri, 25 Nov 2022 15:29:04 -0500 Subject: [PATCH 018/106] PostgreSQL Support (#4486) Co-authored-by: Matthew Penner --- .env.ci | 20 --- .github/workflows/ci.yaml | 115 ++++++++++++++++-- .github/workflows/lint.yaml | 3 - .../Api/Client/ClientController.php | 5 + app/Models/Node.php | 4 +- app/Models/User.php | 4 +- app/Providers/AppServiceProvider.php | 4 +- app/Providers/HashidsServiceProvider.php | 9 +- app/Providers/RepositoryServiceProvider.php | 2 +- app/Providers/RouteServiceProvider.php | 2 +- app/Providers/SettingsServiceProvider.php | 2 +- .../Eloquent/AllocationRepository.php | 6 +- .../Eloquent/EloquentRepository.php | 15 ++- app/Repositories/Eloquent/NodeRepository.php | 6 +- .../Deployment/FindViableNodesService.php | 8 +- bootstrap/tests.php | 2 + config/database.php | 15 +++ ...016_01_23_195641_add_allocations_table.php | 5 +- .../2016_01_23_195851_add_api_keys.php | 5 +- .../2016_01_23_200044_add_api_permissions.php | 5 +- .../2016_01_23_200159_add_downloads.php | 5 +- ..._01_23_200421_create_failed_jobs_table.php | 5 +- .../2016_01_23_200440_create_jobs_table.php | 6 +- .../2016_01_23_200528_add_locations.php | 5 +- .../2016_01_23_200648_add_nodes.php | 7 +- .../2016_01_23_201433_add_password_resets.php | 5 +- .../2016_01_23_201531_add_permissions.php | 5 +- ...2016_01_23_201649_add_server_variables.php | 5 +- .../2016_01_23_201748_add_servers.php | 5 +- .../2016_01_23_202544_add_service_options.php | 5 +- ...2016_01_23_202731_add_service_varibles.php | 5 +- .../2016_01_23_202943_add_services.php | 5 +- ...016_01_23_203119_create_settings_table.php | 5 +- .../2016_01_23_203150_add_subusers.php | 5 +- .../2016_01_23_203159_add_users.php | 5 +- ...016_01_23_203947_create_sessions_table.php | 5 +- ...01_25_234418_rename_permissions_column.php | 5 +- ...2016_02_07_172148_add_databases_tables.php | 5 +- ...2_07_181319_add_database_servers_table.php | 5 +- ...306_add_service_option_default_startup.php | 5 +- ..._02_20_155318_add_unique_service_field.php | 7 +- .../2016_02_27_163411_add_tasks_table.php | 5 +- .../2016_02_27_163447_add_tasks_log_table.php | 5 +- ...3_18_155649_add_nullable_field_lastrun.php | 24 ---- .../2016_08_30_212718_add_ip_alias.php | 5 +- ..._08_30_213301_modify_ip_storage_method.php | 5 +- ...9_01_193520_add_suspension_for_servers.php | 5 +- ...2016_09_01_211924_remove_active_column.php | 5 +- ...09_02_190647_add_sftp_password_storage.php | 5 +- .../2016_09_04_171338_update_jobs_tables.php | 10 +- ..._09_04_172028_update_failed_jobs_table.php | 4 +- ...9_04_182835_create_notifications_table.php | 5 +- ...016_09_07_163017_add_unique_identifier.php | 4 +- ..._09_14_145945_allow_longer_regex_field.php | 4 +- ...6_09_17_194246_add_docker_image_column.php | 5 +- ...9_21_165554_update_servers_column_name.php | 4 +- ..._09_29_213518_rename_double_insurgency.php | 4 +- .../2016_10_07_152117_build_api_log_table.php | 4 +- .../2016_10_14_164802_update_api_keys.php | 4 +- ...16_10_23_181719_update_misnamed_bungee.php | 4 +- ..._10_23_193810_add_foreign_keys_servers.php | 56 ++++----- ...6_10_23_201624_add_foreign_allocations.php | 23 ++-- ...2016_10_23_202222_add_foreign_api_keys.php | 8 +- ..._23_202703_add_foreign_api_permissions.php | 15 ++- ...23_202953_add_foreign_database_servers.php | 8 +- ...016_10_23_203105_add_foreign_databases.php | 12 +- .../2016_10_23_203335_add_foreign_nodes.php | 15 ++- ...6_10_23_203522_add_foreign_permissions.php | 12 +- ...23_203857_add_foreign_server_variables.php | 18 +-- ..._23_204157_add_foreign_service_options.php | 15 ++- ...3_204321_add_foreign_service_variables.php | 15 ++- ...2016_10_23_204454_add_foreign_subusers.php | 12 +- .../2016_10_23_204610_add_foreign_tasks.php | 4 +- ...04_000949_add_ark_service_option_fixed.php | 4 +- .../2016_11_11_220649_add_pack_support.php | 4 +- ...6_11_11_231731_set_service_name_unique.php | 4 +- .../2016_11_27_142519_add_pack_column.php | 4 +- ...1_173018_add_configurable_upload_limit.php | 4 +- ...12_02_185206_correct_service_variables.php | 4 +- ...7_01_03_150436_fix_misnamed_option_tag.php | 4 +- ...create_node_configuration_tokens_table.php | 4 +- .../2017_01_12_135449_add_more_user_data.php | 4 +- .../2017_02_02_175548_UpdateColumnNames.php | 29 ++--- .../2017_02_03_140948_UpdateNodesTable.php | 10 +- .../2017_02_03_155554_RenameColumns.php | 18 ++- .../2017_02_05_164123_AdjustColumnNames.php | 11 +- ...64516_AdjustColumnNamesForServicePacks.php | 11 +- ...2_09_174834_SetupPermissionsPivotTable.php | 18 ++- ...7_02_10_171858_UpdateAPIKeyColumnNames.php | 8 +- ...3_224254_UpdateNodeConfigTokensColumns.php | 4 +- ...5_212803_DeleteServiceExecutableOption.php | 4 +- ..._10_162934_AddNewServiceOptionsColumns.php | 4 +- ...03_10_173607_MigrateToNewServiceSystem.php | 4 +- ..._ChangeServiceVariablesValidationRules.php | 4 +- ...150648_MoveFunctionsFromFileToDatabase.php | 4 +- ...5631_RenameServicePacksToSingluarPacks.php | 4 +- ...17_03_14_200326_AddLockedStatusToTable.php | 4 +- ...eOrganizeDatabaseServersToDatabaseHost.php | 4 +- ..._03_16_181515_CleanupDatabasesDatabase.php | 4 +- ...2017_03_18_204953_AddForeignKeyToPacks.php | 4 +- ...3_31_221948_AddServerDescriptionColumn.php | 4 +- ..._163232_DropDeletedAtColumnFromServers.php | 4 +- .../2017_04_15_125021_UpgradeTaskSystem.php | 4 +- ...4_20_171943_AddScriptsToServiceOptions.php | 4 +- ...1432_AddServiceScriptTrackingToServers.php | 4 +- ...7_04_27_145300_AddCopyScriptFromColumn.php | 4 +- ...ConnectionOverSSLWithDaemonBehindProxy.php | 4 +- .../2017_05_01_141528_DeleteDownloadTable.php | 4 +- ...01_141559_DeleteNodeConfigurationTable.php | 4 +- ..._06_10_152951_add_external_id_to_users.php | 4 +- ...23_ChangeForeignKeyToBeOnCascadeDelete.php | 4 +- ...eUserPermissionsToDeleteOnUserDeletion.php | 4 +- ...llocationToReferenceNullOnServerDelete.php | 4 +- ...DeletionWhenAServerOrVariableIsDeleted.php | 4 +- ...33_DeleteTaskWhenParentServerIsDeleted.php | 4 +- ...ValuesForDatabaseHostWhenNodeIsDeleted.php | 4 +- ...4_AllowNegativeValuesForOverallocation.php | 6 +- ...SetAllocationUnqiueUsingMultipleFields.php | 4 +- ...adeDeletionWhenAParentServiceIsDeleted.php | 4 +- ...vePackWhenParentServiceOptionIsDeleted.php | 4 +- ...9_RenameTasksTableForStructureRefactor.php | 4 +- ...2017_09_10_225941_CreateSchedulesTable.php | 4 +- ...230309_CreateNewTasksTableForSchedules.php | 4 +- ..._002938_TransferOldTasksToNewScheduler.php | 63 +++++----- ...dPermissionsToPointToNewScheduleSystem.php | 4 +- ...017_09_23_170933_CreateDaemonKeysTable.php | 4 +- ...628_RemoveDaemonSecretFromServersTable.php | 4 +- ...22_RemoveDaemonSecretFromSubusersTable.php | 5 +- ...angeServicesToUseAMoreUniqueIdentifier.php | 4 +- ...ngeToABetterUniqueServiceConfiguration.php | 4 +- ...cadeDeletionWhenServiceOptionIsDeleted.php | 4 +- ...10_06_214026_ServicesToNestsConversion.php | 5 +- ..._214053_ServiceOptionsToEggsConversion.php | 5 +- ...rviceVariablesToEggVariablesConversion.php | 5 +- ..._24_222238_RemoveLegacySFTPInformation.php | 4 +- ...1922_Add2FaLastAuthorizationTimeColumn.php | 4 +- ...122708_MigratePubPrivFormatToSingleKey.php | 29 +++-- ...84012_DropAllocationsWhenNodeIsDeleted.php | 4 +- ...220426_MigrateSettingsTableToNewFormat.php | 4 +- ...22821_AllowNegativeValuesForServerSwap.php | 8 +- ...1_11_213943_AddApiKeyPermissionColumns.php | 8 +- ...1_13_142012_SetupTableForKeyEncryption.php | 8 +- .../2018_01_13_145209_AddLastUsedAtColumn.php | 8 +- ...02_04_145617_AllowTextInUserExternalId.php | 8 +- ...ove_unique_index_on_external_id_column.php | 8 +- ..._unique_allocation_id_on_servers_table.php | 8 +- ...dd_external_id_column_to_servers_table.php | 8 +- ...152_remove_default_null_value_on_table.php | 6 +- ...fine_unique_index_on_users_external_id.php | 8 +- ...nd_port_limit_columns_to_servers_table.php | 8 +- ..._03_15_124536_add_description_to_nodes.php | 8 +- ..._05_04_123826_add_maintenance_to_nodes.php | 8 +- ...ow_egg_variables_to_have_longer_values.php | 8 +- ...server_variables_to_have_longer_values.php | 8 +- ...2328_set_allocation_limit_default_null.php | 8 +- ...1_fix_unique_index_to_account_for_host.php | 8 +- ..._merge_permissions_table_into_subusers.php | 15 +-- ...20_03_22_164814_drop_permissions_table.php | 8 +- ...24_add_threads_column_to_servers_table.php | 8 +- ...2020_04_03_230614_create_backups_table.php | 8 +- ...4_04_131016_add_table_server_transfers.php | 14 +-- ...4_store_node_tokens_as_encrypted_value.php | 11 +- ..._17_203438_allow_nullable_descriptions.php | 8 +- ...4_22_055500_add_max_connections_column.php | 8 +- ..._26_111208_add_backup_limit_to_servers.php | 8 +- .../2020_05_20_234655_add_mounts_table.php | 8 +- ...20_05_21_192756_add_mount_server_table.php | 8 +- ...3612_create_user_recovery_tokens_table.php | 8 +- ...01845_add_notes_column_for_allocations.php | 8 +- ...533_add_backup_state_column_to_backups.php | 8 +- ...132500_update_bytes_to_unsigned_bigint.php | 8 +- ...31_modify_checksums_column_for_backups.php | 8 +- ...0_09_13_110007_drop_packs_from_servers.php | 8 +- ...21_drop_packs_from_api_key_permissions.php | 8 +- .../2020_09_13_110047_drop_packs_table.php | 8 +- ...020_09_13_113503_drop_daemon_key_table.php | 8 +- ...ue_database_name_to_account_for_server.php | 8 +- ...move_nullable_from_schedule_name_field.php | 8 +- ..._02_201014_add_features_column_to_eggs.php | 8 +- ...ort_multiple_docker_images_and_updates.php | 31 +++-- ...uccessful_nullable_in_server_transfers.php | 8 +- ...chived_field_to_server_transfers_table.php | 12 +- ..._24_092449_make_allocation_fields_json.php | 34 ------ ..._add_upload_id_column_to_backups_table.php | 8 +- ...53937_add_file_denylist_to_egg_configs.php | 8 +- .../2021_01_13_013420_add_cron_month.php | 8 +- ...1_01_17_102401_create_audit_logs_table.php | 8 +- ...52623_add_generic_server_status_column.php | 24 ++-- ...26_210502_update_file_denylist_to_json.php | 8 +- ...205021_add_index_for_server_and_action.php | 8 +- ..._23_212657_make_sftp_port_unsigned_int.php | 8 +- ...n_month_field_to_have_value_if_missing.php | 14 +-- ...dd_continue_on_failure_option_to_tasks.php | 8 +- ...when_server_online_option_to_schedules.php | 8 +- ...01016_add_support_for_locking_a_backup.php | 8 +- ...21_07_12_013420_remove_userinteraction.php | 39 ++++-- ...7_17_211512_create_user_ssh_keys_table.php | 4 +- ...d_to_default_to_false_on_backups_table.php | 8 +- ...1_add_foreign_keys_to_mount_node_table.php | 8 +- ...add_foreign_keys_to_mount_server_table.php | 8 +- ...21_add_foreign_keys_to_egg_mount_table.php | 8 +- ...022_01_25_030847_drop_google_analytics.php | 14 +-- ...migrate_egg_images_array_to_new_format.php | 6 +- ...5_28_135717_create_activity_logs_table.php | 8 +- ...40349_create_activity_log_actors_table.php | 8 +- ...rack_api_key_usage_for_activity_events.php | 8 +- ...force_outgoing_ip_column_to_eggs_table.php | 8 +- ...d_installed_at_column_to_servers_table.php | 8 +- ...files_column_nullable_on_backups_table.php | 27 ++++ ...ix_language_column_type_on_users_table.php | 36 ++++++ phpunit.xml | 2 +- .../Location/LocationControllerTest.php | 2 +- .../Application/Nests/EggControllerTest.php | 2 +- .../Application/Nests/NestControllerTest.php | 2 +- .../Users/ExternalUserControllerTest.php | 2 +- .../Application/Users/UserControllerTest.php | 2 +- .../Api/Client/ClientControllerTest.php | 26 ++-- .../Startup/GetStartupAndVariablesTest.php | 2 +- tests/Integration/IntegrationTestCase.php | 2 +- .../Servers/ServerCreationServiceTest.php | 7 +- .../StartupModificationServiceTest.php | 19 +-- .../Servers/VariableValidatorServiceTest.php | 11 +- tests/Traits/MocksPdoConnection.php | 48 -------- 223 files changed, 912 insertions(+), 1052 deletions(-) delete mode 100644 .env.ci delete mode 100644 database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php delete mode 100644 database/migrations/2020_12_24_092449_make_allocation_fields_json.php create mode 100644 database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php create mode 100644 database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php delete mode 100644 tests/Traits/MocksPdoConnection.php diff --git a/.env.ci b/.env.ci deleted file mode 100644 index 1a9e848e3..000000000 --- a/.env.ci +++ /dev/null @@ -1,20 +0,0 @@ -APP_ENV=testing -APP_DEBUG=true -APP_KEY=SomeRandomString3232RandomString -APP_THEME=pterodactyl -APP_TIMEZONE=UTC -APP_URL=http://localhost/ -APP_ENVIRONMENT_ONLY=true - -DB_CONNECTION=mysql -DB_HOST=127.0.0.1 -DB_DATABASE=testing -DB_USERNAME=root -DB_PASSWORD= - -CACHE_DRIVER=array -SESSION_DRIVER=array -MAIL_DRIVER=array -QUEUE_DRIVER=sync - -HASHIDS_SALT=test123 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4da19b6ed..28ce63e4a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,23 +11,43 @@ on: - "1.0-develop" jobs: - tests: - name: Tests + mysql: + name: MySQL runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - php: [8.0, 8.1] - database: ["mariadb:10.2", "mysql:8"] + php: [8.1] + database: ["mariadb:10.2", "mariadb:10.9", "mysql:8"] services: database: - image: ${{ matrix.database }} + image: docker.io/library/${{ matrix.database }} env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: testing ports: - - 3306 + - 3306/tcp options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + env: + APP_ENV: testing + APP_DEBUG: "true" + APP_KEY: SomeRandomString3232RandomString + APP_THEME: pterodactyl + APP_TIMEZONE: UTC + APP_URL: http://localhost/ + APP_ENVIRONMENT_ONLY: "true" + + DB_CONNECTION: mysql + DB_HOST: 127.0.0.1 + DB_DATABASE: testing + DB_USERNAME: root + + CACHE_DRIVER: array + MAIL_MAILER: array + SESSION_DRIVER: array + QUEUE_CONNECTION: sync + + HASHIDS_SALT: test123 steps: - name: Code Checkout uses: actions/checkout@v3 @@ -53,9 +73,6 @@ jobs: tools: composer:v2 coverage: none - - name: Setup .env - run: cp .env.ci .env - - name: Install dependencies run: composer install --no-interaction --no-progress --no-suggest --prefer-dist @@ -69,4 +86,82 @@ jobs: run: vendor/bin/phpunit tests/Integration env: DB_PORT: ${{ job.services.database.ports[3306] }} - DB_USERNAME: root + + postgres: + name: PostgreSQL + runs-on: ubuntu-20.04 + if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" + strategy: + fail-fast: false + matrix: + php: [8.1] + database: ["postgres:13", "postgres:14", "postgres:15"] + services: + database: + image: docker.io/library/${{ matrix.database }} + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: testing + ports: + - 5432/tcp + options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 + env: + APP_ENV: testing + APP_DEBUG: "true" + APP_KEY: SomeRandomString3232RandomString + APP_THEME: pterodactyl + APP_TIMEZONE: UTC + APP_URL: http://localhost/ + APP_ENVIRONMENT_ONLY: "true" + + DB_CONNECTION: pgsql + DB_HOST: 127.0.0.1 + DB_DATABASE: testing + DB_USERNAME: postgres + DB_PASSWORD: postgres + + CACHE_DRIVER: array + MAIL_MAILER: array + SESSION_DRIVER: array + QUEUE_CONNECTION: sync + + HASHIDS_SALT: test123 + steps: + - name: Code Checkout + uses: actions/checkout@v3 + + - name: Get cache directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache + uses: actions/cache@v3 + with: + path: | + ~/.php_cs.cache + ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: composer install --no-interaction --no-progress --no-suggest --prefer-dist + + - name: Unit tests + run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit + if: ${{ always() }} + env: + DB_HOST: UNIT_NO_DB + + - name: Integration tests + run: vendor/bin/phpunit tests/Integration + env: + DB_PORT: ${{ job.services.database.ports[5432] }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 4ac292b68..a48c6b14e 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -26,9 +26,6 @@ jobs: tools: composer:v2 coverage: none - - name: Setup .env - run: cp .env.ci .env - - name: Install dependencies run: composer install --no-interaction --no-progress --no-suggest --prefer-dist diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index 9afb72628..dcdb5964b 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -40,6 +40,11 @@ class ClientController extends ClientApiController AllowedFilter::custom('*', new MultiFieldServerFilter()), ]); + $loweredBindings = collect($builder->getBindings()) + ->map(fn ($f, $key) => is_string($f) ? strtolower($f) : $f) + ->all(); + $builder->setBindings($loweredBindings); + $type = $request->input('type'); // Either return all the servers the user has access to because they are an admin `?type=admin` or // just return all the servers the user has access to because they are the owner or a subuser of the diff --git a/app/Models/Node.php b/app/Models/Node.php index 62ec82871..504a28c24 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -225,8 +225,8 @@ class Node extends Model */ public function isViable(int $memory, int $disk): bool { - $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100)); - $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100)); + $memoryLimit = $this->memory * (1.0 + ($this->memory_overallocate / 100.0)); + $diskLimit = $this->disk * (1.0 + ($this->disk_overallocate / 100.0)); return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; } diff --git a/app/Models/User.php b/app/Models/User.php index df8271cf4..eb3c15d22 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -76,7 +76,9 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @method static Builder|User whereUsername($value) * @method static Builder|User whereUuid($value) * - * @mixin \Eloquent + * @mixin \Barryvdh\LaravelIdeHelper\Eloquent + * @mixin \Illuminate\Database\Query\Builder + * @mixin \Illuminate\Database\Eloquent\Builder */ class User extends Model implements AuthenticatableContract, diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d4ffdadbb..6a805bad3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,12 +2,12 @@ namespace Pterodactyl\Providers; -use View; -use Cache; use Pterodactyl\Models; use Illuminate\Support\Str; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; +use Illuminate\Support\Facades\View; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Pterodactyl\Extensions\Themes\Theme; diff --git a/app/Providers/HashidsServiceProvider.php b/app/Providers/HashidsServiceProvider.php index f094878d0..4e48208ef 100644 --- a/app/Providers/HashidsServiceProvider.php +++ b/app/Providers/HashidsServiceProvider.php @@ -14,13 +14,10 @@ class HashidsServiceProvider extends ServiceProvider public function register() { $this->app->singleton(HashidsInterface::class, function () { - /** @var \Illuminate\Contracts\Config\Repository $config */ - $config = $this->app['config']; - return new Hashids( - $config->get('hashids.salt', ''), - $config->get('hashids.length', 0), - $config->get('hashids.alphabet', 'abcdefghijkmlnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + config('hashids.salt', ''), + config('hashids.length', 0), + config('hashids.alphabet', 'abcdefghijkmlnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') ); }); diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 8a0434f52..e5a16dd50 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -41,7 +41,7 @@ use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; class RepositoryServiceProvider extends ServiceProvider { /** - * Register all of the repository bindings. + * Register all the repository bindings. */ public function register() { diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f5ac5565d..25f18c524 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -30,7 +30,7 @@ class RouteServiceProvider extends ServiceProvider }); // This is needed to make use of the "resolveRouteBinding" functionality in the - // model. Without it you'll never trigger that logic flow thus resulting in a 404 + // model. Without it, you'll never trigger that logic flow thus resulting in a 404 // error because we request databases with a HashID, and not with a normal ID. Route::model('database', Database::class); diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 9c8a7445e..e2bcdafe6 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -80,7 +80,7 @@ class SettingsServiceProvider extends ServiceProvider if (in_array($key, self::$encrypted)) { try { $value = $encrypter->decrypt($value); - } catch (DecryptException $exception) { + } catch (DecryptException) { } } diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php index 6eb8b6d1e..01ed1c5be 100644 --- a/app/Repositories/Eloquent/AllocationRepository.php +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -40,14 +40,14 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos */ protected function getDiscardableDedicatedAllocations(array $nodes = []): array { - $query = Allocation::query()->selectRaw('CONCAT_WS("-", node_id, ip) as result'); + $query = Allocation::query()->selectRaw('CONCAT_WS(\'-\', node_id, ip) as result'); if (!empty($nodes)) { $query->whereIn('node_id', $nodes); } return $query->whereNotNull('server_id') - ->groupByRaw('CONCAT(node_id, ip)') + ->groupByRaw('result') ->get() ->pluck('result') ->toArray(); @@ -89,7 +89,7 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos if (!empty($discard)) { $query->whereNotIn( - $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip)'), + $this->getBuilder()->raw('CONCAT_WS(\'-\', node_id, ip)'), $discard ); } diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index a4e7f2fd7..a78295be4 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -2,9 +2,12 @@ namespace Pterodactyl\Repositories\Eloquent; +use PDO; +use RuntimeException; use Illuminate\Http\Request; use Webmozart\Assert\Assert; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Model; use Pterodactyl\Repositories\Repository; use Illuminate\Database\Eloquent\Builder; @@ -271,7 +274,17 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf return sprintf('(%s)', $grammar->parameterize($record)); })->implode(', '); - $statement = "insert ignore into $table ($columns) values $parameters"; + $driver = DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); + switch ($driver) { + case 'mysql': + $statement = "insert ignore into $table ($columns) values $parameters"; + break; + case 'pgsql': + $statement = "insert into $table ($columns) values $parameters on conflict do nothing"; + break; + default: + throw new RuntimeException("Unsupported database driver \"$driver\" for insert ignore."); + } return $this->getBuilder()->getConnection()->statement($statement, $bindings); } diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index fe019e50a..d7a3818f3 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -22,7 +22,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa public function getUsageStats(Node $node): array { $stats = $this->getBuilder() - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') ->join('servers', 'servers.node_id', '=', 'nodes.id') ->where('node_id', '=', $node->id) ->first(); @@ -54,7 +54,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa public function getUsageStatsRaw(Node $node): array { $stats = $this->getBuilder()->select( - $this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + $this->getBuilder()->raw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') )->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first(); return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) { @@ -143,7 +143,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa { $instance = $this->getBuilder() ->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate']) - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') ->where('nodes.id', $node_id); diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index 71c830bf9..a95211c3f 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -72,8 +72,8 @@ class FindViableNodesService Assert::integer($this->memory, 'Memory usage must be an int, got %s'); $query = Node::query()->select('nodes.*') - ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory') - ->selectRaw('IFNULL(SUM(servers.disk), 0) as sum_disk') + ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory') + ->selectRaw('COALESCE(SUM(servers.disk), 0) as sum_disk') ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') ->where('nodes.public', 1); @@ -82,8 +82,8 @@ class FindViableNodesService } $results = $query->groupBy('nodes.id') - ->havingRaw('(IFNULL(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1 + (nodes.memory_overallocate / 100)))', [$this->memory]) - ->havingRaw('(IFNULL(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1 + (nodes.disk_overallocate / 100)))', [$this->disk]); + ->havingRaw('(COALESCE(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1.0 + (nodes.memory_overallocate / 100.0)))', [$this->memory]) + ->havingRaw('(COALESCE(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1.0 + (nodes.disk_overallocate / 100.0)))', [$this->disk]); if (!is_null($page)) { $results = $results->paginate($perPage ?? 50, ['*'], 'page', $page); diff --git a/bootstrap/tests.php b/bootstrap/tests.php index 5b5449355..66f23147d 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -40,6 +40,8 @@ if (!env('SKIP_MIGRATIONS')) { $output->writeln('Seeding database for Integration tests...' . PHP_EOL); $kernel->call('db:seed'); + + $output->writeln('Database configured, running Integration tests...' . PHP_EOL); } else { $output->writeln(PHP_EOL . 'Skipping database migrations...' . PHP_EOL); } diff --git a/config/database.php b/config/database.php index b3a460ba2..1a8d9bf65 100644 --- a/config/database.php +++ b/config/database.php @@ -57,6 +57,21 @@ return [ PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => env('MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', true), ]) : [], ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'panel'), + 'username' => env('DB_USERNAME', 'pterodactyl'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => env('DB_PREFIX', ''), + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], ], /* diff --git a/database/migrations/2016_01_23_195641_add_allocations_table.php b/database/migrations/2016_01_23_195641_add_allocations_table.php index cfff2b359..e6306c3b2 100644 --- a/database/migrations/2016_01_23_195641_add_allocations_table.php +++ b/database/migrations/2016_01_23_195641_add_allocations_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ class AddAllocationsTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('allocations'); } diff --git a/database/migrations/2016_01_23_195851_add_api_keys.php b/database/migrations/2016_01_23_195851_add_api_keys.php index af7deb62d..1a7824b1c 100644 --- a/database/migrations/2016_01_23_195851_add_api_keys.php +++ b/database/migrations/2016_01_23_195851_add_api_keys.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ class AddApiKeys extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('api_keys'); } diff --git a/database/migrations/2016_01_23_200044_add_api_permissions.php b/database/migrations/2016_01_23_200044_add_api_permissions.php index e6f6bcbf8..e587da0a3 100644 --- a/database/migrations/2016_01_23_200044_add_api_permissions.php +++ b/database/migrations/2016_01_23_200044_add_api_permissions.php @@ -1,5 +1,6 @@ increments('id'); @@ -20,7 +21,7 @@ class AddApiPermissions extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('api_permissions'); } diff --git a/database/migrations/2016_01_23_200159_add_downloads.php b/database/migrations/2016_01_23_200159_add_downloads.php index b1771c5e4..9424578fb 100644 --- a/database/migrations/2016_01_23_200159_add_downloads.php +++ b/database/migrations/2016_01_23_200159_add_downloads.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ class AddDownloads extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('downloads'); } diff --git a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php index 83923e7d0..50d42ccc9 100644 --- a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php +++ b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ class CreateFailedJobsTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('failed_jobs'); } diff --git a/database/migrations/2016_01_23_200440_create_jobs_table.php b/database/migrations/2016_01_23_200440_create_jobs_table.php index 277acae31..fe7f9686c 100644 --- a/database/migrations/2016_01_23_200440_create_jobs_table.php +++ b/database/migrations/2016_01_23_200440_create_jobs_table.php @@ -1,5 +1,6 @@ bigIncrements('id'); @@ -19,6 +20,7 @@ class CreateJobsTable extends Migration $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); + $table->index(['queue', 'reserved', 'reserved_at']); }); } @@ -26,7 +28,7 @@ class CreateJobsTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('jobs'); } diff --git a/database/migrations/2016_01_23_200528_add_locations.php b/database/migrations/2016_01_23_200528_add_locations.php index b34a5fbcc..38d1e1710 100644 --- a/database/migrations/2016_01_23_200528_add_locations.php +++ b/database/migrations/2016_01_23_200528_add_locations.php @@ -1,5 +1,6 @@ increments('id'); @@ -21,7 +22,7 @@ class AddLocations extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('locations'); } diff --git a/database/migrations/2016_01_23_200648_add_nodes.php b/database/migrations/2016_01_23_200648_add_nodes.php index 52c0a29e6..371ebf049 100644 --- a/database/migrations/2016_01_23_200648_add_nodes.php +++ b/database/migrations/2016_01_23_200648_add_nodes.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ class AddNodes extends Migration $table->mediumInteger('disk_overallocate')->unsigned()->nullable(); $table->char('daemonSecret', 36)->unique(); $table->smallInteger('daemonListen')->unsigned()->default(8080); - $table->smallInteger('daemonSFTP')->unsgined()->default(2022); + $table->smallInteger('daemonSFTP')->unsigned()->default(2022); $table->string('daemonBase')->default('/home/daemon-files'); $table->timestamps(); }); @@ -32,7 +33,7 @@ class AddNodes extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('nodes'); } diff --git a/database/migrations/2016_01_23_201433_add_password_resets.php b/database/migrations/2016_01_23_201433_add_password_resets.php index 0584e3617..47c49146d 100644 --- a/database/migrations/2016_01_23_201433_add_password_resets.php +++ b/database/migrations/2016_01_23_201433_add_password_resets.php @@ -1,5 +1,6 @@ string('email')->index(); @@ -20,7 +21,7 @@ class AddPasswordResets extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('password_resets'); } diff --git a/database/migrations/2016_01_23_201531_add_permissions.php b/database/migrations/2016_01_23_201531_add_permissions.php index 12c9bbe0f..120a0e034 100644 --- a/database/migrations/2016_01_23_201531_add_permissions.php +++ b/database/migrations/2016_01_23_201531_add_permissions.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ class AddPermissions extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('permissions'); } diff --git a/database/migrations/2016_01_23_201649_add_server_variables.php b/database/migrations/2016_01_23_201649_add_server_variables.php index d9a436e6d..596c619d0 100644 --- a/database/migrations/2016_01_23_201649_add_server_variables.php +++ b/database/migrations/2016_01_23_201649_add_server_variables.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ class AddServerVariables extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('server_variables'); } diff --git a/database/migrations/2016_01_23_201748_add_servers.php b/database/migrations/2016_01_23_201748_add_servers.php index 5e1061069..901c1ff5c 100644 --- a/database/migrations/2016_01_23_201748_add_servers.php +++ b/database/migrations/2016_01_23_201748_add_servers.php @@ -1,5 +1,6 @@ increments('id'); @@ -39,7 +40,7 @@ class AddServers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('servers'); } diff --git a/database/migrations/2016_01_23_202544_add_service_options.php b/database/migrations/2016_01_23_202544_add_service_options.php index 7b0a33609..382f67a1a 100644 --- a/database/migrations/2016_01_23_202544_add_service_options.php +++ b/database/migrations/2016_01_23_202544_add_service_options.php @@ -1,5 +1,6 @@ increments('id'); @@ -24,7 +25,7 @@ class AddServiceOptions extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('service_options'); } diff --git a/database/migrations/2016_01_23_202731_add_service_varibles.php b/database/migrations/2016_01_23_202731_add_service_varibles.php index e79fa1fe9..bb96d83b6 100644 --- a/database/migrations/2016_01_23_202731_add_service_varibles.php +++ b/database/migrations/2016_01_23_202731_add_service_varibles.php @@ -1,5 +1,6 @@ increments('id'); @@ -28,7 +29,7 @@ class AddServiceVaribles extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('service_variables'); } diff --git a/database/migrations/2016_01_23_202943_add_services.php b/database/migrations/2016_01_23_202943_add_services.php index 31f723445..caddd964b 100644 --- a/database/migrations/2016_01_23_202943_add_services.php +++ b/database/migrations/2016_01_23_202943_add_services.php @@ -1,5 +1,6 @@ increments('id'); @@ -24,7 +25,7 @@ class AddServices extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('services'); } diff --git a/database/migrations/2016_01_23_203119_create_settings_table.php b/database/migrations/2016_01_23_203119_create_settings_table.php index 2cd6922c2..40dec55c6 100644 --- a/database/migrations/2016_01_23_203119_create_settings_table.php +++ b/database/migrations/2016_01_23_203119_create_settings_table.php @@ -1,5 +1,6 @@ string('key')->unique(); @@ -19,7 +20,7 @@ class CreateSettingsTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('settings'); } diff --git a/database/migrations/2016_01_23_203150_add_subusers.php b/database/migrations/2016_01_23_203150_add_subusers.php index 2f0e46310..e7561c0fd 100644 --- a/database/migrations/2016_01_23_203150_add_subusers.php +++ b/database/migrations/2016_01_23_203150_add_subusers.php @@ -1,5 +1,6 @@ increments('id'); @@ -22,7 +23,7 @@ class AddSubusers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('subusers'); } diff --git a/database/migrations/2016_01_23_203159_add_users.php b/database/migrations/2016_01_23_203159_add_users.php index 05ace7e22..1b6f9b5bb 100644 --- a/database/migrations/2016_01_23_203159_add_users.php +++ b/database/migrations/2016_01_23_203159_add_users.php @@ -1,5 +1,6 @@ increments('id'); @@ -27,7 +28,7 @@ class AddUsers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('users'); } diff --git a/database/migrations/2016_01_23_203947_create_sessions_table.php b/database/migrations/2016_01_23_203947_create_sessions_table.php index 533fa8aa2..7f708195e 100644 --- a/database/migrations/2016_01_23_203947_create_sessions_table.php +++ b/database/migrations/2016_01_23_203947_create_sessions_table.php @@ -1,5 +1,6 @@ string('id')->unique(); @@ -23,7 +24,7 @@ class CreateSessionsTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('sessions'); } diff --git a/database/migrations/2016_01_25_234418_rename_permissions_column.php b/database/migrations/2016_01_25_234418_rename_permissions_column.php index ae46dceb2..6b75986f9 100644 --- a/database/migrations/2016_01_25_234418_rename_permissions_column.php +++ b/database/migrations/2016_01_25_234418_rename_permissions_column.php @@ -1,5 +1,6 @@ renameColumn('permissions', 'permission'); @@ -18,7 +19,7 @@ class RenamePermissionsColumn extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { }); diff --git a/database/migrations/2016_02_07_172148_add_databases_tables.php b/database/migrations/2016_02_07_172148_add_databases_tables.php index 7b1048b15..26fdbf389 100644 --- a/database/migrations/2016_02_07_172148_add_databases_tables.php +++ b/database/migrations/2016_02_07_172148_add_databases_tables.php @@ -1,5 +1,6 @@ increments('id'); @@ -25,7 +26,7 @@ class AddDatabasesTables extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('databases'); } diff --git a/database/migrations/2016_02_07_181319_add_database_servers_table.php b/database/migrations/2016_02_07_181319_add_database_servers_table.php index 5a6740ae6..16d2d3cf5 100644 --- a/database/migrations/2016_02_07_181319_add_database_servers_table.php +++ b/database/migrations/2016_02_07_181319_add_database_servers_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -26,7 +27,7 @@ class AddDatabaseServersTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('database_servers'); } diff --git a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php index c8255ff47..a5d14b6d5 100644 --- a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php +++ b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php @@ -1,5 +1,6 @@ text('executable')->after('docker_image')->nullable()->default(null); @@ -19,7 +20,7 @@ class AddServiceOptionDefaultStartup extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('executable'); diff --git a/database/migrations/2016_02_20_155318_add_unique_service_field.php b/database/migrations/2016_02_20_155318_add_unique_service_field.php index 01ff91359..241e278ff 100644 --- a/database/migrations/2016_02_20_155318_add_unique_service_field.php +++ b/database/migrations/2016_02_20_155318_add_unique_service_field.php @@ -1,5 +1,6 @@ string('file')->unique()->change(); @@ -18,10 +19,10 @@ class AddUniqueServiceField extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { - $table->dropUnique('services_file_unique'); + $table->dropUnique(['file']); }); } } diff --git a/database/migrations/2016_02_27_163411_add_tasks_table.php b/database/migrations/2016_02_27_163411_add_tasks_table.php index f4cb7b1e3..8fb1efb4a 100644 --- a/database/migrations/2016_02_27_163411_add_tasks_table.php +++ b/database/migrations/2016_02_27_163411_add_tasks_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -32,7 +33,7 @@ class AddTasksTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('tasks'); } diff --git a/database/migrations/2016_02_27_163447_add_tasks_log_table.php b/database/migrations/2016_02_27_163447_add_tasks_log_table.php index 265e7fd96..6014a69b8 100644 --- a/database/migrations/2016_02_27_163447_add_tasks_log_table.php +++ b/database/migrations/2016_02_27_163447_add_tasks_log_table.php @@ -1,5 +1,6 @@ increments('id'); @@ -23,7 +24,7 @@ class AddTasksLogTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('tasks_log'); } diff --git a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php deleted file mode 100644 index 9d4752eb6..000000000 --- a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php +++ /dev/null @@ -1,24 +0,0 @@ -wrapTable('tasks'); - DB::statement('ALTER TABLE ' . $table . ' CHANGE `last_run` `last_run` TIMESTAMP NULL;'); - } - - /** - * Reverse the migrations. - */ - public function down() - { - $table = DB::getQueryGrammar()->wrapTable('tasks'); - DB::statement('ALTER TABLE ' . $table . ' CHANGE `last_run` `last_run` TIMESTAMP;'); - } -} diff --git a/database/migrations/2016_08_30_212718_add_ip_alias.php b/database/migrations/2016_08_30_212718_add_ip_alias.php index 26aa5eaa5..17272a2cc 100644 --- a/database/migrations/2016_08_30_212718_add_ip_alias.php +++ b/database/migrations/2016_08_30_212718_add_ip_alias.php @@ -1,5 +1,6 @@ text('ip_alias')->nullable()->after('ip'); @@ -29,7 +30,7 @@ class AddIpAlias extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropColumn('ip_alias'); diff --git a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php index ee7e704fb..7c8b1d46b 100644 --- a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php +++ b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php @@ -1,5 +1,6 @@ mediumInteger('allocation')->unsigned()->after('oom_disabled'); @@ -47,7 +48,7 @@ class ModifyIpStorageMethod extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->text('ip')->after('allocation'); diff --git a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php index 7bfb75b20..19cd96522 100644 --- a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php +++ b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php @@ -1,5 +1,6 @@ tinyInteger('suspended')->unsigned()->default(0)->after('active'); @@ -18,7 +19,7 @@ class AddSuspensionForServers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('suspended'); diff --git a/database/migrations/2016_09_01_211924_remove_active_column.php b/database/migrations/2016_09_01_211924_remove_active_column.php index 22a2bde13..7450c932d 100644 --- a/database/migrations/2016_09_01_211924_remove_active_column.php +++ b/database/migrations/2016_09_01_211924_remove_active_column.php @@ -1,5 +1,6 @@ dropColumn('active'); @@ -18,7 +19,7 @@ class RemoveActiveColumn extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->tinyInteger('active')->after('name')->unsigned()->default(0); diff --git a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php index 565957d59..57ce1f3b5 100644 --- a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php +++ b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php @@ -1,5 +1,6 @@ text('sftp_password')->after('username')->nullable(); @@ -18,7 +19,7 @@ class AddSftpPasswordStorage extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('sftp_password'); diff --git a/database/migrations/2016_09_04_171338_update_jobs_tables.php b/database/migrations/2016_09_04_171338_update_jobs_tables.php index 840ecacb5..4c5bff23f 100644 --- a/database/migrations/2016_09_04_171338_update_jobs_tables.php +++ b/database/migrations/2016_09_04_171338_update_jobs_tables.php @@ -9,11 +9,12 @@ class UpdateJobsTables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('jobs', function (Blueprint $table) { - $table->dropIndex('jobs_queue_reserved_reserved_at_index'); + $table->dropIndex(['queue', 'reserved', 'reserved_at']); $table->dropColumn('reserved'); + $table->index(['queue', 'reserved_at']); }); } @@ -21,10 +22,11 @@ class UpdateJobsTables extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('jobs', function (Blueprint $table) { - $table->dropIndex('jobs_queue_reserved_at_index'); + $table->dropIndex(['queue', 'reserved_at']); + $table->tinyInteger('reserved')->unsigned(); $table->index(['queue', 'reserved', 'reserved_at']); }); diff --git a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php index a00f5f18d..b5157a1ef 100644 --- a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php +++ b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php @@ -9,7 +9,7 @@ class UpdateFailedJobsTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('failed_jobs', function (Blueprint $table) { $table->text('exception'); @@ -19,7 +19,7 @@ class UpdateFailedJobsTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('failed_jobs', function (Blueprint $table) { $table->dropColumn('exception'); diff --git a/database/migrations/2016_09_04_182835_create_notifications_table.php b/database/migrations/2016_09_04_182835_create_notifications_table.php index 30fc23a59..8918f3009 100644 --- a/database/migrations/2016_09_04_182835_create_notifications_table.php +++ b/database/migrations/2016_09_04_182835_create_notifications_table.php @@ -1,5 +1,6 @@ string('id')->primary(); @@ -23,7 +24,7 @@ class CreateNotificationsTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('notifications'); } diff --git a/database/migrations/2016_09_07_163017_add_unique_identifier.php b/database/migrations/2016_09_07_163017_add_unique_identifier.php index e1bab9ccc..685a718a4 100644 --- a/database/migrations/2016_09_07_163017_add_unique_identifier.php +++ b/database/migrations/2016_09_07_163017_add_unique_identifier.php @@ -9,7 +9,7 @@ class AddUniqueIdentifier extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->char('author', 36)->after('id'); @@ -19,7 +19,7 @@ class AddUniqueIdentifier extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('author'); diff --git a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php index a7df1ca1b..8d0ab04ac 100644 --- a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php +++ b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php @@ -9,7 +9,7 @@ class AllowLongerRegexField extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->text('regex')->change(); @@ -19,7 +19,7 @@ class AllowLongerRegexField extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->string('regex')->change(); diff --git a/database/migrations/2016_09_17_194246_add_docker_image_column.php b/database/migrations/2016_09_17_194246_add_docker_image_column.php index 05d26112e..0e66f649e 100644 --- a/database/migrations/2016_09_17_194246_add_docker_image_column.php +++ b/database/migrations/2016_09_17_194246_add_docker_image_column.php @@ -1,5 +1,6 @@ string('image')->after('daemonSecret'); @@ -32,7 +33,7 @@ class AddDockerImageColumn extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('image'); diff --git a/database/migrations/2016_09_21_165554_update_servers_column_name.php b/database/migrations/2016_09_21_165554_update_servers_column_name.php index 14ae07c4a..919cdcaab 100644 --- a/database/migrations/2016_09_21_165554_update_servers_column_name.php +++ b/database/migrations/2016_09_21_165554_update_servers_column_name.php @@ -9,7 +9,7 @@ class UpdateServersColumnName extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->renameColumn('server', 'server_id'); @@ -19,7 +19,7 @@ class UpdateServersColumnName extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->renameColumn('server_id', 'server'); diff --git a/database/migrations/2016_09_29_213518_rename_double_insurgency.php b/database/migrations/2016_09_29_213518_rename_double_insurgency.php index adb577754..5f21c7036 100644 --- a/database/migrations/2016_09_29_213518_rename_double_insurgency.php +++ b/database/migrations/2016_09_29_213518_rename_double_insurgency.php @@ -7,7 +7,7 @@ class RenameDoubleInsurgency extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $model = DB::table('service_options')->where('parent_service', 2)->where('id', 3)->where('name', 'Insurgency')->first(); @@ -21,7 +21,7 @@ class RenameDoubleInsurgency extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2016_10_07_152117_build_api_log_table.php b/database/migrations/2016_10_07_152117_build_api_log_table.php index 08ea312dc..39356c5ba 100644 --- a/database/migrations/2016_10_07_152117_build_api_log_table.php +++ b/database/migrations/2016_10_07_152117_build_api_log_table.php @@ -9,7 +9,7 @@ class BuildApiLogTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('api_logs', function (Blueprint $table) { $table->increments('id'); @@ -28,7 +28,7 @@ class BuildApiLogTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('api_logs'); } diff --git a/database/migrations/2016_10_14_164802_update_api_keys.php b/database/migrations/2016_10_14_164802_update_api_keys.php index 56c3e8097..b43eef1b1 100644 --- a/database/migrations/2016_10_14_164802_update_api_keys.php +++ b/database/migrations/2016_10_14_164802_update_api_keys.php @@ -9,7 +9,7 @@ class UpdateApiKeys extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedInteger('user')->after('id'); @@ -21,7 +21,7 @@ class UpdateApiKeys extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('user'); diff --git a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php index 70ec18b33..a9cf3a35c 100644 --- a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php +++ b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php @@ -7,7 +7,7 @@ class UpdateMisnamedBungee extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::table('service_variables')->select('env_variable')->where('env_variable', 'BUNGE_VERSION')->update([ 'env_variable' => 'BUNGEE_VERSION', @@ -17,7 +17,7 @@ class UpdateMisnamedBungee extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php index 1412720c9..da0fc7c83 100644 --- a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php +++ b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php @@ -9,22 +9,21 @@ class AddForeignKeysServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE servers - MODIFY COLUMN node INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN owner INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN allocation INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN service INT(10) UNSIGNED NOT NULL, - MODIFY COLUMN `option` INT(10) UNSIGNED NOT NULL - '); - Schema::table('servers', function (Blueprint $table) { + $table->integer('node', false, true)->change(); + $table->integer('owner', false, true)->change(); + $table->integer('allocation', false, true)->change(); + $table->integer('service', false, true)->change(); + $table->integer('option', false, true)->change(); + $table->foreign('node')->references('id')->on('nodes'); $table->foreign('owner')->references('id')->on('users'); $table->foreign('allocation')->references('id')->on('allocations'); $table->foreign('service')->references('id')->on('services'); $table->foreign('option')->references('id')->on('service_options'); + $table->softDeletes(); }); } @@ -32,30 +31,31 @@ class AddForeignKeysServers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign('servers_node_foreign'); - $table->dropForeign('servers_owner_foreign'); - $table->dropForeign('servers_allocation_foreign'); - $table->dropForeign('servers_service_foreign'); - $table->dropForeign('servers_option_foreign'); + $table->dropForeign(['node']); + $table->dropIndex(['node']); - $table->dropIndex('servers_node_foreign'); - $table->dropIndex('servers_owner_foreign'); - $table->dropIndex('servers_allocation_foreign'); - $table->dropIndex('servers_service_foreign'); - $table->dropIndex('servers_option_foreign'); + $table->dropForeign(['owner']); + $table->dropIndex(['owner']); + + $table->dropForeign(['allocation']); + $table->dropIndex(['allocation']); + + $table->dropForeign(['service']); + $table->dropIndex(['service']); + + $table->dropForeign(['option']); + $table->dropIndex(['option']); $table->dropColumn('deleted_at'); - }); - DB::statement('ALTER TABLE servers - MODIFY COLUMN node MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN owner MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN allocation MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN service MEDIUMINT(8) UNSIGNED NOT NULL, - MODIFY COLUMN `option` MEDIUMINT(8) UNSIGNED NOT NULL - '); + $table->mediumInteger('node', false, true)->change(); + $table->mediumInteger('owner', false, true)->change(); + $table->mediumInteger('allocation', false, true)->change(); + $table->mediumInteger('service', false, true)->change(); + $table->mediumInteger('option', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_201624_add_foreign_allocations.php b/database/migrations/2016_10_23_201624_add_foreign_allocations.php index 0660081cb..7ae4b040d 100644 --- a/database/migrations/2016_10_23_201624_add_foreign_allocations.php +++ b/database/migrations/2016_10_23_201624_add_foreign_allocations.php @@ -1,5 +1,6 @@ integer('assigned_to', false, true)->nullable()->change(); + $table->integer('node', false, true)->nullable(false)->change(); $table->foreign('assigned_to')->references('id')->on('servers'); $table->foreign('node')->references('id')->on('nodes'); }); @@ -25,14 +23,17 @@ class AddForeignAllocations extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropForeign('allocations_node_foreign'); + $table->dropForeign(['assigned_to']); + $table->dropIndex(['assigned_to']); - $table->dropIndex('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); + $table->dropForeign(['node']); + $table->dropIndex(['node']); + + $table->mediumInteger('assigned_to', false, true)->nullable()->change(); + $table->mediumInteger('node', false, true)->nullable(false)->change(); }); DB::statement('ALTER TABLE allocations diff --git a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php index 700342d74..44b11d0e5 100644 --- a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php +++ b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php @@ -9,7 +9,7 @@ class AddForeignApiKeys extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->foreign('user')->references('id')->on('users'); @@ -19,11 +19,11 @@ class AddForeignApiKeys extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_foreign'); - $table->dropIndex('api_keys_user_foreign'); + $table->dropForeign(['user']); + $table->dropIndex(['user']); }); } } diff --git a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php index d8eb3504d..2494eaba7 100644 --- a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php +++ b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php @@ -9,11 +9,10 @@ class AddForeignApiPermissions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE api_permissions MODIFY key_id INT(10) UNSIGNED NOT NULL'); - Schema::table('api_permissions', function (Blueprint $table) { + $table->integer('key_id', false, true)->nullable(false)->change(); $table->foreign('key_id')->references('id')->on('api_keys'); }); } @@ -21,13 +20,13 @@ class AddForeignApiPermissions extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_permissions', function (Blueprint $table) { - $table->dropForeign('api_permissions_key_id_foreign'); - $table->dropIndex('api_permissions_key_id_foreign'); - }); + $table->dropForeign(['key_id']); + $table->dropIndex(['key_id']); - DB::statement('ALTER TABLE api_permissions MODIFY key_id MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('key_id', false, true)->nullable(false)->change(); + }); } } diff --git a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php index 769b7daa3..78ee8264d 100644 --- a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php +++ b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php @@ -9,7 +9,7 @@ class AddForeignDatabaseServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_servers', function (Blueprint $table) { $table->foreign('linked_node')->references('id')->on('nodes'); @@ -19,11 +19,11 @@ class AddForeignDatabaseServers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_servers', function (Blueprint $table) { - $table->dropForeign('database_servers_linked_node_foreign'); - $table->dropIndex('database_servers_linked_node_foreign'); + $table->dropForeign(['linked_node']); + $table->dropIndex(['linked_node']); }); } } diff --git a/database/migrations/2016_10_23_203105_add_foreign_databases.php b/database/migrations/2016_10_23_203105_add_foreign_databases.php index be26e3cb0..bea43049b 100644 --- a/database/migrations/2016_10_23_203105_add_foreign_databases.php +++ b/database/migrations/2016_10_23_203105_add_foreign_databases.php @@ -9,7 +9,7 @@ class AddForeignDatabases extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->foreign('server_id')->references('id')->on('servers'); @@ -20,14 +20,14 @@ class AddForeignDatabases extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { - $table->dropForeign('databases_server_id_foreign'); - $table->dropForeign('databases_db_server_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); - $table->dropIndex('databases_server_id_foreign'); - $table->dropIndex('databases_db_server_foreign'); + $table->dropForeign(['db_server']); + $table->dropIndex(['db_server']); }); } } diff --git a/database/migrations/2016_10_23_203335_add_foreign_nodes.php b/database/migrations/2016_10_23_203335_add_foreign_nodes.php index f861e0a7d..375189a7f 100644 --- a/database/migrations/2016_10_23_203335_add_foreign_nodes.php +++ b/database/migrations/2016_10_23_203335_add_foreign_nodes.php @@ -9,11 +9,10 @@ class AddForeignNodes extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE nodes MODIFY location INT(10) UNSIGNED NOT NULL'); - Schema::table('nodes', function (Blueprint $table) { + $table->integer('location', false, true)->nullable(false)->change(); $table->foreign('location')->references('id')->on('locations'); }); } @@ -21,13 +20,13 @@ class AddForeignNodes extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_foreign'); - $table->dropIndex('nodes_location_foreign'); - }); + $table->dropForeign(['location']); + $table->dropIndex(['location']); - DB::statement('ALTER TABLE nodes MODIFY location MEDIUMINT(10) UNSIGNED NOT NULL'); + $table->mediumInteger('location', false, true)->nullable(false)->change(); + }); } } diff --git a/database/migrations/2016_10_23_203522_add_foreign_permissions.php b/database/migrations/2016_10_23_203522_add_foreign_permissions.php index a43f0eacf..78bbf32a5 100644 --- a/database/migrations/2016_10_23_203522_add_foreign_permissions.php +++ b/database/migrations/2016_10_23_203522_add_foreign_permissions.php @@ -9,7 +9,7 @@ class AddForeignPermissions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->foreign('user_id')->references('id')->on('users'); @@ -20,14 +20,14 @@ class AddForeignPermissions extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_user_id_foreign'); - $table->dropForeign('permissions_server_id_foreign'); + $table->dropForeign(['user_id']); + $table->dropIndex(['user_id']); - $table->dropIndex('permissions_user_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); }); } } diff --git a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php index b4720495d..3ccc3d183 100644 --- a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php +++ b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php @@ -9,14 +9,11 @@ class AddForeignServerVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE server_variables - MODIFY COLUMN server_id INT(10) UNSIGNED NULL, - MODIFY COLUMN variable_id INT(10) UNSIGNED NOT NULL - '); - Schema::table('server_variables', function (Blueprint $table) { + $table->integer('server_id', false, true)->nullable()->change(); + $table->integer('variable_id', false, true)->nullable(false)->change(); $table->foreign('server_id')->references('id')->on('servers'); $table->foreign('variable_id')->references('id')->on('service_variables'); }); @@ -25,16 +22,13 @@ class AddForeignServerVariables extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); $table->dropForeign(['variable_id']); + $table->mediumInteger('server_id', false, true)->nullable()->change(); + $table->mediumInteger('variable_id', false, true)->nullable(false)->change(); }); - - DB::statement('ALTER TABLE server_variables - MODIFY COLUMN server_id MEDIUMINT(8) UNSIGNED NULL, - MODIFY COLUMN variable_id MEDIUMINT(8) UNSIGNED NOT NULL - '); } } diff --git a/database/migrations/2016_10_23_204157_add_foreign_service_options.php b/database/migrations/2016_10_23_204157_add_foreign_service_options.php index cb8c0e2e8..9f01905b7 100644 --- a/database/migrations/2016_10_23_204157_add_foreign_service_options.php +++ b/database/migrations/2016_10_23_204157_add_foreign_service_options.php @@ -9,11 +9,10 @@ class AddForeignServiceOptions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE service_options MODIFY parent_service INT(10) UNSIGNED NOT NULL'); - Schema::table('service_options', function (Blueprint $table) { + $table->integer('parent_service', false, true)->change(); $table->foreign('parent_service')->references('id')->on('services'); }); } @@ -21,13 +20,13 @@ class AddForeignServiceOptions extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); - }); + $table->dropForeign(['parent_service']); + $table->dropIndex(['parent_service']); - DB::statement('ALTER TABLE service_options MODIFY parent_service MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('parent_service', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php index 02bbc46f2..df998efaf 100644 --- a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php +++ b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php @@ -9,11 +9,10 @@ class AddForeignServiceVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - DB::statement('ALTER TABLE service_variables MODIFY option_id INT(10) UNSIGNED NOT NULL'); - Schema::table('service_variables', function (Blueprint $table) { + $table->integer('option_id', false, true)->change(); $table->foreign('option_id')->references('id')->on('service_options'); }); } @@ -21,13 +20,13 @@ class AddForeignServiceVariables extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { - $table->dropForeign('service_variables_option_id_foreign'); - $table->dropIndex('service_variables_option_id_foreign'); - }); + $table->dropForeign(['option_id']); + $table->dropIndex(['option_id']); - DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); + $table->mediumInteger('option_id', false, true)->change(); + }); } } diff --git a/database/migrations/2016_10_23_204454_add_foreign_subusers.php b/database/migrations/2016_10_23_204454_add_foreign_subusers.php index b637c80ae..ff4bb95a3 100644 --- a/database/migrations/2016_10_23_204454_add_foreign_subusers.php +++ b/database/migrations/2016_10_23_204454_add_foreign_subusers.php @@ -9,7 +9,7 @@ class AddForeignSubusers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('subusers', function (Blueprint $table) { $table->foreign('user_id')->references('id')->on('users'); @@ -20,14 +20,14 @@ class AddForeignSubusers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { - $table->dropForeign('subusers_user_id_foreign'); - $table->dropForeign('subusers_server_id_foreign'); + $table->dropForeign(['user_id']); + $table->dropIndex(['user_id']); - $table->dropIndex('subusers_user_id_foreign'); - $table->dropIndex('subusers_server_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropIndex(['server_id']); }); } } diff --git a/database/migrations/2016_10_23_204610_add_foreign_tasks.php b/database/migrations/2016_10_23_204610_add_foreign_tasks.php index 18ea297e5..f32d89230 100644 --- a/database/migrations/2016_10_23_204610_add_foreign_tasks.php +++ b/database/migrations/2016_10_23_204610_add_foreign_tasks.php @@ -9,7 +9,7 @@ class AddForeignTasks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->foreign('server')->references('id')->on('servers'); @@ -19,7 +19,7 @@ class AddForeignTasks extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server']); diff --git a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php index 1547e32cc..c5fff5523 100644 --- a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php +++ b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php @@ -7,7 +7,7 @@ class AddArkServiceOptionFixed extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $service = DB::table('services')->select('id')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('name', 'Source Engine')->first(); @@ -73,7 +73,7 @@ class AddArkServiceOptionFixed extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::transaction(function () { $service = DB::table('services')->select('id')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('name', 'Source Engine')->first(); diff --git a/database/migrations/2016_11_11_220649_add_pack_support.php b/database/migrations/2016_11_11_220649_add_pack_support.php index b6fa0972b..8fd638ae6 100644 --- a/database/migrations/2016_11_11_220649_add_pack_support.php +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -9,7 +9,7 @@ class AddPackSupport extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('service_packs', function (Blueprint $table) { $table->increments('id'); @@ -29,7 +29,7 @@ class AddPackSupport extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::drop('service_packs'); } diff --git a/database/migrations/2016_11_11_231731_set_service_name_unique.php b/database/migrations/2016_11_11_231731_set_service_name_unique.php index 42b0f6953..261fdb356 100644 --- a/database/migrations/2016_11_11_231731_set_service_name_unique.php +++ b/database/migrations/2016_11_11_231731_set_service_name_unique.php @@ -9,7 +9,7 @@ class SetServiceNameUnique extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->unique('name'); @@ -19,7 +19,7 @@ class SetServiceNameUnique extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropUnique('services_name_unique'); diff --git a/database/migrations/2016_11_27_142519_add_pack_column.php b/database/migrations/2016_11_27_142519_add_pack_column.php index d520466a8..3911ecb41 100644 --- a/database/migrations/2016_11_27_142519_add_pack_column.php +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -9,7 +9,7 @@ class AddPackColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('pack')->nullable()->after('option'); @@ -21,7 +21,7 @@ class AddPackColumn extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack']); diff --git a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php index d2d14f4d0..c5136fe9e 100644 --- a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php +++ b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php @@ -9,7 +9,7 @@ class AddConfigurableUploadLimit extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->unsignedInteger('upload_size')->after('disk_overallocate')->default(100); @@ -19,7 +19,7 @@ class AddConfigurableUploadLimit extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('upload_size'); diff --git a/database/migrations/2016_12_02_185206_correct_service_variables.php b/database/migrations/2016_12_02_185206_correct_service_variables.php index e9c87989a..d94b3b78b 100644 --- a/database/migrations/2016_12_02_185206_correct_service_variables.php +++ b/database/migrations/2016_12_02_185206_correct_service_variables.php @@ -7,7 +7,7 @@ class CorrectServiceVariables extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { // Modify Default Spigot Startup Line @@ -66,7 +66,7 @@ class CorrectServiceVariables extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { // do nothing } diff --git a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php index 7cdf96807..35248d6bb 100644 --- a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php +++ b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php @@ -7,7 +7,7 @@ class FixMisnamedOptionTag extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { DB::table('service_options')->where([ @@ -23,7 +23,7 @@ class FixMisnamedOptionTag extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::table('service_options')->where([ ['name', 'Sponge (SpongeVanilla)'], diff --git a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php index 77693c265..c4369f975 100644 --- a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php +++ b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php @@ -9,7 +9,7 @@ class CreateNodeConfigurationTokensTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('node_configuration_tokens', function (Blueprint $table) { $table->increments('id'); @@ -24,7 +24,7 @@ class CreateNodeConfigurationTokensTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('node_configuration_tokens'); } diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php index 0206040b5..82ae8c9e9 100644 --- a/database/migrations/2017_01_12_135449_add_more_user_data.php +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -10,7 +10,7 @@ class AddMoreUserData extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('name_first')->after('email')->nullable(); @@ -34,7 +34,7 @@ class AddMoreUserData extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('name_first'); diff --git a/database/migrations/2017_02_02_175548_UpdateColumnNames.php b/database/migrations/2017_02_02_175548_UpdateColumnNames.php index c88aa8de7..719513313 100644 --- a/database/migrations/2017_02_02_175548_UpdateColumnNames.php +++ b/database/migrations/2017_02_02_175548_UpdateColumnNames.php @@ -9,22 +9,15 @@ class UpdateColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign('servers_node_foreign'); - $table->dropForeign('servers_owner_foreign'); - $table->dropForeign('servers_allocation_foreign'); - $table->dropForeign('servers_service_foreign'); - $table->dropForeign('servers_option_foreign'); - $table->dropForeign('servers_pack_foreign'); - - $table->dropIndex('servers_node_foreign'); - $table->dropIndex('servers_owner_foreign'); - $table->dropIndex('servers_allocation_foreign'); - $table->dropIndex('servers_service_foreign'); - $table->dropIndex('servers_option_foreign'); - $table->dropIndex('servers_pack_foreign'); + $table->dropForeign(['node']); + $table->dropForeign(['owner']); + $table->dropForeign(['allocation']); + $table->dropForeign(['service']); + $table->dropForeign(['option']); + $table->dropForeign(['pack']); $table->renameColumn('node', 'node_id'); $table->renameColumn('owner', 'owner_id'); @@ -47,14 +40,10 @@ class UpdateColumnNames extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { - $table->dropForeign(['node_id']); - $table->dropForeign(['owner_id']); - $table->dropForeign(['allocation_id']); - $table->dropForeign(['service_id']); - $table->dropForeign(['option_id']); + $table->dropForeign(['node_id', 'owner_id', 'allocation_id', 'service_id', 'option_id']); $table->renameColumn('node_id', 'node'); $table->renameColumn('owner_id', 'owner'); diff --git a/database/migrations/2017_02_03_140948_UpdateNodesTable.php b/database/migrations/2017_02_03_140948_UpdateNodesTable.php index 58ec63ef4..e797cc704 100644 --- a/database/migrations/2017_02_03_140948_UpdateNodesTable.php +++ b/database/migrations/2017_02_03_140948_UpdateNodesTable.php @@ -9,11 +9,10 @@ class UpdateNodesTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_foreign'); - $table->dropIndex('nodes_location_foreign'); + $table->dropForeign(['location']); $table->renameColumn('location', 'location_id'); $table->foreign('location_id')->references('id')->on('locations'); @@ -23,11 +22,10 @@ class UpdateNodesTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->dropForeign('nodes_location_id_foreign'); - $table->dropIndex('nodes_location_id_foreign'); + $table->dropForeign(['location_id']); $table->renameColumn('location_id', 'location'); $table->foreign('location')->references('id')->on('locations'); diff --git a/database/migrations/2017_02_03_155554_RenameColumns.php b/database/migrations/2017_02_03_155554_RenameColumns.php index 5f617abec..bd50e16be 100644 --- a/database/migrations/2017_02_03_155554_RenameColumns.php +++ b/database/migrations/2017_02_03_155554_RenameColumns.php @@ -9,13 +9,11 @@ class RenameColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_node_foreign'); - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); - $table->dropIndex('allocations_assigned_to_foreign'); + $table->dropForeign(['node']); + $table->dropForeign(['assigned_to']); $table->renameColumn('node', 'node_id'); $table->renameColumn('assigned_to', 'server_id'); @@ -27,13 +25,13 @@ class RenameColumns extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_node_id_foreign'); - $table->dropForeign('allocations_server_id_foreign'); - $table->dropIndex('allocations_node_id_foreign'); - $table->dropIndex('allocations_server_id_foreign'); + $table->dropForeign(['node_id']); + $table->dropForeign(['server_id']); + $table->dropIndex(['node_id']); + $table->dropIndex(['server_id']); $table->renameColumn('node_id', 'node'); $table->renameColumn('server_id', 'assigned_to'); diff --git a/database/migrations/2017_02_05_164123_AdjustColumnNames.php b/database/migrations/2017_02_05_164123_AdjustColumnNames.php index c7688f056..51c8818c7 100644 --- a/database/migrations/2017_02_05_164123_AdjustColumnNames.php +++ b/database/migrations/2017_02_05_164123_AdjustColumnNames.php @@ -9,11 +9,10 @@ class AdjustColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); + $table->dropForeign(['parent_service']); $table->renameColumn('parent_service', 'service_id'); $table->foreign('service_id')->references('id')->on('services'); @@ -23,11 +22,11 @@ class AdjustColumnNames extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_service_id_foreign'); - $table->dropIndex('service_options_service_id_foreign'); + $table->dropForeign(['service_id']); + $table->dropIndex(['service_id']); $table->renameColumn('service_id', 'parent_service'); $table->foreign('parent_service')->references('id')->on('services'); diff --git a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php index 6f86b3b6e..69dc33dda 100644 --- a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php +++ b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php @@ -9,11 +9,10 @@ class AdjustColumnNamesForServicePacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_packs', function (Blueprint $table) { - $table->dropForeign('service_packs_option_foreign'); - $table->dropIndex('service_packs_option_foreign'); + $table->dropForeign(['option']); $table->renameColumn('option', 'option_id'); $table->foreign('option_id')->references('id')->on('service_options'); @@ -23,11 +22,11 @@ class AdjustColumnNamesForServicePacks extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_packs', function (Blueprint $table) { - $table->dropForeign('service_packs_option_id_foreign'); - $table->dropIndex('service_packs_option_id_foreign'); + $table->dropForeign(['option_id']); + $table->dropIndex(['option_id']); $table->renameColumn('option_id', 'option'); $table->foreign('option')->references('id')->on('service_options'); diff --git a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php index 45efce83a..bf6469506 100644 --- a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php +++ b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php @@ -11,7 +11,7 @@ class SetupPermissionsPivotTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->unsignedInteger('subuser_id')->after('id'); @@ -19,17 +19,15 @@ class SetupPermissionsPivotTable extends Migration DB::transaction(function () { foreach (Subuser::all() as &$subuser) { - Permission::where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ + Permission::query()->where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->update([ 'subuser_id' => $subuser->id, ]); } }); Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_server_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); - $table->dropForeign('permissions_user_id_foreign'); - $table->dropIndex('permissions_user_id_foreign'); + $table->dropForeign(['server_id']); + $table->dropForeign(['user_id']); $table->dropColumn('server_id'); $table->dropColumn('user_id'); @@ -42,7 +40,7 @@ class SetupPermissionsPivotTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('permissions', function (Blueprint $table) { $table->unsignedInteger('server_id')->after('subuser_id'); @@ -52,7 +50,7 @@ class SetupPermissionsPivotTable extends Migration DB::transaction(function () { foreach (Subuser::all() as &$subuser) { - Permission::where('subuser_id', $subuser->id)->update([ + Permission::query()->where('subuser_id', $subuser->id)->update([ 'user_id' => $subuser->user_id, 'server_id' => $subuser->server_id, ]); @@ -60,8 +58,8 @@ class SetupPermissionsPivotTable extends Migration }); Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_subuser_id_foreign'); - $table->dropIndex('permissions_subuser_id_foreign'); + $table->dropForeign(['subuser_id']); + $table->dropIndex(['subuser_id']); $table->dropColumn('subuser_id'); $table->foreign('server_id')->references('id')->on('servers'); diff --git a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php index 8b541d941..8ae28c2c9 100644 --- a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php +++ b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php @@ -9,10 +9,10 @@ class UpdateAPIKeyColumnNames extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_foreign')->dropIndex('api_keys_user_foreign'); + $table->dropForeign(['user']); $table->renameColumn('user', 'user_id'); $table->foreign('user_id')->references('id')->on('users'); @@ -22,10 +22,10 @@ class UpdateAPIKeyColumnNames extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { - $table->dropForeign('api_keys_user_id_foreign')->dropIndex('api_keys_user_id_foreign'); + $table->dropForeign(['user_id']); $table->renameColumn('user_id', 'user'); $table->foreign('user')->references('id')->on('users'); diff --git a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php index 4f27346fa..aab6c2b95 100644 --- a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php +++ b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php @@ -9,7 +9,7 @@ class UpdateNodeConfigTokensColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('node_configuration_tokens', function (Blueprint $table) { $table->dropForeign(['node']); @@ -23,7 +23,7 @@ class UpdateNodeConfigTokensColumns extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('node_configuration_tokens', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php index 6792f265a..d697a3315 100644 --- a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php +++ b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php @@ -9,7 +9,7 @@ class DeleteServiceExecutableOption extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->renameColumn('file', 'folder'); @@ -22,7 +22,7 @@ class DeleteServiceExecutableOption extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->string('executable')->after('folder'); diff --git a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php index 385004fa4..06c04694c 100644 --- a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php +++ b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php @@ -9,7 +9,7 @@ class AddNewServiceOptionsColumns extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('executable'); @@ -27,7 +27,7 @@ class AddNewServiceOptionsColumns extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['config_from']); diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php index 7cf5707c4..40aef1524 100644 --- a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -7,7 +7,7 @@ class MigrateToNewServiceSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::transaction(function () { $service = DB::table('services')->where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); @@ -32,7 +32,7 @@ class MigrateToNewServiceSystem extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { // Not doing reversals right now... } diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php index 21fa51465..3e7e5f18b 100644 --- a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -9,7 +9,7 @@ class ChangeServiceVariablesValidationRules extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->renameColumn('regex', 'rules'); @@ -30,7 +30,7 @@ class ChangeServiceVariablesValidationRules extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->renameColumn('rules', 'regex'); diff --git a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php index 3628ba7a4..26599246c 100644 --- a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php +++ b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php @@ -85,7 +85,7 @@ EOF; /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->text('index_file')->after('startup'); @@ -105,7 +105,7 @@ EOF; /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('index_file'); diff --git a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php index d01012e41..f73befdba 100644 --- a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php +++ b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php @@ -9,7 +9,7 @@ class RenameServicePacksToSingluarPacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_packs', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -25,7 +25,7 @@ class RenameServicePacksToSingluarPacks extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php index b1a8ee3a0..b396954e0 100644 --- a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php +++ b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php @@ -9,7 +9,7 @@ class AddLockedStatusToTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('packs', function (Blueprint $table) { $table->boolean('locked')->default(false)->after('visible'); @@ -19,7 +19,7 @@ class AddLockedStatusToTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropColumn('locked'); diff --git a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php index a7166df9e..c973faa55 100644 --- a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php +++ b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php @@ -9,7 +9,7 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_servers', function (Blueprint $table) { $table->dropForeign(['linked_node']); @@ -27,7 +27,7 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php index bc6fb45c7..2b689c481 100644 --- a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php +++ b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php @@ -9,7 +9,7 @@ class CleanupDatabasesDatabase extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['db_server']); @@ -23,7 +23,7 @@ class CleanupDatabasesDatabase extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['database_host_id']); diff --git a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php index 3f26a1e34..bdd1f1a54 100644 --- a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php +++ b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php @@ -9,7 +9,7 @@ class AddForeignKeyToPacks extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->foreign('pack_id')->references('id')->on('packs'); @@ -19,7 +19,7 @@ class AddForeignKeyToPacks extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack_id']); diff --git a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php index e8ebcb20d..69d044582 100644 --- a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php +++ b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php @@ -9,7 +9,7 @@ class AddServerDescriptionColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->text('description')->after('name'); @@ -19,7 +19,7 @@ class AddServerDescriptionColumn extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('description'); diff --git a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php index 3cd08f1a9..0c193192b 100644 --- a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php +++ b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php @@ -9,7 +9,7 @@ class DropDeletedAtColumnFromServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('deleted_at'); @@ -19,7 +19,7 @@ class DropDeletedAtColumnFromServers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->timestamp('deleted_at')->nullable(); diff --git a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php index d069e1ba1..f5e48cfea 100644 --- a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php +++ b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php @@ -10,7 +10,7 @@ class UpgradeTaskSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server']); @@ -33,7 +33,7 @@ class UpgradeTaskSystem extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { // $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php index ba2f57c41..96a85be92 100644 --- a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php +++ b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php @@ -9,7 +9,7 @@ class AddScriptsToServiceOptions extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->text('script_install')->after('startup')->nullable(); @@ -22,7 +22,7 @@ class AddScriptsToServiceOptions extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('script_install'); diff --git a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php index 2bc8f27b3..970b41773 100644 --- a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php +++ b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php @@ -9,7 +9,7 @@ class AddServiceScriptTrackingToServers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->boolean('skip_scripts')->default(false)->after('description'); @@ -19,7 +19,7 @@ class AddServiceScriptTrackingToServers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('skip_scripts'); diff --git a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php index 514d17e1c..8888600fb 100644 --- a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php +++ b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php @@ -9,7 +9,7 @@ class AddCopyScriptFromColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->unsignedInteger('copy_script_from')->nullable()->after('script_container'); @@ -21,7 +21,7 @@ class AddCopyScriptFromColumn extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['copy_script_from']); diff --git a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php index aa5e04498..96bb9aec5 100644 --- a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php +++ b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php @@ -9,7 +9,7 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->boolean('behind_proxy')->after('scheme')->default(false); @@ -19,7 +19,7 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('behind_proxy'); diff --git a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php index 7dcae3c6f..967c12615 100644 --- a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php +++ b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php @@ -9,7 +9,7 @@ class DeleteDownloadTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::dropIfExists('downloads'); } @@ -17,7 +17,7 @@ class DeleteDownloadTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('downloads', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php index 90c8c4b1e..d230bc19a 100644 --- a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php +++ b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php @@ -9,7 +9,7 @@ class DeleteNodeConfigurationTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::dropIfExists('node_configuration_tokens'); } @@ -17,7 +17,7 @@ class DeleteNodeConfigurationTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('node_configuration_tokens', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_06_10_152951_add_external_id_to_users.php b/database/migrations/2017_06_10_152951_add_external_id_to_users.php index 9ce5057e8..bccfb43fd 100644 --- a/database/migrations/2017_06_10_152951_add_external_id_to_users.php +++ b/database/migrations/2017_06_10_152951_add_external_id_to_users.php @@ -9,7 +9,7 @@ class AddExternalIdToUsers extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->unsignedInteger('external_id')->after('id')->nullable()->unique(); @@ -19,7 +19,7 @@ class AddExternalIdToUsers extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('external_id'); diff --git a/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php index a089ab4db..6f36d0e05 100644 --- a/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php +++ b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php @@ -9,7 +9,7 @@ class ChangeForeignKeyToBeOnCascadeDelete extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('api_permissions', function (Blueprint $table) { $table->dropForeign(['key_id']); @@ -21,7 +21,7 @@ class ChangeForeignKeyToBeOnCascadeDelete extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('api_permissions', function (Blueprint $table) { $table->dropForeign(['key_id']); diff --git a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php index 0bfc7d527..10058c8cc 100644 --- a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php +++ b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php @@ -9,7 +9,7 @@ class ChangeUserPermissionsToDeleteOnUserDeletion extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('permissions', function (Blueprint $table) { $table->dropForeign(['subuser_id']); @@ -29,7 +29,7 @@ class ChangeUserPermissionsToDeleteOnUserDeletion extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { $table->dropForeign(['user_id']); diff --git a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php index fb156ba8c..8ac6eccec 100644 --- a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php +++ b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php @@ -9,7 +9,7 @@ class SetAllocationToReferenceNullOnServerDelete extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -21,7 +21,7 @@ class SetAllocationToReferenceNullOnServerDelete extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php index 5ae9a29f9..ca5a4623f 100644 --- a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php +++ b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenAServerOrVariableIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -23,7 +23,7 @@ class CascadeDeletionWhenAServerOrVariableIsDeleted extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php index 89e110228..cf0a4bba1 100644 --- a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -9,7 +9,7 @@ class DeleteTaskWhenParentServerIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropForeign(['server_id']); @@ -21,7 +21,7 @@ class DeleteTaskWhenParentServerIsDeleted extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { } } diff --git a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php index a33b78af6..0eabe77db 100644 --- a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php +++ b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php @@ -9,7 +9,7 @@ class CascadeNullValuesForDatabaseHostWhenNodeIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); @@ -20,7 +20,7 @@ class CascadeNullValuesForDatabaseHostWhenNodeIsDeleted extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('database_hosts', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php index 77b7f984c..3fb457dc4 100644 --- a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php +++ b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php @@ -9,7 +9,7 @@ class AllowNegativeValuesForOverallocation extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->integer('disk_overallocate')->default(0)->nullable(false)->change(); @@ -20,10 +20,10 @@ class AllowNegativeValuesForOverallocation extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { - DB::statement('ALTER TABLE nodes MODIFY disk_overallocate MEDIUMINT UNSIGNED NULL, + DB::statement('ALTER TABLE nodes MODIFY disk_overallocate MEDIUMINT UNSIGNED NULL, MODIFY memory_overallocate MEDIUMINT UNSIGNED NULL'); }); } diff --git a/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php index f7aab7c04..f2d8ad9bf 100644 --- a/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php +++ b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php @@ -9,7 +9,7 @@ class SetAllocationUnqiueUsingMultipleFields extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->unique(['node_id', 'ip', 'port']); @@ -19,7 +19,7 @@ class SetAllocationUnqiueUsingMultipleFields extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php index 074f872e0..fbea750bc 100644 --- a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php +++ b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenAParentServiceIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['service_id']); @@ -21,7 +21,7 @@ class CascadeDeletionWhenAParentServiceIsDeleted extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropForeign(['service_id']); diff --git a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php index 1b8f1a567..7c59d801e 100644 --- a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php +++ b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php @@ -9,7 +9,7 @@ class RemovePackWhenParentServiceOptionIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -21,7 +21,7 @@ class RemovePackWhenParentServiceOptionIsDeleted extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('packs', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php index 12eada73c..14f60b3b6 100644 --- a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php +++ b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php @@ -8,7 +8,7 @@ class RenameTasksTableForStructureRefactor extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::rename('tasks', 'tasks_old'); } @@ -16,7 +16,7 @@ class RenameTasksTableForStructureRefactor extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::rename('tasks_old', 'tasks'); } diff --git a/database/migrations/2017_09_10_225941_CreateSchedulesTable.php b/database/migrations/2017_09_10_225941_CreateSchedulesTable.php index 3d5baa6d3..588f48c8f 100644 --- a/database/migrations/2017_09_10_225941_CreateSchedulesTable.php +++ b/database/migrations/2017_09_10_225941_CreateSchedulesTable.php @@ -9,7 +9,7 @@ class CreateSchedulesTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('schedules', function (Blueprint $table) { $table->increments('id'); @@ -32,7 +32,7 @@ class CreateSchedulesTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('schedules'); } diff --git a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php index 9c225a834..969c15361 100644 --- a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php +++ b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php @@ -9,7 +9,7 @@ class CreateNewTasksTableForSchedules extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('tasks', function (Blueprint $table) { $table->increments('id'); @@ -29,7 +29,7 @@ class CreateNewTasksTableForSchedules extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('tasks'); } diff --git a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php index 2a20ef10e..4656e272e 100644 --- a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php +++ b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php @@ -2,6 +2,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; @@ -10,40 +11,40 @@ class TransferOldTasksToNewScheduler extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { - $tasks = DB::table('tasks_old')->get(); + DB::transaction(function () { + $tasks = DB::table('tasks_old')->get(); - DB::beginTransaction(); - $tasks->each(function ($task) { - $schedule = DB::table('schedules')->insertGetId([ - 'server_id' => $task->server_id, - 'name' => null, - 'cron_day_of_week' => $task->day_of_week, - 'cron_day_of_month' => $task->day_of_month, - 'cron_hour' => $task->hour, - 'cron_minute' => $task->minute, - 'is_active' => (bool) $task->active, - 'is_processing' => false, - 'last_run_at' => $task->last_run, - 'next_run_at' => $task->next_run, - 'created_at' => $task->created_at, - 'updated_at' => Carbon::now()->toDateTimeString(), - ]); + $tasks->each(function ($task) { + $schedule = DB::table('schedules')->insertGetId([ + 'server_id' => $task->server_id, + 'name' => null, + 'cron_day_of_week' => $task->day_of_week, + 'cron_day_of_month' => $task->day_of_month, + 'cron_hour' => $task->hour, + 'cron_minute' => $task->minute, + 'is_active' => (bool) $task->active, + 'is_processing' => false, + 'last_run_at' => $task->last_run, + 'next_run_at' => $task->next_run, + 'created_at' => $task->created_at, + 'updated_at' => Carbon::now()->toDateTimeString(), + ]); - DB::table('tasks')->insert([ - 'schedule_id' => $schedule, - 'sequence_id' => 1, - 'action' => $task->action, - 'payload' => $task->data, - 'time_offset' => 0, - 'is_queued' => false, - 'updated_at' => Carbon::now()->toDateTimeString(), - 'created_at' => Carbon::now()->toDateTimeString(), - ]); + DB::table('tasks')->insert([ + 'schedule_id' => $schedule, + 'sequence_id' => 1, + 'action' => $task->action, + 'payload' => $task->data, + 'time_offset' => 0, + 'is_queued' => false, + 'updated_at' => Carbon::now()->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + ]); - DB::table('tasks_old')->delete($task->id); - DB::commit(); + DB::table('tasks_old')->delete($task->id); + }); }); Schema::dropIfExists('tasks_old'); @@ -52,7 +53,7 @@ class TransferOldTasksToNewScheduler extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::create('tasks_old', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php index ba3a8bac0..7c0c57447 100644 --- a/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php +++ b/database/migrations/2017_09_13_211810_UpdateOldPermissionsToPointToNewScheduleSystem.php @@ -8,7 +8,7 @@ class UpdateOldPermissionsToPointToNewScheduleSystem extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { $permissions = DB::table('permissions')->where('permission', 'like', '%-task%')->get(); foreach ($permissions as $record) { @@ -26,7 +26,7 @@ class UpdateOldPermissionsToPointToNewScheduleSystem extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { $permissions = DB::table('permissions')->where('permission', 'like', '%-schedule%')->get(); foreach ($permissions as $record) { diff --git a/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php index cfbfc88b0..64ff02666 100644 --- a/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php +++ b/database/migrations/2017_09_23_170933_CreateDaemonKeysTable.php @@ -9,7 +9,7 @@ class CreateDaemonKeysTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('daemon_keys', function (Blueprint $table) { $table->increments('id'); @@ -28,7 +28,7 @@ class CreateDaemonKeysTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('daemon_keys'); } diff --git a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php index 84cb2d92b..b284905a0 100644 --- a/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php +++ b/database/migrations/2017_09_23_173628_RemoveDaemonSecretFromServersTable.php @@ -12,7 +12,7 @@ class RemoveDaemonSecretFromServersTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { $inserts = []; @@ -41,7 +41,7 @@ class RemoveDaemonSecretFromServersTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->char('daemonSecret', 36)->after('startup')->unique(); diff --git a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php index d4d2dd695..9ea90cff2 100644 --- a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php +++ b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php @@ -1,6 +1,7 @@ get(); @@ -39,7 +40,7 @@ class RemoveDaemonSecretFromSubusersTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('subusers', function (Blueprint $table) { $table->char('daemonSecret', 36)->after('server_id'); diff --git a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php index dffa7687a..aae62921a 100644 --- a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php +++ b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php @@ -11,7 +11,7 @@ class ChangeServicesToUseAMoreUniqueIdentifier extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('services', function (Blueprint $table) { $table->dropUnique(['name']); @@ -39,7 +39,7 @@ class ChangeServicesToUseAMoreUniqueIdentifier extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('services', function (Blueprint $table) { $table->dropColumn('uuid'); diff --git a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php index 5c9df79a5..679a8b5e0 100644 --- a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php +++ b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php @@ -11,7 +11,7 @@ class ChangeToABetterUniqueServiceConfiguration extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_options', function (Blueprint $table) { $table->char('uuid', 36)->after('id'); @@ -40,7 +40,7 @@ class ChangeToABetterUniqueServiceConfiguration extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_options', function (Blueprint $table) { $table->dropColumn('uuid'); diff --git a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php index 3b19e3d99..9a64abf07 100644 --- a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php +++ b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php @@ -9,7 +9,7 @@ class CascadeDeletionWhenServiceOptionIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('service_variables', function (Blueprint $table) { $table->dropForeign(['option_id']); @@ -21,7 +21,7 @@ class CascadeDeletionWhenServiceOptionIsDeleted extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('service_variables', function (Blueprint $table) { $table->dropForeign(['option_id']); diff --git a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php index e7b70136c..cea7460e6 100644 --- a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php +++ b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php @@ -1,5 +1,6 @@ dropUnique(['username']); @@ -22,7 +22,7 @@ class RemoveLegacySFTPInformation extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->string('username')->nullable()->after('image')->unique(); diff --git a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php index b90b150bd..b02e326c6 100644 --- a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php +++ b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php @@ -12,7 +12,7 @@ class Add2FaLastAuthorizationTimeColumn extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->text('totp_secret')->nullable()->change(); @@ -36,7 +36,7 @@ class Add2FaLastAuthorizationTimeColumn extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { DB::transaction(function () { DB::table('users')->get()->each(function ($user) { diff --git a/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php index c2947ee07..229827dbc 100644 --- a/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php +++ b/database/migrations/2017_11_19_122708_MigratePubPrivFormatToSingleKey.php @@ -1,5 +1,6 @@ get()->each(function ($item) { try { $decrypted = Crypt::decrypt($item->secret); - } catch (DecryptException $exception) { + } catch (DecryptException) { $decrypted = str_random(32); } finally { DB::table('api_keys')->where('id', $item->id)->update([ @@ -30,27 +31,41 @@ class MigratePubPrivFormatToSingleKey extends Migration Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('public'); - $table->string('secret', 32)->change(); + $table->renameColumn('secret', 'token'); }); - DB::statement('ALTER TABLE `api_keys` CHANGE `secret` `token` CHAR(32) NOT NULL, ADD UNIQUE INDEX `api_keys_token_unique` (`token`(32))'); + Schema::table('api_keys', function (Blueprint $table) { + $table->char('token', 32)->change(); + $table->unique('token'); + }); } /** * Reverse the migrations. */ - public function down() + public function down(): void { - DB::statement('ALTER TABLE `api_keys` CHANGE `token` `secret` TEXT, DROP INDEX `api_keys_token_unique`'); + Schema::table('api_keys', function (Blueprint $table) { + $table->dropUnique(['token']); + $table->renameColumn('token', 'secret'); + }); Schema::table('api_keys', function (Blueprint $table) { + $table->dropUnique('token'); + $table->text('token')->change(); + }); + + Schema::table('api_keys', function (Blueprint $table) { + $table->renameColumn('token', 'secret'); + + $table->text('secret')->nullable()->change(); $table->char('public', 16)->after('user_id'); }); DB::transaction(function () { DB::table('api_keys')->get()->each(function ($item) { DB::table('api_keys')->where('id', $item->id)->update([ - 'public' => str_random(16), + 'public' => Str::random(16), 'secret' => Crypt::encrypt($item->secret), ]); }); diff --git a/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php index d28109598..9a0cc9114 100644 --- a/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php +++ b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php @@ -9,7 +9,7 @@ class DropAllocationsWhenNodeIsDeleted extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); @@ -21,7 +21,7 @@ class DropAllocationsWhenNodeIsDeleted extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php index 1bdaf6477..8d639a37a 100644 --- a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php +++ b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php @@ -10,7 +10,7 @@ class MigrateSettingsTableToNewFormat extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { DB::table('settings')->truncate(); Schema::table('settings', function (Blueprint $table) { @@ -21,7 +21,7 @@ class MigrateSettingsTableToNewFormat extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::table('settings', function (Blueprint $table) { $table->dropColumn('id'); diff --git a/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php b/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php index 8f9938da1..7ccae5d61 100644 --- a/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php +++ b/database/migrations/2018_01_01_122821_AllowNegativeValuesForServerSwap.php @@ -8,10 +8,8 @@ class AllowNegativeValuesForServerSwap extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->integer('swap')->change(); @@ -20,10 +18,8 @@ class AllowNegativeValuesForServerSwap extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('swap')->change(); diff --git a/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php b/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php index adc6d2648..118a422f4 100644 --- a/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php +++ b/database/migrations/2018_01_11_213943_AddApiKeyPermissionColumns.php @@ -8,10 +8,8 @@ class AddApiKeyPermissionColumns extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('api_permissions'); @@ -31,10 +29,8 @@ class AddApiKeyPermissionColumns extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('api_permissions', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php index 1d36b3648..d7e33210d 100644 --- a/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php +++ b/database/migrations/2018_01_13_142012_SetupTableForKeyEncryption.php @@ -9,12 +9,10 @@ class SetupTableForKeyEncryption extends Migration /** * Run the migrations. * - * @return void - * * @throws \Exception * @throws \Throwable */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->char('identifier', 16)->nullable()->unique()->after('user_id'); @@ -29,12 +27,10 @@ class SetupTableForKeyEncryption extends Migration /** * Reverse the migrations. * - * @return void - * * @throws \Exception * @throws \Throwable */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('identifier'); diff --git a/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php index e0f86b9de..f78f7a5d1 100644 --- a/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php +++ b/database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php @@ -8,10 +8,8 @@ class AddLastUsedAtColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedTinyInteger('key_type')->after('user_id')->default(0); @@ -28,10 +26,8 @@ class AddLastUsedAtColumn extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->timestamp('expires_at')->after('memo')->nullable(); diff --git a/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php index 6a4a04e7d..6166f016e 100644 --- a/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php +++ b/database/migrations/2018_02_04_145617_AllowTextInUserExternalId.php @@ -8,10 +8,8 @@ class AllowTextInUserExternalId extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('external_id')->nullable()->change(); @@ -20,10 +18,8 @@ class AllowTextInUserExternalId extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->unsignedInteger('external_id')->change(); diff --git a/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php b/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php index b587cdcb0..64dbaf0dc 100644 --- a/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php +++ b/database/migrations/2018_02_10_151150_remove_unique_index_on_external_id_column.php @@ -8,10 +8,8 @@ class RemoveUniqueIndexOnExternalIdColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->dropUnique(['external_id']); @@ -20,10 +18,8 @@ class RemoveUniqueIndexOnExternalIdColumn extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->unique(['external_id']); diff --git a/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php b/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php index bff7bbfb0..99f1db454 100644 --- a/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php +++ b/database/migrations/2018_02_17_134254_ensure_unique_allocation_id_on_servers_table.php @@ -8,10 +8,8 @@ class EnsureUniqueAllocationIdOnServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unique(['allocation_id']); @@ -20,10 +18,8 @@ class EnsureUniqueAllocationIdOnServersTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['allocation_id']); diff --git a/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php index 2c8af99e2..c7d0f8fd4 100644 --- a/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php +++ b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddExternalIdColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('external_id')->after('id')->nullable()->unique(); @@ -20,10 +18,8 @@ class AddExternalIdColumnToServersTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('external_id'); diff --git a/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php b/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php index 6469867f2..e432e56dd 100644 --- a/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php +++ b/database/migrations/2018_02_25_160152_remove_default_null_value_on_table.php @@ -13,7 +13,7 @@ class RemoveDefaultNullValueOnTable extends Migration * @throws \Exception * @throws \Throwable */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('external_id')->default(null)->change(); @@ -28,10 +28,8 @@ class RemoveDefaultNullValueOnTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { // This should not be rolled back. } diff --git a/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php b/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php index 0a9b8afe2..38469af23 100644 --- a/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php +++ b/database/migrations/2018_02_25_160604_define_unique_index_on_users_external_id.php @@ -8,10 +8,8 @@ class DefineUniqueIndexOnUsersExternalId extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('users', function (Blueprint $table) { $table->index(['external_id']); @@ -20,10 +18,8 @@ class DefineUniqueIndexOnUsersExternalId extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropIndex(['external_id']); diff --git a/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php b/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php index 4e85e8aeb..00fbd11c2 100644 --- a/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php +++ b/database/migrations/2018_03_01_192831_add_database_and_port_limit_columns_to_servers_table.php @@ -8,10 +8,8 @@ class AddDatabaseAndPortLimitColumnsToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('database_limit')->after('installed')->nullable()->default(0); @@ -21,10 +19,8 @@ class AddDatabaseAndPortLimitColumnsToServersTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn(['database_limit', 'allocation_limit']); diff --git a/database/migrations/2018_03_15_124536_add_description_to_nodes.php b/database/migrations/2018_03_15_124536_add_description_to_nodes.php index 7208a4207..a5c1b7542 100644 --- a/database/migrations/2018_03_15_124536_add_description_to_nodes.php +++ b/database/migrations/2018_03_15_124536_add_description_to_nodes.php @@ -8,10 +8,8 @@ class AddDescriptionToNodes extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->text('description')->after('name'); @@ -20,10 +18,8 @@ class AddDescriptionToNodes extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('description'); diff --git a/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php index 04fdf000f..e85eca8cd 100644 --- a/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php +++ b/database/migrations/2018_05_04_123826_add_maintenance_to_nodes.php @@ -8,10 +8,8 @@ class AddMaintenanceToNodes extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->boolean('maintenance_mode')->after('behind_proxy')->default(false); @@ -20,10 +18,8 @@ class AddMaintenanceToNodes extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropColumn('maintenance_mode'); diff --git a/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php b/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php index 199650940..e7a4089fa 100644 --- a/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php +++ b/database/migrations/2018_09_03_143756_allow_egg_variables_to_have_longer_values.php @@ -8,10 +8,8 @@ class AllowEggVariablesToHaveLongerValues extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('egg_variables', function (Blueprint $table) { $table->text('default_value')->change(); @@ -20,10 +18,8 @@ class AllowEggVariablesToHaveLongerValues extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('egg_variables', function (Blueprint $table) { $table->string('default_value')->change(); diff --git a/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php b/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php index cc90e0e06..a1d581819 100644 --- a/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php +++ b/database/migrations/2018_09_03_144005_allow_server_variables_to_have_longer_values.php @@ -8,10 +8,8 @@ class AllowServerVariablesToHaveLongerValues extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_variables', function (Blueprint $table) { $table->text('variable_value')->change(); @@ -20,10 +18,8 @@ class AllowServerVariablesToHaveLongerValues extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_variables', function (Blueprint $table) { $table->string('variable_value')->change(); diff --git a/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php b/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php index d91ce6372..6d43197de 100644 --- a/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php +++ b/database/migrations/2019_03_02_142328_set_allocation_limit_default_null.php @@ -8,10 +8,8 @@ class SetAllocationLimitDefaultNull extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('allocation_limit')->nullable()->default(null)->change(); @@ -20,10 +18,8 @@ class SetAllocationLimitDefaultNull extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('allocation_limit')->nullable()->default(0)->change(); diff --git a/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php b/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php index 59425aee7..de110a06b 100644 --- a/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php +++ b/database/migrations/2019_03_02_151321_fix_unique_index_to_account_for_host.php @@ -8,10 +8,8 @@ class FixUniqueIndexToAccountForHost extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database']); @@ -24,10 +22,8 @@ class FixUniqueIndexToAccountForHost extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropForeign(['database_host_id']); diff --git a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php index 27d26674f..b71189fe0 100644 --- a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php +++ b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php @@ -61,10 +61,8 @@ class MergePermissionsTableIntoSubusers extends Migration /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('subusers', function (Blueprint $table) { $table->json('permissions')->nullable()->after('server_id'); @@ -72,7 +70,12 @@ class MergePermissionsTableIntoSubusers extends Migration $cursor = DB::table('permissions') ->select(['subuser_id']) - ->selectRaw('GROUP_CONCAT(permission) as permissions') + ->when(DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql', function ($query) { + $query->selectRaw('group_concat(permission) as permissions'); + }) + ->when(DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'pgsql', function ($query) { + $query->selectRaw("string_agg(permission, ',') as permissions"); + }) ->from('permissions') ->groupBy(['subuser_id']) ->cursor(); @@ -98,10 +101,8 @@ class MergePermissionsTableIntoSubusers extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { $flipped = array_flip(array_filter(self::$permissionsMap)); diff --git a/database/migrations/2020_03_22_164814_drop_permissions_table.php b/database/migrations/2020_03_22_164814_drop_permissions_table.php index da9d677a8..030a8a6ba 100644 --- a/database/migrations/2020_03_22_164814_drop_permissions_table.php +++ b/database/migrations/2020_03_22_164814_drop_permissions_table.php @@ -8,20 +8,16 @@ class DropPermissionsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('permissions'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('permissions', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php index 9b0202cab..d4c08c5e5 100644 --- a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php +++ b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddThreadsColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('threads')->nullable()->after('cpu'); @@ -20,10 +18,8 @@ class AddThreadsColumnToServersTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('threads'); diff --git a/database/migrations/2020_04_03_230614_create_backups_table.php b/database/migrations/2020_04_03_230614_create_backups_table.php index daa35dd3b..a8c28d96d 100644 --- a/database/migrations/2020_04_03_230614_create_backups_table.php +++ b/database/migrations/2020_04_03_230614_create_backups_table.php @@ -9,10 +9,8 @@ class CreateBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { $db = config('database.default'); // There exists a backups plugin for the 0.7 version of the Panel. However, it didn't properly @@ -49,10 +47,8 @@ class CreateBackupsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('backups'); } diff --git a/database/migrations/2020_04_04_131016_add_table_server_transfers.php b/database/migrations/2020_04_04_131016_add_table_server_transfers.php index 096b5384f..c9f3e849a 100644 --- a/database/migrations/2020_04_04_131016_add_table_server_transfers.php +++ b/database/migrations/2020_04_04_131016_add_table_server_transfers.php @@ -8,10 +8,8 @@ class AddTableServerTransfers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Nuclear approach to whatever plugins are out there and not properly namespacing their own tables // leading to constant support requests from people... @@ -20,13 +18,13 @@ class AddTableServerTransfers extends Migration Schema::create('server_transfers', function (Blueprint $table) { $table->increments('id'); $table->integer('server_id')->unsigned(); - $table->tinyInteger('successful')->unsigned()->default(0); + $table->boolean('successful')->unsigned()->default(0); $table->integer('old_node')->unsigned(); $table->integer('new_node')->unsigned(); $table->integer('old_allocation')->unsigned(); $table->integer('new_allocation')->unsigned(); - $table->string('old_additional_allocations')->nullable(); - $table->string('new_additional_allocations')->nullable(); + $table->json('old_additional_allocations')->nullable(); + $table->json('new_additional_allocations')->nullable(); $table->timestamps(); $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); @@ -35,10 +33,8 @@ class AddTableServerTransfers extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('server_transfers'); } diff --git a/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php index 6544679fe..a69dafc89 100644 --- a/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php +++ b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php @@ -13,11 +13,9 @@ class StoreNodeTokensAsEncryptedValue extends Migration /** * Run the migrations. * - * @return void - * * @throws \Exception */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->dropUnique(['daemonSecret']); @@ -26,7 +24,8 @@ class StoreNodeTokensAsEncryptedValue extends Migration Schema::table('nodes', function (Blueprint $table) { $table->char('uuid', 36)->after('id'); $table->char('daemon_token_id', 16)->after('upload_size'); - $table->renameColumn('daemonSecret', 'daemon_token'); + + $table->renameColumn('`daemonSecret`', 'daemon_token'); }); Schema::table('nodes', function (Blueprint $table) { @@ -53,10 +52,8 @@ class StoreNodeTokensAsEncryptedValue extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { DB::transaction(function () { /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ diff --git a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php index dfd55fb42..8c2c149cf 100644 --- a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php +++ b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php @@ -8,10 +8,8 @@ class AllowNullableDescriptions extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('description')->nullable()->change(); @@ -32,10 +30,8 @@ class AllowNullableDescriptions extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('description')->nullable(false)->change(); diff --git a/database/migrations/2020_04_22_055500_add_max_connections_column.php b/database/migrations/2020_04_22_055500_add_max_connections_column.php index 02253dfd7..57604117c 100644 --- a/database/migrations/2020_04_22_055500_add_max_connections_column.php +++ b/database/migrations/2020_04_22_055500_add_max_connections_column.php @@ -8,10 +8,8 @@ class AddMaxConnectionsColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->integer('max_connections')->nullable()->default(0)->after('password'); @@ -20,10 +18,8 @@ class AddMaxConnectionsColumn extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropColumn('max_connections'); diff --git a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php index b0f859c9f..af1b72e64 100644 --- a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php +++ b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php @@ -9,10 +9,8 @@ class AddBackupLimitToServers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { $db = config('database.default'); // Same as in the backups migration, we need to handle that plugin messing with the data structure @@ -35,10 +33,8 @@ class AddBackupLimitToServers extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('backup_limit'); diff --git a/database/migrations/2020_05_20_234655_add_mounts_table.php b/database/migrations/2020_05_20_234655_add_mounts_table.php index 09846a0a5..db3b409d8 100644 --- a/database/migrations/2020_05_20_234655_add_mounts_table.php +++ b/database/migrations/2020_05_20_234655_add_mounts_table.php @@ -8,10 +8,8 @@ class AddMountsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('mounts', function (Blueprint $table) { $table->increments('id')->unique(); @@ -41,10 +39,8 @@ class AddMountsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('mount_node'); Schema::dropIfExists('egg_mount'); diff --git a/database/migrations/2020_05_21_192756_add_mount_server_table.php b/database/migrations/2020_05_21_192756_add_mount_server_table.php index 682bd578d..7d8e9438b 100644 --- a/database/migrations/2020_05_21_192756_add_mount_server_table.php +++ b/database/migrations/2020_05_21_192756_add_mount_server_table.php @@ -8,10 +8,8 @@ class AddMountServerTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('mount_server', function (Blueprint $table) { $table->integer('server_id'); @@ -23,10 +21,8 @@ class AddMountServerTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('mount_server'); } diff --git a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php index 9b0743af2..11a6f513c 100644 --- a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php +++ b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php @@ -8,10 +8,8 @@ class CreateUserRecoveryTokensTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('recovery_tokens', function (Blueprint $table) { $table->id(); @@ -25,10 +23,8 @@ class CreateUserRecoveryTokensTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('recovery_tokens'); } diff --git a/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php index 711495edf..a93b48053 100644 --- a/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php +++ b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php @@ -8,10 +8,8 @@ class AddNotesColumnForAllocations extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('allocations', function (Blueprint $table) { $table->string('notes')->nullable()->after('server_id'); @@ -20,10 +18,8 @@ class AddNotesColumnForAllocations extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('allocations', function (Blueprint $table) { $table->dropColumn('notes'); diff --git a/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php index 9e6faa42b..4ac99fedd 100644 --- a/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php +++ b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php @@ -8,10 +8,8 @@ class AddBackupStateColumnToBackups extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(true); @@ -20,10 +18,8 @@ class AddBackupStateColumnToBackups extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('is_successful'); diff --git a/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php index e8e9c38f3..5c4adca1a 100644 --- a/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php +++ b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php @@ -8,10 +8,8 @@ class UpdateBytesToUnsignedBigint extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->unsignedBigInteger('bytes')->default(0)->change(); @@ -20,10 +18,8 @@ class UpdateBytesToUnsignedBigint extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->integer('bytes')->default(0)->change(); diff --git a/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php index 0de248bfd..763b20457 100644 --- a/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php +++ b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php @@ -9,10 +9,8 @@ class ModifyChecksumsColumnForBackups extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->renameColumn('sha256_hash', 'checksum'); @@ -25,10 +23,8 @@ class ModifyChecksumsColumnForBackups extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->renameColumn('checksum', 'sha256_hash'); diff --git a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php index 638435a81..53cba54f5 100644 --- a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php +++ b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php @@ -8,10 +8,8 @@ class DropPacksFromServers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->dropForeign(['pack_id']); @@ -21,10 +19,8 @@ class DropPacksFromServers extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedInteger('pack_id')->after('egg_id')->nullable(); diff --git a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php index 9bcce8d4d..7c051db2c 100644 --- a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php +++ b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php @@ -8,10 +8,8 @@ class DropPacksFromApiKeyPermissions extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('api_keys', function (Blueprint $table) { $table->dropColumn('r_packs'); @@ -20,10 +18,8 @@ class DropPacksFromApiKeyPermissions extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('api_keys', function (Blueprint $table) { $table->unsignedTinyInteger('r_packs')->default(0); diff --git a/database/migrations/2020_09_13_110047_drop_packs_table.php b/database/migrations/2020_09_13_110047_drop_packs_table.php index 4f83c0f2e..58194b8fa 100644 --- a/database/migrations/2020_09_13_110047_drop_packs_table.php +++ b/database/migrations/2020_09_13_110047_drop_packs_table.php @@ -8,20 +8,16 @@ class DropPacksTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('packs'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('packs', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php index 7b90d41b9..274f9fd97 100644 --- a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php +++ b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php @@ -8,20 +8,16 @@ class DropDaemonKeyTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::dropIfExists('daemon_keys'); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::create('daemon_keys', function (Blueprint $table) { $table->increments('id'); diff --git a/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php index 7420989a7..6a277e449 100644 --- a/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php +++ b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php @@ -8,10 +8,8 @@ class ChangeUniqueDatabaseNameToAccountForServer extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database_host_id', 'database']); @@ -24,10 +22,8 @@ class ChangeUniqueDatabaseNameToAccountForServer extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('databases', function (Blueprint $table) { $table->dropUnique(['database_host_id', 'server_id', 'database']); diff --git a/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php b/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php index 69593e656..d0b3118e5 100644 --- a/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php +++ b/database/migrations/2020_10_26_194904_remove_nullable_from_schedule_name_field.php @@ -9,10 +9,8 @@ class RemoveNullableFromScheduleNameField extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { DB::update("UPDATE schedules SET name = 'Schedule' WHERE name IS NULL OR name = ''"); @@ -23,10 +21,8 @@ class RemoveNullableFromScheduleNameField extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->string('name')->nullable()->change(); diff --git a/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php b/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php index 1a001ae98..e134fe9a6 100644 --- a/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php +++ b/database/migrations/2020_11_02_201014_add_features_column_to_eggs.php @@ -8,10 +8,8 @@ class AddFeaturesColumnToEggs extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->json('features')->after('description')->nullable(); @@ -20,10 +18,8 @@ class AddFeaturesColumnToEggs extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('features'); diff --git a/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php b/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php index 776d3c6ba..c7d2cff7f 100644 --- a/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php +++ b/database/migrations/2020_12_12_102435_support_multiple_docker_images_and_updates.php @@ -9,19 +9,22 @@ class SupportMultipleDockerImagesAndUpdates extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->json('docker_images')->after('docker_image')->nullable(); $table->text('update_url')->after('docker_images')->nullable(); }); - Schema::table('eggs', function (Blueprint $table) { - DB::statement('UPDATE `eggs` SET `docker_images` = JSON_ARRAY(docker_image)'); - }); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_ARRAY(docker_image)')]); + break; + case 'pgsql': + DB::table('eggs')->update(['docker_images' => DB::raw('jsonb_build_array(docker_image)')]); + break; + } Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('docker_image'); @@ -30,18 +33,22 @@ class SupportMultipleDockerImagesAndUpdates extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('docker_image')->after('docker_images'); }); - Schema::table('eggs', function (Blueprint $table) { - DB::statement('UPDATE `eggs` SET `docker_image` = JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]"))'); - }); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]")')]); + break; + case 'pgsql': + DB::table('eggs')->update(['docker_images' => DB::raw('JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]")')]); + DB::table('eggs')->update(['docker_images' => DB::raw('docker_images->>0')]); + break; + } Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('docker_images'); diff --git a/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php index 0a2885284..4c28b6c2b 100644 --- a/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php +++ b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php @@ -8,10 +8,8 @@ class MakeSuccessfulNullableInServerTransfers extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('successful')->nullable()->default(null)->change(); @@ -20,10 +18,8 @@ class MakeSuccessfulNullableInServerTransfers extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('successful')->default(0)->change(); diff --git a/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php index 1162d8a4f..bc5d3356d 100644 --- a/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php +++ b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php @@ -9,27 +9,21 @@ class AddArchivedFieldToServerTransfersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->boolean('archived')->default(0)->after('new_additional_allocations'); }); // Update archived to all be true on existing transfers. - Schema::table('server_transfers', function (Blueprint $table) { - DB::statement('UPDATE `server_transfers` SET `archived` = 1 WHERE `successful` = 1'); - }); + DB::table('server_transfers')->where('successful', true)->update(['archived' => 1]); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('server_transfers', function (Blueprint $table) { $table->dropColumn('archived'); diff --git a/database/migrations/2020_12_24_092449_make_allocation_fields_json.php b/database/migrations/2020_12_24_092449_make_allocation_fields_json.php deleted file mode 100644 index bceec9de7..000000000 --- a/database/migrations/2020_12_24_092449_make_allocation_fields_json.php +++ /dev/null @@ -1,34 +0,0 @@ -json('old_additional_allocations')->nullable()->change(); - $table->json('new_additional_allocations')->nullable()->change(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('server_transfers', function (Blueprint $table) { - $table->string('old_additional_allocations')->nullable()->change(); - $table->string('new_additional_allocations')->nullable()->change(); - }); - } -} diff --git a/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php b/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php index 2e1c50556..771e06ab1 100644 --- a/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php +++ b/database/migrations/2020_12_26_184914_add_upload_id_column_to_backups_table.php @@ -8,10 +8,8 @@ class AddUploadIdColumnToBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->text('upload_id')->nullable()->after('uuid'); @@ -20,10 +18,8 @@ class AddUploadIdColumnToBackupsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('upload_id'); diff --git a/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php b/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php index 8d617fc19..4a956625e 100644 --- a/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php +++ b/database/migrations/2021_01_10_153937_add_file_denylist_to_egg_configs.php @@ -8,10 +8,8 @@ class AddFileDenylistToEggConfigs extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->text('file_denylist')->after('docker_images'); @@ -20,10 +18,8 @@ class AddFileDenylistToEggConfigs extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); diff --git a/database/migrations/2021_01_13_013420_add_cron_month.php b/database/migrations/2021_01_13_013420_add_cron_month.php index 85e534248..da0bf8841 100644 --- a/database/migrations/2021_01_13_013420_add_cron_month.php +++ b/database/migrations/2021_01_13_013420_add_cron_month.php @@ -8,10 +8,8 @@ class AddCronMonth extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('schedules', function (Blueprint $table) { $table->string('cron_month')->after('cron_day_of_week'); @@ -20,10 +18,8 @@ class AddCronMonth extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->dropColumn('cron_month'); diff --git a/database/migrations/2021_01_17_102401_create_audit_logs_table.php b/database/migrations/2021_01_17_102401_create_audit_logs_table.php index f67e7d647..d0fddf801 100644 --- a/database/migrations/2021_01_17_102401_create_audit_logs_table.php +++ b/database/migrations/2021_01_17_102401_create_audit_logs_table.php @@ -8,10 +8,8 @@ class CreateAuditLogsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('audit_logs', function (Blueprint $table) { $table->id(); @@ -32,10 +30,8 @@ class CreateAuditLogsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('audit_logs'); } diff --git a/database/migrations/2021_01_17_152623_add_generic_server_status_column.php b/database/migrations/2021_01_17_152623_add_generic_server_status_column.php index 12e6abb95..f8c9dec2c 100644 --- a/database/migrations/2021_01_17_152623_add_generic_server_status_column.php +++ b/database/migrations/2021_01_17_152623_add_generic_server_status_column.php @@ -9,20 +9,16 @@ class AddGenericServerStatusColumn extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->string('status')->nullable()->after('description'); }); - DB::transaction(function () { - DB::update('UPDATE servers SET `status` = \'suspended\' WHERE `suspended` = 1'); - DB::update('UPDATE servers SET `status` = \'installing\' WHERE `installed` = 0'); - DB::update('UPDATE servers SET `status` = \'install_failed\' WHERE `installed` = 2'); - }); + DB::table('servers')->where('suspended', 1)->update(['status' => 'suspended']); + DB::table('servers')->where('installed', 1)->update(['status' => 'installing']); + DB::table('servers')->where('installed', 1)->update(['status' => 'install_failed']); Schema::table('servers', function (Blueprint $table) { $table->dropColumn('suspended'); @@ -32,21 +28,17 @@ class AddGenericServerStatusColumn extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->unsignedTinyInteger('suspended')->default(0); $table->unsignedTinyInteger('installed')->default(0); }); - DB::transaction(function () { - DB::update('UPDATE servers SET `suspended` = 1 WHERE `status` = \'suspended\''); - DB::update('UPDATE servers SET `installed` = 1 WHERE `status` IS NULL'); - DB::update('UPDATE servers SET `installed` = 2 WHERE `status` = \'install_failed\''); - }); + DB::table('servers')->where('status', 'suspended')->update(['suspended' => 1]); + DB::table('servers')->whereNull('status')->update(['installed' => 1]); + DB::table('servers')->where('status', 'install_failed')->update(['installed' => 2]); Schema::table('servers', function (Blueprint $table) { $table->dropColumn('status'); diff --git a/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php b/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php index af4961135..7c2c5fa06 100644 --- a/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php +++ b/database/migrations/2021_01_26_210502_update_file_denylist_to_json.php @@ -8,10 +8,8 @@ class UpdateFileDenylistToJson extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); @@ -24,10 +22,8 @@ class UpdateFileDenylistToJson extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('file_denylist'); diff --git a/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php b/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php index 888125468..1fd63559c 100644 --- a/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php +++ b/database/migrations/2021_02_23_205021_add_index_for_server_and_action.php @@ -8,10 +8,8 @@ class AddIndexForServerAndAction extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('audit_logs', function (Blueprint $table) { // Doing the index in this order lets me use the action alone without the server @@ -27,10 +25,8 @@ class AddIndexForServerAndAction extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('audit_logs', function (Blueprint $table) { $table->dropIndex(['action', 'server_id']); diff --git a/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php b/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php index 8eea84819..b9196a0ae 100644 --- a/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php +++ b/database/migrations/2021_02_23_212657_make_sftp_port_unsigned_int.php @@ -8,10 +8,8 @@ class MakeSftpPortUnsignedInt extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('nodes', function (Blueprint $table) { $table->unsignedSmallInteger('daemonSFTP')->default(2022)->change(); @@ -20,10 +18,8 @@ class MakeSftpPortUnsignedInt extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('nodes', function (Blueprint $table) { $table->smallInteger('daemonSFTP')->default(2022)->change(); diff --git a/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php b/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php index 57e129952..61abdbd6c 100644 --- a/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php +++ b/database/migrations/2021_03_21_104718_force_cron_month_field_to_have_value_if_missing.php @@ -1,30 +1,22 @@ where('cron_month', '')->update(['cron_month' => '*']); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { // No down function. } diff --git a/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php b/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php index 703f1524f..079045496 100644 --- a/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php +++ b/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php @@ -8,10 +8,8 @@ class AddContinueOnFailureOptionToTasks extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('tasks', function (Blueprint $table) { $table->unsignedTinyInteger('continue_on_failure')->after('is_queued')->default(0); @@ -20,10 +18,8 @@ class AddContinueOnFailureOptionToTasks extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('tasks', function (Blueprint $table) { $table->dropColumn('continue_on_failure'); diff --git a/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php b/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php index 91bb43be9..a0143fdd6 100644 --- a/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php +++ b/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php @@ -8,10 +8,8 @@ class AddOnlyRunWhenServerOnlineOptionToSchedules extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('schedules', function (Blueprint $table) { $table->unsignedTinyInteger('only_when_online')->after('is_processing')->default(0); @@ -20,10 +18,8 @@ class AddOnlyRunWhenServerOnlineOptionToSchedules extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('schedules', function (Blueprint $table) { $table->dropColumn('only_when_online'); diff --git a/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php b/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php index bafa4dd76..3296e3e8f 100644 --- a/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php +++ b/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php @@ -8,10 +8,8 @@ class AddSupportForLockingABackup extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->unsignedTinyInteger('is_locked')->after('is_successful')->default(0); @@ -20,10 +18,8 @@ class AddSupportForLockingABackup extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->dropColumn('is_locked'); diff --git a/database/migrations/2021_07_12_013420_remove_userinteraction.php b/database/migrations/2021_07_12_013420_remove_userinteraction.php index 05321d4b3..33b4d5d3b 100644 --- a/database/migrations/2021_07_12_013420_remove_userinteraction.php +++ b/database/migrations/2021_07_12_013420_remove_userinteraction.php @@ -7,22 +7,41 @@ class RemoveUserInteraction extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Remove User Interaction from startup config - DB::table('eggs')->update([ - 'config_startup' => DB::raw('JSON_REMOVE(config_startup, \'$.userInteraction\')'), - ]); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('JSON_REMOVE(config_startup, \'$.userInteraction\')'), + ]); + break; + case 'pgsql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('config_startup::jsonb - \'userInteraction\''), + ]); + break; + } } - public function down() + /** + * Reverse the migrations. + */ + public function down(): void { // Add blank User Interaction array back to startup config - DB::table('eggs')->update([ - 'config_startup' => DB::raw('JSON_SET(config_startup, \'$.userInteraction\', JSON_ARRAY())'), - ]); + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('JSON_SET(config_startup, \'$.userInteraction\', JSON_ARRAY())'), + ]); + break; + case 'pgsql': + DB::table('eggs')->update([ + 'config_startup' => DB::raw('jsonb_set(config_startup::jsonb, \'$.userInteraction\', jsonb_build_array())'), + ]); + break; + } } } diff --git a/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php index d5b8a13c6..a33bb4d31 100644 --- a/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php +++ b/database/migrations/2021_07_17_211512_create_user_ssh_keys_table.php @@ -9,7 +9,7 @@ class CreateUserSshKeysTable extends Migration /** * Run the migrations. */ - public function up() + public function up(): void { Schema::create('user_ssh_keys', function (Blueprint $table) { $table->increments('id'); @@ -27,7 +27,7 @@ class CreateUserSshKeysTable extends Migration /** * Reverse the migrations. */ - public function down() + public function down(): void { Schema::dropIfExists('user_ssh_keys'); } diff --git a/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php b/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php index d47b0e5d2..b9284eb0c 100644 --- a/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php +++ b/database/migrations/2021_08_03_210600_change_successful_field_to_default_to_false_on_backups_table.php @@ -9,10 +9,8 @@ class ChangeSuccessfulFieldToDefaultToFalseOnBackupsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(false)->change(); @@ -26,10 +24,8 @@ class ChangeSuccessfulFieldToDefaultToFalseOnBackupsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('backups', function (Blueprint $table) { $table->boolean('is_successful')->after('uuid')->default(true)->change(); diff --git a/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php b/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php index fad8dc193..5210f60d2 100644 --- a/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php +++ b/database/migrations/2021_08_21_175111_add_foreign_keys_to_mount_node_table.php @@ -9,10 +9,8 @@ class AddForeignKeysToMountNodeTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('mount_node', function (Blueprint $table) { @@ -46,10 +44,8 @@ class AddForeignKeysToMountNodeTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('mount_node', function (Blueprint $table) { $table->dropForeign(['node_id']); diff --git a/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php b/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php index 9c5a403b2..f564ec491 100644 --- a/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php +++ b/database/migrations/2021_08_21_175118_add_foreign_keys_to_mount_server_table.php @@ -8,10 +8,8 @@ class AddForeignKeysToMountServerTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('mount_server', function (Blueprint $table) { @@ -45,10 +43,8 @@ class AddForeignKeysToMountServerTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('mount_server', function (Blueprint $table) { $table->dropForeign(['server_id']); diff --git a/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php b/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php index 7bf99506b..11285a5c5 100644 --- a/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php +++ b/database/migrations/2021_08_21_180921_add_foreign_keys_to_egg_mount_table.php @@ -8,10 +8,8 @@ class AddForeignKeysToEggMountTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { // Fix the columns having a different type than their relations. Schema::table('egg_mount', function (Blueprint $table) { @@ -45,10 +43,8 @@ class AddForeignKeysToEggMountTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('egg_mount', function (Blueprint $table) { $table->dropForeign(['egg_id']); diff --git a/database/migrations/2022_01_25_030847_drop_google_analytics.php b/database/migrations/2022_01_25_030847_drop_google_analytics.php index 5daf0bc39..0d65e5d4d 100644 --- a/database/migrations/2022_01_25_030847_drop_google_analytics.php +++ b/database/migrations/2022_01_25_030847_drop_google_analytics.php @@ -7,25 +7,19 @@ class DropGoogleAnalytics extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { DB::table('settings')->where('key', 'settings::app:analytics')->delete(); } /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { - DB::table('settings')->insert( - [ + DB::table('settings')->insert([ 'key' => 'settings::app:analytics', - ] - ); + ]); } } diff --git a/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php index 78dfe6e37..057ce9d74 100644 --- a/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php +++ b/database/migrations/2022_05_07_165334_migrate_egg_images_array_to_new_format.php @@ -10,7 +10,7 @@ class MigrateEggImagesArrayToNewFormat extends Migration * images array to both exist, and have key => value pairings to support naming the * images provided. */ - public function up() + public function up(): void { DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { $images = is_null($egg->docker_images) ? [] : json_decode($egg->docker_images, true, 512, JSON_THROW_ON_ERROR); @@ -26,10 +26,8 @@ class MigrateEggImagesArrayToNewFormat extends Migration /** * Reverse the migrations. This just keeps the values from the docker images array. - * - * @return void */ - public function down() + public function down(): void { DB::table('eggs')->select(['id', 'docker_images'])->cursor()->each(function ($egg) { DB::table('eggs')->where('id', $egg->id)->update([ diff --git a/database/migrations/2022_05_28_135717_create_activity_logs_table.php b/database/migrations/2022_05_28_135717_create_activity_logs_table.php index 448439dc8..4c4f05f1f 100644 --- a/database/migrations/2022_05_28_135717_create_activity_logs_table.php +++ b/database/migrations/2022_05_28_135717_create_activity_logs_table.php @@ -8,10 +8,8 @@ class CreateActivityLogsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('activity_logs', function (Blueprint $table) { $table->id(); @@ -27,10 +25,8 @@ class CreateActivityLogsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('activity_logs'); } diff --git a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php index 6dc45d7f8..c9997ac41 100644 --- a/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php +++ b/database/migrations/2022_05_29_140349_create_activity_log_actors_table.php @@ -8,10 +8,8 @@ class CreateActivityLogActorsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::create('activity_log_subjects', function (Blueprint $table) { $table->id(); @@ -22,10 +20,8 @@ class CreateActivityLogActorsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::dropIfExists('activity_log_subjects'); } diff --git a/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php b/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php index 6e35df9a2..f13878637 100644 --- a/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php +++ b/database/migrations/2022_06_18_112822_track_api_key_usage_for_activity_events.php @@ -7,10 +7,8 @@ use Illuminate\Database\Migrations\Migration; return new class () extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('activity_logs', function (Blueprint $table) { $table->unsignedInteger('api_key_id')->nullable()->after('actor_id'); @@ -19,10 +17,8 @@ return new class () extends Migration { /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('activity_logs', function (Blueprint $table) { $table->dropColumn('api_key_id'); diff --git a/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php b/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php index eb3a56bf2..c5b30a49a 100644 --- a/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php +++ b/database/migrations/2022_08_16_214400_add_force_outgoing_ip_column_to_eggs_table.php @@ -8,10 +8,8 @@ class AddForceOutgoingIpColumnToEggsTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('eggs', function (Blueprint $table) { $table->boolean('force_outgoing_ip')->default(false); @@ -20,10 +18,8 @@ class AddForceOutgoingIpColumnToEggsTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('eggs', function (Blueprint $table) { $table->dropColumn('force_outgoing_ip'); diff --git a/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php b/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php index 50a5e23f8..541117a3a 100644 --- a/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php +++ b/database/migrations/2022_08_16_230204_add_installed_at_column_to_servers_table.php @@ -8,10 +8,8 @@ class AddInstalledAtColumnToServersTable extends Migration { /** * Run the migrations. - * - * @return void */ - public function up() + public function up(): void { Schema::table('servers', function (Blueprint $table) { $table->timestamp('installed_at')->nullable(); @@ -20,10 +18,8 @@ class AddInstalledAtColumnToServersTable extends Migration /** * Reverse the migrations. - * - * @return void */ - public function down() + public function down(): void { Schema::table('servers', function (Blueprint $table) { $table->dropColumn('installed_at'); diff --git a/database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php b/database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php new file mode 100644 index 000000000..ed36c6468 --- /dev/null +++ b/database/migrations/2022_11_01_163744_make_ignored_files_column_nullable_on_backups_table.php @@ -0,0 +1,27 @@ +text('ignored_files')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('backups', function (Blueprint $table) { + $table->text('ignored_files')->change(); + }); + } +}; diff --git a/database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php b/database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php new file mode 100644 index 000000000..0268810fd --- /dev/null +++ b/database/migrations/2022_11_01_165830_fix_language_column_type_on_users_table.php @@ -0,0 +1,36 @@ +getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::statement('ALTER TABLE users MODIFY COLUMN language VARCHAR(5)'); + break; + case 'pgsql': + DB::statement('ALTER TABLE users ALTER COLUMN language TYPE varchar(5)'); + break; + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + switch (DB::getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + DB::statement('ALTER TABLE users MODIFY COLUMN language CHAR(5)'); + break; + case 'pgsql': + DB::statement('ALTER TABLE users ALTER COLUMN language TYPE CHAR(5)'); + break; + } + } +}; diff --git a/phpunit.xml b/phpunit.xml index efc42d687..5319118fd 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,8 +24,8 @@ + - diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index ed75413ac..7a02092b1 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -249,7 +249,7 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetMissingLocation() { - $response = $this->getJson('/api/application/locations/nil'); + $response = $this->getJson('/api/application/locations/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php index 07a527e0f..e41b20eb4 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -104,7 +104,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase { $egg = Egg::query()->findOrFail(1); - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); + $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index 799fc18ac..5cbed783c 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -108,7 +108,7 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetMissingNest() { - $response = $this->getJson('/api/application/nests/nil'); + $response = $this->getJson('/api/application/nests/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index fc37b7232..1c02df7fc 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -51,7 +51,7 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetMissingUser() { - $response = $this->getJson('/api/application/users/external/nil'); + $response = $this->getJson('/api/application/users/external/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 713f6264a..0aebe3d9b 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -184,7 +184,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetMissingUser() { - $response = $this->getJson('/api/application/users/nil'); + $response = $this->getJson('/api/application/users/0'); $this->assertNotFoundJson($response); } diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php index 033b893c3..ed0cada00 100644 --- a/tests/Integration/Api/Client/ClientControllerTest.php +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -18,10 +18,10 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testOnlyLoggedInUsersServersAreReturned() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); - /** @var \Pterodactyl\Models\Server[] $servers */ + /** @var Server[] $servers */ $servers = [ $this->createServerModel(['user_id' => $users[0]->id]), $this->createServerModel(['user_id' => $users[1]->id]), @@ -45,11 +45,11 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testServersAreFilteredUsingNameAndUuidInformation() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(2)->create(); $users[0]->update(['root_admin' => true]); - /** @var \Pterodactyl\Models\Server[] $servers */ + /** @var Server[] $servers */ $servers = [ $this->createServerModel(['user_id' => $users[0]->id, 'name' => 'Julia']), $this->createServerModel(['user_id' => $users[1]->id, 'uuidShort' => '12121212', 'name' => 'Janice']), @@ -101,8 +101,8 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testServersAreFilteredUsingAllocationInformation() { - /** @var \Pterodactyl\Models\User $user */ - /** @var \Pterodactyl\Models\Server $server */ + /** @var User $user */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount(); $server2 = $this->createServerModel(['user_id' => $user->id, 'node_id' => $server->node_id]); @@ -143,7 +143,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testServersUserIsASubuserOfAreReturned() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); $servers = [ $this->createServerModel(['user_id' => $users[0]->id]), @@ -174,7 +174,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testFilterOnlyOwnerServers() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); $servers = [ $this->createServerModel(['user_id' => $users[0]->id]), @@ -203,7 +203,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testPermissionsAreReturned() { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = User::factory()->create(); $this->actingAs($user) @@ -223,7 +223,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testOnlyAdminLevelServersAreReturned() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(4)->create(); $users[0]->update(['root_admin' => true]); @@ -258,7 +258,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testAllServersAreReturnedToAdmin() { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(4)->create(); $users[0]->update(['root_admin' => true]); @@ -290,7 +290,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testNoServersAreReturnedIfAdminFilterIsPassedByRegularUser(string $type) { - /** @var \Pterodactyl\Models\User[] $users */ + /** @var User[] $users */ $users = User::factory()->times(3)->create(); $this->createServerModel(['user_id' => $users[0]->id]); @@ -309,7 +309,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase */ public function testOnlyPrimaryAllocationIsReturnedToSubuser() { - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); $server->allocation->notes = 'Test notes'; $server->allocation->save(); diff --git a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php index a8a5b88e0..3024ecf86 100644 --- a/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php +++ b/tests/Integration/Api/Client/Server/Startup/GetStartupAndVariablesTest.php @@ -42,7 +42,7 @@ class GetStartupAndVariablesTest extends ClientApiIntegrationTestCase $response->assertJsonPath('object', 'list'); $response->assertJsonCount(1, 'data'); $response->assertJsonPath('data.0.object', EggVariable::RESOURCE_NAME); - $this->assertJsonTransformedWith($response->json('data.0.attributes'), $egg->variables[1]); + $this->assertJsonTransformedWith($response->json('data.0.attributes'), $egg->variables()->orderBy('id', 'desc')->first()); } /** diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 103c97b9b..6672960d3 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -16,7 +16,7 @@ abstract class IntegrationTestCase extends TestCase use CreatesTestModels; use AssertsActivityLogged; - protected array $connectionsToTransact = ['mysql']; +// protected array $connectionsToTransact = ['pgsql']; protected $defaultHeaders = [ 'Accept' => 'application/json', diff --git a/tests/Integration/Services/Servers/ServerCreationServiceTest.php b/tests/Integration/Services/Servers/ServerCreationServiceTest.php index 92a873a18..aef7e8c1e 100644 --- a/tests/Integration/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Integration/Services/Servers/ServerCreationServiceTest.php @@ -127,9 +127,10 @@ class ServerCreationServiceTest extends IntegrationTestCase $this->assertNotNull($response->uuid); $this->assertSame($response->uuidShort, substr($response->uuid, 0, 8)); $this->assertSame($egg->id, $response->egg_id); - $this->assertCount(2, $response->variables); - $this->assertSame('123', $response->variables[0]->server_value); - $this->assertSame('server2.jar', $response->variables[1]->server_value); + $variables = $response->variables->sortBy('server_value')->values(); + $this->assertCount(2, $variables); + $this->assertSame('123', $variables->get(0)->server_value); + $this->assertSame('server2.jar', $variables->get(1)->server_value); foreach ($data as $key => $value) { if (in_array($key, ['allocation_additional', 'environment', 'start_on_completion'])) { diff --git a/tests/Integration/Services/Servers/StartupModificationServiceTest.php b/tests/Integration/Services/Servers/StartupModificationServiceTest.php index 47f4595f0..9106b4740 100644 --- a/tests/Integration/Services/Servers/StartupModificationServiceTest.php +++ b/tests/Integration/Services/Servers/StartupModificationServiceTest.php @@ -109,7 +109,7 @@ class StartupModificationServiceTest extends IntegrationTestCase $clone = $this->cloneEggAndVariables($server->egg); // This makes the BUNGEE_VERSION variable not user editable. - $clone->variables()->first()->update([ + $clone->variables()->orderBy('id')->first()->update([ 'user_editable' => false, ]); @@ -118,7 +118,7 @@ class StartupModificationServiceTest extends IntegrationTestCase ServerVariable::query()->updateOrCreate([ 'server_id' => $server->id, - 'variable_id' => $server->variables[0]->id, + 'variable_id' => $server->variables()->orderBy('id')->first()->id, ], ['variable_value' => 'EXIST']); $response = $this->getService()->handle($server, [ @@ -128,9 +128,10 @@ class StartupModificationServiceTest extends IntegrationTestCase ], ]); - $this->assertCount(2, $response->variables); - $this->assertSame('EXIST', $response->variables[0]->server_value); - $this->assertSame('test.jar', $response->variables[1]->server_value); + $variables = $response->variables->sortBy('server_value')->values(); + $this->assertCount(2, $variables); + $this->assertSame('EXIST', $variables->get(0)->server_value); + $this->assertSame('test.jar', $variables->get(1)->server_value); $response = $this->getService() ->setUserLevel(User::USER_LEVEL_ADMIN) @@ -141,9 +142,11 @@ class StartupModificationServiceTest extends IntegrationTestCase ], ]); - $this->assertCount(2, $response->variables); - $this->assertSame('1234', $response->variables[0]->server_value); - $this->assertSame('test.jar', $response->variables[1]->server_value); + $variables = $response->variables->sortBy('server_value')->values(); + + $this->assertCount(2, $variables); + $this->assertSame('1234', $variables->get(0)->server_value); + $this->assertSame('test.jar', $variables->get(1)->server_value); } /** diff --git a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php index 7f0e157fa..36cfb05d6 100644 --- a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php @@ -108,11 +108,12 @@ class VariableValidatorServiceTest extends IntegrationTestCase ]); $this->assertInstanceOf(Collection::class, $response); - $this->assertCount(2, $response); - $this->assertSame('BUNGEE_VERSION', $response->get(0)->key); - $this->assertSame('123', $response->get(0)->value); - $this->assertSame('SERVER_JARFILE', $response->get(1)->key); - $this->assertSame('server.jar', $response->get(1)->value); + $variables = $response->sortBy('key')->values(); + $this->assertCount(2, $variables); + $this->assertSame('BUNGEE_VERSION', $variables->get(0)->key); + $this->assertSame('123', $variables->get(0)->value); + $this->assertSame('SERVER_JARFILE', $variables->get(1)->key); + $this->assertSame('server.jar', $variables->get(1)->value); } public function testNullableEnvironmentVariablesCanBeUsedCorrectly() diff --git a/tests/Traits/MocksPdoConnection.php b/tests/Traits/MocksPdoConnection.php deleted file mode 100644 index a93896463..000000000 --- a/tests/Traits/MocksPdoConnection.php +++ /dev/null @@ -1,48 +0,0 @@ - $connection]); - $resolver->setDefaultConnection('mocked'); - - Model::setConnectionResolver($resolver); - - return $mock; - } - - /** - * Resets the mock state. - */ - protected function tearDownPdoMock(): void - { - if (!self::$initialResolver) { - return; - } - - Model::setConnectionResolver(self::$initialResolver); - - self::$initialResolver = null; - } -} From c475d601f30b6a591b740e0411c03514f4014753 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 27 Nov 2022 11:53:46 -0700 Subject: [PATCH 019/106] ui: remove unused codemirror 5 editor --- package.json | 2 - .../components/elements/CodemirrorEditor.tsx | 219 ------------------ yarn.lock | 24 -- 3 files changed, 245 deletions(-) delete mode 100644 resources/scripts/components/elements/CodemirrorEditor.tsx diff --git a/package.json b/package.json index ef5fb95d6..68314a370 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "boring-avatars": "1.7.0", "chart.js": "3.9.1", "classnames": "2.3.2", - "codemirror": "5.57.0", "copy-to-clipboard": "3.3.2", "date-fns": "2.29.3", "debounce": "1.2.1", @@ -89,7 +88,6 @@ "@testing-library/dom": "8.19.0", "@testing-library/react": "13.4.0", "@testing-library/user-event": "14.4.3", - "@types/codemirror": "0.0.109", "@types/debounce": "1.2.1", "@types/events": "3.0.0", "@types/node": "18.11.9", diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx deleted file mode 100644 index 48948ad74..000000000 --- a/resources/scripts/components/elements/CodemirrorEditor.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import CodeMirror from 'codemirror'; -import type { CSSProperties } from 'react'; -import { useCallback, useEffect, useState } from 'react'; -import styled from 'styled-components'; -import tw from 'twin.macro'; - -import modes from '@/modes'; - -require('codemirror/lib/codemirror.css'); -require('codemirror/theme/ayu-mirage.css'); -require('codemirror/addon/edit/closebrackets'); -require('codemirror/addon/edit/closetag'); -require('codemirror/addon/edit/matchbrackets'); -require('codemirror/addon/edit/matchtags'); -require('codemirror/addon/edit/trailingspace'); -require('codemirror/addon/fold/foldcode'); -require('codemirror/addon/fold/foldgutter.css'); -require('codemirror/addon/fold/foldgutter'); -require('codemirror/addon/fold/brace-fold'); -require('codemirror/addon/fold/comment-fold'); -require('codemirror/addon/fold/indent-fold'); -require('codemirror/addon/fold/markdown-fold'); -require('codemirror/addon/fold/xml-fold'); -require('codemirror/addon/hint/css-hint'); -require('codemirror/addon/hint/html-hint'); -require('codemirror/addon/hint/javascript-hint'); -require('codemirror/addon/hint/show-hint.css'); -require('codemirror/addon/hint/show-hint'); -require('codemirror/addon/hint/sql-hint'); -require('codemirror/addon/hint/xml-hint'); -require('codemirror/addon/mode/simple'); -require('codemirror/addon/dialog/dialog.css'); -require('codemirror/addon/dialog/dialog'); -require('codemirror/addon/scroll/annotatescrollbar'); -require('codemirror/addon/scroll/scrollpastend'); -require('codemirror/addon/scroll/simplescrollbars.css'); -require('codemirror/addon/scroll/simplescrollbars'); -require('codemirror/addon/search/jump-to-line'); -require('codemirror/addon/search/match-highlighter'); -require('codemirror/addon/search/matchesonscrollbar.css'); -require('codemirror/addon/search/matchesonscrollbar'); -require('codemirror/addon/search/search'); -require('codemirror/addon/search/searchcursor'); - -require('codemirror/mode/brainfuck/brainfuck'); -require('codemirror/mode/clike/clike'); -require('codemirror/mode/css/css'); -require('codemirror/mode/dart/dart'); -require('codemirror/mode/diff/diff'); -require('codemirror/mode/dockerfile/dockerfile'); -require('codemirror/mode/erlang/erlang'); -require('codemirror/mode/gfm/gfm'); -require('codemirror/mode/go/go'); -require('codemirror/mode/handlebars/handlebars'); -require('codemirror/mode/htmlembedded/htmlembedded'); -require('codemirror/mode/htmlmixed/htmlmixed'); -require('codemirror/mode/http/http'); -require('codemirror/mode/javascript/javascript'); -require('codemirror/mode/jsx/jsx'); -require('codemirror/mode/julia/julia'); -require('codemirror/mode/lua/lua'); -require('codemirror/mode/markdown/markdown'); -require('codemirror/mode/nginx/nginx'); -require('codemirror/mode/perl/perl'); -require('codemirror/mode/php/php'); -require('codemirror/mode/properties/properties'); -require('codemirror/mode/protobuf/protobuf'); -require('codemirror/mode/pug/pug'); -require('codemirror/mode/python/python'); -require('codemirror/mode/rpm/rpm'); -require('codemirror/mode/ruby/ruby'); -require('codemirror/mode/rust/rust'); -require('codemirror/mode/sass/sass'); -require('codemirror/mode/shell/shell'); -require('codemirror/mode/smarty/smarty'); -require('codemirror/mode/sql/sql'); -require('codemirror/mode/swift/swift'); -require('codemirror/mode/toml/toml'); -require('codemirror/mode/twig/twig'); -require('codemirror/mode/vue/vue'); -require('codemirror/mode/xml/xml'); -require('codemirror/mode/yaml/yaml'); - -const EditorContainer = styled.div` - min-height: 16rem; - height: calc(100vh - 20rem); - ${tw`relative`}; - - > div { - ${tw`rounded h-full`}; - } - - .CodeMirror { - font-size: 12px; - line-height: 1.375rem; - } - - .CodeMirror-linenumber { - padding: 1px 12px 0 12px !important; - } - - .CodeMirror-foldmarker { - color: #cbccc6; - text-shadow: none; - margin-left: 0.25rem; - margin-right: 0.25rem; - } -`; - -export interface Props { - style?: CSSProperties; - initialContent?: string; - mode: string; - filename?: string; - onModeChanged: (mode: string) => void; - fetchContent: (callback: () => Promise) => void; - onContentSaved: () => void; -} - -const findModeByFilename = (filename: string) => { - for (let i = 0; i < modes.length; i++) { - const info = modes[i]; - - if (info?.file !== undefined && info.file.test(filename)) { - return info; - } - } - - const dot = filename.lastIndexOf('.'); - const ext = dot > -1 && filename.substring(dot + 1, filename.length); - - if (ext) { - for (let i = 0; i < modes.length; i++) { - const info = modes[i]; - if (info?.ext !== undefined) { - for (let j = 0; j < info.ext.length; j++) { - if (info.ext[j] === ext) { - return info; - } - } - } - } - } - - return undefined; -}; - -export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { - const [editor, setEditor] = useState(); - - const ref = useCallback<(_?: unknown) => void>(node => { - if (node === undefined) { - return; - } - - const e = CodeMirror.fromTextArea(node as HTMLTextAreaElement, { - mode: 'text/plain', - theme: 'ayu-mirage', - indentUnit: 4, - smartIndent: true, - tabSize: 4, - indentWithTabs: false, - lineWrapping: true, - lineNumbers: true, - fixedGutter: true, - scrollbarStyle: 'overlay', - coverGutterNextToScrollbar: false, - readOnly: false, - showCursorWhenSelecting: false, - autofocus: false, - spellcheck: true, - autocorrect: false, - autocapitalize: false, - lint: false, - // @ts-expect-error this property is actually used, the d.ts file for CodeMirror is incorrect. - autoCloseBrackets: true, - matchBrackets: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - }); - - setEditor(e); - }, []); - - useEffect(() => { - if (filename === undefined) { - return; - } - - onModeChanged(findModeByFilename(filename)?.mime || 'text/plain'); - }, [filename]); - - useEffect(() => { - editor && editor.setOption('mode', mode); - }, [editor, mode]); - - useEffect(() => { - editor && editor.setValue(initialContent || ''); - }, [editor, initialContent]); - - useEffect(() => { - if (!editor) { - fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); - return; - } - - editor.addKeyMap({ - 'Ctrl-S': () => onContentSaved(), - 'Cmd-S': () => onContentSaved(), - }); - - fetchContent(() => Promise.resolve(editor.getValue())); - }, [editor, fetchContent, onContentSaved]); - - return ( - - "; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 only -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: jQuery.isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -function manipulationTarget( elem, content ) { - if ( jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return elem.getElementsByTagName( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rmargin = ( /^margin/ ); - -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - div.style.cssText = - "box-sizing:border-box;" + - "position:relative;display:block;" + - "margin:auto;border:1px;padding:1px;" + - "top:1%;width:50%"; - div.innerHTML = ""; - documentElement.appendChild( container ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = divStyle.marginLeft === "2px"; - boxSizingReliableVal = divStyle.width === "4px"; - - // Support: Android 4.0 - 4.3 only - // Some styles come back with percentage values, even though they shouldn't - div.style.marginRight = "50%"; - pixelMarginRightVal = divStyle.marginRight === "4px"; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + - "padding:0;margin-top:1px;position:absolute"; - container.appendChild( div ); - - jQuery.extend( support, { - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelMarginRight: function() { - computeStyleTests(); - return pixelMarginRightVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - style = elem.style; - - computed = computed || getStyles( elem ); - - // Support: IE <=9 only - // getPropertyValue is only needed for .css('filter') (#12537) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property -function vendorPropName( name ) { - - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -function setPositiveNumber( elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i, - val = 0; - - // If we already have the right measurement, avoid augmentation - if ( extra === ( isBorderBox ? "border" : "content" ) ) { - i = 4; - - // Otherwise initialize for horizontal or vertical properties - } else { - i = name === "width" ? 1 : 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // At this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - - // At this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // At this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with offset property, which is equivalent to the border-box value - var val, - valueIsBorderBox = true, - styles = getStyles( elem ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - if ( elem.getClientRects().length ) { - val = elem.getBoundingClientRect()[ name ]; - } - - // Some non-html elements return undefined for offsetWidth, so check for null/undefined - // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 - // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 - if ( val <= 0 || val == null ) { - - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, styles ); - if ( val < 0 || val == null ) { - val = elem.style[ name ]; - } - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test( val ) ) { - return val; - } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && - ( support.boxSizingReliable() || val === elem.style[ name ] ); - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - } - - // Use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - "float": "cssFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - style = elem.style; - - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - style[ name ] = value; - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = jQuery.camelCase( name ); - - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); - } ) : - getWidthOrHeight( elem, name, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = extra && getStyles( elem ), - subtract = extra && augmentWidthOrHeight( - elem, - name, - extra, - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles - ); - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ name ] = value; - value = jQuery.css( elem, name ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( !rmargin.test( prefix ) ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( jQuery.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, timerId, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function raf() { - if ( timerId ) { - window.requestAnimationFrame( raf ); - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = jQuery.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 13 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = jQuery.camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( jQuery.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - if ( percent < 1 && length ) { - return remaining; - } else { - deferred.resolveWith( elem, [ animation ] ); - return false; - } - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( jQuery.isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - jQuery.proxy( result.stop, result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( jQuery.isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - // attach callbacks from options - return animation.progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing - }; - - // Go to the end state if fx are off or if document is hidden - if ( jQuery.fx.off || document.hidden ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = jQuery.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Checks the timer has not already been removed - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - if ( timer() ) { - jQuery.fx.start(); - } else { - jQuery.timers.pop(); - } -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( !timerId ) { - timerId = window.requestAnimationFrame ? - window.requestAnimationFrame( raf ) : - window.setInterval( jQuery.fx.tick, jQuery.fx.interval ); - } -}; - -jQuery.fx.stop = function() { - if ( window.cancelAnimationFrame ) { - window.cancelAnimationFrame( timerId ); - } else { - window.clearInterval( timerId ); - } - - timerId = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - jQuery.nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value; - - if ( typeof stateVal === "boolean" && type === "string" ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( jQuery.isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( type === "string" ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = value.match( rnothtmlwhite ) || []; - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, isFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - - - - -support.focusin = "onfocusin" in window; - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = jQuery.now(); - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( jQuery.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && jQuery.type( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = jQuery.isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( jQuery.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( jQuery.isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 13 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available, append data to url - if ( s.data ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - - -jQuery._evalUrl = function( url ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - "throws": true - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( jQuery.isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " -@endsection diff --git a/resources/views/admin/api/new.blade.php b/resources/views/admin/api/new.blade.php deleted file mode 100644 index b5876ee8c..000000000 --- a/resources/views/admin/api/new.blade.php +++ /dev/null @@ -1,70 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Application API -@endsection - -@section('content-header') -

    Application APICreate a new application API key.

    - -@endsection - -@section('content') -
    - -
    -
    -
    -

    Select Permissions

    -
    -
    - - @foreach($resources as $resource) - - - - - - - @endforeach -
    {{ str_replace('_', ' ', title_case($resource)) }} - - - - - - - - -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -

    Once you have assigned permissions and created this set of credentials you will be unable to come back and edit it. If you need to make changes down the road you will need to create a new set of credentials.

    -
    - -
    -
    - -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/databases/index.blade.php b/resources/views/admin/databases/index.blade.php deleted file mode 100644 index e4c69c513..000000000 --- a/resources/views/admin/databases/index.blade.php +++ /dev/null @@ -1,130 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Database Hosts -@endsection - -@section('content-header') -

    Database HostsDatabase hosts that servers can have databases created on.

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Host List

    -
    - -
    -
    -
    - - - - - - - - - - - - @foreach ($hosts as $host) - - - - - - - - - - @endforeach - -
    IDNameHostPortUsernameDatabasesNode
    {{ $host->id }}{{ $host->name }}{{ $host->host }}{{ $host->port }}{{ $host->username }}{{ $host->databases_count }} - @if(! is_null($host->node)) - {{ $host->node->name }} - @else - None - @endif -
    -
    -
    -
    -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/databases/view.blade.php b/resources/views/admin/databases/view.blade.php deleted file mode 100644 index e105751a0..000000000 --- a/resources/views/admin/databases/view.blade.php +++ /dev/null @@ -1,135 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Database Hosts → View → {{ $host->name }} -@endsection - -@section('content-header') -

    {{ $host->name }}Viewing associated databases and details for this database host.

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    Host Details

    -
    -
    -
    - - -
    -
    - - -

    The IP address or FQDN that should be used when attempting to connect to this MySQL host from the panel to add new databases.

    -
    -
    - - -

    The port that MySQL is running on for this host.

    -
    -
    - - -

    This setting does nothing other than default to this database host when adding a database to a server on the selected node.

    -
    -
    -
    -
    -
    -
    -
    -

    User Details

    -
    -
    -
    - - -

    The username of an account that has enough permissions to create new users and databases on the system.

    -
    -
    - - -

    The password to the account defined. Leave blank to continue using the assigned password.

    -
    -
    -

    The account defined for this database host must have the WITH GRANT OPTION permission. If the defined account does not have this permission requests to create databases will fail. Do not use the same account details for MySQL that you have defined for this panel.

    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -

    Databases

    -
    -
    - - - - - - - - - - @foreach($databases as $database) - - - - - - @if($database->max_connections != null) - - @else - - @endif - - - @endforeach -
    ServerDatabase NameUsernameConnections FromMax Connections
    {{ $database->getRelation('server')->name }}{{ $database->database }}{{ $database->username }}{{ $database->remote }}{{ $database->max_connections }}Unlimited - - - -
    -
    - @if($databases->hasPages()) - - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/eggs/new.blade.php b/resources/views/admin/eggs/new.blade.php deleted file mode 100644 index 9a0a07849..000000000 --- a/resources/views/admin/eggs/new.blade.php +++ /dev/null @@ -1,165 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → New Egg -@endsection - -@section('content-header') -

    New EggCreate a new Egg to assign to servers.

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    Configuration

    -
    -
    -
    -
    -
    - -
    - -

    Think of a Nest as a category. You can put multiple Eggs in a nest, but consider putting only Eggs that are related to each other in each Nest.

    -
    -
    -
    - - -

    A simple, human-readable name to use as an identifier for this Egg. This is what users will see as their game server type.

    -
    -
    - - -

    A description of this Egg.

    -
    -
    -
    - - -

    - Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP. - Required for certain games to work properly when the Node has multiple public IP addresses. -
    - - Enabling this option will disable internal networking for any servers using this egg, - causing them to be unable to internally access other servers on the same node. - -

    -
    -
    -
    -
    -
    - - -

    The docker images available to servers using this egg. Enter one per line. Users will be able to select from this list of images if more than one value is provided.

    -
    -
    - - -

    The default startup command that should be used for new servers created with this Egg. You can change this per-server as needed.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Process Management

    -
    -
    -
    -
    -
    -

    All fields are required unless you select a separate option from the 'Copy Settings From' dropdown, in which case fields may be left blank to use the values from that option.

    -
    -
    -
    -
    - - -

    If you would like to default to settings from another Egg select it from the dropdown above.

    -
    -
    - - -

    The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.

    -
    -
    - - -

    This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.

    -
    -
    -
    -
    - - -

    This should be a JSON representation of configuration files to modify and what parts should be changed.

    -
    -
    - - -

    This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.

    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - -@endsection diff --git a/resources/views/admin/eggs/scripts.blade.php b/resources/views/admin/eggs/scripts.blade.php deleted file mode 100644 index 5bbc9ee3a..000000000 --- a/resources/views/admin/eggs/scripts.blade.php +++ /dev/null @@ -1,118 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → Egg: {{ $egg->name }} → Install Script -@endsection - -@section('content-header') -

    {{ $egg->name }}Manage the install script for this Egg.

    - -@endsection - -@section('content') - -
    -
    -
    -
    -
    -

    Install Script

    -
    - @if(! is_null($egg->copyFrom)) -
    -
    - This service option is copying installation scripts and container options from {{ $egg->copyFrom->name }}. Any changes you make to this script will not apply unless you select "None" from the dropdown box below. -
    -
    - @endif -
    -
    {{ $egg->script_install }}
    -
    -
    -
    -
    - - -

    If selected, script above will be ignored and script from selected option will be used in place.

    -
    -
    - - -

    Docker container to use when running this script for the server.

    -
    -
    - - -

    The entrypoint command to use for this script.

    -
    -
    -
    -
    - The following service options rely on this script: - @if(count($relyOnScript) > 0) - @foreach($relyOnScript as $rely) - - {{ $rely->name }}@if(!$loop->last), @endif - - @endforeach - @else - none - @endif -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/ace/ace.js') !!} - {!! Theme::js('vendor/ace/ext-modelist.js') !!} - - -@endsection diff --git a/resources/views/admin/eggs/variables.blade.php b/resources/views/admin/eggs/variables.blade.php deleted file mode 100644 index fbc14781e..000000000 --- a/resources/views/admin/eggs/variables.blade.php +++ /dev/null @@ -1,156 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Egg → {{ $egg->name }} → Variables -@endsection - -@section('content-header') -

    {{ $egg->name }}Managing variables for this Egg.

    - -@endsection - -@section('content') - -
    -
    - -
    -
    -
    - @foreach($egg->variables as $variable) -
    -
    -
    -

    {{ $variable->name }}

    -
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    -
    - - -
    -
    -

    This variable can be accessed in the startup command by using {{ $variable->env_variable }}.

    -
    -
    -
    - - -
    -
    - - -

    These rules are defined using standard Laravel Framework validation rules.

    -
    -
    - -
    -
    -
    - @endforeach -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/eggs/view.blade.php b/resources/views/admin/eggs/view.blade.php deleted file mode 100644 index b999d2a91..000000000 --- a/resources/views/admin/eggs/view.blade.php +++ /dev/null @@ -1,206 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → Egg: {{ $egg->name }} -@endsection - -@section('content-header') -

    {{ $egg->name }}{{ str_limit($egg->description, 50) }}

    - -@endsection - -@section('content') - -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -

    If you would like to replace settings for this Egg by uploading a new JSON file, simply select it here and press "Update Egg". This will not change any existing startup strings or Docker images for existing servers.

    -
    -
    -
    -
    - {!! csrf_field() !!} - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Configuration

    -
    -
    -
    -
    -
    - - -

    A simple, human-readable name to use as an identifier for this Egg.

    -
    -
    - - -

    This is the globally unique identifier for this Egg which the Daemon uses as an identifier.

    -
    -
    - - -

    The author of this version of the Egg. Uploading a new Egg configuration from a different author will change this.

    -
    -
    - - -

    - The docker images available to servers using this egg. Enter one per line. Users - will be able to select from this list of images if more than one value is provided. - Optionally, a display name may be provided by prefixing the image with the name - followed by a pipe character, and then the image URL. Example: Display Name|ghcr.io/my/egg -

    -
    -
    -
    - force_outgoing_ip) checked @endif /> - -

    - Forces all outgoing network traffic to have its Source IP NATed to the IP of the server's primary allocation IP. - Required for certain games to work properly when the Node has multiple public IP addresses. -
    - - Enabling this option will disable internal networking for any servers using this egg, - causing them to be unable to internally access other servers on the same node. - -

    -
    -
    - -
    -
    -
    - - -

    A description of this Egg that will be displayed throughout the Panel as needed.

    -
    -
    - - -

    The default startup command that should be used for new servers using this Egg.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Process Management

    -
    -
    -
    -
    -
    -

    The following configuration options should not be edited unless you understand how this system works. If wrongly modified it is possible for the daemon to break.

    -

    All fields are required unless you select a separate option from the 'Copy Settings From' dropdown, in which case fields may be left blank to use the values from that Egg.

    -
    -
    -
    -
    - - -

    If you would like to default to settings from another Egg select it from the menu above.

    -
    -
    - - -

    The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.

    -
    -
    - - -

    This should be a JSON representation of where log files are stored, and whether or not the daemon should be creating custom logs.

    -
    -
    -
    -
    - - -

    This should be a JSON representation of configuration files to modify and what parts should be changed.

    -
    -
    - - -

    This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.

    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php deleted file mode 100644 index ed26a92ab..000000000 --- a/resources/views/admin/index.blade.php +++ /dev/null @@ -1,53 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Administration -@endsection - -@section('content-header') -

    Administrative OverviewA quick glance at your system.

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    System Information

    -
    -
    - @if ($version->isLatestPanel()) - You are running Pterodactyl Panel version {{ config('app.version') }}. Your panel is up-to-date! - @else - Your panel is not up-to-date! The latest version is {{ $version->getPanel() }} and you are currently running version {{ config('app.version') }}. - @endif -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/locations/index.blade.php b/resources/views/admin/locations/index.blade.php deleted file mode 100644 index c54ffe0fa..000000000 --- a/resources/views/admin/locations/index.blade.php +++ /dev/null @@ -1,81 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Locations -@endsection - -@section('content-header') -

    LocationsAll locations that nodes can be assigned to for easier categorization.

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Location List

    -
    - -
    -
    -
    - - - - - - - - - - @foreach ($locations as $location) - - - - - - - - @endforeach - -
    IDShort CodeDescriptionNodesServers
    {{ $location->id }}{{ $location->short }}{{ $location->long }}{{ $location->nodes_count }}{{ $location->servers_count }}
    -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/locations/view.blade.php b/resources/views/admin/locations/view.blade.php deleted file mode 100644 index f34dcf3f5..000000000 --- a/resources/views/admin/locations/view.blade.php +++ /dev/null @@ -1,69 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Locations → View → {{ $location->short }} -@endsection - -@section('content-header') -

    {{ $location->short }}{{ str_limit($location->long, 75) }}

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Location Details

    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - -
    -
    -
    -
    -
    -
    -

    Nodes

    -
    -
    - - - - - - - - @foreach($location->nodes as $node) - - - - - - - @endforeach -
    IDNameFQDNServers
    {{ $node->id }}{{ $node->name }}{{ $node->fqdn }}{{ $node->servers->count() }}
    -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/mounts/index.blade.php b/resources/views/admin/mounts/index.blade.php deleted file mode 100644 index a3b989243..000000000 --- a/resources/views/admin/mounts/index.blade.php +++ /dev/null @@ -1,144 +0,0 @@ - -@extends('layouts.admin') - -@section('title') - Mounts -@endsection - -@section('content-header') -

    MountsConfigure and manage additional mount points for servers.

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Mount List

    - -
    - -
    -
    - -
    - - - - - - - - - - - - - @foreach ($mounts as $mount) - - - - - - - - - - @endforeach - -
    IDNameSourceTargetEggsNodesServers
    {{ $mount->id }}{{ $mount->name }}{{ $mount->source }}{{ $mount->target }}{{ $mount->eggs_count }}{{ $mount->nodes_count }}{{ $mount->servers_count }}
    -
    -
    -
    -
    - - -@endsection diff --git a/resources/views/admin/mounts/view.blade.php b/resources/views/admin/mounts/view.blade.php deleted file mode 100644 index 96a156d24..000000000 --- a/resources/views/admin/mounts/view.blade.php +++ /dev/null @@ -1,314 +0,0 @@ - -@extends('layouts.admin') - -@section('title') - Mounts → View → {{ $mount->id }} -@endsection - -@section('content-header') -

    {{ $mount->name }}{{ str_limit($mount->description, 75) }}

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Mount Details

    -
    - -
    -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    -
    - - -
    - -
    - - -
    -
    - -
    -
    - - -
    -
    - read_only) checked @endif> - -
    - -
    - read_only) checked @endif> - -
    -
    -
    - -
    - - -
    -
    - user_mountable) checked @endif> - -
    - -
    - user_mountable) checked @endif> - -
    -
    -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -

    Eggs

    - -
    - -
    -
    - -
    - - - - - - - - @foreach ($mount->eggs as $egg) - - - - - - @endforeach -
    IDName
    {{ $egg->id }}{{ $egg->name }} - -
    -
    -
    - -
    -
    -

    Nodes

    - -
    - -
    -
    - -
    - - - - - - - - - @foreach ($mount->nodes as $node) - - - - - - - @endforeach -
    IDNameFQDN
    {{ $node->id }}{{ $node->name }}{{ $node->fqdn }} - -
    -
    -
    -
    -
    - - - - -@endsection - -@section('footer-scripts') - @parent - - -@endsection diff --git a/resources/views/admin/nests/index.blade.php b/resources/views/admin/nests/index.blade.php deleted file mode 100644 index 4bb6ae784..000000000 --- a/resources/views/admin/nests/index.blade.php +++ /dev/null @@ -1,102 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests -@endsection - -@section('content-header') -

    NestsAll nests currently available on this system.

    - -@endsection - -@section('content') -
    -
    -
    - Eggs are a powerful feature of Pterodactyl Panel that allow for extreme flexibility and configuration. Please note that while powerful, modifying an egg wrongly can very easily brick your servers and cause more problems. Please avoid editing our default eggs — those provided by support@pterodactyl.io — unless you are absolutely sure of what you are doing. -
    -
    -
    -
    -
    -
    -
    -

    Configured Nests

    - -
    -
    - - - - - - - - - @foreach($nests as $nest) - - - - - - - - @endforeach -
    IDNameDescriptionEggsServers
    {{ $nest->id }}{{ $nest->name }}{{ $nest->description }}{{ $nest->eggs_count }}{{ $nest->servers_count }}
    -
    -
    -
    -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nests/new.blade.php b/resources/views/admin/nests/new.blade.php deleted file mode 100644 index 9a1cb0edc..000000000 --- a/resources/views/admin/nests/new.blade.php +++ /dev/null @@ -1,47 +0,0 @@ -@extends('layouts.admin') - -@section('title') - New Nest -@endsection - -@section('content-header') -

    New NestConfigure a new nest to deploy to all nodes.

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    New Nest

    -
    -
    -
    - -
    - -

    This should be a descriptive category name that encompasses all of the eggs within the nest.

    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/nests/view.blade.php b/resources/views/admin/nests/view.blade.php deleted file mode 100644 index 9a6730746..000000000 --- a/resources/views/admin/nests/view.blade.php +++ /dev/null @@ -1,117 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nests → {{ $nest->name }} -@endsection - -@section('content-header') -

    {{ $nest->name }}{{ str_limit($nest->description, 50) }}

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    - -
    - -

    This should be a descriptive category name that encompasses all of the options within the service.

    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -

    A unique ID used for identification of this nest internally and through the API.

    -
    -
    -
    - -
    - -

    The author of this service option. Please direct questions and issues to them unless this is an official option authored by support@pterodactyl.io.

    -
    -
    -
    - -
    - -

    A UUID that all servers using this option are assigned for identification purposes.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Nest Eggs

    -
    -
    - - - - - - - - - @foreach($nest->eggs as $egg) - - - - - - - - @endforeach -
    IDNameDescriptionServers
    {{ $egg->id }}{{ $egg->name }}{{ $egg->description }}{{ $egg->servers->count() }} - -
    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php deleted file mode 100644 index 3b3a3fa48..000000000 --- a/resources/views/admin/nodes/index.blade.php +++ /dev/null @@ -1,107 +0,0 @@ -@extends('layouts.admin') - -@section('title') - List Nodes -@endsection - -@section('scripts') - @parent - {!! Theme::css('vendor/fontawesome/animation.min.css') !!} -@endsection - -@section('content-header') -

    NodesAll nodes available on the system.

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Node List

    -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - @foreach ($nodes as $node) - - - - - - - - - - - @endforeach - -
    NameLocationMemoryDiskServersSSLPublic
    {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }}{{ $node->location->short }}{{ $node->memory }} MiB{{ $node->disk }} MiB{{ $node->servers_count }}
    -
    - @if($nodes->hasPages()) - - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php deleted file mode 100644 index b10d08a76..000000000 --- a/resources/views/admin/nodes/new.blade.php +++ /dev/null @@ -1,175 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Nodes → New -@endsection - -@section('content-header') -

    New NodeCreate a new local or remote node for servers to be installed to.

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    Basic Details

    -
    -
    -
    - - -

    Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

    -
    -
    - - -
    -
    - - -
    -
    - -
    -
    - - - -
    -
    - - -
    -
    -

    By setting a node to private you will be denying the ability to auto-deploy to this node. -

    -
    - - -

    Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may be used only if you are not using SSL for this node.

    -
    -
    - -
    -
    - - -
    -
    - isSecure()) disabled @endif> - -
    -
    - @if(request()->isSecure()) -

    Your Panel is currently configured to use a secure connection. In order for browsers to connect to your node it must use a SSL connection.

    - @else -

    In most cases you should select to use a SSL connection. If using an IP Address or you do not wish to use SSL at all, select a HTTP connection.

    - @endif -
    -
    - -
    -
    - - -
    -
    - - -
    -
    -

    If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.

    -
    -
    -
    -
    -
    -
    -
    -

    Configuration

    -
    -
    -
    -
    - - -

    Enter the directory where server files should be stored. If you use OVH you should check your partition scheme. You may need to use /home/daemon-data to have enough space.

    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of memory available for new servers. If you would like to allow overallocation of memory enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

    -
    -
    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of disk space available for new servers. If you would like to allow overallocation of disk space enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

    -
    -
    -
    -
    - - -
    -
    - - -
    -
    -

    The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physical server's SSH process. If you will be running the daemon behind CloudFlare® you should set the daemon port to 8443 to allow websocket proxying over SSL.

    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/allocation.blade.php b/resources/views/admin/nodes/view/allocation.blade.php deleted file mode 100644 index 396428b61..000000000 --- a/resources/views/admin/nodes/view/allocation.blade.php +++ /dev/null @@ -1,356 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Allocations -@endsection - -@section('content-header') -

    {{ $node->name }}Control allocations available for servers on this node.

    - -@endsection - -@section('content') -
    - -
    -
    -
    -
    -
    -

    Existing Allocations

    -
    -
    - - - - - - - - - - @foreach($node->allocations as $allocation) - - - - - - - - - @endforeach -
    - - IP Address IP AliasPortAssigned To - -
    - @if(is_null($allocation->server_id)) - - @else - - @endif - {{ $allocation->ip }} - - - {{ $allocation->port }} - @if(! is_null($allocation->server)) - {{ $allocation->server->name }} - @endif - - @if(is_null($allocation->server_id)) - - @endif -
    -
    - @if($node->allocations->hasPages()) - - @endif -
    -
    -
    -
    -
    -
    -

    Assign New Allocations

    -
    -
    -
    - -
    - -

    Enter an IP address to assign ports to here.

    -
    -
    -
    - -
    - -

    If you would like to assign a default alias to these allocations enter it here.

    -
    -
    -
    - -
    - -

    Enter individual ports or port ranges here separated by commas or spaces.

    -
    -
    -
    - -
    -
    -
    -
    - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/configuration.blade.php b/resources/views/admin/nodes/view/configuration.blade.php deleted file mode 100644 index 1bbf15f33..000000000 --- a/resources/views/admin/nodes/view/configuration.blade.php +++ /dev/null @@ -1,88 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Configuration -@endsection - -@section('content-header') -

    {{ $node->name }}Your daemon configuration file.

    - -@endsection - -@section('content') -
    - -
    -
    -
    -
    -
    -

    Configuration File

    -
    -
    -
    {{ $node->getYamlConfiguration() }}
    -
    - -
    -
    -
    -
    -
    -

    Auto-Deploy

    -
    -
    -

    - Use the button below to generate a custom deployment command that can be used to configure - wings on the target server with a single command. -

    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php deleted file mode 100644 index 2d0bb3287..000000000 --- a/resources/views/admin/nodes/view/index.blade.php +++ /dev/null @@ -1,164 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }} -@endsection - -@section('content-header') -

    {{ $node->name }}A quick overview of your node.

    - -@endsection - -@section('content') -
    - -
    -
    -
    -
    -
    -
    -
    -

    Information

    -
    -
    - - - - - - - - - - - - - -
    Daemon Version (Latest: {{ $version->getDaemon() }})
    System Information
    Total CPU Threads
    -
    -
    -
    - @if ($node->description) -
    -
    -
    - Description -
    -
    -
    {{ $node->description }}
    -
    -
    -
    - @endif -
    -
    -
    -

    Delete Node

    -
    -
    -

    Deleting a node is a irreversible action and will immediately remove this node from the panel. There must be no servers associated with this node in order to continue.

    -
    - -
    -
    -
    -
    -
    -
    -
    -

    At-a-Glance

    -
    -
    -
    - @if($node->maintenance_mode) -
    -
    - -
    - This node is under - Maintenance -
    -
    -
    - @endif -
    -
    - -
    - Disk Space Allocated - {{ $stats['disk']['value'] }} / {{ $stats['disk']['max'] }} MiB -
    -
    -
    -
    -
    -
    -
    -
    - -
    - Memory Allocated - {{ $stats['memory']['value'] }} / {{ $stats['memory']['max'] }} MiB -
    -
    -
    -
    -
    -
    -
    -
    - -
    - Total Servers - {{ $node->servers_count }} -
    -
    -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/nodes/view/servers.blade.php b/resources/views/admin/nodes/view/servers.blade.php deleted file mode 100644 index b8b9ee514..000000000 --- a/resources/views/admin/nodes/view/servers.blade.php +++ /dev/null @@ -1,63 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Servers -@endsection - -@section('content-header') -

    {{ $node->name }}All servers currently assigned to this node.

    - -@endsection - -@section('content') -
    - -
    -
    -
    -
    -
    -

    Process Manager

    -
    -
    - - - - - - - - @foreach($servers as $server) - - - - - - - @endforeach -
    IDServer NameOwnerService
    {{ $server->uuidShort }}{{ $server->name }}{{ $server->user->username }}{{ $server->nest->name }} ({{ $server->egg->name }})
    - @if($servers->hasPages()) - - @endif -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/nodes/view/settings.blade.php b/resources/views/admin/nodes/view/settings.blade.php deleted file mode 100644 index bfc9d8caf..000000000 --- a/resources/views/admin/nodes/view/settings.blade.php +++ /dev/null @@ -1,240 +0,0 @@ -@extends('layouts.admin') - -@section('title') - {{ $node->name }}: Settings -@endsection - -@section('content-header') -

    {{ $node->name }}Configure your node settings.

    - -@endsection - -@section('content') -
    - -
    -
    -
    -
    -
    -
    -

    Settings

    -
    -
    -
    - -
    - -

    Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - public)) ? 'checked' : '' }} id="public_1" checked>
    - public)) ? '' : 'checked' }} id="public_0"> -
    -
    -
    - -
    - -
    -

    Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node. - Why? -

    -
    -
    - -
    -
    - scheme) === 'https') ? 'checked' : '' }}> - -
    -
    - scheme) !== 'https') ? 'checked' : '' }}> - -
    -
    -

    In most cases you should select to use a SSL connection. If using an IP Address or you do not wish to use SSL at all, select a HTTP connection.

    -
    -
    - -
    -
    - behind_proxy) == false) ? 'checked' : '' }}> - -
    -
    - behind_proxy) == true) ? 'checked' : '' }}> - -
    -
    -

    If you are running the daemon behind a proxy such as Cloudflare, select this to have the daemon skip looking for certificates on boot.

    -
    -
    - -
    -
    - maintenance_mode) == false) ? 'checked' : '' }}> - -
    -
    - maintenance_mode) == true) ? 'checked' : '' }}> - -
    -
    -

    If the node is marked as 'Under Maintenance' users won't be able to access servers that are on this node.

    -
    -
    -
    -
    -
    -
    -
    -

    Allocation Limits

    -
    -
    -
    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of memory available on this node for allocation to servers. You may also provide a percentage that can allow allocation of more than the defined memory.

    -
    -
    -
    -
    - -
    - - MiB -
    -
    -
    - -
    - - % -
    -
    -
    -

    Enter the total amount of disk space available on this node for server allocation. You may also provide a percentage that will determine the amount of disk space over the set limit to allow.

    -
    -
    -
    -
    -
    -
    -
    -

    General Configuration

    -
    -
    -
    - -
    - - MiB -
    -

    Enter the maximum size of files that can be uploaded through the web-based file manager.

    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -

    The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physical server's SSH process.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Save Settings

    -
    -
    -
    -
    - -
    -

    Resetting the daemon master key will void any request coming from the old key. This key is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this key regularly for security.

    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php deleted file mode 100644 index 4d5ebcdcd..000000000 --- a/resources/views/admin/servers/index.blade.php +++ /dev/null @@ -1,89 +0,0 @@ -@extends('layouts.admin') - -@section('title') - List Servers -@endsection - -@section('content-header') -

    ServersAll servers available on the system.

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Server List

    -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - @foreach ($servers as $server) - - - - - - - - - - @endforeach - -
    Server NameUUIDOwnerNodeConnection
    {{ $server->name }}{{ $server->uuid }}{{ $server->user->username }}{{ $server->node->name }} - {{ $server->allocation->alias }}:{{ $server->allocation->port }} - - @if($server->isSuspended()) - Suspended - @elseif(! $server->isInstalled()) - Installing - @else - Active - @endif - - -
    -
    - @if($servers->hasPages()) - - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php deleted file mode 100644 index 4198634f1..000000000 --- a/resources/views/admin/servers/new.blade.php +++ /dev/null @@ -1,395 +0,0 @@ -@extends('layouts.admin') - -@section('title') - New Server -@endsection - -@section('content-header') -

    Create ServerAdd a new server to the panel.

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    Core Details

    -
    - -
    -
    -
    - - -

    Character limits: a-z A-Z 0-9 _ - . and [Space].

    -
    - -
    - - -

    Email address of the Server Owner.

    -
    -
    - -
    -
    - - -

    A brief description of this server.

    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -

    Allocation Management

    -
    - -
    -
    - - - -

    The node which this server will be deployed to.

    -
    - -
    - - -

    The main allocation that will be assigned to this server.

    -
    - -
    - - -

    Additional allocations to assign to this server on creation.

    -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -

    Application Feature Limits

    -
    - -
    -
    - -
    - -
    -

    The total number of databases a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of allocations a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of backups that can be created for this server.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Resource Management

    -
    - -
    -
    - - -
    - - % -
    - -

    If you do not want to limit CPU usage, set the value to 0. To determine a value, take the number of threads and multiply it by 100. For example, on a quad core system without hyperthreading (4 * 100 = 400) there is 400% available. To limit a server to using half of a single thread, you would set the value to 50. To allow a server to use up to two threads, set the value to 200.

    -

    - -
    - - -
    - -
    - -

    Advanced: Enter the specific CPU threads that this process can run on, or leave blank to allow all threads. This can be a single number, or a comma separated list. Example: 0, 0-1,3, or 0,1,3,4.

    -
    -
    - -
    -
    - - -
    - - MiB -
    - -

    The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

    -
    - -
    - - -
    - - MiB -
    - -

    Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

    -
    -
    - -
    -
    - - -
    - - MiB -
    - -

    This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

    -
    - -
    - - -
    - -
    - -

    Advanced: The IO performance of this server relative to other running containers on the system. Value should be between 10 and 1000. Please see this documentation for more information about it.

    -
    -
    -
    - - -
    - -

    Terminates the server if it breaches the memory limits. Enabling OOM killer may cause server processes to exit unexpectedly.

    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -

    Nest Configuration

    -
    - -
    -
    - - - - -

    Select the Nest that this server will be grouped under.

    -
    - -
    - - -

    Select the Egg that will define how this server should operate.

    -
    -
    -
    - - -
    - -

    If the selected Egg has an install script attached to it, the script will run during the install. If you would like to skip this step, check this box.

    -
    -
    -
    -
    - -
    -
    -
    -

    Docker Configuration

    -
    - -
    -
    - - - -

    This is the default Docker image that will be used to run this server. Select an image from the dropdown above, or enter a custom image in the text field above.

    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -

    Startup Configuration

    -
    - -
    -
    - - -

    The following data substitutes are available for the startup command: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}. They will be replaced with the allocated memory, server IP, and server port respectively.

    -
    -
    - -
    -

    Service Variables

    -
    - -
    - - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - - - - {!! Theme::js('js/admin/new-server.js?v=20220530') !!} - - -@endsection diff --git a/resources/views/admin/servers/partials/navigation.blade.php b/resources/views/admin/servers/partials/navigation.blade.php deleted file mode 100644 index 964eac8e3..000000000 --- a/resources/views/admin/servers/partials/navigation.blade.php +++ /dev/null @@ -1,40 +0,0 @@ -@php - /** @var \Pterodactyl\Models\Server $server */ - $router = app('router'); -@endphp -
    -
    - -
    -
    diff --git a/resources/views/admin/servers/view/build.blade.php b/resources/views/admin/servers/view/build.blade.php deleted file mode 100644 index 655ea36af..000000000 --- a/resources/views/admin/servers/view/build.blade.php +++ /dev/null @@ -1,187 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Build Details -@endsection - -@section('content-header') -

    {{ $server->name }}Control allocations and system resources for this server.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -
    -

    Resource Management

    -
    -
    -
    - -
    - - % -
    -

    Each virtual core (thread) on the system is considered to be 100%. Setting this value to 0 will allow a server to use CPU time without restrictions.

    -
    -
    - -
    - -
    -

    Advanced: Enter the specific CPU cores that this process can run on, or leave blank to allow all cores. This can be a single number, or a comma seperated list. Example: 0, 0-1,3, or 0,1,3,4.

    -
    -
    - -
    - - MiB -
    -

    The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

    -
    -
    - -
    - - MiB -
    -

    Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

    -
    -
    - -
    - - MiB -
    -

    This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.

    -
    -
    - -
    - -
    -

    Advanced: The IO performance of this server relative to other running containers on the system. Value should be between 10 and 1000.

    -
    -
    - -
    -
    - oom_disabled)checked @endif> - -
    -
    - oom_disabled)checked @endif> - -
    -

    - Enabling OOM killer may cause server processes to exit unexpectedly. -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Application Feature Limits

    -
    -
    -
    -
    - -
    - -
    -

    The total number of databases a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of allocations a user is allowed to create for this server.

    -
    -
    - -
    - -
    -

    The total number of backups that can be created for this server.

    -
    -
    -
    -
    -
    -
    -
    -
    -

    Allocation Management

    -
    -
    -
    - - -

    The default connection address that will be used for this game server.

    -
    -
    - -
    - -
    -

    Please note that due to software limitations you cannot assign identical ports on different IPs to the same server.

    -
    -
    - -
    - -
    -

    Simply select which ports you would like to remove from the list above. If you want to assign a port on a different IP that is already in use you can select it from the left and delete it here.

    -
    -
    - -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/database.blade.php b/resources/views/admin/servers/view/database.blade.php deleted file mode 100644 index 385ce0f3f..000000000 --- a/resources/views/admin/servers/view/database.blade.php +++ /dev/null @@ -1,169 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Databases -@endsection - -@section('content-header') -

    {{ $server->name }}Manage server databases.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    - Database passwords can be viewed when visiting this server on the front-end. -
    -
    -
    -

    Active Databases

    -
    -
    - - - - - - - - - - @foreach($server->databases as $database) - - - - - - @if($database->max_connections != null) - - @else - - @endif - - - @endforeach -
    DatabaseUsernameConnections FromHostMax Connections
    {{ $database->database }}{{ $database->username }}{{ $database->remote }}{{ $database->host->host }}:{{ $database->host->port }}{{ $database->max_connections }}Unlimited - - -
    -
    -
    -
    -
    -
    -
    -

    Create New Database

    -
    -
    -
    -
    - - -

    Select the host database server that this database should be created on.

    -
    -
    - -
    - s{{ $server->id }}_ - -
    -
    -
    - - -

    This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as %.

    -
    -
    - - -

    This should reflect the max number of concurrent connections from this user to the database. Leave empty for unlimited.

    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/delete.blade.php b/resources/views/admin/servers/view/delete.blade.php deleted file mode 100644 index c23709632..000000000 --- a/resources/views/admin/servers/view/delete.blade.php +++ /dev/null @@ -1,91 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Delete -@endsection - -@section('content-header') -

    {{ $server->name }}Delete this server from the panel.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -

    Safely Delete Server

    -
    -
    -

    This action will attempt to delete the server from both the panel and daemon. If either one reports an error the action will be cancelled.

    -

    Deleting a server is an irreversible action. All server data (including files and users) will be removed from the system.

    -
    - -
    -
    -
    -
    -
    -

    Force Delete Server

    -
    -
    -

    This action will attempt to delete the server from both the panel and daemon. If the daemon does not respond, or reports an error the deletion will continue.

    -

    Deleting a server is an irreversible action. All server data (including files and users) will be removed from the system. This method may leave dangling files on your daemon if it reports an error.

    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/details.blade.php b/resources/views/admin/servers/view/details.blade.php deleted file mode 100644 index 9b4464b2c..000000000 --- a/resources/views/admin/servers/view/details.blade.php +++ /dev/null @@ -1,121 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Details -@endsection - -@section('content-header') -

    {{ $server->name }}Edit details for this server including owner and container.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -

    Base Information

    -
    -
    -
    -
    - - -

    Character limits: a-zA-Z0-9_- and [Space].

    -
    -
    - - -

    Leave empty to not assign an external identifier for this server. The external ID should be unique to this server and not be in use by any other servers.

    -
    -
    - - -

    You can change the owner of this server by changing this field to an email matching another use on this system. If you do this a new daemon security token will be generated automatically.

    -
    -
    - - -

    A brief description of this server.

    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php deleted file mode 100644 index f94c6d42f..000000000 --- a/resources/views/admin/servers/view/index.blade.php +++ /dev/null @@ -1,178 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }} -@endsection - -@section('content-header') -

    {{ $server->name }}{{ str_limit($server->description) }}

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -
    -
    -

    Information

    -
    -
    - - - - - - - - @if(is_null($server->external_id)) - - @else - - @endif - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Internal Identifier{{ $server->id }}
    External IdentifierNot Set{{ $server->external_id }}
    UUID / Docker Container ID{{ $server->uuid }}
    Current Egg - {{ $server->nest->name }} :: - {{ $server->egg->name }} -
    Server Name{{ $server->name }}
    CPU Limit - @if($server->cpu === 0) - Unlimited - @else - {{ $server->cpu }}% - @endif -
    CPU Pinning - @if($server->threads != null) - {{ $server->threads }} - @else - Not Set - @endif -
    Memory - @if($server->memory === 0) - Unlimited - @else - {{ $server->memory }}MiB - @endif - / - @if($server->swap === 0) - Not Set - @elseif($server->swap === -1) - Unlimited - @else - {{ $server->swap }}MiB - @endif -
    Disk Space - @if($server->disk === 0) - Unlimited - @else - {{ $server->disk }}MiB - @endif -
    Block IO Weight{{ $server->io }}
    Default Connection{{ $server->allocation->ip }}:{{ $server->allocation->port }}
    Connection Alias - @if($server->allocation->alias !== $server->allocation->ip) - {{ $server->allocation->alias }}:{{ $server->allocation->port }} - @else - No Alias Assigned - @endif -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - @if($server->isSuspended()) -
    -
    -
    -

    Suspended

    -
    -
    -
    - @endif - @if(!$server->isInstalled()) -
    -
    -
    -

    {{ (! $server->isInstalled()) ? 'Installing' : 'Install Failed' }}

    -
    -
    -
    - @endif -
    -
    -
    -

    {{ str_limit($server->user->username, 16) }}

    -

    Server Owner

    -
    -
    - - More info - -
    -
    -
    -
    -
    -

    {{ str_limit($server->node->name, 16) }}

    -

    Server Node

    -
    -
    - - More info - -
    -
    -
    -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/servers/view/manage.blade.php b/resources/views/admin/servers/view/manage.blade.php deleted file mode 100644 index e6177a43b..000000000 --- a/resources/views/admin/servers/view/manage.blade.php +++ /dev/null @@ -1,202 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Manage -@endsection - -@section('content-header') -

    {{ $server->name }}Additional actions to control this server.

    - -@endsection - -@section('content') - @include('admin.servers.partials.navigation') -
    -
    -
    -
    -

    Reinstall Server

    -
    -
    -

    This will reinstall the server with the assigned service scripts. Danger! This could overwrite server data.

    -
    - -
    -
    -
    -
    -
    -

    Install Status

    -
    -
    -

    If you need to change the install status from uninstalled to installed, or vice versa, you may do so with the button below.

    -
    - -
    -
    - - @if(! $server->isSuspended()) -
    -
    -
    -

    Suspend Server

    -
    -
    -

    This will suspend the server, stop any running processes, and immediately block the user from being able to access their files or otherwise manage the server through the panel or API.

    -
    - -
    -
    - @else -
    -
    -
    -

    Unsuspend Server

    -
    -
    -

    This will unsuspend the server and restore normal user access.

    -
    - -
    -
    - @endif - - @if(is_null($server->transfer)) -
    -
    -
    -

    Transfer Server

    -
    -
    -

    - Transfer this server to another node connected to this panel. - Warning! This feature has not been fully tested and may have bugs. -

    -
    - - -
    -
    - @else -
    -
    -
    -

    Transfer Server

    -
    -
    -

    - This server is currently being transferred to another node. - Transfer was initiated at {{ $server->transfer->created_at }} -

    -
    - - -
    -
    - @endif -
    - - -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - - @if($canTransfer) - {!! Theme::js('js/admin/server/transfer.js') !!} - @endif -@endsection diff --git a/resources/views/admin/servers/view/mounts.blade.php b/resources/views/admin/servers/view/mounts.blade.php deleted file mode 100644 index 36ca98ddc..000000000 --- a/resources/views/admin/servers/view/mounts.blade.php +++ /dev/null @@ -1,78 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Mounts -@endsection - -@section('content-header') -

    {{ $server->name }}Manage server mounts.

    - -@endsection - -@section('content') - @include('admin.servers.partials.navigation') - -
    -
    -
    -
    -

    Available Mounts

    -
    - -
    - - - - - - - - - - - @foreach ($mounts as $mount) - - - - - - - @if (! in_array($mount->id, $server->mounts->pluck('id')->toArray())) - - - - @else - - - - @endif - - @endforeach -
    IDNameSourceTargetStatus
    {{ $mount->id }}{{ $mount->name }}{{ $mount->source }}{{ $mount->target }} - Unmounted - -
    - {!! csrf_field() !!} - - -
    -
    - Mounted - -
    - @method('DELETE') - {!! csrf_field() !!} - - -
    -
    -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/servers/view/startup.blade.php b/resources/views/admin/servers/view/startup.blade.php deleted file mode 100644 index 05330298e..000000000 --- a/resources/views/admin/servers/view/startup.blade.php +++ /dev/null @@ -1,186 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Startup -@endsection - -@section('content-header') -

    {{ $server->name }}Control startup command as well as variables.

    - -@endsection - -@section('content') -@include('admin.servers.partials.navigation') -
    -
    -
    -
    -
    -

    Startup Command Modification

    -
    -
    - - -

    Edit your server's startup command here. The following variables are available by default: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}.

    -
    -
    - - -
    - -
    -
    -
    -
    -
    -
    -
    -

    Service Configuration

    -
    -
    -
    -

    - Changing any of the below values will result in the server processing a re-install command. The server will be stopped and will then proceed. - If you would like the service scripts to not run, ensure the box is checked at the bottom. -

    -

    - This is a destructive operation in many cases. This server will be stopped immediately in order for this action to proceed. -

    -
    -
    - - -

    Select the Nest that this server will be grouped into.

    -
    -
    - - -

    Select the Egg that will provide processing data for this server.

    -
    -
    -
    - skip_scripts) checked @endif /> - -
    -

    If the selected Egg has an install script attached to it, the script will run during install. If you would like to skip this step, check this box.

    -
    -
    -
    -
    -
    -

    Docker Image Configuration

    -
    -
    -
    - - - -

    This is the Docker image that will be used to run this server. Select an image from the dropdown or enter a custom image in the text field above.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/lodash/lodash.js') !!} - -@endsection diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php deleted file mode 100644 index 0f2056ca4..000000000 --- a/resources/views/admin/settings/advanced.blade.php +++ /dev/null @@ -1,127 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'advanced']) - -@section('title') - Advanced Settings -@endsection - -@section('content-header') -

    Advanced SettingsConfigure advanced settings for Pterodactyl.

    - -@endsection - -@section('content') - @yield('settings::nav') -
    -
    -
    -
    -
    -

    reCAPTCHA

    -
    -
    -
    -
    - -
    - -

    If enabled, login forms and password reset forms will do a silent captcha check and display a visible captcha if needed.

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -

    Used for communication between your site and Google. Be sure to keep it a secret.

    -
    -
    -
    - @if($showRecaptchaWarning) -
    -
    -
    - You are currently using reCAPTCHA keys that were shipped with this Panel. For improved security it is recommended to generate new invisible reCAPTCHA keys that tied specifically to your website. -
    -
    -
    - @endif -
    -
    -
    -
    -

    HTTP Connections

    -
    -
    -
    -
    - -
    - -

    The amount of time in seconds to wait for a connection to be opened before throwing an error.

    -
    -
    -
    - -
    - -

    The amount of time in seconds to wait for a request to be completed before throwing an error.

    -
    -
    -
    -
    -
    -
    -
    -

    Automatic Allocation Creation

    -
    -
    -
    -
    - -
    - -

    If enabled users will have the option to automatically create new allocations for their server via the frontend.

    -
    -
    -
    - -
    - -

    The starting port in the range that can be automatically allocated.

    -
    -
    -
    - -
    - -

    The ending port in the range that can be automatically allocated.

    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/settings/index.blade.php b/resources/views/admin/settings/index.blade.php deleted file mode 100644 index 489646dc9..000000000 --- a/resources/views/admin/settings/index.blade.php +++ /dev/null @@ -1,75 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'basic']) - -@section('title') - Settings -@endsection - -@section('content-header') -

    Panel SettingsConfigure Pterodactyl to your liking.

    - -@endsection - -@section('content') - @yield('settings::nav') -
    -
    -
    -
    -

    Panel Settings

    -
    -
    -
    -
    -
    - -
    - -

    This is the name that is used throughout the panel and in emails sent to clients.

    -
    -
    -
    - -
    -
    - @php - $level = old('pterodactyl:auth:2fa_required', config('pterodactyl.auth.2fa_required')); - @endphp - - - -
    -

    If enabled, any account falling into the selected grouping will be required to have 2-Factor authentication enabled to use the Panel.

    -
    -
    -
    - -
    - -

    The default language to use when rendering UI components.

    -
    -
    -
    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/views/admin/settings/mail.blade.php b/resources/views/admin/settings/mail.blade.php deleted file mode 100644 index 9e99acd30..000000000 --- a/resources/views/admin/settings/mail.blade.php +++ /dev/null @@ -1,202 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'mail']) - -@section('title') - Mail Settings -@endsection - -@section('content-header') -

    Mail SettingsConfigure how Pterodactyl should handle sending emails.

    - -@endsection - -@section('content') - @yield('settings::nav') -
    -
    -
    -
    -

    Email Settings

    -
    - @if($disabled) -
    -
    -
    -
    - This interface is limited to instances using SMTP as the mail driver. Please either use php artisan p:environment:mail command to update your email settings, or set MAIL_DRIVER=smtp in your environment file. -
    -
    -
    -
    - @else -
    -
    -
    -
    - -
    - -

    Enter the SMTP server address that mail should be sent through.

    -
    -
    -
    - -
    - -

    Enter the SMTP server port that mail should be sent through.

    -
    -
    -
    - -
    - @php - $encryption = old('mail:mailers:smtp:encryption', config('mail.mailers.smtp.encryption')); - @endphp - -

    Select the type of encryption to use when sending mail.

    -
    -
    -
    - -
    - -

    The username to use when connecting to the SMTP server.

    -
    -
    -
    - -
    - -

    The password to use in conjunction with the SMTP username. Leave blank to continue using the existing password. To set the password to an empty value enter !e into the field.

    -
    -
    -
    -
    -
    -
    - -
    - -

    Enter an email address that all outgoing emails will originate from.

    -
    -
    -
    - -
    - -

    The name that emails should appear to come from.

    -
    -
    -
    -
    - -
    - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - - -@endsection diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php deleted file mode 100644 index 0c8e906c3..000000000 --- a/resources/views/admin/users/index.blade.php +++ /dev/null @@ -1,79 +0,0 @@ -@extends('layouts.admin') - -@section('title') - List Users -@endsection - -@section('content-header') -

    UsersAll registered users on the system.

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    User List

    -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - @foreach ($users as $user) - - - - - - - - - - - @endforeach - -
    IDEmailClient NameUsername2FAServers OwnedCan Access
    {{ $user->id }}{{ $user->email }} @if($user->root_admin)@endif{{ $user->name_last }}, {{ $user->name_first }}{{ $user->username }} - @if($user->use_totp) - - @else - - @endif - - {{ $user->servers_count }} - {{ $user->subuser_of_count }}
    -
    - @if($users->hasPages()) - - @endif -
    -
    -
    -@endsection diff --git a/resources/views/admin/users/new.blade.php b/resources/views/admin/users/new.blade.php deleted file mode 100644 index 2ff50164d..000000000 --- a/resources/views/admin/users/new.blade.php +++ /dev/null @@ -1,128 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Create User -@endsection - -@section('content-header') -

    Create UserAdd a new user to the system.

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    Identity

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -

    The default language to use when rendering the Panel for this user.

    -
    -
    -
    - -
    -
    -
    -
    -
    -

    Permissions

    -
    -
    -
    - -
    - -

    Setting this to 'Yes' gives a user full administrative access.

    -
    -
    -
    -
    -
    -
    -
    -
    -

    Password

    -
    -
    -
    -

    Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.

    -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/users/view.blade.php b/resources/views/admin/users/view.blade.php deleted file mode 100644 index f042d892d..000000000 --- a/resources/views/admin/users/view.blade.php +++ /dev/null @@ -1,123 +0,0 @@ -@extends('layouts.admin') - -@section('title') - Manager User: {{ $user->username }} -@endsection - -@section('content-header') -

    {{ $user->name_first }} {{ $user->name_last}}{{ $user->username }}

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    Identity

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -

    The default language to use when rendering the Panel for this user.

    -
    -
    -
    - -
    -
    -
    -
    -
    -

    Password

    -
    -
    - -
    - -
    - -

    Leave blank to keep this user's password the same. User will not receive any notification if password is changed.

    -
    -
    -
    -
    -
    -
    -
    -
    -

    Permissions

    -
    -
    -
    - -
    - -

    Setting this to 'Yes' gives a user full administrative access.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Delete User

    -
    -
    -

    There must be no servers associated with this account in order for it to be deleted.

    -
    - -
    -
    -
    -@endsection diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php deleted file mode 100644 index 1543ddf72..000000000 --- a/resources/views/layouts/admin.blade.php +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - {{ config('app.name', 'Pterodactyl') }} - @yield('title') - - - - - - - - - - - - - @include('layouts.scripts') - - @section('scripts') - {!! Theme::css('vendor/select2/select2.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/admin.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/colors/skin-blue.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/sweetalert/sweetalert.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/animate/animate.min.css?t={cache-version}') !!} - {!! Theme::css('css/pterodactyl.css?t={cache-version}') !!} - - - - - @show - - -
    -
    - - -
    - -
    -
    - @yield('content-header') -
    -
    -
    -
    - @if (count($errors) > 0) -
    - There was an error validating the data provided.

    -
      - @foreach ($errors->all() as $error) -
    • {{ $error }}
    • - @endforeach -
    -
    - @endif - @foreach (Alert::getMessages() as $type => $messages) - @foreach ($messages as $message) - - @endforeach - @endforeach -
    -
    - @yield('content') -
    -
    -
    -
    - {{ $appVersion }}
    - {{ round(microtime(true) - LARAVEL_START, 3) }}s -
    - Copyright © 2015 - {{ date('Y') }} Pterodactyl Software. -
    -
    - @section('footer-scripts') - - - - {!! Theme::js('vendor/jquery/jquery.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/sweetalert/sweetalert.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap/bootstrap.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/slimscroll/jquery.slimscroll.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/adminlte/app.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap-notify/bootstrap-notify.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/select2/select2.full.min.js?t={cache-version}') !!} - {!! Theme::js('js/admin/functions.js?t={cache-version}') !!} - - - @if(Auth::user()->root_admin) - - @endif - - - @show - - diff --git a/resources/views/partials/admin/settings/nav.blade.php b/resources/views/partials/admin/settings/nav.blade.php deleted file mode 100644 index 9f1ace7f3..000000000 --- a/resources/views/partials/admin/settings/nav.blade.php +++ /dev/null @@ -1,16 +0,0 @@ -@include('partials/admin.settings.notice') - -@section('settings::nav') - @yield('settings::notice') -
    -
    - -
    -
    -@endsection diff --git a/resources/views/partials/admin/settings/notice.blade.php b/resources/views/partials/admin/settings/notice.blade.php deleted file mode 100644 index 2dc0bc112..000000000 --- a/resources/views/partials/admin/settings/notice.blade.php +++ /dev/null @@ -1,11 +0,0 @@ -@section('settings::notice') - @if(config('pterodactyl.load_environment_only', false)) -
    -
    -
    - Your Panel is currently configured to read settings from the environment only. You will need to set APP_ENVIRONMENT_ONLY=false in your environment file in order to load settings dynamically. -
    -
    -
    - @endif -@endsection diff --git a/resources/views/partials/schedules/task-template.blade.php b/resources/views/partials/schedules/task-template.blade.php deleted file mode 100644 index efcbbc47e..000000000 --- a/resources/views/partials/schedules/task-template.blade.php +++ /dev/null @@ -1,42 +0,0 @@ -@section('tasks::chain-template') - -@show diff --git a/routes/admin.php b/routes/admin.php index 4c6732e76..8fd9d5ba4 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -2,227 +2,6 @@ use Illuminate\Support\Facades\Route; use Pterodactyl\Http\Controllers\Admin; -use Pterodactyl\Http\Middleware\Admin\Servers\ServerInstalled; -Route::get('/', [Admin\BaseController::class, 'index'])->name('admin.index'); - -/* -|-------------------------------------------------------------------------- -| Location Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/api -| -*/ -Route::group(['prefix' => 'api'], function () { - Route::get('/', [Admin\ApiController::class, 'index'])->name('admin.api.index'); - Route::get('/new', [Admin\ApiController::class, 'create'])->name('admin.api.new'); - - Route::post('/new', [Admin\ApiController::class, 'store']); - - Route::delete('/revoke/{identifier}', [Admin\ApiController::class, 'delete'])->name('admin.api.delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Location Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/locations -| -*/ -Route::group(['prefix' => 'locations'], function () { - Route::get('/', [Admin\LocationController::class, 'index'])->name('admin.locations'); - Route::get('/view/{location:id}', [Admin\LocationController::class, 'view'])->name('admin.locations.view'); - - Route::post('/', [Admin\LocationController::class, 'create']); - Route::patch('/view/{location:id}', [Admin\LocationController::class, 'update']); -}); - -/* -|-------------------------------------------------------------------------- -| Database Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/databases -| -*/ -Route::group(['prefix' => 'databases'], function () { - Route::get('/', [Admin\DatabaseController::class, 'index'])->name('admin.databases'); - Route::get('/view/{host:id}', [Admin\DatabaseController::class, 'view'])->name('admin.databases.view'); - - Route::post('/', [Admin\DatabaseController::class, 'create']); - Route::patch('/view/{host:id}', [Admin\DatabaseController::class, 'update']); - Route::delete('/view/{host:id}', [Admin\DatabaseController::class, 'delete']); -}); - -/* -|-------------------------------------------------------------------------- -| Settings Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/settings -| -*/ -Route::group(['prefix' => 'settings'], function () { - Route::get('/', [Admin\Settings\IndexController::class, 'index'])->name('admin.settings'); - Route::get('/mail', [Admin\Settings\MailController::class, 'index'])->name('admin.settings.mail'); - Route::get('/advanced', [Admin\Settings\AdvancedController::class, 'index'])->name('admin.settings.advanced'); - - Route::post('/mail/test', [Admin\Settings\MailController::class, 'test'])->name('admin.settings.mail.test'); - - Route::patch('/', [Admin\Settings\IndexController::class, 'update']); - Route::patch('/mail', [Admin\Settings\MailController::class, 'update']); - Route::patch('/advanced', [Admin\Settings\AdvancedController::class, 'update']); -}); - -/* -|-------------------------------------------------------------------------- -| User Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/users -| -*/ -Route::group(['prefix' => 'users'], function () { - Route::get('/', [Admin\UserController::class, 'index'])->name('admin.users'); - Route::get('/accounts.json', [Admin\UserController::class, 'json'])->name('admin.users.json'); - Route::get('/new', [Admin\UserController::class, 'create'])->name('admin.users.new'); - Route::get('/view/{user:id}', [Admin\UserController::class, 'view'])->name('admin.users.view'); - - Route::post('/new', [Admin\UserController::class, 'store']); - - Route::patch('/view/{user:id}', [Admin\UserController::class, 'update']); - Route::delete('/view/{user:id}', [Admin\UserController::class, 'delete']); -}); - -/* -|-------------------------------------------------------------------------- -| Server Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/servers -| -*/ -Route::group(['prefix' => 'servers'], function () { - Route::get('/', [Admin\Servers\ServerController::class, 'index'])->name('admin.servers'); - Route::get('/new', [Admin\Servers\CreateServerController::class, 'index'])->name('admin.servers.new'); - Route::get('/view/{server:id}', [Admin\Servers\ServerViewController::class, 'index'])->name('admin.servers.view'); - - Route::group(['middleware' => [ServerInstalled::class]], function () { - Route::get('/view/{server:id}/details', [Admin\Servers\ServerViewController::class, 'details'])->name('admin.servers.view.details'); - Route::get('/view/{server:id}/build', [Admin\Servers\ServerViewController::class, 'build'])->name('admin.servers.view.build'); - Route::get('/view/{server:id}/startup', [Admin\Servers\ServerViewController::class, 'startup'])->name('admin.servers.view.startup'); - Route::get('/view/{server:id}/database', [Admin\Servers\ServerViewController::class, 'database'])->name('admin.servers.view.database'); - Route::get('/view/{server:id}/mounts', [Admin\Servers\ServerViewController::class, 'mounts'])->name('admin.servers.view.mounts'); - }); - - Route::get('/view/{server:id}/manage', [Admin\Servers\ServerViewController::class, 'manage'])->name('admin.servers.view.manage'); - Route::get('/view/{server:id}/delete', [Admin\Servers\ServerViewController::class, 'delete'])->name('admin.servers.view.delete'); - - Route::post('/new', [Admin\Servers\CreateServerController::class, 'store']); - Route::post('/view/{server:id}/build', [Admin\ServersController::class, 'updateBuild']); - Route::post('/view/{server:id}/startup', [Admin\ServersController::class, 'saveStartup']); - Route::post('/view/{server:id}/database', [Admin\ServersController::class, 'newDatabase']); - Route::post('/view/{server:id}/mounts', [Admin\ServersController::class, 'addMount'])->name('admin.servers.view.mounts.store'); - Route::post('/view/{server:id}/manage/toggle', [Admin\ServersController::class, 'toggleInstall'])->name('admin.servers.view.manage.toggle'); - Route::post('/view/{server:id}/manage/suspension', [Admin\ServersController::class, 'manageSuspension'])->name('admin.servers.view.manage.suspension'); - Route::post('/view/{server:id}/manage/reinstall', [Admin\ServersController::class, 'reinstallServer'])->name('admin.servers.view.manage.reinstall'); - Route::post('/view/{server:id}/manage/transfer', [Admin\Servers\ServerTransferController::class, 'transfer'])->name('admin.servers.view.manage.transfer'); - Route::post('/view/{server:id}/delete', [Admin\ServersController::class, 'delete']); - - Route::patch('/view/{server:id}/details', [Admin\ServersController::class, 'setDetails']); - Route::patch('/view/{server:id}/database', [Admin\ServersController::class, 'resetDatabasePassword']); - - Route::delete('/view/{server:id}/database/{database:id}/delete', [Admin\ServersController::class, 'deleteDatabase'])->name('admin.servers.view.database.delete'); - Route::delete('/view/{server:id}/mounts/{mount:id}', [Admin\ServersController::class, 'deleteMount']) - ->name('admin.servers.view.mounts.delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Node Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/nodes -| -*/ -Route::group(['prefix' => 'nodes'], function () { - Route::get('/', [Admin\Nodes\NodeController::class, 'index'])->name('admin.nodes'); - Route::get('/new', [Admin\NodesController::class, 'create'])->name('admin.nodes.new'); - Route::get('/view/{node:id}', [Admin\Nodes\NodeViewController::class, 'index'])->name('admin.nodes.view'); - Route::get('/view/{node:id}/settings', [Admin\Nodes\NodeViewController::class, 'settings'])->name('admin.nodes.view.settings'); - Route::get('/view/{node:id}/configuration', [Admin\Nodes\NodeViewController::class, 'configuration'])->name('admin.nodes.view.configuration'); - Route::get('/view/{node:id}/allocation', [Admin\Nodes\NodeViewController::class, 'allocations'])->name('admin.nodes.view.allocation'); - Route::get('/view/{node:id}/servers', [Admin\Nodes\NodeViewController::class, 'servers'])->name('admin.nodes.view.servers'); - Route::get('/view/{node:id}/system-information', Admin\Nodes\SystemInformationController::class); - - Route::post('/new', [Admin\NodesController::class, 'store']); - Route::post('/view/{node:id}/allocation', [Admin\NodesController::class, 'createAllocation']); - Route::post('/view/{node:id}/allocation/remove', [Admin\NodesController::class, 'allocationRemoveBlock'])->name('admin.nodes.view.allocation.removeBlock'); - Route::post('/view/{node:id}/allocation/alias', [Admin\NodesController::class, 'allocationSetAlias'])->name('admin.nodes.view.allocation.setAlias'); - Route::post('/view/{node:id}/settings/token', Admin\NodeAutoDeployController::class)->name('admin.nodes.view.configuration.token'); - - Route::patch('/view/{node:id}/settings', [Admin\NodesController::class, 'updateSettings']); - - Route::delete('/view/{node:id}/delete', [Admin\NodesController::class, 'delete'])->name('admin.nodes.view.delete'); - Route::delete('/view/{node:id}/allocation/remove/{allocation:id}', [Admin\NodesController::class, 'allocationRemoveSingle'])->name('admin.nodes.view.allocation.removeSingle'); - Route::delete('/view/{node:id}/allocations', [Admin\NodesController::class, 'allocationRemoveMultiple'])->name('admin.nodes.view.allocation.removeMultiple'); -}); - -/* -|-------------------------------------------------------------------------- -| Mount Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/mounts -| -*/ -Route::group(['prefix' => 'mounts'], function () { - Route::get('/', [Admin\MountController::class, 'index'])->name('admin.mounts'); - Route::get('/view/{mount:id}', [Admin\MountController::class, 'view'])->name('admin.mounts.view'); - - Route::post('/', [Admin\MountController::class, 'create']); - Route::post('/{mount:id}/eggs', [Admin\MountController::class, 'addEggs'])->name('admin.mounts.eggs'); - Route::post('/{mount:id}/nodes', [Admin\MountController::class, 'addNodes'])->name('admin.mounts.nodes'); - - Route::patch('/view/{mount:id}', [Admin\MountController::class, 'update']); - - Route::delete('/{mount:id}/eggs/{egg_id}', [Admin\MountController::class, 'deleteEgg']); - Route::delete('/{mount:id}/nodes/{node_id}', [Admin\MountController::class, 'deleteNode']); -}); - -/* -|-------------------------------------------------------------------------- -| Nest Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/nests -| -*/ -Route::group(['prefix' => 'nests'], function () { - Route::get('/', [Admin\Nests\NestController::class, 'index'])->name('admin.nests'); - Route::get('/new', [Admin\Nests\NestController::class, 'create'])->name('admin.nests.new'); - Route::get('/view/{nest:id}', [Admin\Nests\NestController::class, 'view'])->name('admin.nests.view'); - Route::get('/egg/new', [Admin\Nests\EggController::class, 'create'])->name('admin.nests.egg.new'); - Route::get('/egg/{egg:id}', [Admin\Nests\EggController::class, 'view'])->name('admin.nests.egg.view'); - Route::get('/egg/{egg:id}/export', [Admin\Nests\EggShareController::class, 'export'])->name('admin.nests.egg.export'); - Route::get('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'view'])->name('admin.nests.egg.variables'); - Route::get('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'index'])->name('admin.nests.egg.scripts'); - - Route::post('/new', [Admin\Nests\NestController::class, 'store']); - Route::post('/import', [Admin\Nests\EggShareController::class, 'import'])->name('admin.nests.egg.import'); - Route::post('/egg/new', [Admin\Nests\EggController::class, 'store']); - Route::post('/egg/{egg:id}/variables', [Admin\Nests\EggVariableController::class, 'store']); - - Route::put('/egg/{egg:id}', [Admin\Nests\EggShareController::class, 'update']); - - Route::patch('/view/{nest:id}', [Admin\Nests\NestController::class, 'update']); - Route::patch('/egg/{egg:id}', [Admin\Nests\EggController::class, 'update']); - Route::patch('/egg/{egg:id}/scripts', [Admin\Nests\EggScriptController::class, 'update']); - Route::patch('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'update'])->name('admin.nests.egg.variables.edit'); - - Route::delete('/view/{nest:id}', [Admin\Nests\NestController::class, 'destroy']); - Route::delete('/egg/{egg:id}', [Admin\Nests\EggController::class, 'destroy']); - Route::delete('/egg/{egg:id}/variables/{variable:id}', [Admin\Nests\EggVariableController::class, 'destroy']); -}); +Route::get('/', [Admin\BaseController::class, 'index'])->name('admin.index')->fallback(); +Route::get('/{react}', [Admin\BaseController::class, 'index'])->where('react', '.+'); diff --git a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php b/tests/Integration/Http/Controllers/Admin/UserControllerTest.php deleted file mode 100644 index 34cf9f938..000000000 --- a/tests/Integration/Http/Controllers/Admin/UserControllerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -create(['username' => $unique . '_1']), - User::factory()->create(['username' => $unique . '_2']), - ]; - - $servers = [ - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[0]->id]), - $this->createServerModel(['owner_id' => $users[1]->id]), - ]; - - Subuser::query()->forceCreate(['server_id' => $servers[0]->id, 'user_id' => $users[1]->id]); - Subuser::query()->forceCreate(['server_id' => $servers[1]->id, 'user_id' => $users[1]->id]); - - /** @var \Pterodactyl\Http\Controllers\Admin\UserController $controller */ - $controller = $this->app->make(UserController::class); - - $request = Request::create('/admin/users?filter[username]=' . $unique); - $this->app->instance(Request::class, $request); - - $data = $controller->index($request)->getData(); - $this->assertArrayHasKey('users', $data); - $this->assertInstanceOf(LengthAwarePaginator::class, $data['users']); - - /** @var \Pterodactyl\Models\User[] $response */ - $response = $data['users']->items(); - $this->assertCount(2, $response); - $this->assertInstanceOf(User::class, $response[0]); - $this->assertSame(3, (int) $response[0]->servers_count); - $this->assertSame(0, (int) $response[0]->subuser_of_count); - $this->assertSame(1, (int) $response[1]->servers_count); - $this->assertSame(2, (int) $response[1]->subuser_of_count); - } -} From 67bf3e342eebef04931e3c814ae2811d9f460bee Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 17:05:46 -0700 Subject: [PATCH 067/106] api(application): v2 backport --- app/Exceptions/Handler.php | 2 +- .../QueryValueOutOfRangeHttpException.php | 21 ++ .../ManifestDoesNotExistException.php | 14 -- .../Service/Egg/BadYamlFormatException.php | 9 + .../ManifestDoesNotExistSolution.php | 25 --- .../InvalidTransformerLevelException.php | 9 - .../Application/ApplicationApiController.php | 21 +- .../Databases/DatabaseController.php | 99 +++++++++ .../Api/Application/Eggs/EggController.php | 120 ++++++++++ .../Eggs/EggVariableController.php | 75 +++++++ .../Locations/LocationController.php | 25 ++- .../Application/Mounts/MountController.php | 163 ++++++++++++++ .../Api/Application/Nests/EggController.php | 33 --- .../Api/Application/Nests/NestController.php | 97 +++++++- .../Nodes/AllocationController.php | 39 ++-- .../Nodes/NodeConfigurationController.php | 6 +- .../Api/Application/Nodes/NodeController.php | 31 +-- .../Nodes/NodeDeploymentController.php | 4 +- .../Nodes/NodeInformationController.php | 47 ++++ .../Api/Application/Roles/RoleController.php | 96 ++++++++ .../Servers/DatabaseController.php | 22 +- .../Servers/ExternalServerController.php | 2 +- .../Application/Servers/ServerController.php | 49 ++++- .../Servers/ServerDetailsController.php | 16 +- .../Servers/ServerManagementController.php | 6 +- .../Application/Servers/StartupController.php | 7 +- .../Users/ExternalUserController.php | 2 +- .../Api/Application/Users/UserController.php | 65 ++++-- .../Api/Application/VersionController.php | 25 +++ .../Api/Client/AccountController.php | 2 +- .../Api/Client/ActivityLogController.php | 2 +- .../Api/Client/ApiKeyController.php | 4 +- .../Api/Client/ClientApiController.php | 22 +- .../Api/Client/ClientController.php | 2 +- .../Api/Client/SSHKeyController.php | 4 +- .../Client/Servers/ActivityLogController.php | 2 +- .../Api/Client/Servers/BackupController.php | 8 +- .../Api/Client/Servers/DatabaseController.php | 6 +- .../Api/Client/Servers/FileController.php | 4 +- .../Servers/NetworkAllocationController.php | 8 +- .../Servers/ResourceUtilizationController.php | 2 +- .../Api/Client/Servers/ScheduleController.php | 8 +- .../Client/Servers/ScheduleTaskController.php | 4 +- .../Api/Client/Servers/ServerController.php | 2 +- .../Api/Client/Servers/StartupController.php | 4 +- .../Api/Client/Servers/SubuserController.php | 8 +- .../Auth/AbstractLoginController.php | 2 +- app/Http/Requests/Api/ApiRequest.php | 83 +++++++ .../Allocations/DeleteAllocationRequest.php | 4 - .../Allocations/GetAllocationsRequest.php | 4 - .../Allocations/StoreAllocationRequest.php | 18 +- .../Api/Application/ApplicationApiRequest.php | 88 +------- .../Databases/DeleteDatabaseRequest.php | 9 + .../Databases/GetDatabaseRequest.php | 7 + .../Databases/GetDatabasesRequest.php | 9 + .../Databases/StoreDatabaseRequest.php | 14 ++ .../Databases/UpdateDatabaseRequest.php | 13 ++ .../Api/Application/Eggs/DeleteEggRequest.php | 16 ++ .../Api/Application/Eggs/ExportEggRequest.php | 9 + .../Api/Application/Eggs/GetEggRequest.php | 7 + .../Api/Application/Eggs/GetEggsRequest.php | 9 + .../Api/Application/Eggs/ImportEggRequest.php | 9 + .../Api/Application/Eggs/StoreEggRequest.php | 30 +++ .../Api/Application/Eggs/UpdateEggRequest.php | 28 +++ .../Variables/StoreEggVariableRequest.php | 22 ++ .../Variables/UpdateEggVariablesRequest.php | 24 ++ .../Locations/DeleteLocationRequest.php | 4 - .../Locations/GetLocationsRequest.php | 4 - .../Locations/StoreLocationRequest.php | 11 - .../Locations/UpdateLocationRequest.php | 7 +- .../Application/Mounts/DeleteMountRequest.php | 9 + .../Application/Mounts/GetMountRequest.php | 7 + .../Application/Mounts/GetMountsRequest.php | 9 + .../Application/Mounts/MountEggsRequest.php | 13 ++ .../Application/Mounts/MountNodesRequest.php | 13 ++ .../Application/Mounts/StoreMountRequest.php | 14 ++ .../Application/Mounts/UpdateMountRequest.php | 13 ++ .../Application/Nests/DeleteNestRequest.php | 9 + .../Application/Nests/Eggs/GetEggRequest.php | 13 -- .../Application/Nests/Eggs/GetEggsRequest.php | 13 -- .../Api/Application/Nests/GetNestRequest.php | 7 + .../Api/Application/Nests/GetNestsRequest.php | 4 - .../Application/Nests/StoreNestRequest.php | 14 ++ .../Application/Nests/UpdateNestRequest.php | 13 ++ .../Application/Nodes/DeleteNodeRequest.php | 4 - .../Api/Application/Nodes/GetNodesRequest.php | 4 - .../Application/Nodes/StoreNodeRequest.php | 45 ++-- .../Application/Nodes/UpdateNodeRequest.php | 9 +- .../Application/Roles/DeleteRoleRequest.php | 9 + .../Api/Application/Roles/GetRoleRequest.php | 7 + .../Api/Application/Roles/GetRolesRequest.php | 9 + .../Application/Roles/StoreRoleRequest.php | 14 ++ .../Application/Roles/UpdateRoleRequest.php | 13 ++ .../Databases/GetServerDatabaseRequest.php | 4 - .../Databases/GetServerDatabasesRequest.php | 4 - .../Databases/ServerDatabaseWriteRequest.php | 3 - .../Databases/StoreServerDatabaseRequest.php | 28 +-- .../Servers/GetExternalServerRequest.php | 4 - .../Application/Servers/GetServerRequest.php | 4 - .../Servers/ServerWriteRequest.php | 4 - .../Servers/StoreServerRequest.php | 134 +++-------- .../UpdateServerBuildConfigurationRequest.php | 24 +- .../Servers/UpdateServerDetailsRequest.php | 14 +- .../Servers/UpdateServerRequest.php | 77 +++++++ .../Servers/UpdateServerStartupRequest.php | 29 +-- .../Application/Users/DeleteUserRequest.php | 4 - .../Users/GetExternalUserRequest.php | 4 - .../Api/Application/Users/GetUserRequest.php | 7 + .../Api/Application/Users/GetUsersRequest.php | 4 - .../Application/Users/StoreUserRequest.php | 42 +--- .../Application/Users/UpdateUserRequest.php | 7 +- app/Models/AdminRole.php | 68 ++++++ app/Models/AuditLog.php | 80 ------- app/Models/Backup.php | 1 - app/Models/Server.php | 3 +- app/Models/User.php | 41 +++- app/Policies/.gitkeep | 0 app/Providers/AppServiceProvider.php | 33 --- app/Services/Eggs/EggParserService.php | 10 +- .../Eggs/Sharing/EggImporterService.php | 91 +++++++- .../Eggs/Sharing/EggUpdateImporterService.php | 20 +- .../Eggs/Variables/VariableUpdateService.php | 22 +- .../Helpers/SoftwareVersionService.php | 103 +++++++-- .../Servers/BuildModificationService.php | 2 +- .../ServerConfigurationStructureService.php | 9 +- .../Api/Application/AdminRoleTransformer.php | 29 +++ .../Api/Application/AllocationTransformer.php | 39 +--- .../Api/Application/BaseTransformer.php | 114 ---------- .../Application/DatabaseHostTransformer.php | 21 +- .../Api/Application/EggTransformer.php | 90 +++----- .../Application/EggVariableTransformer.php | 8 +- .../Api/Application/LocationTransformer.php | 47 ++-- .../Api/Application/MountTransformer.php | 75 +++++++ .../Api/Application/NestTransformer.php | 25 +-- .../Api/Application/NodeTransformer.php | 49 ++--- .../Application/ServerDatabaseTransformer.php | 48 ++-- .../Api/Application/ServerTransformer.php | 117 ++++------ .../Application/ServerVariableTransformer.php | 14 +- .../Api/Application/SubuserTransformer.php | 51 ++--- .../Api/Application/UserTransformer.php | 36 ++- .../Api/Client/AccountTransformer.php | 3 +- .../Api/Client/ActivityLogTransformer.php | 9 +- .../Api/Client/AllocationTransformer.php | 3 +- .../Api/Client/ApiKeyTransformer.php | 3 +- .../Api/Client/BackupTransformer.php | 23 +- .../Api/Client/BaseClientTransformer.php | 43 ---- .../Api/Client/DatabaseTransformer.php | 10 +- .../Api/Client/EggTransformer.php | 9 +- .../Api/Client/EggVariableTransformer.php | 21 +- .../Api/Client/FileObjectTransformer.php | 39 ++-- .../Api/Client/ScheduleTransformer.php | 20 +- .../Api/Client/ServerTransformer.php | 32 +-- .../Api/Client/StatsTransformer.php | 21 +- .../Api/Client/SubuserTransformer.php | 7 +- .../Api/Client/TaskTransformer.php | 7 +- .../Api/Client/UserSSHKeyTransformer.php | 5 +- .../Api/Client/UserTransformer.php | 7 +- app/Transformers/Api/Transformer.php | 156 +++++++++++++ database/Seeders/EggSeeder.php | 16 +- ..._09_25_021109_create_admin_roles_table.php | 31 +++ ...dd_admin_role_id_column_to_users_table.php | 30 +++ ...database_host_id_column_to_nodes_table.php | 41 ++++ ...658_change_port_columns_on_nodes_table.php | 58 +++++ ..._29_032255_yeet_names_from_users_table.php | 28 +++ ...rop_config_logs_column_from_eggs_table.php | 27 +++ ..._202643_update_default_values_for_eggs.php | 28 +++ ...tartup_field_nullable_on_servers_table.php | 27 +++ resources/views/templates/wrapper.blade.php | 2 +- routes/api-application.php | 208 +++++++++++++----- .../ApplicationApiIntegrationTestCase.php | 19 +- tests/Integration/IntegrationTestCase.php | 5 +- tests/Traits/Http/MocksMiddlewareClosure.php | 2 +- 172 files changed, 2922 insertions(+), 1579 deletions(-) create mode 100644 app/Exceptions/Http/QueryValueOutOfRangeHttpException.php delete mode 100644 app/Exceptions/ManifestDoesNotExistException.php create mode 100644 app/Exceptions/Service/Egg/BadYamlFormatException.php delete mode 100644 app/Exceptions/Solutions/ManifestDoesNotExistSolution.php delete mode 100644 app/Exceptions/Transformer/InvalidTransformerLevelException.php create mode 100644 app/Http/Controllers/Api/Application/Databases/DatabaseController.php create mode 100644 app/Http/Controllers/Api/Application/Eggs/EggController.php create mode 100644 app/Http/Controllers/Api/Application/Eggs/EggVariableController.php create mode 100644 app/Http/Controllers/Api/Application/Mounts/MountController.php delete mode 100644 app/Http/Controllers/Api/Application/Nests/EggController.php create mode 100644 app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php create mode 100644 app/Http/Controllers/Api/Application/Roles/RoleController.php create mode 100644 app/Http/Controllers/Api/Application/VersionController.php create mode 100644 app/Http/Requests/Api/ApiRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/GetDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/GetDatabasesRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/StoreDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/GetEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php create mode 100644 app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/GetMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/GetMountsRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/MountEggsRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php delete mode 100644 app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php delete mode 100644 app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/GetNestRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/StoreNestRequest.php create mode 100644 app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/GetRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/GetRolesRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/StoreRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php create mode 100644 app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php create mode 100644 app/Http/Requests/Api/Application/Users/GetUserRequest.php create mode 100644 app/Models/AdminRole.php delete mode 100644 app/Models/AuditLog.php delete mode 100644 app/Policies/.gitkeep create mode 100644 app/Transformers/Api/Application/AdminRoleTransformer.php delete mode 100644 app/Transformers/Api/Application/BaseTransformer.php create mode 100644 app/Transformers/Api/Application/MountTransformer.php delete mode 100644 app/Transformers/Api/Client/BaseClientTransformer.php create mode 100644 app/Transformers/Api/Transformer.php create mode 100644 database/migrations/2020_09_25_021109_create_admin_roles_table.php create mode 100644 database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php create mode 100644 database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php create mode 100644 database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php create mode 100644 database/migrations/2021_07_29_032255_yeet_names_from_users_table.php create mode 100644 database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php create mode 100644 database/migrations/2021_10_23_202643_update_default_values_for_eggs.php create mode 100644 database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 6fd1b5d8e..4cfee8e46 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -233,7 +233,7 @@ final class Handler extends ExceptionHandler /** * Return an array of exceptions that should not be reported. */ - public static function isReportable(\Exception $exception): bool + public static function isReportable(Exception $exception): bool { return (new static(Container::getInstance()))->shouldReport($exception); } diff --git a/app/Exceptions/Http/QueryValueOutOfRangeHttpException.php b/app/Exceptions/Http/QueryValueOutOfRangeHttpException.php new file mode 100644 index 000000000..4610f0e2e --- /dev/null +++ b/app/Exceptions/Http/QueryValueOutOfRangeHttpException.php @@ -0,0 +1,21 @@ + 'https://github.com/pterodactyl/panel/blob/develop/package.json', - ]; - } -} diff --git a/app/Exceptions/Transformer/InvalidTransformerLevelException.php b/app/Exceptions/Transformer/InvalidTransformerLevelException.php deleted file mode 100644 index 3d4c24248..000000000 --- a/app/Exceptions/Transformer/InvalidTransformerLevelException.php +++ /dev/null @@ -1,9 +0,0 @@ - $abstract - * - * @return T - * - * @noinspection PhpDocSignatureInspection + * Return an HTTP/201 response for the API. */ - public function getTransformer(string $abstract) + protected function returnAccepted(): Response { - Assert::subclassOf($abstract, BaseTransformer::class); - - return $abstract::fromRequest($this->request); + return new Response('', Response::HTTP_ACCEPTED); } /** diff --git a/app/Http/Controllers/Api/Application/Databases/DatabaseController.php b/app/Http/Controllers/Api/Application/Databases/DatabaseController.php new file mode 100644 index 000000000..2acd2fa18 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Databases/DatabaseController.php @@ -0,0 +1,99 @@ +query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $databases = QueryBuilder::for(DatabaseHost::query()) + ->allowedFilters(['name', 'host']) + ->allowedSorts(['id', 'name', 'host']) + ->paginate($perPage); + + return $this->fractal->collection($databases) + ->transformWith(DatabaseHostTransformer::class) + ->toArray(); + } + + /** + * Returns a single database host. + */ + public function view(GetDatabaseRequest $request, DatabaseHost $databaseHost): array + { + return $this->fractal->item($databaseHost) + ->transformWith(DatabaseHostTransformer::class) + ->toArray(); + } + + /** + * Creates a new database host. + * + * @throws \Throwable + */ + public function store(StoreDatabaseRequest $request): JsonResponse + { + $databaseHost = $this->creationService->handle($request->validated()); + + return $this->fractal->item($databaseHost) + ->transformWith(DatabaseHostTransformer::class) + ->respond(JsonResponse::HTTP_CREATED); + } + + /** + * Updates a database host. + * + * @throws \Throwable + */ + public function update(UpdateDatabaseRequest $request, DatabaseHost $databaseHost): array + { + $databaseHost = $this->updateService->handle($databaseHost->id, $request->validated()); + + return $this->fractal->item($databaseHost) + ->transformWith(DatabaseHostTransformer::class) + ->toArray(); + } + + /** + * Deletes a database host. + * + * @throws \Exception + */ + public function delete(DeleteDatabaseRequest $request, DatabaseHost $databaseHost): Response + { + $databaseHost->delete(); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Eggs/EggController.php b/app/Http/Controllers/Api/Application/Eggs/EggController.php new file mode 100644 index 000000000..96c0b8a4f --- /dev/null +++ b/app/Http/Controllers/Api/Application/Eggs/EggController.php @@ -0,0 +1,120 @@ +eggExporterService = $eggExporterService; + } + + /** + * Return an array of all eggs on a given nest. + */ + public function index(GetEggsRequest $request, Nest $nest): array + { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + // @phpstan-ignore-next-line + $eggs = QueryBuilder::for(Egg::query()) + ->where('nest_id', '=', $nest->id) + ->allowedFilters(['id', 'name', 'author']) + ->allowedSorts(['id', 'name', 'author']); + if ($perPage > 0) { + $eggs = $eggs->paginate($perPage); + } + + return $this->fractal->collection($eggs) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Returns a single egg. + */ + public function view(GetEggRequest $request, Egg $egg): array + { + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Creates a new egg. + */ + public function store(StoreEggRequest $request): JsonResponse + { + $validated = $request->validated(); + $merged = array_merge($validated, [ + 'uuid' => Uuid::uuid4()->toString(), + // TODO: allow this to be set in the request, and default to config value if null or not present. + 'author' => config('pterodactyl.service.author'), + ]); + + $egg = Egg::query()->create($merged); + + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->respond(Response::HTTP_CREATED); + } + + /** + * Updates an egg. + */ + public function update(UpdateEggRequest $request, Egg $egg): array + { + $egg->update($request->validated()); + + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Deletes an egg. + * + * @throws \Exception + */ + public function delete(DeleteEggRequest $request, Egg $egg): Response + { + $egg->delete(); + + return $this->returnNoContent(); + } + + /** + * Exports an egg. + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function export(ExportEggRequest $request, int $eggId): JsonResponse + { + return new JsonResponse($this->eggExporterService->handle($eggId)); + } +} diff --git a/app/Http/Controllers/Api/Application/Eggs/EggVariableController.php b/app/Http/Controllers/Api/Application/Eggs/EggVariableController.php new file mode 100644 index 000000000..c837244d7 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Eggs/EggVariableController.php @@ -0,0 +1,75 @@ +variableCreationService->handle($egg->id, $request->validated()); + + return $this->fractal->item($variable) + ->transformWith(EggVariableTransformer::class) + ->toArray(); + } + + /** + * Updates multiple egg variables. + * + * @throws \Throwable + */ + public function update(UpdateEggVariablesRequest $request, Egg $egg): array + { + $validated = $request->validated(); + + $this->connection->transaction(function () use ($egg, $validated) { + foreach ($validated as $data) { + $this->variableUpdateService->handle($egg, $data); + } + }); + + return $this->fractal->collection($egg->refresh()->variables) + ->transformWith(EggVariableTransformer::class) + ->toArray(); + } + + /** + * Deletes a single egg variable. + */ + public function delete(Request $request, Egg $egg, EggVariable $eggVariable): Response + { + EggVariable::query() + ->where('id', $eggVariable->id) + ->where('egg_id', $egg->id) + ->delete(); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index b95a0776a..337f5e76d 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -10,6 +10,7 @@ use Pterodactyl\Services\Locations\LocationUpdateService; use Pterodactyl\Services\Locations\LocationCreationService; use Pterodactyl\Services\Locations\LocationDeletionService; use Pterodactyl\Transformers\Api\Application\LocationTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest; use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest; @@ -35,13 +36,18 @@ class LocationController extends ApplicationApiController */ public function index(GetLocationsRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $locations = QueryBuilder::for(Location::query()) ->allowedFilters(['short', 'long']) - ->allowedSorts(['id']) - ->paginate($request->query('per_page') ?? 50); + ->allowedSorts(['id', 'short', 'long']) + ->paginate($perPage); return $this->fractal->collection($locations) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -51,7 +57,7 @@ class LocationController extends ApplicationApiController public function view(GetLocationRequest $request, Location $location): array { return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -66,12 +72,7 @@ class LocationController extends ApplicationApiController $location = $this->creationService->handle($request->validated()); return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.locations.view', [ - 'location' => $location->id, - ]), - ]) + ->transformWith(LocationTransformer::class) ->respond(201); } @@ -86,7 +87,7 @@ class LocationController extends ApplicationApiController $location = $this->updateService->handle($location, $request->validated()); return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -99,6 +100,6 @@ class LocationController extends ApplicationApiController { $this->deletionService->handle($location); - return response('', 204); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Mounts/MountController.php b/app/Http/Controllers/Api/Application/Mounts/MountController.php new file mode 100644 index 000000000..6606004fd --- /dev/null +++ b/app/Http/Controllers/Api/Application/Mounts/MountController.php @@ -0,0 +1,163 @@ +query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $mounts = QueryBuilder::for(Mount::query()) + ->allowedFilters(['id', 'name', 'source', 'target']) + ->allowedSorts(['id', 'name', 'source', 'target']) + ->paginate($perPage); + + return $this->fractal->collection($mounts) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Returns a single mount. + */ + public function view(GetMountRequest $request, Mount $mount): array + { + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Creates a new mount. + */ + public function store(StoreMountRequest $request): JsonResponse + { + $mount = Mount::query()->create($request->validated()); + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->respond(JsonResponse::HTTP_CREATED); + } + + /** + * Updates a mount. + */ + public function update(UpdateMountRequest $request, Mount $mount): array + { + $mount->update($request->validated()); + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Deletes a mount. + * + * @throws \Exception + */ + public function delete(DeleteMountRequest $request, Mount $mount): Response + { + $mount->delete(); + + return $this->returnNoContent(); + } + + /** + * Attaches eggs to a mount. + */ + public function addEggs(MountEggsRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $eggs = $data['eggs'] ?? []; + if (count($eggs) > 0) { + $mount->eggs()->syncWithoutDetaching($eggs); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Attaches nodes to a mount. + */ + public function addNodes(MountNodesRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $nodes = $data['nodes'] ?? []; + if (count($nodes) > 0) { + $mount->nodes()->syncWithoutDetaching($nodes); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Detaches eggs from a mount. + */ + public function deleteEggs(MountEggsRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $eggs = $data['eggs'] ?? []; + if (count($eggs) > 0) { + $mount->eggs()->detach($eggs); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } + + /** + * Detaches nodes from a mount. + */ + public function deleteNodes(MountNodesRequest $request, Mount $mount): array + { + $data = $request->validated(); + + $nodes = $data['nodes'] ?? []; + if (count($nodes) > 0) { + $mount->nodes()->detach($nodes); + } + + return $this->fractal->item($mount) + ->transformWith(MountTransformer::class) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Application/Nests/EggController.php b/app/Http/Controllers/Api/Application/Nests/EggController.php deleted file mode 100644 index 83c3f77a0..000000000 --- a/app/Http/Controllers/Api/Application/Nests/EggController.php +++ /dev/null @@ -1,33 +0,0 @@ -fractal->collection($nest->eggs) - ->transformWith($this->getTransformer(EggTransformer::class)) - ->toArray(); - } - - /** - * Return a single egg that exists on the specified nest. - */ - public function view(GetEggRequest $request, Nest $nest, Egg $egg): array - { - return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) - ->toArray(); - } -} diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index f0044f53c..708f59195 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -3,9 +3,21 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nests; use Pterodactyl\Models\Nest; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Illuminate\Http\Response; +use Spatie\QueryBuilder\QueryBuilder; +use Pterodactyl\Services\Nests\NestUpdateService; +use Pterodactyl\Services\Nests\NestCreationService; +use Pterodactyl\Services\Nests\NestDeletionService; +use Pterodactyl\Services\Eggs\Sharing\EggImporterService; +use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Transformers\Api\Application\NestTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; +use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest; +use Pterodactyl\Http\Requests\Api\Application\Eggs\ImportEggRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest; +use Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest; +use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest; +use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class NestController extends ApplicationApiController @@ -13,8 +25,12 @@ class NestController extends ApplicationApiController /** * NestController constructor. */ - public function __construct(private NestRepositoryInterface $repository) - { + public function __construct( + private NestCreationService $nestCreationService, + private NestDeletionService $nestDeletionService, + private NestUpdateService $nestUpdateService, + private EggImporterService $eggImporterService + ) { parent::__construct(); } @@ -23,20 +39,87 @@ class NestController extends ApplicationApiController */ public function index(GetNestsRequest $request): array { - $nests = $this->repository->paginated($request->query('per_page') ?? 50); + $perPage = (int) $request->query('per_page', '10'); + if ($perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $nests = QueryBuilder::for(Nest::query()) + ->allowedFilters(['id', 'name', 'author']) + ->allowedSorts(['id', 'name', 'author']); + if ($perPage > 0) { + $nests = $nests->paginate($perPage); + } return $this->fractal->collection($nests) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } /** * Return information about a single Nest model. */ - public function view(GetNestsRequest $request, Nest $nest): array + public function view(GetNestRequest $request, Nest $nest): array { return $this->fractal->item($nest) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } + + /** + * Creates a new nest. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreNestRequest $request): array + { + $nest = $this->nestCreationService->handle($request->validated()); + + return $this->fractal->item($nest) + ->transformWith(NestTransformer::class) + ->toArray(); + } + + /** + * Imports an egg. + */ + public function import(ImportEggRequest $request, Nest $nest): array + { + $egg = $this->eggImporterService->handleContent( + $nest->id, + $request->getContent(), + $request->headers->get('Content-Type'), + ); + + return $this->fractal->item($egg) + ->transformWith(EggTransformer::class) + ->toArray(); + } + + /** + * Updates an existing nest. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateNestRequest $request, Nest $nest): array + { + $this->nestUpdateService->handle($nest->id, $request->validated()); + + return $this->fractal->item($nest) + ->transformWith(NestTransformer::class) + ->toArray(); + } + + /** + * Deletes an existing nest. + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function delete(DeleteNestRequest $request, Nest $nest): Response + { + $this->nestDeletionService->handle($nest->id); + + return $this->returnNoContent(); + } } diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index 77404945a..41483bf41 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -3,13 +3,14 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nodes; use Pterodactyl\Models\Node; -use Illuminate\Http\JsonResponse; +use Illuminate\Http\Response; use Pterodactyl\Models\Allocation; use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\AllowedFilter; use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Allocations\AssignmentService; use Pterodactyl\Services\Allocations\AllocationDeletionService; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Transformers\Api\Application\AllocationTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest; @@ -33,23 +34,27 @@ class AllocationController extends ApplicationApiController */ public function index(GetAllocationsRequest $request, Node $node): array { - $allocations = QueryBuilder::for($node->allocations()) - ->allowedFilters([ - AllowedFilter::exact('ip'), - AllowedFilter::exact('port'), - 'ip_alias', - AllowedFilter::callback('server_id', function (Builder $builder, $value) { - if (empty($value) || is_bool($value) || !ctype_digit((string) $value)) { - return $builder->whereNull('server_id'); - } + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } - return $builder->where('server_id', $value); + $allocations = QueryBuilder::for(Allocation::query()->where('node_id', '=', $node->id)) + ->allowedFilters([ + 'id', 'ip', 'port', 'alias', + AllowedFilter::callback('server_id', function (Builder $query, $value) { + if ($value === '0') { + $query->whereNull('server_id'); + } else { + $query->where('server_id', '=', $value); + } }), ]) - ->paginate($request->query('per_page') ?? 50); + ->allowedSorts(['id', 'ip', 'port', 'server_id']) + ->paginate($perPage); return $this->fractal->collection($allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -62,11 +67,11 @@ class AllocationController extends ApplicationApiController * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function store(StoreAllocationRequest $request, Node $node): JsonResponse + public function store(StoreAllocationRequest $request, Node $node): Response { $this->assignmentService->handle($node, $request->validated()); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } /** @@ -74,10 +79,10 @@ class AllocationController extends ApplicationApiController * * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException */ - public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): JsonResponse + public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): Response { $this->deletionService->handle($allocation); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php index 2d812a89b..4709e109a 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php @@ -14,8 +14,12 @@ class NodeConfigurationController extends ApplicationApiController * to remote machines so long as an API key is provided to the machine to make the request * with, and the node is known. */ - public function __invoke(GetNodeRequest $request, Node $node): JsonResponse + public function __invoke(GetNodeRequest $request, Node $node): JsonResponse|string { + if ($request->query('format') === 'yaml') { + return $node->getYamlConfiguration(); + } + return new JsonResponse($node->getConfiguration()); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index e0e3575d8..c30272d07 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -3,12 +3,14 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nodes; use Pterodactyl\Models\Node; +use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Nodes\NodeUpdateService; use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Services\Nodes\NodeDeletionService; use Pterodactyl\Transformers\Api\Application\NodeTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest; use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest; use Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest; @@ -34,13 +36,18 @@ class NodeController extends ApplicationApiController */ public function index(GetNodesRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $nodes = QueryBuilder::for(Node::query()) - ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) - ->allowedSorts(['id', 'uuid', 'memory', 'disk']) - ->paginate($request->query('per_page') ?? 50); + ->allowedFilters(['id', 'uuid', 'name', 'fqdn', 'daemon_token_id']) + ->allowedSorts(['id', 'uuid', 'name', 'location_id', 'fqdn', 'memory', 'disk']) + ->paginate($perPage); return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -50,7 +57,7 @@ class NodeController extends ApplicationApiController public function view(GetNodeRequest $request, Node $node): array { return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -65,12 +72,7 @@ class NodeController extends ApplicationApiController $node = $this->creationService->handle($request->validated()); return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.nodes.view', [ - 'node' => $node->id, - ]), - ]) + ->transformWith(NodeTransformer::class) ->respond(201); } @@ -84,11 +86,10 @@ class NodeController extends ApplicationApiController $node = $this->updateService->handle( $node, $request->validated(), - $request->input('reset_secret') === true ); return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -98,10 +99,10 @@ class NodeController extends ApplicationApiController * * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function delete(DeleteNodeRequest $request, Node $node): JsonResponse + public function delete(DeleteNodeRequest $request, Node $node): Response { $this->deletionService->handle($node); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index 4b49fa9c6..fa09e9707 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -30,10 +30,10 @@ class NodeDeploymentController extends ApplicationApiController $nodes = $this->viableNodesService->setLocations($data['location_ids'] ?? []) ->setMemory($data['memory']) ->setDisk($data['disk']) - ->handle($request->query('per_page'), $request->query('page')); + ->handle($request->query('per_page'), $request->query('page')); // @phpstan-ignore-line return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php new file mode 100644 index 000000000..ff099e15b --- /dev/null +++ b/app/Http/Controllers/Api/Application/Nodes/NodeInformationController.php @@ -0,0 +1,47 @@ +cache + ->tags(['nodes']) + ->remember($node->uuid, Carbon::now()->addSeconds(30), function () use ($node) { + return $this->repository->setNode($node)->getSystemInformation(); + }); + + return new JsonResponse([ + 'version' => $data['version'] ?? null, + 'system' => [ + 'type' => Str::title($data['os'] ?? 'Unknown'), + 'arch' => $data['architecture'] ?? null, + 'release' => $data['kernel_version'] ?? null, + 'cpus' => $data['cpu_count'] ?? null, + ], + ]); + } +} diff --git a/app/Http/Controllers/Api/Application/Roles/RoleController.php b/app/Http/Controllers/Api/Application/Roles/RoleController.php new file mode 100644 index 000000000..21ec404aa --- /dev/null +++ b/app/Http/Controllers/Api/Application/Roles/RoleController.php @@ -0,0 +1,96 @@ +query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + + $roles = QueryBuilder::for(AdminRole::query()) + ->allowedFilters(['id', 'name']) + ->allowedSorts(['id', 'name']) + ->paginate($perPage); + + return $this->fractal->collection($roles) + ->transformWith(AdminRoleTransformer::class) + ->toArray(); + } + + /** + * Returns a single role. + */ + public function view(GetRoleRequest $request, AdminRole $role): array + { + return $this->fractal->item($role) + ->transformWith(AdminRoleTransformer::class) + ->toArray(); + } + + /** + * Creates a new role. + */ + public function store(StoreRoleRequest $request): JsonResponse + { + $data = array_merge($request->validated(), [ + 'sort_id' => 99, + ]); + $role = AdminRole::query()->create($data); + + return $this->fractal->item($role) + ->transformWith(AdminRoleTransformer::class) + ->respond(JsonResponse::HTTP_CREATED); + } + + /** + * Updates a role. + */ + public function update(UpdateRoleRequest $request, AdminRole $role): array + { + $role->update($request->validated()); + + return $this->fractal->item($role) + ->transformWith(AdminRoleTransformer::class) + ->toArray(); + } + + /** + * Deletes a role. + * + * @throws \Exception + */ + public function delete(DeleteRoleRequest $request, AdminRole $role): Response + { + $role->delete(); + + return $this->returnNoContent(); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index 1717c5dd8..21f0da45f 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -34,7 +34,7 @@ class DatabaseController extends ApplicationApiController public function index(GetServerDatabasesRequest $request, Server $server): array { return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->transformWith(ServerDatabaseTransformer::class) ->toArray(); } @@ -44,7 +44,7 @@ class DatabaseController extends ApplicationApiController public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array { return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->transformWith(ServerDatabaseTransformer::class) ->toArray(); } @@ -53,11 +53,11 @@ class DatabaseController extends ApplicationApiController * * @throws \Throwable */ - public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse + public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response { $this->databasePasswordService->handle($database); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } /** @@ -72,23 +72,19 @@ class DatabaseController extends ApplicationApiController ])); return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.servers.databases.view', [ - 'server' => $server->id, - 'database' => $database->id, - ]), - ]) + ->transformWith(ServerDatabaseTransformer::class) ->respond(Response::HTTP_CREATED); } /** * Handle a request to delete a specific server database from the Panel. + * + * @throws \Exception */ - public function delete(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response + public function delete(ServerDatabaseWriteRequest $request, Database $database): Response { $this->databaseManagementService->delete($database); - return response('', 204); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php index 869472f72..3f6a3624c 100644 --- a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php @@ -17,7 +17,7 @@ class ExternalServerController extends ApplicationApiController $server = Server::query()->where('external_id', $external_id)->firstOrFail(); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 2eb69162d..ef633d46b 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -8,12 +8,16 @@ use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; +use Pterodactyl\Services\Servers\BuildModificationService; +use Pterodactyl\Services\Servers\DetailsModificationService; use Pterodactyl\Transformers\Api\Application\ServerTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; +use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerRequest; class ServerController extends ApplicationApiController { @@ -21,6 +25,8 @@ class ServerController extends ApplicationApiController * ServerController constructor. */ public function __construct( + private BuildModificationService $buildModificationService, + private DetailsModificationService $detailsModificationService, private ServerCreationService $creationService, private ServerDeletionService $deletionService ) { @@ -32,13 +38,18 @@ class ServerController extends ApplicationApiController */ public function index(GetServersRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $servers = QueryBuilder::for(Server::query()) - ->allowedFilters(['uuid', 'uuidShort', 'name', 'description', 'image', 'external_id']) - ->allowedSorts(['id', 'uuid']) - ->paginate($request->query('per_page') ?? 50); + ->allowedFilters(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'external_id']) + ->allowedSorts(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'status']) + ->paginate($perPage); return $this->fractal->collection($servers) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } @@ -48,18 +59,17 @@ class ServerController extends ApplicationApiController * @throws \Throwable * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException */ public function store(StoreServerRequest $request): JsonResponse { - $server = $this->creationService->handle($request->validated(), $request->getDeploymentObject()); + $server = $this->creationService->handle($request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) - ->respond(201); + ->transformWith(ServerTransformer::class) + ->respond(Response::HTTP_CREATED); } /** @@ -68,7 +78,7 @@ class ServerController extends ApplicationApiController public function view(GetServerRequest $request, Server $server): array { return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } @@ -76,6 +86,7 @@ class ServerController extends ApplicationApiController * Deletes a server. * * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Throwable */ public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response { @@ -83,4 +94,24 @@ class ServerController extends ApplicationApiController return $this->returnNoContent(); } + + /** + * Update a server. + * + * @throws \Throwable + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + */ + public function update(UpdateServerRequest $request, Server $server): array + { + $server = $this->buildModificationService->handle($server, $request->validated()); + $server = $this->detailsModificationService->returnUpdatedModel()->handle($server, $request->validated()); + + return $this->fractal->item($server) + ->transformWith(ServerTransformer::class) + ->toArray(); + } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index ae5f5438c..3201c7b7d 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -25,35 +25,31 @@ class ServerDetailsController extends ApplicationApiController /** * Update the details for a specific server. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function details(UpdateServerDetailsRequest $request, Server $server): array { - $updated = $this->detailsModificationService->returnUpdatedModel()->handle( + $server = $this->detailsModificationService->returnUpdatedModel()->handle( $server, $request->validated() ); - return $this->fractal->item($updated) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->fractal->item($server) + ->transformWith(ServerTransformer::class) ->toArray(); } /** * Update the build details for a specific server. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function build(UpdateServerBuildConfigurationRequest $request, Server $server): array { $server = $this->buildModificationService->handle($server, $request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php index d4dcaa24c..8e20d098b 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php @@ -12,7 +12,7 @@ use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class ServerManagementController extends ApplicationApiController { /** - * ServerManagementController constructor. + * SuspensionController constructor. */ public function __construct( private ReinstallServerService $reinstallServerService, @@ -48,9 +48,7 @@ class ServerManagementController extends ApplicationApiController /** * Mark a server as needing to be reinstalled. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function reinstall(ServerWriteRequest $request, Server $server): Response { diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php index 6c2da078a..ea6c28ff0 100644 --- a/app/Http/Controllers/Api/Application/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -22,10 +22,7 @@ class StartupController extends ApplicationApiController /** * Update the startup and environment settings for a specific server. * - * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function index(UpdateServerStartupRequest $request, Server $server): array { @@ -34,7 +31,7 @@ class StartupController extends ApplicationApiController ->handle($server, $request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php index 2a8f4f07e..380c0cc3b 100644 --- a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php +++ b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php @@ -17,7 +17,7 @@ class ExternalUserController extends ApplicationApiController $user = User::query()->where('external_id', $external_id)->firstOrFail(); return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 0ddae27b1..48fd58133 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -2,13 +2,19 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; +use Illuminate\Support\Arr; use Pterodactyl\Models\User; +use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; +use Spatie\QueryBuilder\AllowedFilter; +use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; use Pterodactyl\Transformers\Api\Application\UserTransformer; +use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; +use Pterodactyl\Http\Requests\Api\Application\Users\GetUserRequest; use Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest; use Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest; use Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest; @@ -35,24 +41,48 @@ class UserController extends ApplicationApiController */ public function index(GetUsersRequest $request): array { + $perPage = (int) $request->query('per_page', '10'); + if ($perPage < 1 || $perPage > 100) { + throw new QueryValueOutOfRangeHttpException('per_page', 1, 100); + } + $users = QueryBuilder::for(User::query()) - ->allowedFilters(['email', 'uuid', 'username', 'external_id']) - ->allowedSorts(['id', 'uuid']) - ->paginate($request->query('per_page') ?? 50); + ->allowedFilters([ + AllowedFilter::exact('id'), + AllowedFilter::exact('uuid'), + AllowedFilter::exact('external_id'), + 'username', + 'email', + AllowedFilter::callback('*', function (Builder $builder, $value) { + foreach (Arr::wrap($value) as $datum) { + $datum = '%' . $datum . '%'; + $builder->where(function (Builder $builder) use ($datum) { + $builder->where('uuid', 'LIKE', $datum) + ->orWhere('username', 'LIKE', $datum) + ->orWhere('email', 'LIKE', $datum) + ->orWhere('external_id', 'LIKE', $datum); + }); + } + }), + ]) + ->allowedSorts(['id', 'uuid', 'username', 'email', 'admin_role_id']) + ->paginate($perPage); return $this->fractal->collection($users) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } /** * Handle a request to view a single user. Includes any relations that * were defined in the request. + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function view(GetUsersRequest $request, User $user): array + public function view(GetUserRequest $request, User $user): array { return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } @@ -64,22 +94,20 @@ class UserController extends ApplicationApiController * Revocation errors are returned under the 'revocation_errors' key in the response * meta. If there are no errors this is an empty array. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function update(UpdateUserRequest $request, User $user): array { $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); $user = $this->updateService->handle($user, $request->validated()); - $response = $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)); - - return $response->toArray(); + return $this->fractal->item($user) + ->transformWith(UserTransformer::class) + ->toArray(); } /** - * Store a new user on the system. Returns the created user and an HTTP/201 + * Store a new user on the system. Returns the created user and a HTTP/201 * header on successful creation. * * @throws \Exception @@ -90,12 +118,7 @@ class UserController extends ApplicationApiController $user = $this->creationService->handle($request->validated()); return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) - ->addMeta([ - 'resource' => route('api.application.users.view', [ - 'user' => $user->id, - ]), - ]) + ->transformWith(UserTransformer::class) ->respond(201); } @@ -105,10 +128,10 @@ class UserController extends ApplicationApiController * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(DeleteUserRequest $request, User $user): JsonResponse + public function delete(DeleteUserRequest $request, User $user): Response { $this->deletionService->handle($user); - return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + return $this->returnNoContent(); } } diff --git a/app/Http/Controllers/Api/Application/VersionController.php b/app/Http/Controllers/Api/Application/VersionController.php new file mode 100644 index 000000000..9bd2203ed --- /dev/null +++ b/app/Http/Controllers/Api/Application/VersionController.php @@ -0,0 +1,25 @@ +softwareVersionService->getVersionData()); + } +} diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index cbe8b0315..468a751c0 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -25,7 +25,7 @@ class AccountController extends ClientApiController public function index(Request $request): array { return $this->fractal->item($request->user()) - ->transformWith($this->getTransformer(AccountTransformer::class)) + ->transformWith(AccountTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/ActivityLogController.php b/app/Http/Controllers/Api/Client/ActivityLogController.php index dbbd06c06..f6e9033e2 100644 --- a/app/Http/Controllers/Api/Client/ActivityLogController.php +++ b/app/Http/Controllers/Api/Client/ActivityLogController.php @@ -24,7 +24,7 @@ class ActivityLogController extends ClientApiController ->appends($request->query()); return $this->fractal->collection($activity) - ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + ->transformWith(ActivityLogTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index ac00a4d8f..e60ac59cd 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -18,7 +18,7 @@ class ApiKeyController extends ClientApiController public function index(ClientApiRequest $request): array { return $this->fractal->collection($request->user()->apiKeys) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + ->transformWith(ApiKeyTransformer::class) ->toArray(); } @@ -44,7 +44,7 @@ class ApiKeyController extends ClientApiController ->log(); return $this->fractal->item($token->accessToken) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + ->transformWith(ApiKeyTransformer::class) ->addMeta(['secret_token' => $token->plainTextToken]) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index a468d762a..380cbf548 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -3,7 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Webmozart\Assert\Assert; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; abstract class ClientApiController extends ApplicationApiController @@ -11,7 +11,7 @@ abstract class ClientApiController extends ApplicationApiController /** * Returns only the includes which are valid for the given transformer. */ - protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []): array + protected function getIncludesForTransformer(Transformer $transformer, array $merge = []): array { $filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) { return in_array($datum, $transformer->getAvailableIncludes()); @@ -35,22 +35,4 @@ abstract class ClientApiController extends ApplicationApiController return trim($item); }, explode(',', $includes)); } - - /** - * Return an instance of an application transformer. - * - * @template T of \Pterodactyl\Transformers\Api\Client\BaseClientTransformer - * - * @param class-string $abstract - * - * @return T - * - * @noinspection PhpDocSignatureInspection - */ - public function getTransformer(string $abstract) - { - Assert::subclassOf($abstract, BaseClientTransformer::class); - - return $abstract::fromRequest($this->request); - } } diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index dcdb5964b..d4d5ae6c5 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -27,7 +27,7 @@ class ClientController extends ClientApiController public function index(GetServersRequest $request): array { $user = $request->user(); - $transformer = $this->getTransformer(ServerTransformer::class); + $transformer = new ServerTransformer(); // Start the query builder and ensure we eager load any requested relationships from the request. $builder = QueryBuilder::for( diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index aa0f0c686..794c44cf0 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -17,7 +17,7 @@ class SSHKeyController extends ClientApiController public function index(ClientApiRequest $request): array { return $this->fractal->collection($request->user()->sshKeys) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + ->transformWith(UserSSHKeyTransformer::class) ->toArray(); } @@ -38,7 +38,7 @@ class SSHKeyController extends ClientApiController ->log(); return $this->fractal->item($model) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + ->transformWith(UserSSHKeyTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php index f569c4122..e3e083d48 100644 --- a/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php +++ b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php @@ -48,7 +48,7 @@ class ActivityLogController extends ClientApiController ->appends($request->query()); return $this->fractal->collection($activity) - ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + ->transformWith(ActivityLogTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index 7a35341c1..13c9ac7ea 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -49,7 +49,7 @@ class BackupController extends ClientApiController $limit = min($request->query('per_page') ?? 20, 50); return $this->fractal->collection($server->backups()->paginate($limit)) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->addMeta([ 'backup_count' => $this->repository->getNonFailedBackups($server)->count(), ]) @@ -84,7 +84,7 @@ class BackupController extends ClientApiController ->log(); return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } @@ -107,7 +107,7 @@ class BackupController extends ClientApiController Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } @@ -123,7 +123,7 @@ class BackupController extends ClientApiController } return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 728c67e91..5173cab2c 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -35,7 +35,7 @@ class DatabaseController extends ClientApiController public function index(GetDatabasesRequest $request, Server $server): array { return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } @@ -57,7 +57,7 @@ class DatabaseController extends ClientApiController return $this->fractal->item($database) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } @@ -79,7 +79,7 @@ class DatabaseController extends ClientApiController return $this->fractal->item($database) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 23e6718a5..eaee7f8cc 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -47,7 +47,7 @@ class FileController extends ClientApiController ->getDirectory($request->get('directory') ?? '/'); return $this->fractal->collection($contents) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->transformWith(FileObjectTransformer::class) ->toArray(); } @@ -183,7 +183,7 @@ class FileController extends ClientApiController ->log(); return $this->fractal->item($file) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->transformWith(FileObjectTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 4e3c5f9bb..2fd97101b 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -36,7 +36,7 @@ class NetworkAllocationController extends ClientApiController public function index(GetNetworkRequest $request, Server $server): array { return $this->fractal->collection($server->allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -60,7 +60,7 @@ class NetworkAllocationController extends ClientApiController } return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -80,7 +80,7 @@ class NetworkAllocationController extends ClientApiController ->log(); return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -104,7 +104,7 @@ class NetworkAllocationController extends ClientApiController ->log(); return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index dcaf48115..daf3219a1 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -35,7 +35,7 @@ class ResourceUtilizationController extends ClientApiController }); return $this->fractal->item($stats) - ->transformWith($this->getTransformer(StatsTransformer::class)) + ->transformWith(StatsTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index b955fb16b..20fc4e658 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -40,7 +40,7 @@ class ScheduleController extends ClientApiController $schedules = $server->schedules->loadMissing('tasks'); return $this->fractal->collection($schedules) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -72,7 +72,7 @@ class ScheduleController extends ClientApiController ->log(); return $this->fractal->item($model) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -88,7 +88,7 @@ class ScheduleController extends ClientApiController $schedule->loadMissing('tasks'); return $this->fractal->item($schedule) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -131,7 +131,7 @@ class ScheduleController extends ClientApiController ->log(); return $this->fractal->item($schedule->refresh()) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index addca2876..e6427753b 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -64,7 +64,7 @@ class ScheduleTaskController extends ClientApiController ->log(); return $this->fractal->item($task) - ->transformWith($this->getTransformer(TaskTransformer::class)) + ->transformWith(TaskTransformer::class) ->toArray(); } @@ -97,7 +97,7 @@ class ScheduleTaskController extends ClientApiController ->log(); return $this->fractal->item($task->refresh()) - ->transformWith($this->getTransformer(TaskTransformer::class)) + ->transformWith(TaskTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php index 63eb9b988..ac898e2b6 100644 --- a/app/Http/Controllers/Api/Client/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -25,7 +25,7 @@ class ServerController extends ClientApiController public function index(GetServerRequest $request, Server $server): array { return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->addMeta([ 'is_server_owner' => $request->user()->id === $server->owner_id, 'user_permissions' => $this->permissionsService->handle($server, $request->user()), diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index b674145ff..56b632617 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -34,7 +34,7 @@ class StartupController extends ClientApiController return $this->fractal->collection( $server->variables()->where('user_viewable', true)->get() ) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->transformWith(EggVariableTransformer::class) ->addMeta([ 'startup_command' => $startup, 'docker_images' => $server->egg->docker_images, @@ -90,7 +90,7 @@ class StartupController extends ClientApiController } return $this->fractal->item($variable) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->transformWith(EggVariableTransformer::class) ->addMeta([ 'startup_command' => $startup, 'raw_startup_command' => $server->startup, diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 2c403c691..ac402abb9 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -38,7 +38,7 @@ class SubuserController extends ClientApiController public function index(GetSubuserRequest $request, Server $server): array { return $this->fractal->collection($server->subusers) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -50,7 +50,7 @@ class SubuserController extends ClientApiController $subuser = $request->attributes->get('subuser'); return $this->fractal->item($subuser) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -76,7 +76,7 @@ class SubuserController extends ClientApiController ->log(); return $this->fractal->item($response) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -129,7 +129,7 @@ class SubuserController extends ClientApiController $log->reset(); return $this->fractal->item($subuser->refresh()) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index 74e7a9aa3..c76a5afc2 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -85,7 +85,7 @@ abstract class AbstractLoginController extends Controller 'data' => [ 'complete' => true, 'intended' => $this->redirectPath(), - 'user' => $user->toVueObject(), + 'user' => $user->toReactObject(), ], ]); } diff --git a/app/Http/Requests/Api/ApiRequest.php b/app/Http/Requests/Api/ApiRequest.php new file mode 100644 index 000000000..253a61691 --- /dev/null +++ b/app/Http/Requests/Api/ApiRequest.php @@ -0,0 +1,83 @@ +passesAuthorization()) { + $this->failedAuthorization(); + } + + $this->hasValidated = true; + } + + /* + * Determine if the request passes the authorization check as well + * as the exists check. + * + * @return bool + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + protected function passesAuthorization() + { + // If we have already validated we do not need to call this function + // again. This is needed to work around Laravel's normal auth validation + // that occurs after validating the request params since we are doing auth + // validation in the prepareForValidation() function. + if ($this->hasValidated) { + return true; + } + + if (!parent::passesAuthorization()) { + return false; + } + + // Only let the user know that a resource does not exist if they are + // authenticated to access the endpoint. This avoids exposing that + // an item exists (or does not exist) to the user until they can prove + // that they have permission to know about it. + if ($this->attributes->get('is_missing_model', false)) { + throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); + } + + return true; + } +} diff --git a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php index 6529a9a5a..d062f7648 100644 --- a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteAllocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php index f03223f2d..2f536af60 100644 --- a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetAllocationsRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php index a7e0c4da2..0474408ab 100644 --- a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -2,15 +2,11 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Services\Acl\Api\AdminAcl; +use Illuminate\Support\Arr; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreAllocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS; - - protected int $permission = AdminAcl::WRITE; - public function rules(): array { return [ @@ -21,14 +17,22 @@ class StoreAllocationRequest extends ApplicationApiRequest ]; } - public function validated($key = null, $default = null): array + /** + * @param string|null $key + * @param string|array|null $default + * + * @return mixed + */ + public function validated($key = null, $default = null) { $data = parent::validated(); - return [ + $response = [ 'allocation_ip' => $data['ip'], 'allocation_ports' => $data['ports'], 'allocation_alias' => $data['alias'] ?? null, ]; + + return is_null($key) ? $response : Arr::get($response, $key, $default); } } diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 082bc6921..70f6cb219 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -2,94 +2,16 @@ namespace Pterodactyl\Http\Requests\Api\Application; -use Webmozart\Assert\Assert; -use Pterodactyl\Models\ApiKey; -use Laravel\Sanctum\TransientToken; -use Illuminate\Validation\Validator; -use Illuminate\Database\Eloquent\Model; -use Pterodactyl\Services\Acl\Api\AdminAcl; -use Illuminate\Foundation\Http\FormRequest; -use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Http\Requests\Api\ApiRequest; -abstract class ApplicationApiRequest extends FormRequest +abstract class ApplicationApiRequest extends ApiRequest { /** - * The resource that should be checked when performing the authorization - * function for this request. - */ - protected ?string $resource; - - /** - * The permission level that a given API key should have for accessing - * the defined $resource during the request cycle. - */ - protected int $permission = AdminAcl::NONE; - - /** - * Determine if the current user is authorized to perform - * the requested action against the API. - * - * @throws PterodactylException + * This will eventually be replaced with per-request permissions checking + * on the API key and for the user. */ public function authorize(): bool { - if (is_null($this->resource)) { - throw new PterodactylException('An ACL resource must be defined on API requests.'); - } - - $token = $this->user()->currentAccessToken(); - if ($token instanceof TransientToken) { - return true; - } - - /** @var ApiKey $token */ - if ($token->key_type === ApiKey::TYPE_ACCOUNT) { - return true; - } - - return AdminAcl::check($token, $this->resource, $this->permission); - } - - /** - * Default set of rules to apply to API requests. - */ - public function rules(): array - { - return []; - } - - /** - * Helper method allowing a developer to easily hook into this logic without having - * to remember what the method name is called or where to use it. By default this is - * a no-op. - */ - public function withValidator(Validator $validator): void - { - // do nothing - } - - /** - * Returns the named route parameter and asserts that it is a real model that - * exists in the database. - * - * @template T of \Illuminate\Database\Eloquent\Model - * - * @param class-string $expect - * - * @return T - * - * @noinspection PhpDocSignatureInspection - */ - public function parameter(string $key, string $expect) - { - /** @var ApiKey $value */ - $value = $this->route()->parameter($key); - - Assert::isInstanceOf($value, $expect); - Assert::isInstanceOf($value, Model::class); - Assert::true($value->exists); - - /* @var T $value */ - return $value; + return $this->user()->root_admin; } } diff --git a/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php b/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php new file mode 100644 index 000000000..cde56da1c --- /dev/null +++ b/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php @@ -0,0 +1,9 @@ +route()->parameter('databaseHost')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php new file mode 100644 index 000000000..154f06efd --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php @@ -0,0 +1,16 @@ +route()->parameter('egg'); + + return $egg instanceof Egg && $egg->exists; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php b/app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php new file mode 100644 index 000000000..63893df54 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/ExportEggRequest.php @@ -0,0 +1,9 @@ + 'required|bail|numeric|exists:nests,id', + 'name' => 'required|string|max:191', + 'description' => 'sometimes|string|nullable', + 'features' => 'sometimes|array', + 'docker_images' => 'required|array|min:1', + 'docker_images.*' => 'required|string', + 'file_denylist' => 'sometimes|array|nullable', + 'file_denylist.*' => 'sometimes|string', + 'config_files' => 'required|nullable|json', + 'config_startup' => 'required|nullable|json', + 'config_stop' => 'required|nullable|string|max:191', +// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id', + 'startup' => 'required|string', + 'script_container' => 'sometimes|string', + 'script_entry' => 'sometimes|string', + 'script_install' => 'sometimes|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php b/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php new file mode 100644 index 000000000..090f5b51c --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php @@ -0,0 +1,28 @@ + 'sometimes|numeric|exists:nests,id', + 'name' => 'sometimes|string|max:191', + 'description' => 'sometimes|string|nullable', + 'features' => 'sometimes|array', + 'docker_images' => 'sometimes|array|min:1', + 'docker_images.*' => 'sometimes|string', + 'file_denylist' => 'sometimes|array|nullable', + 'file_denylist.*' => 'sometimes|string', + 'config_files' => 'sometimes|nullable|json', + 'config_startup' => 'sometimes|nullable|json', + 'config_stop' => 'sometimes|nullable|string|max:191', +// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id', + 'startup' => 'sometimes|string', + 'script_container' => 'sometimes|string', + 'script_entry' => 'sometimes|string', + 'script_install' => 'sometimes|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php b/app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php new file mode 100644 index 000000000..9c674a9d8 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/Variables/StoreEggVariableRequest.php @@ -0,0 +1,22 @@ + 'required|string|min:1|max:191', + 'description' => 'sometimes|string|nullable', + 'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, + 'default_value' => 'present', + 'user_viewable' => 'required|boolean', + 'user_editable' => 'required|boolean', + 'rules' => 'bail|required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php b/app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php new file mode 100644 index 000000000..c15de2ce3 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/Variables/UpdateEggVariablesRequest.php @@ -0,0 +1,24 @@ + 'array', + '*.id' => 'required|integer', + '*.name' => 'sometimes|string|min:1|max:191', + '*.description' => 'sometimes|string|nullable', + '*.env_variable' => 'sometimes|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, + '*.default_value' => 'sometimes|present', + '*.user_viewable' => 'sometimes|boolean', + '*.user_editable' => 'sometimes|boolean', + '*.rules' => 'sometimes|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php index 3c41e1166..eb2cffd34 100644 --- a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteLocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php index 65157dc47..dea300b91 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetLocationsRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php index cf0b12629..9b403fa10 100644 --- a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php @@ -3,18 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; use Pterodactyl\Models\Location; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreLocationRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS; - - protected int $permission = AdminAcl::WRITE; - - /** - * Rules to validate the request against. - */ public function rules(): array { return collect(Location::getRules())->only([ @@ -23,9 +15,6 @@ class StoreLocationRequest extends ApplicationApiRequest ])->toArray(); } - /** - * Rename fields to be more clear in error messages. - */ public function attributes(): array { return [ diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index b7acac978..91ece11fe 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -6,14 +6,9 @@ use Pterodactyl\Models\Location; class UpdateLocationRequest extends StoreLocationRequest { - /** - * Rules to validate this request against. - */ public function rules(): array { - /** @var Location $location */ - $location = $this->route()->parameter('location'); - $locationId = $location->id; + $locationId = $this->route()->parameter('location')->id; return collect(Location::getRulesForUpdate($locationId))->only([ 'short', diff --git a/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php b/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php new file mode 100644 index 000000000..1325510f2 --- /dev/null +++ b/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php @@ -0,0 +1,9 @@ + 'required|exists:eggs,id']; + } +} diff --git a/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php b/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php new file mode 100644 index 000000000..4810591a8 --- /dev/null +++ b/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php @@ -0,0 +1,13 @@ + 'required|exists:nodes,id']; + } +} diff --git a/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php b/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php new file mode 100644 index 000000000..ba678d186 --- /dev/null +++ b/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php @@ -0,0 +1,14 @@ +route()->parameter('mount')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php new file mode 100644 index 000000000..8d505c93e --- /dev/null +++ b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php @@ -0,0 +1,9 @@ +route()->parameter('nest')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php index 01f503f3f..4bd5159d0 100644 --- a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteNodeRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_NODES; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php index 5c8524b94..ac6191ea5 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetNodesRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_NODES; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 4fe705448..803f0e6a3 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -2,48 +2,50 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; +use Illuminate\Support\Arr; use Pterodactyl\Models\Node; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreNodeRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_NODES; - - protected int $permission = AdminAcl::WRITE; - /** * Validation rules to apply to this request. */ public function rules(array $rules = null): array { return collect($rules ?? Node::getRules())->only([ - 'public', 'name', + 'description', 'location_id', + 'database_host_id', 'fqdn', 'scheme', 'behind_proxy', - 'maintenance_mode', + 'public', + + 'listen_port_http', + 'public_port_http', + 'listen_port_sftp', + 'public_port_sftp', + 'memory', 'memory_overallocate', 'disk', 'disk_overallocate', - 'upload_size', - 'daemonListen', - 'daemonSFTP', - 'daemonBase', - ])->mapWithKeys(function ($value, $key) { - $key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key; + 'upload_size', + 'daemon_base', + ])->mapWithKeys(function ($value, $key) { return [snake_case($key) => $value]; })->toArray(); } /** * Fields to rename for clarity in the API response. + * + * @return array */ - public function attributes(): array + public function attributes() { return [ 'daemon_base' => 'Daemon Base Path', @@ -56,15 +58,20 @@ class StoreNodeRequest extends ApplicationApiRequest /** * Change the formatting of some data keys in the validated response data * to match what the application expects in the services. + * + * @param string|null $key + * @param string|array|null $default + * + * @return mixed */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { $response = parent::validated(); - $response['daemonListen'] = $response['daemon_listen']; - $response['daemonSFTP'] = $response['daemon_sftp']; - $response['daemonBase'] = $response['daemon_base'] ?? (new Node())->getAttribute('daemonBase'); + $response['daemon_base'] = $response['daemon_base'] ?? Node::DEFAULT_DAEMON_BASE; - unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']); + if (!is_null($key)) { + return Arr::get($response, $key, $default); + } return $response; } diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index de6b7b45c..05daebc2e 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -6,15 +6,8 @@ use Pterodactyl\Models\Node; class UpdateNodeRequest extends StoreNodeRequest { - /** - * Apply validation rules to this request. Uses the parent class rules() - * function but passes in the rules for updating rather than creating. - */ public function rules(array $rules = null): array { - /** @var Node $node */ - $node = $this->route()->parameter('node'); - - return parent::rules(Node::getRulesForUpdate($node->id)); + return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node')->id)); } } diff --git a/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php b/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php new file mode 100644 index 000000000..5f6cd34b5 --- /dev/null +++ b/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php @@ -0,0 +1,9 @@ +route()->parameter('role')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php index 01df4af32..dd1dd2fd1 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetServerDatabaseRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php index ce72bbc20..74f942278 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetServerDatabasesRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php index 66cec82c3..827d68c55 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php @@ -2,9 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; -use Pterodactyl\Services\Acl\Api\AdminAcl; - class ServerDatabaseWriteRequest extends GetServerDatabasesRequest { - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index f52bed805..17368cd08 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -2,26 +2,18 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; +use Illuminate\Support\Arr; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; use Illuminate\Database\Query\Builder; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Databases\DatabaseManagementService; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreServerDatabaseRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES; - - protected int $permission = AdminAcl::WRITE; - - /** - * Validation rules for database creation. - */ public function rules(): array { - /** @var Server $server */ $server = $this->route()->parameter('server'); return [ @@ -40,20 +32,22 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest } /** - * Return data formatted in the correct format for the service to consume. + * @param string|null $key + * @param string|array|null $default + * + * @return mixed */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { - return [ + $data = [ 'database' => $this->input('database'), 'remote' => $this->input('remote'), 'database_host_id' => $this->input('host'), ]; + + return is_null($key) ? $data : Arr::get($data, $key, $default); } - /** - * Format error messages in a more understandable format for API output. - */ public function attributes(): array { return [ @@ -63,12 +57,8 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest ]; } - /** - * Returns the database name in the expected format. - */ public function databaseName(): string { - /** @var Server $server */ $server = $this->route()->parameter('server'); Assert::isInstanceOf($server, Server::class); diff --git a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php index 50c9dabf8..790f55798 100644 --- a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalServerRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php index 63c4ea86a..2f4f417cd 100644 --- a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetServerRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php index df2d76cd3..e8d01a115 100644 --- a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class ServerWriteRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index a9d0ecbed..6a8956729 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -2,22 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\Server; -use Illuminate\Validation\Rule; -use Illuminate\Validation\Validator; -use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Models\Objects\DeploymentObject; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreServerRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::WRITE; - - /** - * Rules to be applied to this request. - */ public function rules(): array { $rules = Server::getRules(); @@ -26,15 +16,9 @@ class StoreServerRequest extends ApplicationApiRequest 'external_id' => $rules['external_id'], 'name' => $rules['name'], 'description' => array_merge(['nullable'], $rules['description']), - 'user' => $rules['owner_id'], - 'egg' => $rules['egg_id'], - 'docker_image' => $rules['image'], - 'startup' => $rules['startup'], - 'environment' => 'present|array', - 'skip_scripts' => 'sometimes|boolean', - 'oom_disabled' => 'sometimes|boolean', + 'owner_id' => $rules['owner_id'], + 'node_id' => $rules['node_id'], - // Resource limitations 'limits' => 'required|array', 'limits.memory' => $rules['memory'], 'limits.swap' => $rules['swap'], @@ -42,110 +26,64 @@ class StoreServerRequest extends ApplicationApiRequest 'limits.io' => $rules['io'], 'limits.threads' => $rules['threads'], 'limits.cpu' => $rules['cpu'], + 'limits.oom_killer' => 'required|boolean', - // Application Resource Limits 'feature_limits' => 'required|array', - 'feature_limits.databases' => $rules['database_limit'], 'feature_limits.allocations' => $rules['allocation_limit'], 'feature_limits.backups' => $rules['backup_limit'], + 'feature_limits.databases' => $rules['database_limit'], - // Placeholders for rules added in withValidator() function. - 'allocation.default' => '', - 'allocation.additional.*' => '', + 'allocation.default' => 'required|bail|integer|exists:allocations,id', + 'allocation.additional.*' => 'integer|exists:allocations,id', - // Automatic deployment rules - 'deploy' => 'sometimes|required|array', - 'deploy.locations' => 'array', - 'deploy.locations.*' => 'integer|min:1', - 'deploy.dedicated_ip' => 'required_with:deploy,boolean', - 'deploy.port_range' => 'array', - 'deploy.port_range.*' => 'string', - - 'start_on_completion' => 'sometimes|boolean', + 'startup' => $rules['startup'], + 'environment' => 'present|array', + 'egg_id' => $rules['egg_id'], + 'image' => $rules['image'], + 'skip_scripts' => 'present|boolean', ]; } /** - * Normalize the data into a format that can be consumed by the service. + * @param string|null $key + * @param string|array|null $default + * + * @return array */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { $data = parent::validated(); - return [ + $response = [ 'external_id' => array_get($data, 'external_id'), 'name' => array_get($data, 'name'), 'description' => array_get($data, 'description'), - 'owner_id' => array_get($data, 'user'), - 'egg_id' => array_get($data, 'egg'), - 'image' => array_get($data, 'docker_image'), - 'startup' => array_get($data, 'startup'), - 'environment' => array_get($data, 'environment'), + 'owner_id' => array_get($data, 'owner_id'), + 'node_id' => array_get($data, 'node_id'), + 'memory' => array_get($data, 'limits.memory'), 'swap' => array_get($data, 'limits.swap'), 'disk' => array_get($data, 'limits.disk'), 'io' => array_get($data, 'limits.io'), - 'cpu' => array_get($data, 'limits.cpu'), 'threads' => array_get($data, 'limits.threads'), - 'skip_scripts' => array_get($data, 'skip_scripts', false), - 'allocation_id' => array_get($data, 'allocation.default'), - 'allocation_additional' => array_get($data, 'allocation.additional'), - 'start_on_completion' => array_get($data, 'start_on_completion', false), - 'database_limit' => array_get($data, 'feature_limits.databases'), + 'cpu' => array_get($data, 'limits.cpu'), + 'oom_disabled' => !array_get($data, 'limits.oom_killer'), + 'allocation_limit' => array_get($data, 'feature_limits.allocations'), 'backup_limit' => array_get($data, 'feature_limits.backups'), - 'oom_disabled' => array_get($data, 'oom_disabled'), + 'database_limit' => array_get($data, 'feature_limits.databases'), + + 'allocation_id' => array_get($data, 'allocation.default'), + 'allocation_additional' => array_get($data, 'allocation.additional'), + + 'startup' => array_get($data, 'startup'), + 'environment' => array_get($data, 'environment'), + 'egg_id' => array_get($data, 'egg_id'), + 'image' => array_get($data, 'image'), + 'skip_scripts' => array_get($data, 'skip_scripts'), + 'start_on_completion' => array_get($data, 'start_on_completion', false), ]; - } - /* - * Run validation after the rules above have been applied. - * - * @param \Illuminate\Validation\Validator $validator - */ - public function withValidator(Validator $validator): void - { - $validator->sometimes('allocation.default', [ - 'required', 'integer', 'bail', - Rule::exists('allocations', 'id')->where(function ($query) { - $query->whereNull('server_id'); - }), - ], function ($input) { - return !$input->deploy; - }); - - $validator->sometimes('allocation.additional.*', [ - 'integer', - Rule::exists('allocations', 'id')->where(function ($query) { - $query->whereNull('server_id'); - }), - ], function ($input) { - return !$input->deploy; - }); - - $validator->sometimes('deploy.locations', 'present', function ($input) { - return $input->deploy; - }); - - $validator->sometimes('deploy.port_range', 'present', function ($input) { - return $input->deploy; - }); - } - - /** - * Return a deployment object that can be passed to the server creation service. - */ - public function getDeploymentObject(): ?DeploymentObject - { - if (is_null($this->input('deploy'))) { - return null; - } - - $object = new DeploymentObject(); - $object->setDedicated($this->input('deploy.dedicated_ip', false)); - $object->setLocations($this->input('deploy.locations', [])); - $object->setPorts($this->input('deploy.port_range', [])); - - return $object; + return is_null($key) ? $response : Arr::get($response, $key, $default); } } diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index f1c977f11..1e2f12051 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\Server; use Illuminate\Support\Collection; @@ -12,7 +13,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); return [ 'allocation' => $rules['allocation_id'], @@ -26,7 +27,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest 'limits.threads' => $this->requiredToOptional('threads', $rules['threads'], true), 'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true), - // Legacy rules to maintain backwards compatable API support without requiring + // Legacy rules to maintain backwards compatible API support without requiring // a major version bump. // // @see https://github.com/pterodactyl/panel/issues/1500 @@ -51,8 +52,13 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest /** * Convert the allocation field into the expected format for the service handler. + * + * @param string|null $key + * @param string|array|null $default + * + * @return mixed */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { $data = parent::validated(); @@ -71,13 +77,19 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest unset($data['limits']); } + if (!is_null($key)) { + return Arr::get($data, $key, $default); + } + return $data; } /** * Custom attributes to use in error message responses. + * + * @return array */ - public function attributes(): array + public function attributes() { return [ 'add_allocations' => 'allocations to add', @@ -95,9 +107,11 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest * compatability with the old API endpoint while also supporting a more correct API * call. * + * @return array + * * @see https://github.com/pterodactyl/panel/issues/1500 */ - protected function requiredToOptional(string $field, array $rules, bool $limits = false): array + protected function requiredToOptional(string $field, array $rules, bool $limits = false) { if (!in_array('required', $rules)) { return $rules; diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index aecc1cf02..a4551edcd 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\Server; class UpdateServerDetailsRequest extends ServerWriteRequest @@ -11,7 +12,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->parameter('server', Server::class)); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); return [ 'external_id' => $rules['external_id'], @@ -24,19 +25,24 @@ class UpdateServerDetailsRequest extends ServerWriteRequest /** * Convert the posted data into the correct format that is expected * by the application. + * + * @param string|null $key + * @param string|array|null $default */ - public function validated($key = null, $default = null): array + public function validated($key = null, $default = null) { - return [ + $data = [ 'external_id' => $this->input('external_id'), 'name' => $this->input('name'), 'owner_id' => $this->input('user'), 'description' => $this->input('description'), ]; + + return is_null($key) ? $data : Arr::get($data, $key, $default); } /** - * Rename some attributes in error messages to clarify the field + * Rename some of the attributes in error messages to clarify the field * being discussed. */ public function attributes(): array diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php new file mode 100644 index 000000000..a588c381c --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php @@ -0,0 +1,77 @@ + $rules['external_id'], + 'name' => $rules['name'], + 'description' => array_merge(['nullable'], $rules['description']), + 'owner_id' => $rules['owner_id'], + + 'limits' => 'sometimes|array', + 'limits.memory' => $rules['memory'], + 'limits.swap' => $rules['swap'], + 'limits.disk' => $rules['disk'], + 'limits.io' => $rules['io'], + 'limits.threads' => $rules['threads'], + 'limits.cpu' => $rules['cpu'], + 'limits.oom_killer' => 'sometimes|boolean', + + 'feature_limits' => 'required|array', + 'feature_limits.allocations' => $rules['allocation_limit'], + 'feature_limits.backups' => $rules['backup_limit'], + 'feature_limits.databases' => $rules['database_limit'], + + 'allocation_id' => 'bail|exists:allocations,id', + 'add_allocations' => 'bail|array', + 'add_allocations.*' => 'integer', + 'remove_allocations' => 'bail|array', + 'remove_allocations.*' => 'integer', + ]; + } + + /** + * @param string|null $key + * @param string|array|null $default + * + * @return mixed + */ + public function validated($key = null, $default = null) + { + $data = parent::validated(); + $response = [ + 'external_id' => array_get($data, 'external_id'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), + 'owner_id' => array_get($data, 'owner_id'), + + 'memory' => array_get($data, 'limits.memory'), + 'swap' => array_get($data, 'limits.swap'), + 'disk' => array_get($data, 'limits.disk'), + 'io' => array_get($data, 'limits.io'), + 'threads' => array_get($data, 'limits.threads'), + 'cpu' => array_get($data, 'limits.cpu'), + 'oom_disabled' => !array_get($data, 'limits.oom_killer'), + + 'allocation_limit' => array_get($data, 'feature_limits.allocations'), + 'backup_limit' => array_get($data, 'feature_limits.backups'), + 'database_limit' => array_get($data, 'feature_limits.databases'), + + 'allocation_id' => array_get($data, 'allocation_id'), + 'add_allocations' => array_get($data, 'add_allocations'), + 'remove_allocations' => array_get($data, 'remove_allocations'), + ]; + + return is_null($key) ? $response : Arr::get($response, $key, $default); + } +} diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index 985b10a6f..d8f92a1f8 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -3,41 +3,20 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; use Pterodactyl\Models\Server; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class UpdateServerStartupRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_SERVERS; - - protected int $permission = AdminAcl::WRITE; - - /** - * Validation rules to run the input against. - */ public function rules(): array { - $data = Server::getRulesForUpdate($this->parameter('server', Server::class)); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); return [ - 'startup' => $data['startup'], + 'startup' => $rules['startup'], 'environment' => 'present|array', - 'egg' => $data['egg_id'], - 'image' => $data['image'], + 'egg_id' => $rules['egg_id'], + 'image' => $rules['image'], 'skip_scripts' => 'present|boolean', ]; } - - /** - * Return the validated data in a format that is expected by the service. - */ - public function validated($key = null, $default = null): array - { - $data = parent::validated(); - - return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([ - 'egg_id' => array_get($data, 'egg'), - 'docker_image' => array_get($data, 'image'), - ])->toArray(); - } } diff --git a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php index 5e840a1c0..a2e3841fb 100644 --- a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteUserRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_USERS; - - protected int $permission = AdminAcl::WRITE; } diff --git a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php index 0f44aed3f..b26ef7661 100644 --- a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -2,12 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalUserRequest extends ApplicationApiRequest { - protected ?string $resource = AdminAcl::RESOURCE_USERS; - - protected int $permission = AdminAcl::READ; } diff --git a/app/Http/Requests/Api/Application/Users/GetUserRequest.php b/app/Http/Requests/Api/Application/Users/GetUserRequest.php new file mode 100644 index 000000000..4e16088a5 --- /dev/null +++ b/app/Http/Requests/Api/Application/Users/GetUserRequest.php @@ -0,0 +1,7 @@ +only([ + return collect($rules)->only([ 'external_id', 'email', 'username', 'password', - 'language', + 'admin_role_id', 'root_admin', ])->toArray(); - - $response['first_name'] = $rules['name_first']; - $response['last_name'] = $rules['name_last']; - - return $response; - } - - public function validated($key = null, $default = null): array - { - $data = parent::validated(); - - $data['name_first'] = $data['first_name']; - $data['name_last'] = $data['last_name']; - - unset($data['first_name'], $data['last_name']); - - return $data; - } - - /** - * Rename some fields to be more user friendly. - */ - public function attributes(): array - { - return [ - 'external_id' => 'Third Party Identifier', - 'name_first' => 'First Name', - 'name_last' => 'Last Name', - 'root_admin' => 'Root Administrator Status', - ]; } } diff --git a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php index fa2e1291c..ba6d9da2c 100644 --- a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -6,13 +6,8 @@ use Pterodactyl\Models\User; class UpdateUserRequest extends StoreUserRequest { - /** - * Return the validation rules for this request. - */ public function rules(array $rules = null): array { - $userId = $this->parameter('user', User::class)->id; - - return parent::rules(User::getRulesForUpdate($userId)); + return parent::rules($rules ?? User::getRulesForUpdate($this->route()->parameter('user')->id)); } } diff --git a/app/Models/AdminRole.php b/app/Models/AdminRole.php new file mode 100644 index 000000000..51b485495 --- /dev/null +++ b/app/Models/AdminRole.php @@ -0,0 +1,68 @@ + 'int', + 'permissions' => 'array', + ]; + + public static array $validationRules = [ + 'name' => 'required|string|max:64', + 'description' => 'nullable|string|max:255', + 'sort_id' => 'sometimes|numeric', + ]; + + /** + * @var bool + */ + public $timestamps = false; + + /** + * Gets the permissions associated with a admin role. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function permissions() + { + return $this->hasMany(Permission::class); + } +} diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php deleted file mode 100644 index f42acbfff..000000000 --- a/app/Models/AuditLog.php +++ /dev/null @@ -1,80 +0,0 @@ - 'required|uuid', - 'action' => 'required|string|max:191', - 'subaction' => 'nullable|string|max:191', - 'device' => 'array', - 'device.ip_address' => 'ip', - 'device.user_agent' => 'string', - 'metadata' => 'array', - ]; - - protected $table = 'audit_logs'; - - protected bool $immutableDates = true; - - protected $casts = [ - 'is_system' => 'bool', - 'device' => 'array', - 'metadata' => 'array', - ]; - - protected $guarded = [ - 'id', - 'created_at', - ]; - - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } - - public function server(): BelongsTo - { - return $this->belongsTo(Server::class); - } - - /** - * Creates a new AuditLog model and returns it, attaching device information and the - * currently authenticated user if available. This model is not saved at this point, so - * you can always make modifications to it as needed before saving. - * - * @deprecated - */ - public static function instance(string $action, array $metadata, bool $isSystem = false): self - { - /** @var \Illuminate\Http\Request $request */ - $request = Container::getInstance()->make('request'); - if ($isSystem || !$request instanceof Request) { - $request = null; - } - - return (new self())->fill([ - 'uuid' => Uuid::uuid4()->toString(), - 'is_system' => $isSystem, - 'user_id' => ($request && $request->user()) ? $request->user()->id : null, - 'server_id' => null, - 'action' => $action, - 'device' => $request ? [ - 'ip_address' => $request->getClientIp() ?? '127.0.0.1', - 'user_agent' => $request->userAgent() ?? '', - ] : [], - 'metadata' => $metadata, - ]); - } -} diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 41c94fee2..500205b13 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -22,7 +22,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \Carbon\CarbonImmutable $updated_at * @property \Carbon\CarbonImmutable|null $deleted_at * @property \Pterodactyl\Models\Server $server - * @property \Pterodactyl\Models\AuditLog[] $audits */ class Backup extends Model { diff --git a/app/Models/Server.php b/app/Models/Server.php index 5f2d6a49e..e8560512a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -35,7 +35,7 @@ use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; * @property int $allocation_id * @property int $nest_id * @property int $egg_id - * @property string $startup + * @property string|null $startup * @property string $image * @property int|null $allocation_limit * @property int|null $database_limit @@ -55,6 +55,7 @@ use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; * @property \Pterodactyl\Models\Egg|null $egg * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Mount[] $mounts * @property int|null $mounts_count + * @property \Pterodactyl\Models\Location $location * @property \Pterodactyl\Models\Nest $nest * @property \Pterodactyl\Models\Node $node * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications diff --git a/app/Models/User.php b/app/Models/User.php index bef6da781..50361af8b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Models\Traits\HasAccessTokens; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Relations\HasOne; use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\Access\Authorizable; @@ -29,11 +30,10 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @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 int|null $admin_role_id * @property bool $root_admin * @property bool $use_totp * @property string|null $totp_secret @@ -119,8 +119,6 @@ class User extends Model implements 'external_id', 'username', 'email', - 'name_first', - 'name_last', 'password', 'language', 'use_totp', @@ -169,8 +167,6 @@ class User extends Model implements 'email' => 'required|email|between:1,191|unique:users,email', 'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', 'username' => 'required|between:1,191|unique:users,username', - 'name_first' => 'required|string|between:1,191', - 'name_last' => 'required|string|between:1,191', 'password' => 'sometimes|nullable|string', 'root_admin' => 'boolean', 'language' => 'string', @@ -193,11 +189,15 @@ class User extends Model implements } /** - * Return the user model in a format that can be passed over to Vue templates. + * Return the user model in a format that can be passed over to React templates. */ - public function toVueObject(): array + public function toReactObject(): array { - return Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); + $object = Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); + $object['avatar_url'] = $this->avatarURL(); + $object['role_name'] = $this->adminRoleName(); + + return $object; } /** @@ -231,6 +231,29 @@ class User extends Model implements return trim($this->name_first . ' ' . $this->name_last); } + public function avatarURL(): string + { + return 'https://www.gravatar.com/avatar/' . md5($this->email) . '.jpg'; + } + + /** + * Gets the name of the role assigned to a user. + */ + public function adminRoleName(): ?string + { + $role = $this->adminRole; + if (is_null($role)) { + return $this->root_admin ? 'None' : null; + } + + return $role->name; + } + + public function adminRole(): HasOne + { + return $this->hasOne(AdminRole::class, 'id', 'admin_role_id'); + } + /** * Returns all servers that a user owns. */ diff --git a/app/Policies/.gitkeep b/app/Policies/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c008b0d54..1bb94d258 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -6,8 +6,6 @@ use Pterodactyl\Models; use Illuminate\Support\Str; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; -use Illuminate\Support\Facades\View; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Eloquent\Relations\Relation; @@ -21,9 +19,6 @@ class AppServiceProvider extends ServiceProvider { Schema::defaultStringLength(191); - View::share('appVersion', $this->versionData()['version'] ?? 'undefined'); - View::share('appIsGit', $this->versionData()['is_git'] ?? false); - Paginator::useBootstrap(); // If the APP_URL value is set with https:// make sure we force it here. Theoretically @@ -61,32 +56,4 @@ class AppServiceProvider extends ServiceProvider $this->app->register(SettingsServiceProvider::class); } } - - /** - * Return version information for the footer. - */ - protected function versionData(): array - { - return Cache::remember('git-version', 5, function () { - if (file_exists(base_path('.git/HEAD'))) { - $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); - - if (array_key_exists(1, $head)) { - $path = base_path('.git/' . trim($head[1])); - } - } - - if (isset($path) && file_exists($path)) { - return [ - 'version' => substr(file_get_contents($path), 0, 8), - 'is_git' => true, - ]; - } - - return [ - 'version' => config('app.version'), - 'is_git' => false, - ]; - }); - } } diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php index 6d8545bc2..c442b8e09 100644 --- a/app/Services/Eggs/EggParserService.php +++ b/app/Services/Eggs/EggParserService.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Services\Eggs; use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; -use Illuminate\Http\UploadedFile; use Illuminate\Support\Collection; use Pterodactyl\Exceptions\Service\InvalidFileUploadException; @@ -13,17 +12,10 @@ class EggParserService /** * Takes an uploaded file and parses out the egg configuration from within. * - * @throws \JsonException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException */ - public function handle(UploadedFile $file): array + public function handle(array $parsed): array { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.'); - } - - /** @var array $parsed */ - $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.'); } diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index ecd6eadb6..8be37f0ab 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -6,28 +6,107 @@ use Ramsey\Uuid\Uuid; use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; +use Symfony\Component\Yaml\Yaml; use Illuminate\Http\UploadedFile; use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Services\Eggs\EggParserService; +use Symfony\Component\Yaml\Exception\ParseException; +use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; +use Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; class EggImporterService { - public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) - { + public function __construct( + private ConnectionInterface $connection, + private EggParserService $eggParserService + ) { } /** * Take an uploaded JSON file and parse it into a new egg. * - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable + * @deprecated use `handleFile` or `handleContent` instead + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function handle(UploadedFile $file, int $nest): Egg + public function handle(UploadedFile $file, int $nestId): Egg { - $parsed = $this->parser->handle($file); + return $this->handleFile($nestId, $file); + } + + /** + * ? + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handleFile(int $nestId, UploadedFile $file): Egg + { + if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { + throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); + } + + return $this->handleContent($nestId, $file->openFile()->fread($file->getSize()), 'application/json'); + } + + /** + * ? + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handleContent(int $nestId, string $content, string $contentType): Egg + { + switch (true) { + case str_starts_with($contentType, 'application/json'): + $parsed = json_decode($content, true); + if (json_last_error() !== 0) { + throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); + } + + return $this->handleArray($nestId, $parsed); + case str_starts_with($contentType, 'application/yaml'): + try { + $parsed = Yaml::parse($content); + + return $this->handleArray($nestId, $parsed); + } catch (ParseException $exception) { + throw new BadYamlFormatException('There was an error while attempting to parse the YAML: ' . $exception->getMessage() . '.'); + } + default: + throw new DisplayException('unknown content type'); + } + } + + /** + * ? + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + private function handleArray(int $nestId, array $parsed): Egg + { + $parsed = $this->eggParserService->handle($parsed); /** @var \Pterodactyl\Models\Nest $nest */ - $nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nest); + $nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nestId); return $this->connection->transaction(function () use ($nest, $parsed) { $egg = (new Egg())->forceFill([ diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 89a1f9287..ad17a16f0 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -8,14 +8,18 @@ use Illuminate\Support\Collection; use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Eggs\EggParserService; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; class EggUpdateImporterService { /** * EggUpdateImporterService constructor. */ - public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser) - { + public function __construct( + private ConnectionInterface $connection, + private EggParserService $eggParserService + ) { } /** @@ -25,10 +29,18 @@ class EggUpdateImporterService */ public function handle(Egg $egg, UploadedFile $file): Egg { - $parsed = $this->parser->handle($file); + if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { + throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); + } + + $parsed = json_decode($file->openFile()->fread($file->getSize()), true); + if (json_last_error() !== 0) { + throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); + } + $parsed = $this->eggParserService->handle($parsed); return $this->connection->transaction(function () use ($egg, $parsed) { - $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg = $this->eggParserService->fillFromParsed($egg, $parsed); $egg->save(); // Update existing variables or create new ones. diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index ed70b260f..5d380f4ac 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Services\Eggs\Variables; use Illuminate\Support\Str; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\EggVariable; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Traits\Services\ValidatesValidationRules; @@ -34,24 +35,21 @@ class VariableUpdateService * Update a specific egg variable. * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException */ - public function handle(EggVariable $variable, array $data): mixed + public function handle(Egg $egg, array $data): void { if (!is_null(array_get($data, 'env_variable'))) { if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) { throw new ReservedVariableNameException(trans('exceptions.service.variables.reserved_name', ['name' => array_get($data, 'env_variable')])); } - $search = $this->repository->setColumns('id')->findCountWhere([ - ['env_variable', '=', $data['env_variable']], - ['egg_id', '=', $variable->egg_id], - ['id', '!=', $variable->id], - ]); + $count = $egg->variables() + ->where('egg_variables.env_variable', $data['env_variable']) + ->where('egg_variables.id', '!=', $data['id']) + ->count(); - if ($search > 0) { + if ($count > 0) { throw new DisplayException(trans('exceptions.service.variables.env_not_unique', ['name' => array_get($data, 'env_variable')])); } } @@ -66,13 +64,13 @@ class VariableUpdateService $options = array_get($data, 'options') ?? []; - return $this->repository->withoutFreshModel()->update($variable->id, [ + $egg->variables()->where('egg_variables.id', $data['id'])->update([ 'name' => $data['name'] ?? '', 'description' => $data['description'] ?? '', 'env_variable' => $data['env_variable'] ?? '', 'default_value' => $data['default_value'] ?? '', - 'user_viewable' => in_array('user_viewable', $options), - 'user_editable' => in_array('user_editable', $options), + 'user_viewable' => $data['user_viewable'], + 'user_editable' => $data['user_editable'], 'rules' => $data['rules'] ?? '', ]); } diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 2122b397b..5b8a5aa80 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -3,46 +3,54 @@ namespace Pterodactyl\Services\Helpers; use Exception; -use GuzzleHttp\Client; use Carbon\CarbonImmutable; use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use Illuminate\Support\Facades\Http; use Illuminate\Contracts\Cache\Repository as CacheRepository; use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException; class SoftwareVersionService { public const VERSION_CACHE_KEY = 'pterodactyl:versioning_data'; + public const GIT_VERSION_CACHE_KEY = 'pterodactyl:git_data'; private static array $result; /** * SoftwareVersionService constructor. */ - public function __construct( - protected CacheRepository $cache, - protected Client $client - ) { + public function __construct(private CacheRepository $cache) + { self::$result = $this->cacheVersionData(); } /** - * Get the latest version of the panel from the CDN servers. + * Return the current version of the panel that is being used. */ - public function getPanel(): string + public function getCurrentVersion(): string + { + return config('app.version'); + } + + /** + * Returns the latest version of the panel from the CDN servers. + */ + public function getLatestPanel(): string { return Arr::get(self::$result, 'panel') ?? 'error'; } /** - * Get the latest version of the daemon from the CDN servers. + * Returns the latest version of the Wings from the CDN servers. */ - public function getDaemon(): string + public function getLatestWings(): string { return Arr::get(self::$result, 'wings') ?? 'error'; } /** - * Get the URL to the discord server. + * Returns the URL to the discord server. */ public function getDiscord(): string { @@ -50,7 +58,7 @@ class SoftwareVersionService } /** - * Get the URL for donations. + * Returns the URL for donations. */ public function getDonations(): string { @@ -62,23 +70,80 @@ class SoftwareVersionService */ public function isLatestPanel(): bool { - if (config('app.version') === 'canary') { + $version = $this->getCurrentVersion(); + if ($version === 'canary') { return true; } - return version_compare(config('app.version'), $this->getPanel()) >= 0; + return version_compare($version, $this->getLatestPanel()) >= 0; } /** * Determine if a passed daemon version string is the latest. */ - public function isLatestDaemon(string $version): bool + public function isLatestWings(string $version): bool { - if ($version === 'develop') { + if ($version === 'develop' || Str::startsWith($version, 'dev-')) { return true; } - return version_compare($version, $this->getDaemon()) >= 0; + return version_compare($version, $this->getLatestWings()) >= 0; + } + + /** + * ? + */ + public function getVersionData(): array + { + $versionData = $this->versionData(); + if ($versionData['is_git']) { + $git = $versionData['version']; + } else { + $git = null; + } + + return [ + 'panel' => [ + 'current' => $this->getCurrentVersion(), + 'latest' => $this->getLatestPanel(), + ], + + 'wings' => [ + 'latest' => $this->getLatestWings(), + ], + + 'git' => $git, + ]; + } + + /** + * Return version information for the footer. + */ + protected function versionData(): array + { + return $this->cache->remember(self::GIT_VERSION_CACHE_KEY, CarbonImmutable::now()->addSeconds(15), function () { + $configVersion = config()->get('app.version'); + + if (file_exists(base_path('.git/HEAD'))) { + $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); + + if (array_key_exists(1, $head)) { + $path = base_path('.git/' . trim($head[1])); + } + } + + if (isset($path) && file_exists($path)) { + return [ + 'version' => substr(file_get_contents($path), 0, 8), + 'is_git' => true, + ]; + } + + return [ + 'version' => $configVersion, + 'is_git' => false, + ]; + }); } /** @@ -88,10 +153,10 @@ class SoftwareVersionService { return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config('pterodactyl.cdn.cache_time', 60)), function () { try { - $response = $this->client->request('GET', config('pterodactyl.cdn.url')); + $response = Http::get(config('pterodactyl.cdn.url')); - if ($response->getStatusCode() === 200) { - return json_decode($response->getBody(), true); + if ($response->status() === 200) { + return json_decode($response->body(), true); } throw new CdnVersionFetchingException(); diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index b7a22fdaa..b068933b3 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -49,9 +49,9 @@ class BuildModificationService $merge = Arr::only($data, ['oom_disabled', 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'allocation_id']); $server->forceFill(array_merge($merge, [ - 'database_limit' => Arr::get($data, 'database_limit', 0) ?? null, 'allocation_limit' => Arr::get($data, 'allocation_limit', 0) ?? null, 'backup_limit' => Arr::get($data, 'backup_limit', 0) ?? 0, + 'database_limit' => Arr::get($data, 'database_limit', 0) ?? null, ]))->saveOrFail(); return $server->refresh(); diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index 72f327796..af6ba906f 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -50,7 +50,7 @@ class ServerConfigurationStructureService ], 'suspended' => $server->isSuspended(), 'environment' => $this->environment->handle($server), - 'invocation' => $server->startup, + 'invocation' => !is_null($server->startup) ? $server->startup : $server->egg->startup, 'skip_egg_scripts' => $server->skip_scripts, 'build' => [ 'memory_limit' => $server->memory, @@ -63,18 +63,13 @@ class ServerConfigurationStructureService ], 'container' => [ 'image' => $server->image, - // This field is deprecated — use the value in the "build" block. - // - // TODO: remove this key in V2. - 'oom_disabled' => $server->oom_disabled, - 'requires_rebuild' => false, ], 'allocations' => [ - 'force_outgoing_ip' => $server->egg->force_outgoing_ip, 'default' => [ 'ip' => $server->allocation->ip, 'port' => $server->allocation->port, ], + 'force_outgoing_ip' => $server->egg->force_outgoing_ip, 'mappings' => $server->getAllocationMappings(), ], 'mounts' => $server->mounts->map(function (Mount $mount) { diff --git a/app/Transformers/Api/Application/AdminRoleTransformer.php b/app/Transformers/Api/Application/AdminRoleTransformer.php new file mode 100644 index 000000000..7a561528a --- /dev/null +++ b/app/Transformers/Api/Application/AdminRoleTransformer.php @@ -0,0 +1,29 @@ + $model->id, + 'name' => $model->name, + 'description' => $model->description, + ]; + } +} diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index fcd65f98f..4aafc99f8 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -2,18 +2,14 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Server; use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class AllocationTransformer extends BaseTransformer +class AllocationTransformer extends Transformer { - /** - * Relationships that can be loaded onto allocation transformations. - */ protected array $availableIncludes = ['node', 'server']; /** @@ -27,22 +23,21 @@ class AllocationTransformer extends BaseTransformer /** * Return a generic transformed allocation array. */ - public function transform(Allocation $allocation): array + public function transform(Allocation $model): array { return [ - 'id' => $allocation->id, - 'ip' => $allocation->ip, - 'alias' => $allocation->ip_alias, - 'port' => $allocation->port, - 'notes' => $allocation->notes, - 'assigned' => !is_null($allocation->server_id), + 'id' => $model->id, + 'ip' => $model->ip, + 'alias' => $model->ip_alias, + 'port' => $model->port, + 'notes' => $model->notes, + 'server_id' => $model->server_id, + 'assigned' => !is_null($model->server_id), ]; } /** * Load the node relationship onto a given transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNode(Allocation $allocation): Item|NullResource { @@ -50,17 +45,11 @@ class AllocationTransformer extends BaseTransformer return $this->null(); } - return $this->item( - $allocation->node, - $this->makeTransformer(NodeTransformer::class), - Node::RESOURCE_NAME - ); + return $this->item($allocation->node, new NodeTransformer()); } /** * Load the server relationship onto a given transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServer(Allocation $allocation): Item|NullResource { @@ -68,10 +57,6 @@ class AllocationTransformer extends BaseTransformer return $this->null(); } - return $this->item( - $allocation->server, - $this->makeTransformer(ServerTransformer::class), - Server::RESOURCE_NAME - ); + return $this->item($allocation->server, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php deleted file mode 100644 index 723caa3b8..000000000 --- a/app/Transformers/Api/Application/BaseTransformer.php +++ /dev/null @@ -1,114 +0,0 @@ -call([$this, 'handle']); - } - } - - /** - * Return the resource name for the JSONAPI output. - */ - abstract public function getResourceName(): string; - - /** - * Sets the request on the instance. - */ - public function setRequest(Request $request): self - { - $this->request = $request; - - return $this; - } - - /** - * Returns a new transformer instance with the request set on the instance. - */ - public static function fromRequest(Request $request): BaseTransformer - { - return app(static::class)->setRequest($request); - } - - /** - * Determine if the API key loaded onto the transformer has permission - * to access a different resource. This is used when including other - * models on a transformation request. - * - * @deprecated — prefer $user->can/cannot methods - */ - protected function authorize(string $resource): bool - { - $allowed = [ApiKey::TYPE_ACCOUNT, ApiKey::TYPE_APPLICATION]; - - $token = $this->request->user()->currentAccessToken(); - if (!$token instanceof ApiKey || !in_array($token->key_type, $allowed)) { - return false; - } - - // If this is not a deprecated application token type we can only check that - // the user is a root admin at the moment. In a future release we'll be rolling - // out more specific permissions for keys. - if ($token->key_type === ApiKey::TYPE_ACCOUNT) { - return $this->request->user()->root_admin; - } - - return AdminAcl::check($token, $resource); - } - - /** - * Create a new instance of the transformer and pass along the currently - * set API key. - * - * @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer - * - * @param class-string $abstract - * - * @return T - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - * - * @noinspection PhpDocSignatureInspection - */ - protected function makeTransformer(string $abstract) - { - Assert::subclassOf($abstract, self::class); - - return $abstract::fromRequest($this->request); - } - - /** - * Return an ISO-8601 formatted timestamp to use in the API response. - */ - protected function formatTimestamp(string $timestamp): string - { - return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) - ->setTimezone(self::RESPONSE_TIMEZONE) - ->toAtomString(); - } -} diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index 019fdf261..c69f9dff3 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -2,17 +2,15 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class DatabaseHostTransformer extends BaseTransformer +class DatabaseHostTransformer extends Transformer { - protected array $availableIncludes = [ - 'databases', - ]; + protected array $availableIncludes = ['databases']; /** * Return the resource name for the JSONAPI output. @@ -33,16 +31,14 @@ class DatabaseHostTransformer extends BaseTransformer 'host' => $model->host, 'port' => $model->port, 'username' => $model->username, - 'node' => $model->node_id, - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'node_id' => $model->node_id, + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Include the databases associated with this host. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeDatabases(DatabaseHost $model): Collection|NullResource { @@ -50,8 +46,7 @@ class DatabaseHostTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('databases'); - - return $this->collection($model->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), Database::RESOURCE_NAME); + // TODO + return $this->collection($model->databases, new ServerDatabaseTransformer()); } } diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index 9ed5736b4..c98a5d16a 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -2,26 +2,23 @@ namespace Pterodactyl\Transformers\Api\Application; -use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; -use Pterodactyl\Models\Server; use League\Fractal\Resource\Item; -use Pterodactyl\Models\EggVariable; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class EggTransformer extends BaseTransformer +class EggTransformer extends Transformer { /** * Relationships that can be loaded onto this transformation. */ protected array $availableIncludes = [ - 'nest', - 'servers', 'config', + 'nest', 'script', + 'servers', 'variables', ]; @@ -50,19 +47,14 @@ class EggTransformer extends BaseTransformer 'id' => $model->id, 'uuid' => $model->uuid, 'name' => $model->name, - 'nest' => $model->nest_id, + 'nest_id' => $model->nest_id, 'author' => $model->author, 'description' => $model->description, - // "docker_image" is deprecated, but left here to avoid breaking too many things at once - // in external software. We'll remove it down the road once things have gotten the chance - // to upgrade to using "docker_images". - 'docker_image' => count($model->docker_images) > 0 ? Arr::first($model->docker_images) : '', 'docker_images' => $model->docker_images, 'config' => [ 'files' => $files, 'startup' => json_decode($model->config_startup, true), 'stop' => $model->config_stop, - 'logs' => json_decode($model->config_logs, true), 'file_denylist' => $model->file_denylist, 'extends' => $model->config_from, ], @@ -74,43 +66,11 @@ class EggTransformer extends BaseTransformer 'container' => $model->script_container, 'extends' => $model->copy_script_from, ], - $model->getCreatedAtColumn() => $this->formatTimestamp($model->created_at), - $model->getUpdatedAtColumn() => $this->formatTimestamp($model->updated_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } - /** - * Include the Nest relationship for the given Egg in the transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeNest(Egg $model): Item|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { - return $this->null(); - } - - $model->loadMissing('nest'); - - return $this->item($model->getRelation('nest'), $this->makeTransformer(NestTransformer::class), Nest::RESOURCE_NAME); - } - - /** - * Include the Servers relationship for the given Egg in the transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeServers(Egg $model): Collection|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { - return $this->null(); - } - - $model->loadMissing('servers'); - - return $this->collection($model->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME); - } - /** * Include more detailed information about the configuration if this Egg is * extending another. @@ -121,8 +81,6 @@ class EggTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('configFrom'); - return $this->item($model, function (Egg $model) { return [ 'files' => json_decode($model->inherit_config_files), @@ -133,6 +91,18 @@ class EggTransformer extends BaseTransformer }); } + /** + * Include the Nest relationship for the given Egg in the transformation. + */ + public function includeNest(Egg $model): Item|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_NESTS)) { + return $this->null(); + } + + return $this->item($model->nest, new NestTransformer()); + } + /** * Include more detailed information about the script configuration if the * Egg is extending another. @@ -143,8 +113,6 @@ class EggTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('scriptFrom'); - return $this->item($model, function (Egg $model) { return [ 'privileged' => $model->script_is_privileged, @@ -155,10 +123,20 @@ class EggTransformer extends BaseTransformer }); } + /** + * Include the Servers relationship for the given Egg in the transformation. + */ + public function includeServers(Egg $model): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + return $this->collection($model->servers, new ServerTransformer()); + } + /** * Include the variables that are defined for this Egg. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeVariables(Egg $model): Collection|NullResource { @@ -168,10 +146,6 @@ class EggTransformer extends BaseTransformer $model->loadMissing('variables'); - return $this->collection( - $model->getRelation('variables'), - $this->makeTransformer(EggVariableTransformer::class), - EggVariable::RESOURCE_NAME - ); + return $this->collection($model->variables, new EggVariableTransformer()); } } diff --git a/app/Transformers/Api/Application/EggVariableTransformer.php b/app/Transformers/Api/Application/EggVariableTransformer.php index 2088806d5..613b5768d 100644 --- a/app/Transformers/Api/Application/EggVariableTransformer.php +++ b/app/Transformers/Api/Application/EggVariableTransformer.php @@ -4,8 +4,9 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Egg; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Transformers\Api\Transformer; -class EggVariableTransformer extends BaseTransformer +class EggVariableTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -15,7 +16,10 @@ class EggVariableTransformer extends BaseTransformer return Egg::RESOURCE_NAME; } - public function transform(EggVariable $model) + /** + * Transform egg variable into a representation for the application API. + */ + public function transform(EggVariable $model): array { return $model->toArray(); } diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index 8fea3feb3..5ef3e74d5 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -6,8 +6,9 @@ use Pterodactyl\Models\Location; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class LocationTransformer extends BaseTransformer +class LocationTransformer extends Transformer { /** * List of resources that can be included. @@ -25,37 +26,19 @@ class LocationTransformer extends BaseTransformer /** * Return a generic transformed location array. */ - public function transform(Location $location): array + public function transform(Location $model): array { return [ - 'id' => $location->id, - 'short' => $location->short, - 'long' => $location->long, - $location->getUpdatedAtColumn() => $this->formatTimestamp($location->updated_at), - $location->getCreatedAtColumn() => $this->formatTimestamp($location->created_at), + 'id' => $model->id, + 'short' => $model->short, + 'long' => $model->long, + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeServers(Location $location): Collection|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { - return $this->null(); - } - - $location->loadMissing('servers'); - - return $this->collection($location->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); - } - - /** - * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNodes(Location $location): Collection|NullResource { @@ -63,8 +46,18 @@ class LocationTransformer extends BaseTransformer return $this->null(); } - $location->loadMissing('nodes'); + return $this->collection($location->nodes, new NodeTransformer()); + } - return $this->collection($location->getRelation('nodes'), $this->makeTransformer(NodeTransformer::class), 'node'); + /** + * Return the nodes associated with this location. + */ + public function includeServers(Location $location): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + return $this->collection($location->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/MountTransformer.php b/app/Transformers/Api/Application/MountTransformer.php new file mode 100644 index 000000000..8c0d1f289 --- /dev/null +++ b/app/Transformers/Api/Application/MountTransformer.php @@ -0,0 +1,75 @@ + $model->id, + 'uuid' => $model->uuid, + 'name' => $model->name, + 'description' => $model->description, + 'source' => $model->source, + 'target' => $model->target, + 'read_only' => $model->read_only, + 'user_mountable' => $model->user_mountable, + ]; + } + + /** + * Return the eggs associated with this mount. + */ + public function includeEggs(Mount $mount): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { + return $this->null(); + } + + return $this->collection($mount->eggs, new EggTransformer()); + } + + /** + * Return the nodes associated with this mount. + */ + public function includeNodes(Mount $mount): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { + return $this->null(); + } + + return $this->collection($mount->nodes, new NodeTransformer()); + } + + /** + * Return the servers associated with this mount. + */ + public function includeServers(Mount $mount): Collection|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + return $this->collection($mount->servers, new ServerTransformer()); + } +} diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 2f530d44e..8521564c6 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -2,21 +2,18 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; -use Pterodactyl\Models\Server; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class NestTransformer extends BaseTransformer +class NestTransformer extends Transformer { /** * Relationships that can be loaded onto this transformation. */ - protected array $availableIncludes = [ - 'eggs', 'servers', - ]; + protected array $availableIncludes = ['eggs', 'servers']; /** * Return the resource name for the JSONAPI output. @@ -34,16 +31,14 @@ class NestTransformer extends BaseTransformer { $response = $model->toArray(); - $response[$model->getUpdatedAtColumn()] = $this->formatTimestamp($model->updated_at); - $response[$model->getCreatedAtColumn()] = $this->formatTimestamp($model->created_at); + $response['created_at'] = self::formatTimestamp($model->created_at); + $response['updated_at'] = self::formatTimestamp($model->updated_at); return $response; } /** * Include the Eggs relationship on the given Nest model transformation. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEggs(Nest $model): Collection|NullResource { @@ -51,15 +46,11 @@ class NestTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('eggs'); - - return $this->collection($model->getRelation('eggs'), $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + return $this->collection($model->eggs, new EggTransformer()); } /** * Include the servers relationship on the given Nest model. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(Nest $model): Collection|NullResource { @@ -67,8 +58,6 @@ class NestTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('servers'); - - return $this->collection($model->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME); + return $this->collection($model->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index 6347dfec3..de3fd63da 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -7,8 +7,9 @@ use League\Fractal\Resource\Item; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class NodeTransformer extends BaseTransformer +class NodeTransformer extends Transformer { /** * List of resources that can be included. @@ -27,9 +28,9 @@ class NodeTransformer extends BaseTransformer * Return a node transformed into a format that can be consumed by the * external administrative API. */ - public function transform(Node $node): array + public function transform(Node $model): array { - $response = collect($node->toArray())->mapWithKeys(function ($value, $key) { + $response = collect($model->toArray())->mapWithKeys(function ($value, $key) { // I messed up early in 2016 when I named this column as poorly // as I did. This is the tragic result of my mistakes. $key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key; @@ -37,10 +38,10 @@ class NodeTransformer extends BaseTransformer return [snake_case($key) => $value]; })->toArray(); - $response[$node->getUpdatedAtColumn()] = $this->formatTimestamp($node->updated_at); - $response[$node->getCreatedAtColumn()] = $this->formatTimestamp($node->created_at); + $response['created_at'] = self::formatTimestamp($model->created_at); + $response['updated_at'] = self::formatTimestamp($model->updated_at); - $resources = $node->servers()->select(['memory', 'disk'])->get(); + $resources = $model->servers()->select(['memory', 'disk'])->get(); $response['allocated_resources'] = [ 'memory' => $resources->sum('memory'), @@ -51,9 +52,7 @@ class NodeTransformer extends BaseTransformer } /** - * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return the allocations associated with this node. */ public function includeAllocations(Node $node): Collection|NullResource { @@ -61,19 +60,11 @@ class NodeTransformer extends BaseTransformer return $this->null(); } - $node->loadMissing('allocations'); - - return $this->collection( - $node->getRelation('allocations'), - $this->makeTransformer(AllocationTransformer::class), - 'allocation' - ); + return $this->collection($node->allocations, new AllocationTransformer()); } /** - * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return the location associated with this node. */ public function includeLocation(Node $node): Item|NullResource { @@ -81,19 +72,11 @@ class NodeTransformer extends BaseTransformer return $this->null(); } - $node->loadMissing('location'); - - return $this->item( - $node->getRelation('location'), - $this->makeTransformer(LocationTransformer::class), - 'location' - ); + return $this->item($node->location, new LocationTransformer()); } /** - * Return the nodes associated with this location. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * Return the servers associated with this node. */ public function includeServers(Node $node): Collection|NullResource { @@ -101,12 +84,6 @@ class NodeTransformer extends BaseTransformer return $this->null(); } - $node->loadMissing('servers'); - - return $this->collection( - $node->getRelation('servers'), - $this->makeTransformer(ServerTransformer::class), - 'server' - ); + return $this->collection($node->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 2590482d9..159ac6b2d 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -4,14 +4,14 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Database; use League\Fractal\Resource\Item; -use Pterodactyl\Models\DatabaseHost; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Encryption\Encrypter; -class ServerDatabaseTransformer extends BaseTransformer +class ServerDatabaseTransformer extends Transformer { - protected array $availableIncludes = ['password', 'host']; + protected array $availableIncludes = ['host', 'password']; private Encrypter $encrypter; @@ -38,17 +38,29 @@ class ServerDatabaseTransformer extends BaseTransformer { return [ 'id' => $model->id, - 'server' => $model->server_id, - 'host' => $model->database_host_id, - 'database' => $model->database, + 'database_host_id' => $model->database_host_id, + 'server_id' => $model->server_id, + 'name' => $model->database, 'username' => $model->username, 'remote' => $model->remote, 'max_connections' => $model->max_connections, - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } + /** + * Return the database host relationship for this server database. + */ + public function includeHost(Database $model): Item|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS)) { + return $this->null(); + } + + return $this->item($model->host, new DatabaseHostTransformer()); + } + /** * Include the database password in the request. */ @@ -60,24 +72,4 @@ class ServerDatabaseTransformer extends BaseTransformer ]; }, 'database_password'); } - - /** - * Return the database host relationship for this server database. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeHost(Database $model): Item|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS)) { - return $this->null(); - } - - $model->loadMissing('host'); - - return $this->item( - $model->getRelation('host'), - $this->makeTransformer(DatabaseHostTransformer::class), - DatabaseHost::RESOURCE_NAME - ); - } } diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index e5db01fb2..ddc4f638c 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -7,9 +7,10 @@ use League\Fractal\Resource\Item; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Services\Servers\EnvironmentService; -class ServerTransformer extends BaseTransformer +class ServerTransformer extends Transformer { private EnvironmentService $environmentService; @@ -48,53 +49,47 @@ class ServerTransformer extends BaseTransformer /** * Return a generic transformed server array. */ - public function transform(Server $server): array + public function transform(Server $model): array { return [ - 'id' => $server->getKey(), - 'external_id' => $server->external_id, - 'uuid' => $server->uuid, - 'identifier' => $server->uuidShort, - 'name' => $server->name, - 'description' => $server->description, - 'status' => $server->status, - // This field is deprecated, please use "status". - 'suspended' => $server->isSuspended(), + 'id' => $model->getKey(), + 'external_id' => $model->external_id, + 'uuid' => $model->uuid, + 'identifier' => $model->uuidShort, + 'name' => $model->name, + 'description' => $model->description, + 'status' => $model->status, 'limits' => [ - 'memory' => $server->memory, - 'swap' => $server->swap, - 'disk' => $server->disk, - 'io' => $server->io, - 'cpu' => $server->cpu, - 'threads' => $server->threads, - 'oom_disabled' => $server->oom_disabled, + 'cpu' => $model->cpu, + 'disk' => $model->disk, + 'io' => $model->io, + 'memory' => $model->memory, + 'oom_disabled' => $model->oom_disabled, + 'swap' => $model->swap, + 'threads' => $model->threads, ], 'feature_limits' => [ - 'databases' => $server->database_limit, - 'allocations' => $server->allocation_limit, - 'backups' => $server->backup_limit, + 'allocations' => $model->allocation_limit, + 'backups' => $model->backup_limit, + 'databases' => $model->database_limit, ], - 'user' => $server->owner_id, - 'node' => $server->node_id, - 'allocation' => $server->allocation_id, - 'nest' => $server->nest_id, - 'egg' => $server->egg_id, + 'user_id' => $model->owner_id, + 'node_id' => $model->node_id, + 'allocation_id' => $model->allocation_id, + 'nest_id' => $model->nest_id, + 'egg_id' => $model->egg_id, 'container' => [ - 'startup_command' => $server->startup, - 'image' => $server->image, - // This field is deprecated, please use "status". - 'installed' => $server->isInstalled() ? 1 : 0, - 'environment' => $this->environmentService->handle($server), + 'startup' => $model->startup, + 'image' => $model->image, + 'environment' => $this->environmentService->handle($model), ], - $server->getUpdatedAtColumn() => $this->formatTimestamp($server->updated_at), - $server->getCreatedAtColumn() => $this->formatTimestamp($server->created_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Return a generic array of allocations for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeAllocations(Server $server): Collection|NullResource { @@ -102,15 +97,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('allocations'); - - return $this->collection($server->getRelation('allocations'), $this->makeTransformer(AllocationTransformer::class), 'allocation'); + return $this->collection($server->allocations, new AllocationTransformer()); } /** * Return a generic array of data about subusers for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeSubusers(Server $server): Collection|NullResource { @@ -118,15 +109,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('subusers'); - - return $this->collection($server->getRelation('subusers'), $this->makeTransformer(SubuserTransformer::class), 'subuser'); + return $this->collection($server->subusers, new SubuserTransformer()); } /** * Return a generic array of data about subusers for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeUser(Server $server): Item|NullResource { @@ -134,15 +121,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('user'); - - return $this->item($server->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); + return $this->item($server->user, new UserTransformer()); } /** * Return a generic array with nest information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNest(Server $server): Item|NullResource { @@ -150,15 +133,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('nest'); - - return $this->item($server->getRelation('nest'), $this->makeTransformer(NestTransformer::class), 'nest'); + return $this->item($server->nest, new NestTransformer()); } /** * Return a generic array with egg information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEgg(Server $server): Item|NullResource { @@ -166,15 +145,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('egg'); - - return $this->item($server->getRelation('egg'), $this->makeTransformer(EggTransformer::class), 'egg'); + return $this->item($server->egg, new EggTransformer()); } /** * Return a generic array of data about subusers for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeVariables(Server $server): Collection|NullResource { @@ -182,15 +157,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('variables'); - - return $this->collection($server->getRelation('variables'), $this->makeTransformer(ServerVariableTransformer::class), 'server_variable'); + return $this->collection($server->variables, new ServerVariableTransformer()); } /** * Return a generic array with location information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeLocation(Server $server): Item|NullResource { @@ -198,15 +169,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('location'); - - return $this->item($server->getRelation('location'), $this->makeTransformer(LocationTransformer::class), 'location'); + return $this->item($server->location, new LocationTransformer()); } /** * Return a generic array with node information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNode(Server $server): Item|NullResource { @@ -214,15 +181,11 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('node'); - - return $this->item($server->getRelation('node'), $this->makeTransformer(NodeTransformer::class), 'node'); + return $this->item($server->node, new NodeTransformer()); } /** * Return a generic array with database information for this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeDatabases(Server $server): Collection|NullResource { @@ -230,8 +193,6 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('databases'); - - return $this->collection($server->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), 'databases'); + return $this->collection($server->databases, new ServerDatabaseTransformer()); } } diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index e27d1e013..7c3d1de75 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -7,8 +7,9 @@ use Pterodactyl\Models\EggVariable; use Pterodactyl\Models\ServerVariable; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class ServerVariableTransformer extends BaseTransformer +class ServerVariableTransformer extends Transformer { /** * List of resources that can be included. @@ -26,15 +27,13 @@ class ServerVariableTransformer extends BaseTransformer /** * Return a generic transformed server variable array. */ - public function transform(EggVariable $variable): array + public function transform(EggVariable $model): array { - return $variable->toArray(); + return $model->toArray(); } /** * Return the parent service variable data. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeParent(EggVariable $variable): Item|NullResource { @@ -42,8 +41,7 @@ class ServerVariableTransformer extends BaseTransformer return $this->null(); } - $variable->loadMissing('variable'); - - return $this->item($variable->getRelation('variable'), $this->makeTransformer(EggVariableTransformer::class), 'variable'); + // TODO: what the fuck? + return $this->item($variable->variable, new EggVariableTransformer()); } } diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index 0a51d61d9..7b3ef7c0b 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -6,13 +6,14 @@ use Pterodactyl\Models\Subuser; use League\Fractal\Resource\Item; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class SubuserTransformer extends BaseTransformer +class SubuserTransformer extends Transformer { /** * List of resources that can be included. */ - protected array $availableIncludes = ['user', 'server']; + protected array $availableIncludes = ['server', 'user']; /** * Return the resource name for the JSONAPI output. @@ -25,38 +26,20 @@ class SubuserTransformer extends BaseTransformer /** * Return a transformed Subuser model that can be consumed by external services. */ - public function transform(Subuser $subuser): array + public function transform(Subuser $model): array { return [ - 'id' => $subuser->id, - 'user_id' => $subuser->user_id, - 'server_id' => $subuser->server_id, - 'permissions' => $subuser->permissions, - 'created_at' => $this->formatTimestamp($subuser->created_at), - 'updated_at' => $this->formatTimestamp($subuser->updated_at), + 'id' => $model->id, + 'user_id' => $model->user_id, + 'server_id' => $model->server_id, + 'permissions' => $model->permissions, + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } - /** - * Return a generic item of user for this subuser. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeUser(Subuser $subuser): Item|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { - return $this->null(); - } - - $subuser->loadMissing('user'); - - return $this->item($subuser->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); - } - /** * Return a generic item of server for this subuser. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServer(Subuser $subuser): Item|NullResource { @@ -64,8 +47,18 @@ class SubuserTransformer extends BaseTransformer return $this->null(); } - $subuser->loadMissing('server'); + return $this->item($subuser->server, new ServerTransformer()); + } - return $this->item($subuser->getRelation('server'), $this->makeTransformer(ServerTransformer::class), 'server'); + /** + * Return a generic item of user for this subuser. + */ + public function includeUser(Subuser $subuser): Item|NullResource + { + if (!$this->authorize(AdminAcl::RESOURCE_USERS)) { + return $this->null(); + } + + return $this->item($subuser->user, new UserTransformer()); } } diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index 14e354b45..c50a9d8c6 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -6,8 +6,9 @@ use Pterodactyl\Models\User; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class UserTransformer extends BaseTransformer +class UserTransformer extends Transformer { /** * List of resources that can be included. @@ -25,28 +26,27 @@ class UserTransformer extends BaseTransformer /** * Return a transformed User model that can be consumed by external services. */ - public function transform(User $user): array + public function transform(User $model): array { return [ - 'id' => $user->id, - 'external_id' => $user->external_id, - 'uuid' => $user->uuid, - 'username' => $user->username, - 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, - 'language' => $user->language, - 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->use_totp, - 'created_at' => $this->formatTimestamp($user->created_at), - 'updated_at' => $this->formatTimestamp($user->updated_at), + 'id' => $model->id, + 'external_id' => $model->external_id, + 'uuid' => $model->uuid, + 'username' => $model->username, + 'email' => $model->email, + 'language' => $model->language, + 'root_admin' => (bool) $model->root_admin, + '2fa' => (bool) $model->use_totp, + 'avatar_url' => $model->avatarURL(), + 'admin_role_id' => $model->admin_role_id, + 'role_name' => $model->adminRoleName(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Return the servers associated with this user. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(User $user): Collection|NullResource { @@ -54,8 +54,6 @@ class UserTransformer extends BaseTransformer return $this->null(); } - $user->loadMissing('servers'); - - return $this->collection($user->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); + return $this->collection($user->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php index 1a14555d8..68651d8f3 100644 --- a/app/Transformers/Api/Client/AccountTransformer.php +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\User; +use Pterodactyl\Transformers\Api\Transformer; -class AccountTransformer extends BaseClientTransformer +class AccountTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php index 57c8ac30e..dc0be9613 100644 --- a/app/Transformers/Api/Client/ActivityLogTransformer.php +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -4,10 +4,13 @@ namespace Pterodactyl\Transformers\Api\Client; use Illuminate\Support\Str; use Pterodactyl\Models\User; +use League\Fractal\Resource\Item; use Pterodactyl\Models\ActivityLog; use Illuminate\Database\Eloquent\Model; +use League\Fractal\Resource\NullResource; +use Pterodactyl\Transformers\Api\Transformer; -class ActivityLogTransformer extends BaseClientTransformer +class ActivityLogTransformer extends Transformer { protected array $availableIncludes = ['actor']; @@ -34,13 +37,13 @@ class ActivityLogTransformer extends BaseClientTransformer ]; } - public function includeActor(ActivityLog $model) + public function includeActor(ActivityLog $model): Item|NullResource { if (!$model->actor instanceof User) { return $this->null(); } - return $this->item($model->actor, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME); + return $this->item($model->actor, new UserTransformer()); } /** diff --git a/app/Transformers/Api/Client/AllocationTransformer.php b/app/Transformers/Api/Client/AllocationTransformer.php index 2e63e2bc9..f39f2e397 100644 --- a/app/Transformers/Api/Client/AllocationTransformer.php +++ b/app/Transformers/Api/Client/AllocationTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Allocation; +use Pterodactyl\Transformers\Api\Transformer; -class AllocationTransformer extends BaseClientTransformer +class AllocationTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. diff --git a/app/Transformers/Api/Client/ApiKeyTransformer.php b/app/Transformers/Api/Client/ApiKeyTransformer.php index 92ee1a5c6..022b83ffc 100644 --- a/app/Transformers/Api/Client/ApiKeyTransformer.php +++ b/app/Transformers/Api/Client/ApiKeyTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\ApiKey; +use Pterodactyl\Transformers\Api\Transformer; -class ApiKeyTransformer extends BaseClientTransformer +class ApiKeyTransformer extends Transformer { /** * {@inheritdoc} diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php index 298c99642..6797c40dd 100644 --- a/app/Transformers/Api/Client/BackupTransformer.php +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -3,26 +3,27 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Backup; +use Pterodactyl\Transformers\Api\Transformer; -class BackupTransformer extends BaseClientTransformer +class BackupTransformer extends Transformer { public function getResourceName(): string { return Backup::RESOURCE_NAME; } - public function transform(Backup $backup): array + public function transform(Backup $model): array { return [ - 'uuid' => $backup->uuid, - 'is_successful' => $backup->is_successful, - 'is_locked' => $backup->is_locked, - 'name' => $backup->name, - 'ignored_files' => $backup->ignored_files, - 'checksum' => $backup->checksum, - 'bytes' => $backup->bytes, - 'created_at' => $backup->created_at->toAtomString(), - 'completed_at' => $backup->completed_at ? $backup->completed_at->toAtomString() : null, + 'uuid' => $model->uuid, + 'is_successful' => $model->is_successful, + 'is_locked' => $model->is_locked, + 'name' => $model->name, + 'ignored_files' => $model->ignored_files, + 'checksum' => $model->checksum, + 'bytes' => $model->bytes, + 'created_at' => self::formatTimestamp($model->created_at), + 'completed_at' => self::formatTimestamp($model->completed_at), ]; } } diff --git a/app/Transformers/Api/Client/BaseClientTransformer.php b/app/Transformers/Api/Client/BaseClientTransformer.php deleted file mode 100644 index 0388effb0..000000000 --- a/app/Transformers/Api/Client/BaseClientTransformer.php +++ /dev/null @@ -1,43 +0,0 @@ -request->user(); - } - - /** - * Determine if the API key loaded onto the transformer has permission - * to access a different resource. This is used when including other - * models on a transformation request. - * - * @noinspection PhpParameterNameChangedDuringInheritanceInspection - */ - protected function authorize(string $ability, Server $server = null): bool - { - Assert::isInstanceOf($server, Server::class); - - return $this->request->user()->can($ability, [$server]); - } - - /** - * {@inheritDoc} - */ - protected function makeTransformer(string $abstract) - { - Assert::subclassOf($abstract, self::class); - - return parent::makeTransformer($abstract); - } -} diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php index 23e966637..5ad899ad9 100644 --- a/app/Transformers/Api/Client/DatabaseTransformer.php +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -6,15 +6,15 @@ use Pterodactyl\Models\Database; use League\Fractal\Resource\Item; use Pterodactyl\Models\Permission; use League\Fractal\Resource\NullResource; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Extensions\HashidsInterface; -class DatabaseTransformer extends BaseClientTransformer +class DatabaseTransformer extends Transformer { protected array $availableIncludes = ['password']; private Encrypter $encrypter; - private HashidsInterface $hashids; /** @@ -38,8 +38,8 @@ class DatabaseTransformer extends BaseClientTransformer return [ 'id' => $this->hashids->encode($model->id), 'host' => [ - 'address' => $model->getRelation('host')->host, - 'port' => $model->getRelation('host')->port, + 'address' => $model->host->host, + 'port' => $model->host->port, ], 'name' => $model->database, 'username' => $model->username, @@ -53,7 +53,7 @@ class DatabaseTransformer extends BaseClientTransformer */ public function includePassword(Database $database): Item|NullResource { - if (!$this->request->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { + if ($this->user()->cannot(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { return $this->null(); } diff --git a/app/Transformers/Api/Client/EggTransformer.php b/app/Transformers/Api/Client/EggTransformer.php index 8e2e3474e..493958da5 100644 --- a/app/Transformers/Api/Client/EggTransformer.php +++ b/app/Transformers/Api/Client/EggTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Egg; +use Pterodactyl\Transformers\Api\Transformer; -class EggTransformer extends BaseClientTransformer +class EggTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -14,11 +15,11 @@ class EggTransformer extends BaseClientTransformer return Egg::RESOURCE_NAME; } - public function transform(Egg $egg): array + public function transform(Egg $model): array { return [ - 'uuid' => $egg->uuid, - 'name' => $egg->name, + 'uuid' => $model->uuid, + 'name' => $model->name, ]; } } diff --git a/app/Transformers/Api/Client/EggVariableTransformer.php b/app/Transformers/Api/Client/EggVariableTransformer.php index 09c344249..ea0ead081 100644 --- a/app/Transformers/Api/Client/EggVariableTransformer.php +++ b/app/Transformers/Api/Client/EggVariableTransformer.php @@ -3,31 +3,32 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Transformers\Api\Transformer; -class EggVariableTransformer extends BaseClientTransformer +class EggVariableTransformer extends Transformer { public function getResourceName(): string { return EggVariable::RESOURCE_NAME; } - public function transform(EggVariable $variable): array + public function transform(EggVariable $model): array { // This guards against someone incorrectly retrieving variables (haha, me) and then passing // them into the transformer and along to the user. Just throw an exception and break the entire // pathway since you should never be exposing these types of variables to a client. - if (!$variable->user_viewable) { + if (!$model->user_viewable) { throw new \BadMethodCallException('Cannot transform a hidden egg variable in a client transformer.'); } return [ - 'name' => $variable->name, - 'description' => $variable->description, - 'env_variable' => $variable->env_variable, - 'default_value' => $variable->default_value, - 'server_value' => $variable->server_value, - 'is_editable' => $variable->user_editable, - 'rules' => $variable->rules, + 'name' => $model->name, + 'description' => $model->description, + 'env_variable' => $model->env_variable, + 'default_value' => $model->default_value, + 'server_value' => $model->server_value, + 'is_editable' => $model->user_editable, + 'rules' => $model->rules, ]; } } diff --git a/app/Transformers/Api/Client/FileObjectTransformer.php b/app/Transformers/Api/Client/FileObjectTransformer.php index 6278dad73..94a966000 100644 --- a/app/Transformers/Api/Client/FileObjectTransformer.php +++ b/app/Transformers/Api/Client/FileObjectTransformer.php @@ -4,29 +4,30 @@ namespace Pterodactyl\Transformers\Api\Client; use Carbon\Carbon; use Illuminate\Support\Arr; +use Pterodactyl\Transformers\Api\Transformer; -class FileObjectTransformer extends BaseClientTransformer +class FileObjectTransformer extends Transformer { - /** - * Transform a file object response from the daemon into a standardized response. - */ - public function transform(array $item): array - { - return [ - 'name' => Arr::get($item, 'name'), - 'mode' => Arr::get($item, 'mode'), - 'mode_bits' => Arr::get($item, 'mode_bits'), - 'size' => Arr::get($item, 'size'), - 'is_file' => Arr::get($item, 'file', true), - 'is_symlink' => Arr::get($item, 'symlink', false), - 'mimetype' => Arr::get($item, 'mime', 'application/octet-stream'), - 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toAtomString(), - 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toAtomString(), - ]; - } - public function getResourceName(): string { return 'file_object'; } + + /** + * Transform a file object response from the daemon into a standardized response. + */ + public function transform(array $model): array + { + return [ + 'name' => Arr::get($model, 'name'), + 'mode' => Arr::get($model, 'mode'), + 'mode_bits' => Arr::get($model, 'mode_bits'), + 'size' => Arr::get($model, 'size'), + 'is_file' => Arr::get($model, 'file', true), + 'is_symlink' => Arr::get($model, 'symlink', false), + 'mimetype' => Arr::get($model, 'mime', 'application/octet-stream'), + 'created_at' => Carbon::parse(Arr::get($model, 'created', ''))->toAtomString(), + 'modified_at' => Carbon::parse(Arr::get($model, 'modified', ''))->toAtomString(), + ]; + } } diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 98c783f45..735e8fdd8 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -2,11 +2,11 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\Task; use Pterodactyl\Models\Schedule; use League\Fractal\Resource\Collection; +use Pterodactyl\Transformers\Api\Transformer; -class ScheduleTransformer extends BaseClientTransformer +class ScheduleTransformer extends Transformer { protected array $availableIncludes = ['tasks']; @@ -38,24 +38,18 @@ class ScheduleTransformer extends BaseClientTransformer 'is_active' => $model->is_active, 'is_processing' => $model->is_processing, 'only_when_online' => $model->only_when_online, - 'last_run_at' => $model->last_run_at?->toAtomString(), - 'next_run_at' => $model->next_run_at?->toAtomString(), - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'last_run_at' => self::formatTimestamp($model->last_run_at), + 'next_run_at' => self::formatTimestamp($model->next_run_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } /** * Allows attaching the tasks specific to the schedule in the response. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeTasks(Schedule $model): Collection { - return $this->collection( - $model->tasks, - $this->makeTransformer(TaskTransformer::class), - Task::RESOURCE_NAME - ); + return $this->collection($model->tasks, new TaskTransformer()); } } diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 9f7bce958..782e6b435 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -12,9 +12,10 @@ use Illuminate\Container\Container; use Pterodactyl\Models\EggVariable; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Services\Servers\StartupCommandService; -class ServerTransformer extends BaseClientTransformer +class ServerTransformer extends Transformer { protected array $defaultIncludes = ['allocations', 'variables']; @@ -67,22 +68,16 @@ class ServerTransformer extends BaseClientTransformer 'backups' => $server->backup_limit, ], 'status' => $server->status, - // This field is deprecated, please use "status". - 'is_suspended' => $server->isSuspended(), - // This field is deprecated, please use "status". - 'is_installing' => !$server->isInstalled(), 'is_transferring' => !is_null($server->transfer), ]; } /** * Returns the allocations associated with this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeAllocations(Server $server): Collection { - $transformer = $this->makeTransformer(AllocationTransformer::class); + $transformer = new AllocationTransformer(); $user = $this->request->user(); // While we include this permission, we do need to actually handle it slightly different here @@ -96,42 +91,31 @@ class ServerTransformer extends BaseClientTransformer $primary = clone $server->allocation; $primary->notes = null; - return $this->collection([$primary], $transformer, Allocation::RESOURCE_NAME); + return $this->collection([$primary], $transformer); } - return $this->collection($server->allocations, $transformer, Allocation::RESOURCE_NAME); + return $this->collection($server->allocations, $transformer); } - /** - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ public function includeVariables(Server $server): Collection|NullResource { if (!$this->request->user()->can(Permission::ACTION_STARTUP_READ, $server)) { return $this->null(); } - return $this->collection( - $server->variables->where('user_viewable', true), - $this->makeTransformer(EggVariableTransformer::class), - EggVariable::RESOURCE_NAME - ); + return $this->collection($server->variables->where('user_viewable', true), new EggVariableTransformer()); } /** * Returns the egg associated with this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEgg(Server $server): Item { - return $this->item($server->egg, $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + return $this->item($server->egg, new EggTransformer()); } /** * Returns the subusers associated with this server. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeSubusers(Server $server): Collection|NullResource { @@ -139,6 +123,6 @@ class ServerTransformer extends BaseClientTransformer return $this->null(); } - return $this->collection($server->subusers, $this->makeTransformer(SubuserTransformer::class), Subuser::RESOURCE_NAME); + return $this->collection($server->subusers, new SubuserTransformer()); } } diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index 6b323b315..0ae696cc3 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Illuminate\Support\Arr; +use Pterodactyl\Transformers\Api\Transformer; -class StatsTransformer extends BaseClientTransformer +class StatsTransformer extends Transformer { public function getResourceName(): string { @@ -15,18 +16,18 @@ class StatsTransformer extends BaseClientTransformer * Transform stats from the daemon into a result set that can be used in * the client API. */ - public function transform(array $data): array + public function transform(array $model): array { return [ - 'current_state' => Arr::get($data, 'state', 'stopped'), - 'is_suspended' => Arr::get($data, 'is_suspended', false), + 'current_state' => Arr::get($model, 'state', 'stopped'), + 'is_suspended' => Arr::get($model, 'is_suspended', false), 'resources' => [ - 'memory_bytes' => Arr::get($data, 'utilization.memory_bytes', 0), - 'cpu_absolute' => Arr::get($data, 'utilization.cpu_absolute', 0), - 'disk_bytes' => Arr::get($data, 'utilization.disk_bytes', 0), - 'network_rx_bytes' => Arr::get($data, 'utilization.network.rx_bytes', 0), - 'network_tx_bytes' => Arr::get($data, 'utilization.network.tx_bytes', 0), - 'uptime' => Arr::get($data, 'utilization.uptime', 0), + 'memory_bytes' => Arr::get($model, 'utilization.memory_bytes', 0), + 'cpu_absolute' => Arr::get($model, 'utilization.cpu_absolute', 0), + 'disk_bytes' => Arr::get($model, 'utilization.disk_bytes', 0), + 'network_rx_bytes' => Arr::get($model, 'utilization.network.rx_bytes', 0), + 'network_tx_bytes' => Arr::get($model, 'utilization.network.tx_bytes', 0), + 'uptime' => Arr::get($model, 'utilization.uptime', 0), ], ]; } diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php index 2902d1f7a..4d26ae655 100644 --- a/app/Transformers/Api/Client/SubuserTransformer.php +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Subuser; +use Pterodactyl\Transformers\Api\Transformer; -class SubuserTransformer extends BaseClientTransformer +class SubuserTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -16,13 +17,11 @@ class SubuserTransformer extends BaseClientTransformer /** * Transforms a subuser into a model that can be shown to a front-end user. - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function transform(Subuser $model): array { return array_merge( - $this->makeTransformer(UserTransformer::class)->transform($model->user), + (new UserTransformer())->transform($model->user), ['permissions' => $model->permissions] ); } diff --git a/app/Transformers/Api/Client/TaskTransformer.php b/app/Transformers/Api/Client/TaskTransformer.php index 52f0e2b5d..84054651e 100644 --- a/app/Transformers/Api/Client/TaskTransformer.php +++ b/app/Transformers/Api/Client/TaskTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Task; +use Pterodactyl\Transformers\Api\Transformer; -class TaskTransformer extends BaseClientTransformer +class TaskTransformer extends Transformer { /** * {@inheritdoc} @@ -27,8 +28,8 @@ class TaskTransformer extends BaseClientTransformer 'time_offset' => $model->time_offset, 'is_queued' => $model->is_queued, 'continue_on_failure' => $model->continue_on_failure, - 'created_at' => $model->created_at->toAtomString(), - 'updated_at' => $model->updated_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } } diff --git a/app/Transformers/Api/Client/UserSSHKeyTransformer.php b/app/Transformers/Api/Client/UserSSHKeyTransformer.php index 015a017b6..a13370eb5 100644 --- a/app/Transformers/Api/Client/UserSSHKeyTransformer.php +++ b/app/Transformers/Api/Client/UserSSHKeyTransformer.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\UserSSHKey; +use Pterodactyl\Transformers\Api\Transformer; -class UserSSHKeyTransformer extends BaseClientTransformer +class UserSSHKeyTransformer extends Transformer { public function getResourceName(): string { @@ -20,7 +21,7 @@ class UserSSHKeyTransformer extends BaseClientTransformer 'name' => $model->name, 'fingerprint' => $model->fingerprint, 'public_key' => $model->public_key, - 'created_at' => $model->created_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), ]; } } diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index 04bc70ea6..b6275c3c1 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -4,8 +4,9 @@ namespace Pterodactyl\Transformers\Api\Client; use Illuminate\Support\Str; use Pterodactyl\Models\User; +use Pterodactyl\Transformers\Api\Transformer; -class UserTransformer extends BaseClientTransformer +class UserTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. @@ -25,9 +26,9 @@ class UserTransformer extends BaseClientTransformer 'uuid' => $model->uuid, 'username' => $model->username, 'email' => $model->email, - 'image' => 'https://gravatar.com/avatar/' . md5(Str::lower($model->email)), + 'image' => $model->avatarURL(), '2fa_enabled' => $model->use_totp, - 'created_at' => $model->created_at->toAtomString(), + 'created_at' => self::formatTimestamp($model->created_at), ]; } } diff --git a/app/Transformers/Api/Transformer.php b/app/Transformers/Api/Transformer.php new file mode 100644 index 000000000..8b7e013bb --- /dev/null +++ b/app/Transformers/Api/Transformer.php @@ -0,0 +1,156 @@ +request = Container::getInstance()->make('request'); + + if (method_exists($this, 'handle')) { + Container::getInstance()->call([$this, 'handle']); + } + } + + /** + * Returns the resource name for the transformed item. + */ + abstract public function getResourceName(): string; + + /** + * Returns the authorized user for the request. + */ + protected function user(): User + { + return $this->request->user(); + } + + /** + * Determines if the user making this request is authorized to access the given + * resource on the API. This is used when requested included items to ensure that + * the user and key are authorized to see the result. + * + * TODO: implement this with the new API key formats. + */ + protected function authorize(string $resource): bool + { + return $this->request->user() instanceof User; + } + + /** + * {@inheritDoc} + * + * @param mixed $data + * @param callable|\League\Fractal\TransformerAbstract $transformer + */ + protected function item($data, $transformer, ?string $resourceKey = null): Item + { + if (!$transformer instanceof Closure) { + self::assertSameNamespace($transformer); + } + + $item = parent::item($data, $transformer, $resourceKey); + + if (!$item->getResourceKey() && method_exists($transformer, 'getResourceName')) { + $item->setResourceKey($transformer->getResourceName()); + } + + return $item; + } + + /** + * {@inheritDoc} + * + * @param mixed $data + * @param callable|\League\Fractal\TransformerAbstract $transformer + */ + protected function collection($data, $transformer, ?string $resourceKey = null): Collection + { + if (!$transformer instanceof Closure) { + self::assertSameNamespace($transformer); + } + + $collection = parent::collection($data, $transformer, $resourceKey); + + if (!$collection->getResourceKey() && method_exists($transformer, 'getResourceName')) { + $collection->setResourceKey($transformer->getResourceName()); + } + + return $collection; + } + + /** + * Sets the default timezone to use for transformed responses. Pass a null value + * to return back to the default timezone (UTC). + */ + public static function setTimezone(string $tz = null) + { + static::$timezone = $tz ?? 'UTC'; + } + + /** + * Asserts that the given transformer is the same base namespace as the class that + * implements this abstract transformer class. This prevents a client or application + * transformer from unintentionally transforming a resource using an unexpected type. + * + * @param callable|\League\Fractal\TransformerAbstract $transformer + */ + protected static function assertSameNamespace($transformer) + { + Assert::subclassOf($transformer, TransformerAbstract::class); + + $namespace = substr(get_class($transformer), 0, strlen(class_basename($transformer)) * -1); + $expected = substr(static::class, 0, strlen(class_basename(static::class)) * -1); + + Assert::same($namespace, $expected, 'Cannot invoke a new transformer (%s) that is not in the same namespace (%s).'); + } + + /** + * Returns an ISO-8601 formatted timestamp to use in API responses. This + * time is returned in the default transformer timezone if no timezone value + * is provided. + * + * If no time is provided a null value is returned. + * + * @param string|\DateTimeInterface|null $timestamp + */ + protected static function formatTimestamp($timestamp, string $tz = null): ?string + { + if (empty($timestamp)) { + return null; + } + + if ($timestamp instanceof DateTimeInterface) { + $value = CarbonImmutable::instance($timestamp); + } else { + $value = CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp); + } + + return $value->setTimezone($tz ?? self::$timezone)->toAtomString(); + } +} diff --git a/database/Seeders/EggSeeder.php b/database/Seeders/EggSeeder.php index 234e7b5a4..523494714 100644 --- a/database/Seeders/EggSeeder.php +++ b/database/Seeders/EggSeeder.php @@ -11,10 +11,6 @@ use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService; class EggSeeder extends Seeder { - protected EggImporterService $importerService; - - protected EggUpdateImporterService $updateImporterService; - /** * @var string[] */ @@ -29,15 +25,15 @@ class EggSeeder extends Seeder * EggSeeder constructor. */ public function __construct( - EggImporterService $importerService, - EggUpdateImporterService $updateImporterService + private EggImporterService $importerService, + private EggUpdateImporterService $updateImporterService ) { - $this->importerService = $importerService; - $this->updateImporterService = $updateImporterService; } /** * Run the egg seeder. + * + * @throws \JsonException */ public function run() { @@ -51,6 +47,8 @@ class EggSeeder extends Seeder /** * Loop through the list of egg files and import them. + * + * @throws \JsonException */ protected function parseEggFiles(Nest $nest) { @@ -75,7 +73,7 @@ class EggSeeder extends Seeder $this->updateImporterService->handle($egg, $file); $this->command->info('Updated ' . $decoded['name']); } else { - $this->importerService->handle($file, $nest->id); + $this->importerService->handleFile($nest->id, $file); $this->command->comment('Created ' . $decoded['name']); } } diff --git a/database/migrations/2020_09_25_021109_create_admin_roles_table.php b/database/migrations/2020_09_25_021109_create_admin_roles_table.php new file mode 100644 index 000000000..e67f75074 --- /dev/null +++ b/database/migrations/2020_09_25_021109_create_admin_roles_table.php @@ -0,0 +1,31 @@ +increments('id'); + $table->string('name', 64); + $table->string('description', 255)->nullable(); + $table->integer('sort_id'); + $table->json('permissions')->nullable(); + + $table->unique(['id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admin_roles'); + } +}; diff --git a/database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php b/database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php new file mode 100644 index 000000000..641dc62db --- /dev/null +++ b/database/migrations/2021_01_16_201057_add_admin_role_id_column_to_users_table.php @@ -0,0 +1,30 @@ +integer('admin_role_id')->nullable()->unsigned()->after('language'); + $table->index('admin_role_id'); + $table->foreign('admin_role_id')->references('id')->on('admin_roles')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropForeign(['admin_role_id']); + $table->dropColumn('admin_role_id'); + }); + } +}; diff --git a/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php new file mode 100644 index 000000000..e45ad7618 --- /dev/null +++ b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php @@ -0,0 +1,41 @@ +dropForeign(['node_id']); + $table->dropColumn('node_id'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->integer('database_host_id')->nullable()->unsigned()->after('location_id'); + $table->index('database_host_id')->nullable(); + $table->foreign('database_host_id')->references('id')->on('database_hosts')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropForeign(['database_host_id']); + $table->dropColumn('database_host_id'); + }); + + Schema::table('database_hosts', function (Blueprint $table) { + $table->integer('node_id')->nullable()->unsigned()->after('max_databases'); + $table->index('node_id')->nullable(); + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +}; diff --git a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php new file mode 100644 index 000000000..2e8010c9b --- /dev/null +++ b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php @@ -0,0 +1,58 @@ +renameColumn('daemonListen', 'listen_port_http'); + $table->renameColumn('daemonSFTP', 'listen_port_sftp'); + $table->renameColumn('daemonBase', 'daemon_base'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->integer('listen_port_http')->unsigned()->default(8080)->after('fqdn')->change(); + $table->integer('listen_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp')->change(); + + $table->integer('public_port_http')->unsigned()->default(8080)->after('listen_port_http'); + $table->integer('public_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp'); + }); + + DB::transaction(function () { + foreach (DB::select('SELECT id, listen_port_http, listen_port_sftp FROM nodes') as $datum) { + DB::update('UPDATE nodes SET public_port_http = ?, public_port_sftp = ? WHERE id = ?', [ + $datum->listen_port_http, + $datum->listen_port_sftp, + $datum->id, + ]); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('nodes', function (Blueprint $table) { + $table->renameColumn('listen_port_http', 'daemonListen'); + $table->renameColumn('listen_port_sftp', 'daemonSFTP'); + $table->renameColumn('daemon_base', 'daemonBase'); + + $table->dropColumn('public_port_http'); + $table->dropColumn('public_port_sftp'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->smallInteger('daemonListen')->unsigned()->default(8080)->after('daemon_token')->change(); + $table->smallInteger('daemonSFTP')->unsigned()->default(2022)->after('daemonListen')->change(); + }); + } +}; diff --git a/database/migrations/2021_07_29_032255_yeet_names_from_users_table.php b/database/migrations/2021_07_29_032255_yeet_names_from_users_table.php new file mode 100644 index 000000000..0a22a2059 --- /dev/null +++ b/database/migrations/2021_07_29_032255_yeet_names_from_users_table.php @@ -0,0 +1,28 @@ +dropColumn(['name_first', 'name_last']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->string('name_first')->after('email')->nullable(); + $table->string('name_last')->after('name_first')->nullable(); + }); + } +}; diff --git a/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php b/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php new file mode 100644 index 000000000..b78a9698c --- /dev/null +++ b/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php @@ -0,0 +1,27 @@ +dropColumn('config_logs'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eggs', function (Blueprint $table) { + $table->text('config_logs')->nullable()->after('docker_image'); + }); + } +}; diff --git a/database/migrations/2021_10_23_202643_update_default_values_for_eggs.php b/database/migrations/2021_10_23_202643_update_default_values_for_eggs.php new file mode 100644 index 000000000..fee18b92b --- /dev/null +++ b/database/migrations/2021_10_23_202643_update_default_values_for_eggs.php @@ -0,0 +1,28 @@ +string('script_container')->default('ghcr.io/pterodactyl/installers:alpine')->after('startup')->change(); + $table->string('script_entry')->default('/bin/ash')->after('copy_script_from')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eggs', function (Blueprint $table) { + // You are stuck with the new values because I am too lazy to revert them :) + }); + } +}; diff --git a/database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php b/database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php new file mode 100644 index 000000000..3795eb752 --- /dev/null +++ b/database/migrations/2021_11_01_180130_make_startup_field_nullable_on_servers_table.php @@ -0,0 +1,27 @@ +text('startup')->default(null)->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('servers', function (Blueprint $table) { + $table->text('startup')->change(); + }); + } +}; diff --git a/resources/views/templates/wrapper.blade.php b/resources/views/templates/wrapper.blade.php index 8624d2e85..dd3ed4c5d 100644 --- a/resources/views/templates/wrapper.blade.php +++ b/resources/views/templates/wrapper.blade.php @@ -22,7 +22,7 @@ @section('user-data') @if(!is_null(Auth::user())) @endif @if(!empty($siteConfiguration)) diff --git a/routes/api-application.php b/routes/api-application.php index dc6b0e5bb..cedbcd1d0 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -3,50 +3,47 @@ use Illuminate\Support\Facades\Route; use Pterodactyl\Http\Controllers\Api\Application; +Route::get('/version', [Application\VersionController::class]); + /* |-------------------------------------------------------------------------- -| User Controller Routes +| Database Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /api/application/users +| Endpoint: /api/application/databases | */ +Route::group(['prefix' => '/databases'], function () { + Route::get('/', [Application\Databases\DatabaseController::class, 'index']); + Route::get('/{databaseHost}', [Application\Databases\DatabaseController::class, 'view']); -Route::group(['prefix' => '/users'], function () { - Route::get('/', [Application\Users\UserController::class, 'index'])->name('api.application.users'); - Route::get('/{user:id}', [Application\Users\UserController::class, 'view'])->name('api.application.users.view'); - Route::get('/external/{external_id}', [Application\Users\ExternalUserController::class, 'index'])->name('api.application.users.external'); + Route::post('/', [Application\Databases\DatabaseController::class, 'store']); - Route::post('/', [Application\Users\UserController::class, 'store']); - Route::patch('/{user:id}', [Application\Users\UserController::class, 'update']); + Route::patch('/{databaseHost}', [Application\Databases\DatabaseController::class, 'update']); - Route::delete('/{user:id}', [Application\Users\UserController::class, 'delete']); + Route::delete('/{databaseHost}', [Application\Databases\DatabaseController::class, 'delete']); }); /* |-------------------------------------------------------------------------- -| Node Controller Routes +| Egg Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /api/application/nodes +| Endpoint: /api/application/eggs | */ -Route::group(['prefix' => '/nodes'], function () { - Route::get('/', [Application\Nodes\NodeController::class, 'index'])->name('api.application.nodes'); - Route::get('/deployable', Application\Nodes\NodeDeploymentController::class); - Route::get('/{node:id}', [Application\Nodes\NodeController::class, 'view'])->name('api.application.nodes.view'); - Route::get('/{node:id}/configuration', Application\Nodes\NodeConfigurationController::class); +Route::group(['prefix' => '/eggs'], function () { + Route::get('/{egg}', [Application\Eggs\EggController::class, 'view']); + Route::get('/{egg}/export', [Application\Eggs\EggController::class, 'export']); - Route::post('/', [Application\Nodes\NodeController::class, 'store']); - Route::patch('/{node:id}', [Application\Nodes\NodeController::class, 'update']); + Route::post('/', [Application\Eggs\EggController::class, 'store']); + Route::post('/{egg}/variables', [Application\Eggs\EggVariableController::class, 'store']); - Route::delete('/{node:id}', [Application\Nodes\NodeController::class, 'delete']); + Route::patch('/{egg}', [Application\Eggs\EggController::class, 'update']); + Route::patch('/{egg}/variables', [Application\Eggs\EggVariableController::class, 'update']); - Route::group(['prefix' => '/{node:id}/allocations'], function () { - Route::get('/', [Application\Nodes\AllocationController::class, 'index'])->name('api.application.allocations'); - Route::post('/', [Application\Nodes\AllocationController::class, 'store']); - Route::delete('/{allocation:id}', [Application\Nodes\AllocationController::class, 'delete'])->name('api.application.allocations.view'); - }); + Route::delete('/{egg}', [Application\Eggs\EggController::class, 'delete']); + Route::delete('/{egg}/variables/{eggVariable}', [Application\Eggs\EggVariableController::class, 'delete']); }); /* @@ -58,50 +55,38 @@ Route::group(['prefix' => '/nodes'], function () { | */ Route::group(['prefix' => '/locations'], function () { - Route::get('/', [Application\Locations\LocationController::class, 'index'])->name('api.applications.locations'); - Route::get('/{location:id}', [Application\Locations\LocationController::class, 'view'])->name('api.application.locations.view'); + Route::get('/', [Application\Locations\LocationController::class, 'index']); + Route::get('/{location}', [Application\Locations\LocationController::class, 'view']); Route::post('/', [Application\Locations\LocationController::class, 'store']); - Route::patch('/{location:id}', [Application\Locations\LocationController::class, 'update']); - Route::delete('/{location:id}', [Application\Locations\LocationController::class, 'delete']); + Route::patch('/{location}', [Application\Locations\LocationController::class, 'update']); + + Route::delete('/{location}', [Application\Locations\LocationController::class, 'delete']); }); /* |-------------------------------------------------------------------------- -| Server Controller Routes +| Mount Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /api/application/servers +| Endpoint: /api/application/mounts | */ -Route::group(['prefix' => '/servers'], function () { - Route::get('/', [Application\Servers\ServerController::class, 'index'])->name('api.application.servers'); - Route::get('/{server:id}', [Application\Servers\ServerController::class, 'view'])->name('api.application.servers.view'); - Route::get('/external/{external_id}', [Application\Servers\ExternalServerController::class, 'index'])->name('api.application.servers.external'); +Route::group(['prefix' => '/mounts'], function () { + Route::get('/', [Application\Mounts\MountController::class, 'index']); + Route::get('/{mount}', [Application\Mounts\MountController::class, 'view']); - Route::patch('/{server:id}/details', [Application\Servers\ServerDetailsController::class, 'details'])->name('api.application.servers.details'); - Route::patch('/{server:id}/build', [Application\Servers\ServerDetailsController::class, 'build'])->name('api.application.servers.build'); - Route::patch('/{server:id}/startup', [Application\Servers\StartupController::class, 'index'])->name('api.application.servers.startup'); + Route::post('/', [Application\Mounts\MountController::class, 'store']); - Route::post('/', [Application\Servers\ServerController::class, 'store']); - Route::post('/{server:id}/suspend', [Application\Servers\ServerManagementController::class, 'suspend'])->name('api.application.servers.suspend'); - Route::post('/{server:id}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend'])->name('api.application.servers.unsuspend'); - Route::post('/{server:id}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall'])->name('api.application.servers.reinstall'); + Route::put('/{mount}/eggs', [Application\Mounts\MountController::class, 'addEggs']); + Route::put('/{mount}/nodes', [Application\Mounts\MountController::class, 'addNodes']); - Route::delete('/{server:id}', [Application\Servers\ServerController::class, 'delete']); - Route::delete('/{server:id}/{force?}', [Application\Servers\ServerController::class, 'delete']); + Route::patch('/{mount}', [Application\Mounts\MountController::class, 'update']); - // Database Management Endpoint - Route::group(['prefix' => '/{server:id}/databases'], function () { - Route::get('/', [Application\Servers\DatabaseController::class, 'index'])->name('api.application.servers.databases'); - Route::get('/{database:id}', [Application\Servers\DatabaseController::class, 'view'])->name('api.application.servers.databases.view'); - - Route::post('/', [Application\Servers\DatabaseController::class, 'store']); - Route::post('/{database:id}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); - - Route::delete('/{database:id}', [Application\Servers\DatabaseController::class, 'delete']); - }); + Route::delete('/{mount}', [Application\Mounts\MountController::class, 'delete']); + Route::delete('/{mount}/eggs', [Application\Mounts\MountController::class, 'deleteEggs']); + Route::delete('/{mount}/nodes', [Application\Mounts\MountController::class, 'deleteNodes']); }); /* @@ -113,12 +98,117 @@ Route::group(['prefix' => '/servers'], function () { | */ Route::group(['prefix' => '/nests'], function () { - Route::get('/', [Application\Nests\NestController::class, 'index'])->name('api.application.nests'); - Route::get('/{nest:id}', [Application\Nests\NestController::class, 'view'])->name('api.application.nests.view'); + Route::get('/', [Application\Nests\NestController::class, 'index']); + Route::get('/{nest}', [Application\Nests\NestController::class, 'view']); + Route::get('/{nest}/eggs', [Application\Eggs\EggController::class, 'index']); - // Egg Management Endpoint - Route::group(['prefix' => '/{nest:id}/eggs'], function () { - Route::get('/', [Application\Nests\EggController::class, 'index'])->name('api.application.nests.eggs'); - Route::get('/{egg:id}', [Application\Nests\EggController::class, 'view'])->name('api.application.nests.eggs.view'); + Route::post('/', [Application\Nests\NestController::class, 'store']); + Route::post('/{nest}/import', [Application\Nests\NestController::class, 'import']); + + Route::patch('/{nest}', [Application\Nests\NestController::class, 'update']); + + Route::delete('/{nest}', [Application\Nests\NestController::class, 'delete']); +}); + +/* +|-------------------------------------------------------------------------- +| Node Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/nodes +| +*/ +Route::group(['prefix' => '/nodes'], function () { + Route::get('/', [Application\Nodes\NodeController::class, 'index']); + Route::get('/deployable', [Application\Nodes\NodeDeploymentController::class, '__invoke']); + Route::get('/{node}', [Application\Nodes\NodeController::class, 'view']); + Route::get('/{node}/configuration', [Application\Nodes\NodeConfigurationController::class, '__invoke']); + Route::get('/{node}/information', [Application\Nodes\NodeInformationController::class, '__invoke']); + + Route::post('/', [Application\Nodes\NodeController::class, 'store']); + + Route::patch('/{node}', [Application\Nodes\NodeController::class, 'update']); + + Route::delete('/{node}', [Application\Nodes\NodeController::class, 'delete']); + + Route::group(['prefix' => '/{node}/allocations'], function () { + Route::get('/', [Application\Nodes\AllocationController::class, 'index']); + Route::post('/', [Application\Nodes\AllocationController::class, 'store']); + Route::delete('/{allocation}', [Application\Nodes\AllocationController::class, 'delete']); }); }); + +/* +|-------------------------------------------------------------------------- +| Role Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/roles +| +*/ +Route::group(['prefix' => '/roles'], function () { + Route::get('/', [Application\Roles\RoleController::class, 'index']); + Route::get('/{role}', [Application\Roles\RoleController::class, 'view']); + + Route::post('/', [Application\Roles\RoleController::class, 'store']); + + Route::patch('/{role}', [Application\Roles\RoleController::class, 'update']); + + Route::delete('/{role}', [Application\Roles\RoleController::class, 'delete']); +}); + +/* +|-------------------------------------------------------------------------- +| Server Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/servers +| +*/ +Route::group(['prefix' => '/servers'], function () { + Route::get('/', [Application\Servers\ServerController::class, 'index']); + Route::get('/{server}', [Application\Servers\ServerController::class, 'view']); + Route::get('/external/{external_id}', [Application\Servers\ExternalServerController::class, 'index']); + + Route::patch('/{server}', [Application\Servers\ServerController::class, 'update']); + Route::patch('/{server}/startup', [Application\Servers\StartupController::class, 'index']); + + Route::post('/', [Application\Servers\ServerController::class, 'store']); + Route::post('/{server}/suspend', [Application\Servers\ServerManagementController::class, 'suspend']); + Route::post('/{server}/unsuspend', [Application\Servers\ServerManagementController::class, 'unsuspend']); + Route::post('/{server}/reinstall', [Application\Servers\ServerManagementController::class, 'reinstall']); + + Route::delete('/{server}', [Application\Servers\ServerController::class, 'delete']); + Route::delete('/{server}/{force?}', [Application\Servers\ServerController::class, 'delete']); + + // Database Management Endpoint + Route::group(['prefix' => '/{server}/databases'], function () { + Route::get('/', [Application\Servers\DatabaseController::class, 'index']); + Route::get('/{database}', [Application\Servers\DatabaseController::class, 'view']); + + Route::post('/', [Application\Servers\DatabaseController::class, 'store']); + Route::post('/{database}/reset-password', [Application\Servers\DatabaseController::class, 'resetPassword']); + + Route::delete('/{database}', [Application\Servers\DatabaseController::class, 'delete']); + }); +}); + +/* +|-------------------------------------------------------------------------- +| User Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/application/users +| +*/ +Route::group(['prefix' => '/users'], function () { + Route::get('/', [Application\Users\UserController::class, 'index']); + Route::get('/{user}', [Application\Users\UserController::class, 'view']); + Route::get('/external/{external_id}', [Application\Users\ExternalUserController::class, 'index']); + + Route::post('/', [Application\Users\UserController::class, 'store']); + + Route::patch('/{user}', [Application\Users\UserController::class, 'update']); + + Route::delete('/{user}', [Application\Users\UserController::class, 'delete']); +}); diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 73a4f8f51..53d902e23 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -2,16 +2,13 @@ namespace Pterodactyl\Tests\Integration\Api\Application; -use Illuminate\Http\Request; use Pterodactyl\Models\User; -use PHPUnit\Framework\Assert; use Pterodactyl\Models\ApiKey; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Illuminate\Foundation\Testing\DatabaseTransactions; use Pterodactyl\Tests\Traits\Integration\CreatesTestModels; -use Pterodactyl\Transformers\Api\Application\BaseTransformer; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Tests\Traits\Http\IntegrationJsonRequestAssertions; abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase @@ -95,18 +92,8 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase /** * Return a transformer that can be used for testing purposes. */ - protected function getTransformer(string $abstract): BaseTransformer + protected function getTransformer(string $abstract): Transformer { - $request = Request::createFromGlobals(); - $request->setUserResolver(function () { - return $this->getApiKey()->user; - }); - - $transformer = $abstract::fromRequest($request); - - Assert::assertInstanceOf(BaseTransformer::class, $transformer); - Assert::assertNotInstanceOf(BaseClientTransformer::class, $transformer); - - return $transformer; + return new $abstract(); } } diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 6672960d3..428defd71 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -9,15 +9,12 @@ use Illuminate\Support\Facades\Event; use Pterodactyl\Events\ActivityLogged; use Pterodactyl\Tests\Assertions\AssertsActivityLogged; use Pterodactyl\Tests\Traits\Integration\CreatesTestModels; -use Pterodactyl\Transformers\Api\Application\BaseTransformer; abstract class IntegrationTestCase extends TestCase { use CreatesTestModels; use AssertsActivityLogged; -// protected array $connectionsToTransact = ['pgsql']; - protected $defaultHeaders = [ 'Accept' => 'application/json', ]; @@ -35,7 +32,7 @@ abstract class IntegrationTestCase extends TestCase protected function formatTimestamp(string $timestamp): string { return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) - ->setTimezone(BaseTransformer::RESPONSE_TIMEZONE) + ->setTimezone('UTC') ->toAtomString(); } } diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php index 9cbe315bd..3519238bb 100644 --- a/tests/Traits/Http/MocksMiddlewareClosure.php +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -11,7 +11,7 @@ trait MocksMiddlewareClosure * Provide a closure to be used when validating that the response from the middleware * is the same request object we passed into it. */ - protected function getClosureAssertions(): \Closure + protected function getClosureAssertions(): Closure { if (is_null($this->request)) { throw new \BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); From 363c4fd49f87de5acda9a32b1a69c041e5d2a128 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 17:06:28 -0700 Subject: [PATCH 068/106] php-cs-fixer --- app/Exceptions/Handler.php | 2 +- app/Http/Controllers/Api/Client/ClientApiController.php | 1 - app/Services/Eggs/Sharing/EggUpdateImporterService.php | 2 +- app/Transformers/Api/Client/ServerTransformer.php | 2 -- app/Transformers/Api/Client/UserTransformer.php | 1 - app/Transformers/Api/Transformer.php | 8 +++----- tests/Traits/Http/MocksMiddlewareClosure.php | 2 +- 7 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 4cfee8e46..6fd1b5d8e 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -233,7 +233,7 @@ final class Handler extends ExceptionHandler /** * Return an array of exceptions that should not be reported. */ - public static function isReportable(Exception $exception): bool + public static function isReportable(\Exception $exception): bool { return (new static(Container::getInstance()))->shouldReport($exception); } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index 380cbf548..12a09cb04 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client; -use Webmozart\Assert\Assert; use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index ad17a16f0..d428ce8f4 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -8,8 +8,8 @@ use Illuminate\Support\Collection; use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Eggs\EggParserService; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; class EggUpdateImporterService { diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 782e6b435..401c34905 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -4,12 +4,10 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; use League\Fractal\Resource\Item; use Pterodactyl\Models\Allocation; use Pterodactyl\Models\Permission; use Illuminate\Container\Container; -use Pterodactyl\Models\EggVariable; use League\Fractal\Resource\Collection; use League\Fractal\Resource\NullResource; use Pterodactyl\Transformers\Api\Transformer; diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index b6275c3c1..7a2f07754 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Transformers\Api\Client; -use Illuminate\Support\Str; use Pterodactyl\Models\User; use Pterodactyl\Transformers\Api\Transformer; diff --git a/app/Transformers/Api/Transformer.php b/app/Transformers/Api/Transformer.php index 8b7e013bb..89051d77f 100644 --- a/app/Transformers/Api/Transformer.php +++ b/app/Transformers/Api/Transformer.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Transformers\Api; -use Closure; -use DateTimeInterface; use Carbon\CarbonImmutable; use Carbon\CarbonInterface; use Illuminate\Http\Request; @@ -70,7 +68,7 @@ abstract class Transformer extends TransformerAbstract */ protected function item($data, $transformer, ?string $resourceKey = null): Item { - if (!$transformer instanceof Closure) { + if (!$transformer instanceof \Closure) { self::assertSameNamespace($transformer); } @@ -91,7 +89,7 @@ abstract class Transformer extends TransformerAbstract */ protected function collection($data, $transformer, ?string $resourceKey = null): Collection { - if (!$transformer instanceof Closure) { + if (!$transformer instanceof \Closure) { self::assertSameNamespace($transformer); } @@ -145,7 +143,7 @@ abstract class Transformer extends TransformerAbstract return null; } - if ($timestamp instanceof DateTimeInterface) { + if ($timestamp instanceof \DateTimeInterface) { $value = CarbonImmutable::instance($timestamp); } else { $value = CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp); diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php index 3519238bb..9cbe315bd 100644 --- a/tests/Traits/Http/MocksMiddlewareClosure.php +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -11,7 +11,7 @@ trait MocksMiddlewareClosure * Provide a closure to be used when validating that the response from the middleware * is the same request object we passed into it. */ - protected function getClosureAssertions(): Closure + protected function getClosureAssertions(): \Closure { if (is_null($this->request)) { throw new \BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); From 7ed2be50fdcc40b3ec305229a20393e028394108 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:04:16 -0700 Subject: [PATCH 069/106] php-cs-fixer and phpstan --- app/Console/Commands/InfoCommand.php | 8 +- .../Nodes/NodeDeploymentController.php | 2 +- .../Databases/UpdateDatabaseRequest.php | 2 +- .../Locations/UpdateLocationRequest.php | 2 +- .../Application/Mounts/UpdateMountRequest.php | 2 +- .../Application/Nests/UpdateNestRequest.php | 2 +- .../Application/Nodes/StoreNodeRequest.php | 11 +-- .../Application/Nodes/UpdateNodeRequest.php | 2 +- .../Application/Roles/UpdateRoleRequest.php | 2 +- .../UpdateServerBuildConfigurationRequest.php | 2 +- .../Servers/UpdateServerDetailsRequest.php | 2 +- .../Servers/UpdateServerStartupRequest.php | 2 +- .../Application/Users/UpdateUserRequest.php | 2 +- .../Api/Client/Account/UpdateEmailRequest.php | 1 + app/Models/AdminRole.php | 17 +--- app/Models/EggVariable.php | 13 ++- app/Models/Node.php | 92 ++++++++++++------- .../Eggs/Sharing/EggImporterService.php | 2 +- .../Eggs/Variables/VariableUpdateService.php | 3 +- .../Helpers/SoftwareVersionService.php | 2 +- .../Telemetry/TelemetryCollectionService.php | 6 +- .../Application/ServerVariableTransformer.php | 21 ----- ...database_host_id_column_to_nodes_table.php | 4 +- ...658_change_port_columns_on_nodes_table.php | 6 +- phpstan.neon | 3 - 25 files changed, 102 insertions(+), 109 deletions(-) diff --git a/app/Console/Commands/InfoCommand.php b/app/Console/Commands/InfoCommand.php index 25c774405..6648da298 100644 --- a/app/Console/Commands/InfoCommand.php +++ b/app/Console/Commands/InfoCommand.php @@ -15,7 +15,7 @@ class InfoCommand extends Command /** * VersionCommand constructor. */ - public function __construct(private ConfigRepository $config, private SoftwareVersionService $versionService) + public function __construct(private ConfigRepository $config, private SoftwareVersionService $softwareVersionService) { parent::__construct(); } @@ -27,9 +27,9 @@ class InfoCommand extends Command { $this->output->title('Version Information'); $this->table([], [ - ['Panel Version', $this->config->get('app.version')], - ['Latest Version', $this->versionService->getPanel()], - ['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')], + ['Panel Version', $this->softwareVersionService->getCurrentVersion()], + ['Latest Version', $this->softwareVersionService->getLatestPanel()], + ['Up-to-Date', $this->softwareVersionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')], ['Unique Identifier', $this->config->get('pterodactyl.service.author')], ], 'compact'); diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index fa09e9707..c3e9303a7 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -30,7 +30,7 @@ class NodeDeploymentController extends ApplicationApiController $nodes = $this->viableNodesService->setLocations($data['location_ids'] ?? []) ->setMemory($data['memory']) ->setDisk($data['disk']) - ->handle($request->query('per_page'), $request->query('page')); // @phpstan-ignore-line + ->handle($request->query('per_page'), $request->query('page')); return $this->fractal->collection($nodes) ->transformWith(NodeTransformer::class) diff --git a/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php b/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php index 476719005..51b9f1a59 100644 --- a/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Databases/UpdateDatabaseRequest.php @@ -8,6 +8,6 @@ class UpdateDatabaseRequest extends StoreDatabaseRequest { public function rules(array $rules = null): array { - return $rules ?? DatabaseHost::getRulesForUpdate($this->route()->parameter('databaseHost')->id); + return $rules ?? DatabaseHost::getRulesForUpdate($this->route()->parameter('databaseHost')); } } diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index 91ece11fe..f5d79deb2 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -8,7 +8,7 @@ class UpdateLocationRequest extends StoreLocationRequest { public function rules(): array { - $locationId = $this->route()->parameter('location')->id; + $locationId = $this->route()->parameter('location'); return collect(Location::getRulesForUpdate($locationId))->only([ 'short', diff --git a/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php b/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php index b9b51b12c..c1317f784 100644 --- a/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/UpdateMountRequest.php @@ -8,6 +8,6 @@ class UpdateMountRequest extends StoreMountRequest { public function rules(array $rules = null): array { - return $rules ?? Mount::getRulesForUpdate($this->route()->parameter('mount')->id); + return $rules ?? Mount::getRulesForUpdate($this->route()->parameter('mount')); } } diff --git a/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php b/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php index 1c7aaec89..30016cd01 100644 --- a/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php @@ -8,6 +8,6 @@ class UpdateNestRequest extends StoreNestRequest { public function rules(array $rules = null): array { - return $rules ?? Nest::getRulesForUpdate($this->route()->parameter('nest')->id); + return $rules ?? Nest::getRulesForUpdate($this->route()->parameter('nest')); } } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 803f0e6a3..6d7629302 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -42,10 +42,8 @@ class StoreNodeRequest extends ApplicationApiRequest /** * Fields to rename for clarity in the API response. - * - * @return array */ - public function attributes() + public function attributes(): array { return [ 'daemon_base' => 'Daemon Base Path', @@ -58,13 +56,8 @@ class StoreNodeRequest extends ApplicationApiRequest /** * Change the formatting of some data keys in the validated response data * to match what the application expects in the services. - * - * @param string|null $key - * @param string|array|null $default - * - * @return mixed */ - public function validated($key = null, $default = null) + public function validated($key = null, $default = null): array { $response = parent::validated(); $response['daemon_base'] = $response['daemon_base'] ?? Node::DEFAULT_DAEMON_BASE; diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index 05daebc2e..ae8aab357 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -8,6 +8,6 @@ class UpdateNodeRequest extends StoreNodeRequest { public function rules(array $rules = null): array { - return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node')->id)); + return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node'))); } } diff --git a/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php b/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php index de3221abd..de36ff33e 100644 --- a/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php +++ b/app/Http/Requests/Api/Application/Roles/UpdateRoleRequest.php @@ -8,6 +8,6 @@ class UpdateRoleRequest extends StoreRoleRequest { public function rules(array $rules = null): array { - return $rules ?? AdminRole::getRulesForUpdate($this->route()->parameter('role')->id); + return $rules ?? AdminRole::getRulesForUpdate($this->route()->parameter('role')); } } diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index 1e2f12051..03bbd08ec 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -13,7 +13,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')); return [ 'allocation' => $rules['allocation_id'], diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index a4551edcd..ce181bae3 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -12,7 +12,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')); return [ 'external_id' => $rules['external_id'], diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index d8f92a1f8..fb16ffdac 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -9,7 +9,7 @@ class UpdateServerStartupRequest extends ApplicationApiRequest { public function rules(): array { - $rules = Server::getRulesForUpdate($this->route()->parameter('server')->id); + $rules = Server::getRulesForUpdate($this->route()->parameter('server')); return [ 'startup' => $rules['startup'], diff --git a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php index ba6d9da2c..3d7d9f75c 100644 --- a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -8,6 +8,6 @@ class UpdateUserRequest extends StoreUserRequest { public function rules(array $rules = null): array { - return parent::rules($rules ?? User::getRulesForUpdate($this->route()->parameter('user')->id)); + return parent::rules($rules ?? User::getRulesForUpdate($this->route()->parameter('user'))); } } diff --git a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php index 6287ba585..083e94058 100644 --- a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php @@ -11,6 +11,7 @@ use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; class UpdateEmailRequest extends ClientApiRequest { /** + * @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException */ public function authorize(): bool diff --git a/app/Models/AdminRole.php b/app/Models/AdminRole.php index 51b485495..f03368392 100644 --- a/app/Models/AdminRole.php +++ b/app/Models/AdminRole.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; + /** * @property int $id * @property string $name @@ -19,15 +21,11 @@ class AdminRole extends Model /** * The table associated with the model. - * - * @var string */ protected $table = 'admin_roles'; /** * Fields that are mass assignable. - * - * @var array */ protected $fillable = [ 'name', @@ -37,8 +35,6 @@ class AdminRole extends Model /** * Cast values to correct type. - * - * @var array */ protected $casts = [ 'sort_id' => 'int', @@ -51,17 +47,12 @@ class AdminRole extends Model 'sort_id' => 'sometimes|numeric', ]; - /** - * @var bool - */ public $timestamps = false; /** - * Gets the permissions associated with a admin role. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * Gets the permissions associated with an admin role. */ - public function permissions() + public function permissions(): HasMany { return $this->hasMany(Permission::class); } diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index dbbf8e0bd..b0e15bc5c 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Models; -use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property int $id @@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * @property \Carbon\CarbonImmutable $updated_at * @property bool $required * @property Egg $egg - * @property ServerVariable $serverVariable + * @property ServerVariable $serverVariables * @property string $field_type * * The "server_value" variable is only present on the object if you've loaded this model @@ -81,15 +81,18 @@ class EggVariable extends Model return in_array('required', explode('|', $this->rules)); } - public function egg(): HasOne + /** + * Returns the egg that this variable belongs to. + */ + public function egg(): BelongsTo { - return $this->hasOne(Egg::class); + return $this->belongsTo(Egg::class); } /** * Return server variables associated with this variable. */ - public function serverVariable(): HasMany + public function serverVariables(): HasMany { return $this->hasMany(ServerVariable::class, 'variable_id'); } diff --git a/app/Models/Node.php b/app/Models/Node.php index a1d9688f6..eb7d5cc25 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -19,8 +19,13 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough; * @property string $name * @property string|null $description * @property int $location_id - * @property string $fqdn + * @property int|null $database_host_id * @property string $scheme + * @property string $fqdn + * @property int $listen_port_http + * @property int $listen_port_sftp + * @property int $public_port_http + * @property int $public_port_sftp * @property bool $behind_proxy * @property bool $maintenance_mode * @property int $memory @@ -32,17 +37,16 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough; * @property int $upload_size * @property string $daemon_token_id * @property string $daemon_token - * @property int $daemonListen - * @property int $daemonSFTP - * @property string $daemonBase + * @property string $daemon_base * @property int $servers_count * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at - * @property Location $location - * @property int[]|\Illuminate\Support\Collection $ports - * @property Mount[]|Collection $mounts - * @property Server[]|Collection $servers * @property Allocation[]|Collection $allocations + * @property \Pterodactyl\Models\DatabaseHost|null $databaseHost + * @property Location $location + * @property Mount[]|Collection $mounts + * @property int[]|\Illuminate\Support\Collection $ports + * @property Server[]|Collection $servers */ class Node extends Model { @@ -54,6 +58,11 @@ class Node extends Model */ public const RESOURCE_NAME = 'node'; + /** + * The default location of server files on the Wings instance. + */ + public const DEFAULT_DAEMON_BASE = '/var/lib/pterodactyl/volumes'; + public const DAEMON_TOKEN_ID_LENGTH = 16; public const DAEMON_TOKEN_LENGTH = 64; @@ -72,10 +81,13 @@ class Node extends Model */ protected $casts = [ 'location_id' => 'integer', + 'database_host_id' => 'integer', + 'listen_port_http' => 'integer', + 'listen_port_sftp' => 'integer', + 'public_port_http' => 'integer', + 'public_port_sftp' => 'integer', 'memory' => 'integer', 'disk' => 'integer', - 'daemonListen' => 'integer', - 'daemonSFTP' => 'integer', 'behind_proxy' => 'boolean', 'public' => 'boolean', 'maintenance_mode' => 'boolean', @@ -85,11 +97,11 @@ class Node extends Model * Fields that are mass assignable. */ protected $fillable = [ - 'public', 'name', 'location_id', + 'public', 'name', 'location_id', 'database_host_id', + 'listen_port_http', 'listen_port_sftp', 'public_port_http', 'public_port_sftp', 'fqdn', 'scheme', 'behind_proxy', 'memory', 'memory_overallocate', 'disk', - 'disk_overallocate', 'upload_size', 'daemonBase', - 'daemonSFTP', 'daemonListen', + 'disk_overallocate', 'upload_size', 'daemon_base', 'description', 'maintenance_mode', ]; @@ -97,17 +109,20 @@ class Node extends Model 'name' => 'required|regex:/^([\w .-]{1,100})$/', 'description' => 'string|nullable', 'location_id' => 'required|exists:locations,id', + 'database_host_id' => 'sometimes|nullable|exists:database_hosts,id', 'public' => 'boolean', 'fqdn' => 'required|string', + 'listen_port_http' => 'required|numeric|between:1,65535', + 'listen_port_sftp' => 'required|numeric|between:1,65535', + 'public_port_http' => 'required|numeric|between:1,65535', + 'public_port_sftp' => 'required|numeric|between:1,65535', 'scheme' => 'required', 'behind_proxy' => 'boolean', 'memory' => 'required|numeric|min:1', 'memory_overallocate' => 'required|numeric|min:-1', 'disk' => 'required|numeric|min:1', 'disk_overallocate' => 'required|numeric|min:-1', - 'daemonBase' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', - 'daemonSFTP' => 'required|numeric|between:1,65535', - 'daemonListen' => 'required|numeric|between:1,65535', + 'daemon_base' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', 'maintenance_mode' => 'boolean', 'upload_size' => 'int|between:1,1024', ]; @@ -116,13 +131,15 @@ class Node extends Model * Default values for specific columns that are generally not changed on base installs. */ protected $attributes = [ + 'listen_port_http' => 8080, + 'listen_port_sftp' => 2022, + 'public_port_http' => 8080, + 'public_port_sftp' => 2022, 'public' => true, 'behind_proxy' => false, 'memory_overallocate' => 0, 'disk_overallocate' => 0, - 'daemonBase' => '/var/lib/pterodactyl/volumes', - 'daemonSFTP' => 2022, - 'daemonListen' => 8080, + 'daemon_base' => self::DEFAULT_DAEMON_BASE, 'maintenance_mode' => false, ]; @@ -146,7 +163,7 @@ class Node extends Model 'token' => Container::getInstance()->make(Encrypter::class)->decrypt($this->daemon_token), 'api' => [ 'host' => '0.0.0.0', - 'port' => $this->daemonListen, + 'port' => $this->listen_port_http, 'ssl' => [ 'enabled' => (!$this->behind_proxy && $this->scheme === 'https'), 'cert' => '/etc/letsencrypt/live/' . Str::lower($this->fqdn) . '/fullchain.pem', @@ -155,9 +172,9 @@ class Node extends Model 'upload_limit' => $this->upload_size, ], 'system' => [ - 'data' => $this->daemonBase, + 'data' => $this->daemon_base, 'sftp' => [ - 'bind_port' => $this->daemonSFTP, + 'bind_port' => $this->listen_port_sftp, ], ], 'allowed_mounts' => $this->mounts->pluck('source')->toArray(), @@ -196,9 +213,20 @@ class Node extends Model return $this->maintenance_mode; } - public function mounts(): HasManyThrough + /** + * Gets the allocations associated with a node. + */ + public function allocations(): HasMany { - return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); + return $this->hasMany(Allocation::class); + } + + /** + * Returns the database host associated with a node. + */ + public function databaseHost(): BelongsTo + { + return $this->belongsTo(DatabaseHost::class); } /** @@ -209,6 +237,14 @@ class Node extends Model return $this->belongsTo(Location::class); } + /** + * Returns a HasManyThrough relationship for all the mounts associated with a node. + */ + public function mounts(): HasManyThrough + { + return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); + } + /** * Gets the servers associated with a node. */ @@ -217,14 +253,6 @@ class Node extends Model return $this->hasMany(Server::class); } - /** - * Gets the allocations associated with a node. - */ - public function allocations(): HasMany - { - return $this->hasMany(Allocation::class); - } - public function loadServerSums(): self { $this->loadSum('servers as sum_memory', 'memory'); diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 8be37f0ab..57dd5c8c9 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -116,7 +116,7 @@ class EggImporterService 'copy_script_from' => null, ]); - $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg = $this->eggParserService->fillFromParsed($egg, $parsed); $egg->save(); foreach ($parsed['variables'] ?? [] as $variable) { diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index 5d380f4ac..0bd87b98e 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -8,7 +8,6 @@ use Pterodactyl\Models\EggVariable; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Traits\Services\ValidatesValidationRules; use Illuminate\Contracts\Validation\Factory as ValidationFactory; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; class VariableUpdateService @@ -18,7 +17,7 @@ class VariableUpdateService /** * VariableUpdateService constructor. */ - public function __construct(private EggVariableRepositoryInterface $repository, private ValidationFactory $validator) + public function __construct(private ValidationFactory $validator) { } diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 5b8a5aa80..ad5ca3789 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -122,7 +122,7 @@ class SoftwareVersionService protected function versionData(): array { return $this->cache->remember(self::GIT_VERSION_CACHE_KEY, CarbonImmutable::now()->addSeconds(15), function () { - $configVersion = config()->get('app.version'); + $configVersion = $this->getCurrentVersion(); if (file_exists(base_path('.git/HEAD'))) { $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); diff --git a/app/Services/Telemetry/TelemetryCollectionService.php b/app/Services/Telemetry/TelemetryCollectionService.php index c2fd12830..016cf0817 100644 --- a/app/Services/Telemetry/TelemetryCollectionService.php +++ b/app/Services/Telemetry/TelemetryCollectionService.php @@ -16,6 +16,7 @@ use Pterodactyl\Models\Location; use Illuminate\Support\Facades\DB; use Pterodactyl\Models\Allocation; use Illuminate\Support\Facades\Http; +use Pterodactyl\Services\Helpers\SoftwareVersionService; use Pterodactyl\Repositories\Eloquent\SettingsRepository; use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; @@ -26,7 +27,8 @@ class TelemetryCollectionService */ public function __construct( private DaemonConfigurationRepository $daemonConfigurationRepository, - private SettingsRepository $settingsRepository + private SettingsRepository $settingsRepository, + private SoftwareVersionService $softwareVersionService ) { } @@ -108,7 +110,7 @@ class TelemetryCollectionService 'id' => $uuid, 'panel' => [ - 'version' => config('app.version'), + 'version' => $this->softwareVersionService->getCurrentVersion(), 'phpVersion' => phpversion(), 'drivers' => [ diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index 7c3d1de75..eef631a65 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -2,20 +2,12 @@ namespace Pterodactyl\Transformers\Api\Application; -use League\Fractal\Resource\Item; use Pterodactyl\Models\EggVariable; use Pterodactyl\Models\ServerVariable; -use League\Fractal\Resource\NullResource; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Transformers\Api\Transformer; class ServerVariableTransformer extends Transformer { - /** - * List of resources that can be included. - */ - protected array $availableIncludes = ['parent']; - /** * Return the resource name for the JSONAPI output. */ @@ -31,17 +23,4 @@ class ServerVariableTransformer extends Transformer { return $model->toArray(); } - - /** - * Return the parent service variable data. - */ - public function includeParent(EggVariable $variable): Item|NullResource - { - if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { - return $this->null(); - } - - // TODO: what the fuck? - return $this->item($variable->variable, new EggVariableTransformer()); - } } diff --git a/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php index e45ad7618..04ce1640e 100644 --- a/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php +++ b/database/migrations/2021_01_30_193343_add_database_host_id_column_to_nodes_table.php @@ -17,7 +17,7 @@ return new class () extends Migration { Schema::table('nodes', function (Blueprint $table) { $table->integer('database_host_id')->nullable()->unsigned()->after('location_id'); - $table->index('database_host_id')->nullable(); + $table->index('database_host_id'); $table->foreign('database_host_id')->references('id')->on('database_hosts')->onDelete('set null'); }); } @@ -34,7 +34,7 @@ return new class () extends Migration { Schema::table('database_hosts', function (Blueprint $table) { $table->integer('node_id')->nullable()->unsigned()->after('max_databases'); - $table->index('node_id')->nullable(); + $table->index('node_id'); $table->foreign('node_id')->references('id')->on('nodes'); }); } diff --git a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php index 2e8010c9b..1a61354b7 100644 --- a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php +++ b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php @@ -19,10 +19,10 @@ return new class () extends Migration { Schema::table('nodes', function (Blueprint $table) { $table->integer('listen_port_http')->unsigned()->default(8080)->after('fqdn')->change(); - $table->integer('listen_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp')->change(); + $table->integer('listen_port_sftp')->unsigned()->default(2022)->after('listen_port_http')->change(); - $table->integer('public_port_http')->unsigned()->default(8080)->after('listen_port_http'); - $table->integer('public_port_sftp')->unsigned()->default(2022)->after('listen_port_sftp'); + $table->integer('public_port_http')->unsigned()->default(8080)->after('listen_port_sftp'); + $table->integer('public_port_sftp')->unsigned()->default(2022)->after('public_port_http'); }); DB::transaction(function () { diff --git a/phpstan.neon b/phpstan.neon index e12e72eb5..1a394a28f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -19,9 +19,6 @@ parameters: # Ignore magic spatie calls - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::allowed(\w+)\(\)#' - # This should be replaced with resources instead of a magic transformer factory, robots in disguise - - '#Method Pterodactyl\\Http\\Controllers\\Api\\Client\\ClientApiController::getTransformer\(\) should return T#' - excludePaths: - app/Repositories From 3670dc6d4bc82a75446bde94684510e229a95d92 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:08:50 -0700 Subject: [PATCH 070/106] phpstan --- .../Servers/Databases/StoreServerDatabaseRequest.php | 1 + .../Api/Client/Servers/Files/DownloadFileRequest.php | 2 +- app/Models/Node.php | 2 +- app/Transformers/Api/Application/NodeTransformer.php | 8 +------- app/Transformers/Api/Client/ServerTransformer.php | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index 17368cd08..706af5de6 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -14,6 +14,7 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest { public function rules(): array { + /** @var \Pterodactyl\Models\Server $server */ $server = $this->route()->parameter('server'); return [ diff --git a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php index c588c9b23..af1022bdc 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php @@ -13,6 +13,6 @@ class DownloadFileRequest extends ClientApiRequest */ public function authorize(): bool { - return $this->user()->can('file.read', $this->parameter('server', Server::class)); + return $this->user()->can('file.read', $this->route()->parameter('server')); } } diff --git a/app/Models/Node.php b/app/Models/Node.php index eb7d5cc25..1ab72a5bd 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -148,7 +148,7 @@ class Node extends Model */ public function getConnectionAddress(): string { - return sprintf('%s://%s:%s', $this->scheme, $this->fqdn, $this->daemonListen); + return sprintf('%s://%s:%s', $this->scheme, $this->fqdn, $this->public_port_http); } /** diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index de3fd63da..093fcc962 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -30,13 +30,7 @@ class NodeTransformer extends Transformer */ public function transform(Node $model): array { - $response = collect($model->toArray())->mapWithKeys(function ($value, $key) { - // I messed up early in 2016 when I named this column as poorly - // as I did. This is the tragic result of my mistakes. - $key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key; - - return [snake_case($key) => $value]; - })->toArray(); + $response = $model->toArray(); $response['created_at'] = self::formatTimestamp($model->created_at); $response['updated_at'] = self::formatTimestamp($model->updated_at); diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 401c34905..01a2d135b 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -45,7 +45,7 @@ class ServerTransformer extends Transformer 'is_node_under_maintenance' => $server->node->isUnderMaintenance(), 'sftp_details' => [ 'ip' => $server->node->fqdn, - 'port' => $server->node->daemonSFTP, + 'port' => $server->node->public_port_sftp, ], 'description' => $server->description, 'limits' => [ From f68c46b0a033c07dc71e7be9ffa1db61d9fd5df0 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:10:12 -0700 Subject: [PATCH 071/106] eggs: remove `config_logs` field --- app/Models/Egg.php | 17 ----------------- app/Services/Eggs/EggParserService.php | 1 - .../Eggs/Sharing/EggExporterService.php | 1 - .../Api/Application/EggTransformer.php | 1 - 4 files changed, 20 deletions(-) diff --git a/app/Models/Egg.php b/app/Models/Egg.php index c33e5b194..9ddc2adab 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -20,7 +20,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property array|null $file_denylist * @property string|null $config_files * @property string|null $config_startup - * @property string|null $config_logs * @property string|null $config_stop * @property int|null $config_from * @property string|null $startup @@ -36,7 +35,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string $copy_script_container * @property string|null $inherit_config_files * @property string|null $inherit_config_startup - * @property string|null $inherit_config_logs * @property string|null $inherit_config_stop * @property string $inherit_file_denylist * @property array|null $inherit_features @@ -88,7 +86,6 @@ class Egg extends Model 'file_denylist', 'config_files', 'config_startup', - 'config_logs', 'config_stop', 'config_from', 'startup', @@ -128,7 +125,6 @@ class Egg extends Model 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', 'config_stop' => 'required_without:config_from|nullable|string|max:191', 'config_startup' => 'required_without:config_from|nullable|json', - 'config_logs' => 'required_without:config_from|nullable|json', 'config_files' => 'required_without:config_from|nullable|json', 'update_url' => 'sometimes|nullable|string', 'force_outgoing_ip' => 'sometimes|boolean', @@ -139,7 +135,6 @@ class Egg extends Model 'file_denylist' => null, 'config_stop' => null, 'config_startup' => null, - 'config_logs' => null, 'config_files' => null, 'update_url' => null, ]; @@ -207,18 +202,6 @@ class Egg extends Model return $this->configFrom->config_startup; } - /** - * Return the log reading configuration for an egg. - */ - public function getInheritConfigLogsAttribute(): ?string - { - if (!is_null($this->config_logs) || is_null($this->config_from)) { - return $this->config_logs; - } - - return $this->configFrom->config_logs; - } - /** * Return the stop command configuration for an egg. */ diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php index c442b8e09..0890c3963 100644 --- a/app/Services/Eggs/EggParserService.php +++ b/app/Services/Eggs/EggParserService.php @@ -38,7 +38,6 @@ class EggParserService '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'), diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index 706297b3d..d52fe7371 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -45,7 +45,6 @@ class EggExporterService 'config' => [ 'files' => $egg->inherit_config_files, 'startup' => $egg->inherit_config_startup, - 'logs' => $egg->inherit_config_logs, 'stop' => $egg->inherit_config_stop, ], 'scripts' => [ diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index c98a5d16a..ab7328f94 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -86,7 +86,6 @@ class EggTransformer extends Transformer 'files' => json_decode($model->inherit_config_files), 'startup' => json_decode($model->inherit_config_startup), 'stop' => $model->inherit_config_stop, - 'logs' => json_decode($model->inherit_config_logs), ]; }); } From 2f15d949578efc1138be343093289e85bf92831a Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:17:16 -0700 Subject: [PATCH 072/106] database: fix migrations with postgres --- ..._23_212658_change_port_columns_on_nodes_table.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php index 1a61354b7..8eeef0927 100644 --- a/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php +++ b/database/migrations/2021_02_23_212658_change_port_columns_on_nodes_table.php @@ -12,9 +12,9 @@ return new class () extends Migration { public function up(): void { Schema::table('nodes', function (Blueprint $table) { - $table->renameColumn('daemonListen', 'listen_port_http'); - $table->renameColumn('daemonSFTP', 'listen_port_sftp'); - $table->renameColumn('daemonBase', 'daemon_base'); + $table->renameColumn('`daemonListen`', 'listen_port_http'); + $table->renameColumn('`daemonSFTP`', 'listen_port_sftp'); + $table->renameColumn('`daemonBase`', 'daemon_base'); }); Schema::table('nodes', function (Blueprint $table) { @@ -42,9 +42,9 @@ return new class () extends Migration { public function down(): void { Schema::table('nodes', function (Blueprint $table) { - $table->renameColumn('listen_port_http', 'daemonListen'); - $table->renameColumn('listen_port_sftp', 'daemonSFTP'); - $table->renameColumn('daemon_base', 'daemonBase'); + $table->renameColumn('listen_port_http', '`daemonListen`'); + $table->renameColumn('listen_port_sftp', '`daemonSFTP`'); + $table->renameColumn('daemon_base', '`daemonBase`'); $table->dropColumn('public_port_http'); $table->dropColumn('public_port_sftp'); From 6b11836a410735ecf29b96203a093e0aa80501b5 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:17:27 -0700 Subject: [PATCH 073/106] user: remove `name_first` and `name_last` --- app/Console/Commands/User/DeleteUserCommand.php | 4 ++-- app/Console/Commands/User/MakeUserCommand.php | 5 +---- app/Models/User.php | 9 --------- app/Notifications/AccountCreated.php | 2 +- app/Notifications/MailTested.php | 2 +- app/Observers/SubuserObserver.php | 4 ++-- app/Services/Subusers/SubuserCreationService.php | 2 -- app/Transformers/Api/Client/AccountTransformer.php | 2 -- 8 files changed, 7 insertions(+), 23 deletions(-) diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index 2b13b4866..bac97ddaf 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -44,10 +44,10 @@ class DeleteUserCommand extends Command if ($this->input->isInteractive()) { $tableValues = []; foreach ($results as $user) { - $tableValues[] = [$user->id, $user->email, $user->name]; + $tableValues[] = [$user->id, $user->email, $user->username]; } - $this->table(['User ID', 'Email', 'Name'], $tableValues); + $this->table(['User ID', 'Email', 'Username'], $tableValues); if (!$deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) { return $this->handle(); } diff --git a/app/Console/Commands/User/MakeUserCommand.php b/app/Console/Commands/User/MakeUserCommand.php index 635a95646..f37de218b 100644 --- a/app/Console/Commands/User/MakeUserCommand.php +++ b/app/Console/Commands/User/MakeUserCommand.php @@ -30,8 +30,6 @@ class MakeUserCommand extends Command $root_admin = $this->option('admin') ?? $this->confirm(trans('command/messages.user.ask_admin')); $email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email')); $username = $this->option('username') ?? $this->ask(trans('command/messages.user.ask_username')); - $name_first = $this->option('name-first') ?? $this->ask(trans('command/messages.user.ask_name_first')); - $name_last = $this->option('name-last') ?? $this->ask(trans('command/messages.user.ask_name_last')); if (is_null($password = $this->option('password')) && !$this->option('no-password')) { $this->warn(trans('command/messages.user.ask_password_help')); @@ -39,12 +37,11 @@ class MakeUserCommand extends Command $password = $this->secret(trans('command/messages.user.ask_password')); } - $user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password', 'root_admin')); + $user = $this->creationService->handle(compact('email', 'username', 'password', 'root_admin')); $this->table(['Field', 'Value'], [ ['UUID', $user->uuid], ['Email', $user->email], ['Username', $user->username], - ['Name', $user->name], ['Admin', $user->root_admin ? 'Yes' : 'No'], ]); } diff --git a/app/Models/User.php b/app/Models/User.php index 50361af8b..fa6ebffa1 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -43,7 +43,6 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\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|\Pterodactyl\Models\RecoveryToken[] $recoveryTokens @@ -223,14 +222,6 @@ class User extends Model implements $this->attributes['username'] = mb_strtolower($value); } - /** - * Return a concatenated result for the accounts full name. - */ - public function getNameAttribute(): string - { - return trim($this->name_first . ' ' . $this->name_last); - } - public function avatarURL(): string { return 'https://www.gravatar.com/avatar/' . md5($this->email) . '.jpg'; diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index fb1338012..50d7f1f42 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -33,7 +33,7 @@ class AccountCreated extends Notification implements ShouldQueue public function toMail(): MailMessage { $message = (new MailMessage()) - ->greeting('Hello ' . $this->user->name . '!') + ->greeting('Hello!') ->line('You are receiving this email because an account has been created for you on ' . config('app.name') . '.') ->line('Username: ' . $this->user->username) ->line('Email: ' . $this->user->email); diff --git a/app/Notifications/MailTested.php b/app/Notifications/MailTested.php index 892ff7b0a..3ef25a57b 100644 --- a/app/Notifications/MailTested.php +++ b/app/Notifications/MailTested.php @@ -21,7 +21,7 @@ class MailTested extends Notification { return (new MailMessage()) ->subject('Pterodactyl Test Message') - ->greeting('Hello ' . $this->user->name . '!') + ->greeting('Hello ' . $this->user->username . '!') ->line('This is a test of the Pterodactyl mail system. You\'re good to go!'); } } diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php index f1e028b96..efb24d947 100644 --- a/app/Observers/SubuserObserver.php +++ b/app/Observers/SubuserObserver.php @@ -25,7 +25,7 @@ class SubuserObserver event(new Events\Subuser\Created($subuser)); $subuser->user->notify(new AddedToServer([ - 'user' => $subuser->user->name_first, + 'user' => $subuser->user->username, 'name' => $subuser->server->name, 'uuidShort' => $subuser->server->uuidShort, ])); @@ -47,7 +47,7 @@ class SubuserObserver event(new Events\Subuser\Deleted($subuser)); $subuser->user->notify(new RemovedFromServer([ - 'user' => $subuser->user->name_first, + 'user' => $subuser->user->username, 'name' => $subuser->server->name, ])); } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index c207b0d07..e45f2813a 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -58,8 +58,6 @@ class SubuserCreationService $user = $this->userCreationService->handle([ 'email' => $email, 'username' => $username, - 'name_first' => 'Server', - 'name_last' => 'Subuser', 'root_admin' => false, ]); } diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php index 68651d8f3..3a78792d6 100644 --- a/app/Transformers/Api/Client/AccountTransformer.php +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -25,8 +25,6 @@ class AccountTransformer extends Transformer 'admin' => $model->root_admin, 'username' => $model->username, 'email' => $model->email, - 'first_name' => $model->name_first, - 'last_name' => $model->name_last, 'language' => $model->language, ]; } From a24c594cbdeb6b54edffc2d40482f5ae30c48327 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:23:46 -0700 Subject: [PATCH 074/106] user: remove `name_first` and `name_last` from tests --- database/Factories/NodeFactory.php | 8 +++--- database/Factories/UserFactory.php | 2 -- storage/app/packs/.githold | 0 .../Users/ExternalUserControllerTest.php | 4 +-- .../Application/Users/UserControllerTest.php | 26 ++++++------------- .../Api/Client/AccountControllerTest.php | 2 -- 6 files changed, 14 insertions(+), 28 deletions(-) delete mode 100755 storage/app/packs/.githold diff --git a/database/Factories/NodeFactory.php b/database/Factories/NodeFactory.php index 1dbd5d9f5..65786f702 100644 --- a/database/Factories/NodeFactory.php +++ b/database/Factories/NodeFactory.php @@ -27,6 +27,10 @@ class NodeFactory extends Factory 'public' => true, 'name' => 'FactoryNode_' . Str::random(10), 'fqdn' => $this->faker->unique()->ipv4, + 'listen_port_http' => 8080, + 'listen_port_sftp' => 2022, + 'public_port_http' => 8080, + 'public_port_sftp' => 2022, 'scheme' => 'http', 'behind_proxy' => false, 'memory' => 1024, @@ -36,9 +40,7 @@ class NodeFactory extends Factory 'upload_size' => 100, 'daemon_token_id' => Str::random(Node::DAEMON_TOKEN_ID_LENGTH), 'daemon_token' => Crypt::encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)), - 'daemonListen' => 8080, - 'daemonSFTP' => 2022, - 'daemonBase' => '/var/lib/pterodactyl/volumes', + 'daemon_base' => Node::DEFAULT_DAEMON_BASE, ]; } } diff --git a/database/Factories/UserFactory.php b/database/Factories/UserFactory.php index 1e38eced6..7cb3f0eda 100644 --- a/database/Factories/UserFactory.php +++ b/database/Factories/UserFactory.php @@ -29,8 +29,6 @@ class UserFactory extends Factory 'uuid' => Uuid::uuid4()->toString(), 'username' => $this->faker->userName . '_' . Str::random(10), 'email' => Str::random(32) . '@example.com', - 'name_first' => $this->faker->firstName, - 'name_last' => $this->faker->lastName, 'password' => $password ?: $password = bcrypt('password'), 'language' => 'en', 'root_admin' => false, diff --git a/storage/app/packs/.githold b/storage/app/packs/.githold deleted file mode 100755 index e69de29bb..000000000 diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index 1c02df7fc..053ba36ee 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -22,7 +22,7 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonStructure([ 'object', 'attributes' => [ - 'id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', + 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', ], ]); @@ -35,8 +35,6 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase 'uuid' => $user->uuid, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->totp_enabled, diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 0aebe3d9b..60f104201 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -24,8 +24,8 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonStructure([ 'object', 'data' => [ - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], ], 'meta' => ['pagination' => ['total', 'count', 'per_page', 'current_page', 'total_pages']], ]); @@ -52,11 +52,9 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'uuid' => $this->getApiUser()->uuid, 'username' => $this->getApiUser()->username, 'email' => $this->getApiUser()->email, - 'first_name' => $this->getApiUser()->name_first, - 'last_name' => $this->getApiUser()->name_last, 'language' => $this->getApiUser()->language, 'root_admin' => $this->getApiUser()->root_admin, - '2fa' => (bool) $this->getApiUser()->totp_enabled, + '2fa' => $this->getApiUser()->use_totp, 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), ], @@ -69,11 +67,9 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'uuid' => $user->uuid, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->totp_enabled, + '2fa' => (bool) $user->use_totp, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -92,7 +88,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], ]); $response->assertJson([ @@ -103,8 +99,6 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'uuid' => $user->uuid, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->totp_enabled, @@ -128,7 +122,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonStructure([ 'object', 'attributes' => [ - 'id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', + 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', 'relationships' => ['servers' => ['object', 'data' => [['object', 'attributes' => []]]]], ], ]); @@ -209,15 +203,13 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response = $this->postJson('/api/application/users', [ 'username' => 'testuser', 'email' => 'test@example.com', - 'first_name' => 'Test', - 'last_name' => 'User', ]); $response->assertStatus(Response::HTTP_CREATED); $response->assertJsonCount(3); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], 'meta' => ['resource'], ]); @@ -243,14 +235,12 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response = $this->patchJson('/api/application/users/' . $user->id, [ 'username' => 'new.test.name', 'email' => 'new@emailtest.com', - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, ]); $response->assertStatus(Response::HTTP_OK); $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'first_name', 'last_name', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], ]); $this->assertDatabaseHas('users', ['username' => 'new.test.name', 'email' => 'new@emailtest.com']); diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 0fe1813a3..6c297648d 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -26,8 +26,6 @@ class AccountControllerTest extends ClientApiIntegrationTestCase 'admin' => false, 'username' => $user->username, 'email' => $user->email, - 'first_name' => $user->name_first, - 'last_name' => $user->name_last, 'language' => $user->language, ], ]); From 160c3ddefffa6547ec74ec6ab01644fad54c4c4d Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:41:39 -0700 Subject: [PATCH 075/106] composer: update dependencies --- app/Exceptions/DisplayException.php | 3 - .../RequireTwoFactorAuthentication.php | 10 - composer.json | 44 +-- composer.lock | 374 ++++++++---------- config/app.php | 6 - config/prologue/alerts.php | 37 -- phpstan.neon | 6 - 7 files changed, 176 insertions(+), 304 deletions(-) delete mode 100644 config/prologue/alerts.php diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index c1440dbe0..5ea2140d1 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -9,7 +9,6 @@ use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Illuminate\Container\Container; use Illuminate\Http\RedirectResponse; -use Prologue\Alerts\AlertsMessageBag; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; class DisplayException extends PterodactylException implements HttpExceptionInterface @@ -53,8 +52,6 @@ class DisplayException extends PterodactylException implements HttpExceptionInte return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders()); } - app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); - return redirect()->back()->withInput(); } diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index 1e6856273..a6110edb1 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -5,7 +5,6 @@ namespace Pterodactyl\Http\Middleware; use Illuminate\Support\Str; use Illuminate\Http\Request; use Pterodactyl\Models\User; -use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException; class RequireTwoFactorAuthentication @@ -19,13 +18,6 @@ class RequireTwoFactorAuthentication */ protected string $redirectRoute = '/account'; - /** - * RequireTwoFactorAuthentication constructor. - */ - public function __construct(private AlertsMessageBag $alert) - { - } - /** * Check the user state on the incoming request to determine if they should be allowed to * proceed or not. This checks if the Panel is configured to require 2FA on an account in @@ -66,8 +58,6 @@ class RequireTwoFactorAuthentication throw new TwoFactorAuthRequiredException(); } - $this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash(); - return redirect()->to($this->redirectRoute); } } diff --git a/composer.json b/composer.json index ee181af3f..cf82119e7 100644 --- a/composer.json +++ b/composer.json @@ -24,33 +24,32 @@ "ext-pdo_mysql": "*", "ext-posix": "*", "ext-zip": "*", - "aws/aws-sdk-php": "~3.238.2", - "doctrine/dbal": "~3.4.5", - "guzzlehttp/guzzle": "~7.5.0", - "hashids/hashids": "~4.1.0", - "laracasts/utilities": "~3.2.1", - "laravel/framework": "^9.34.0", - "laravel/helpers": "~1.5.0", - "laravel/sanctum": "~2.15.1", - "laravel/tinker": "~2.7.2", - "laravel/ui": "~3.4.6", - "lcobucci/jwt": "~4.2.1", - "league/flysystem-aws-s3-v3": "~3.5.0", - "league/flysystem-memory": "~3.3.0", + "aws/aws-sdk-php": "~3.253", + "doctrine/dbal": "~3.5", + "guzzlehttp/guzzle": "~7.5", + "hashids/hashids": "~4.1", + "laracasts/utilities": "~3.2", + "laravel/framework": "~9.43", + "laravel/helpers": "~1.5", + "laravel/sanctum": "~3.0", + "laravel/tinker": "~2.7", + "laravel/ui": "~4.1", + "lcobucci/jwt": "~4.2", + "league/flysystem-aws-s3-v3": "~3.10", + "league/flysystem-memory": "~3.10", "matriphe/iso-639": "~1.2", "phpseclib/phpseclib": "~3.0", - "pragmarx/google2fa": "~8.0.0", - "predis/predis": "~2.0.2", - "prologue/alerts": "~1.0.0", - "psr/cache": "~3.0.0", - "s1lentium/iptools": "~1.1.1", - "spatie/laravel-fractal": "~6.0.2", - "spatie/laravel-query-builder": "~5.0.3", - "staudenmeir/belongs-to-through": "~2.12.1", + "pragmarx/google2fa": "~8.0", + "predis/predis": "~2.0", + "psr/cache": "~3.0", + "s1lentium/iptools": "~1.1", + "spatie/laravel-fractal": "~6.0", + "spatie/laravel-query-builder": "~5.1", + "staudenmeir/belongs-to-through": "~2.12", "symfony/http-client": "~6.0", "symfony/mailgun-mailer": "~6.0", "symfony/postmark-mailer": "~6.0", - "symfony/yaml": "~5.4", + "symfony/yaml": "~6.0", "webmozart/assert": "~1.11" }, "require-dev": { @@ -62,6 +61,7 @@ "mockery/mockery": "~1.5", "nunomaduro/collision": "~6.3", "nunomaduro/larastan": "^2.0", + "phpstan/phpstan": "~1.9", "php-mock/php-mock-phpunit": "~2.6", "phpunit/phpunit": "~9.5", "spatie/laravel-ignition": "~1.5" diff --git a/composer.lock b/composer.lock index c2a32a9e6..4ec57b684 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "135afa6c295702de5778650eb2324492", + "content-hash": "a6235c87dc288858ed2c6e8413ab868a", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.238.6", + "version": "3.253.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "79a76b438bd20ae687394561b4f28cb6c10db08e" + "reference": "0f0e24bfae22edcdd62bcaedaff9610f8a328952" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/79a76b438bd20ae687394561b4f28cb6c10db08e", - "reference": "79a76b438bd20ae687394561b4f28cb6c10db08e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0f0e24bfae22edcdd62bcaedaff9610f8a328952", + "reference": "0f0e24bfae22edcdd62bcaedaff9610f8a328952", "shasum": "" }, "require": { @@ -146,9 +146,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.238.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.253.2" }, - "time": "2022-10-17T18:17:10+00:00" + "time": "2022-12-14T19:25:13+00:00" }, { "name": "brick/math", @@ -376,23 +376,23 @@ }, { "name": "doctrine/dbal", - "version": "3.4.6", + "version": "3.5.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "3ce132f7c0b83d33b26ab6ed308e9e9260699bc4" + "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/3ce132f7c0b83d33b26ab6ed308e9e9260699bc4", - "reference": "3ce132f7c0b83d33b26ab6ed308e9e9260699bc4", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", + "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", "shasum": "" }, "require": { "composer-runtime-api": "^2", "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1.0", + "doctrine/event-manager": "^1|^2", "php": "^7.4 || ^8.0", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" @@ -467,7 +467,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.4.6" + "source": "https://github.com/doctrine/dbal/tree/3.5.1" }, "funding": [ { @@ -483,7 +483,7 @@ "type": "tidelift" } ], - "time": "2022-10-21T14:38:43+00:00" + "time": "2022-10-24T07:26:18+00:00" }, { "name": "doctrine/deprecations", @@ -1513,16 +1513,16 @@ }, { "name": "laravel/framework", - "version": "v9.41.0", + "version": "v9.43.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "cc902ce61b4ca08ca7449664cfab2fa96a1d1e28" + "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/cc902ce61b4ca08ca7449664cfab2fa96a1d1e28", - "reference": "cc902ce61b4ca08ca7449664cfab2fa96a1d1e28", + "url": "https://api.github.com/repos/laravel/framework/zipball/011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", + "reference": "011f2e1d49a11c22519a7899b46ddf3bc5b0f40b", "shasum": "" }, "require": { @@ -1695,7 +1695,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-11-22T15:10:46+00:00" + "time": "2022-12-06T14:26:07+00:00" }, { "name": "laravel/helpers", @@ -1755,35 +1755,35 @@ }, { "name": "laravel/sanctum", - "version": "v2.15.1", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473" + "reference": "b71e80a3a8e8029e2ec8c1aa814b999609ce16dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", - "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/b71e80a3a8e8029e2ec8c1aa814b999609ce16dc", + "reference": "b71e80a3a8e8029e2ec8c1aa814b999609ce16dc", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^6.9|^7.0|^8.0|^9.0", - "illuminate/contracts": "^6.9|^7.0|^8.0|^9.0", - "illuminate/database": "^6.9|^7.0|^8.0|^9.0", - "illuminate/support": "^6.9|^7.0|^8.0|^9.0", - "php": "^7.2|^8.0" + "illuminate/console": "^9.21", + "illuminate/contracts": "^9.21", + "illuminate/database": "^9.21", + "illuminate/support": "^9.21", + "php": "^8.0.2" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0", - "phpunit/phpunit": "^8.0|^9.3" + "orchestra/testbench": "^7.0", + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "3.x-dev" }, "laravel": { "providers": [ @@ -1816,7 +1816,7 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2022-04-08T13:39:49+00:00" + "time": "2022-07-29T21:33:30+00:00" }, { "name": "laravel/serializable-closure", @@ -1948,32 +1948,32 @@ }, { "name": "laravel/ui", - "version": "v3.4.6", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c" + "reference": "ac94e596ffd39c63cfa41f5407b765b07df97483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c", + "url": "https://api.github.com/repos/laravel/ui/zipball/ac94e596ffd39c63cfa41f5407b765b07df97483", + "reference": "ac94e596ffd39c63cfa41f5407b765b07df97483", "shasum": "" }, "require": { - "illuminate/console": "^8.42|^9.0", - "illuminate/filesystem": "^8.42|^9.0", - "illuminate/support": "^8.82|^9.0", - "illuminate/validation": "^8.42|^9.0", - "php": "^7.3|^8.0" + "illuminate/console": "^9.21", + "illuminate/filesystem": "^9.21", + "illuminate/support": "^9.21", + "illuminate/validation": "^9.21", + "php": "^8.0" }, "require-dev": { - "orchestra/testbench": "^6.23|^7.0" + "orchestra/testbench": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "4.x-dev" }, "laravel": { "providers": [ @@ -2003,9 +2003,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v3.4.6" + "source": "https://github.com/laravel/ui/tree/v4.1.1" }, - "time": "2022-05-20T13:38:08+00:00" + "time": "2022-12-05T15:09:21+00:00" }, { "name": "lcobucci/clock", @@ -2144,16 +2144,16 @@ }, { "name": "league/commonmark", - "version": "2.3.7", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf" + "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", - "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c493585c130544c4e91d2e0e131e6d35cb0cbc47", + "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47", "shasum": "" }, "require": { @@ -2181,7 +2181,7 @@ "symfony/finder": "^5.3 | ^6.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -2246,20 +2246,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T17:29:46+00:00" + "time": "2022-12-10T16:02:17+00:00" }, { "name": "league/config", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/thephpleague/config.git", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", - "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", "shasum": "" }, "require": { @@ -2268,7 +2268,7 @@ "php": "^7.4 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.90", + "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.5", "scrutinizer/ocular": "^1.8.1", "unleashedtech/php-coding-standard": "^3.1", @@ -2328,20 +2328,20 @@ "type": "github" } ], - "time": "2021-08-14T12:15:32+00:00" + "time": "2022-12-11T20:36:23+00:00" }, { "name": "league/flysystem", - "version": "3.10.4", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a7790f3dd1b27af81d380e6b2afa77c16ab7e181" + "reference": "7e423e5dd240a60adfab9bde058d7668863b7731" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a7790f3dd1b27af81d380e6b2afa77c16ab7e181", - "reference": "a7790f3dd1b27af81d380e6b2afa77c16ab7e181", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7e423e5dd240a60adfab9bde058d7668863b7731", + "reference": "7e423e5dd240a60adfab9bde058d7668863b7731", "shasum": "" }, "require": { @@ -2403,7 +2403,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.10.4" + "source": "https://github.com/thephpleague/flysystem/tree/3.11.0" }, "funding": [ { @@ -2419,25 +2419,25 @@ "type": "tidelift" } ], - "time": "2022-11-26T19:48:01+00:00" + "time": "2022-12-02T14:39:57+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.5.0", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "adb6633f325c934c15a099c363dc5362bdcb07a2" + "reference": "f593bf91f94f2adf4f71513d29f1dfa693f2f640" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/adb6633f325c934c15a099c363dc5362bdcb07a2", - "reference": "adb6633f325c934c15a099c363dc5362bdcb07a2", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/f593bf91f94f2adf4f71513d29f1dfa693f2f640", + "reference": "f593bf91f94f2adf4f71513d29f1dfa693f2f640", "shasum": "" }, "require": { "aws/aws-sdk-php": "^3.132.4", - "league/flysystem": "^3.0.0", + "league/flysystem": "^3.10.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" }, @@ -2473,7 +2473,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.5.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.10.3" }, "funding": [ { @@ -2489,20 +2489,20 @@ "type": "tidelift" } ], - "time": "2022-09-17T21:00:35+00:00" + "time": "2022-10-26T18:15:09+00:00" }, { "name": "league/flysystem-memory", - "version": "3.3.0", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-memory.git", - "reference": "d2a80fbfd3337fac39eeff64c6c0820b6a4902ce" + "reference": "5405162ac81f4de5aa5fa01aae7d07382b7c797b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d2a80fbfd3337fac39eeff64c6c0820b6a4902ce", - "reference": "d2a80fbfd3337fac39eeff64c6c0820b6a4902ce", + "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/5405162ac81f4de5aa5fa01aae7d07382b7c797b", + "reference": "5405162ac81f4de5aa5fa01aae7d07382b7c797b", "shasum": "" }, "require": { @@ -2536,7 +2536,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-memory/issues", - "source": "https://github.com/thephpleague/flysystem-memory/tree/3.3.0" + "source": "https://github.com/thephpleague/flysystem-memory/tree/3.10.3" }, "funding": [ { @@ -2552,7 +2552,7 @@ "type": "tidelift" } ], - "time": "2022-09-09T10:03:42+00:00" + "time": "2022-10-26T18:30:26+00:00" }, { "name": "league/fractal", @@ -2893,16 +2893,16 @@ }, { "name": "nesbot/carbon", - "version": "2.63.0", + "version": "2.64.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "ad35dd71a6a212b98e4b87e97389b6fa85f0e347" + "reference": "889546413c97de2d05063b8cb7b193c2531ea211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ad35dd71a6a212b98e4b87e97389b6fa85f0e347", - "reference": "ad35dd71a6a212b98e4b87e97389b6fa85f0e347", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/889546413c97de2d05063b8cb7b193c2531ea211", + "reference": "889546413c97de2d05063b8cb7b193c2531ea211", "shasum": "" }, "require": { @@ -2913,7 +2913,7 @@ "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/dbal": "^2.0 || ^3.1.4", "doctrine/orm": "^2.7", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", @@ -2991,7 +2991,7 @@ "type": "tidelift" } ], - "time": "2022-10-30T18:34:28+00:00" + "time": "2022-11-26T17:36:00+00:00" }, { "name": "nette/schema", @@ -3707,75 +3707,6 @@ ], "time": "2022-10-11T16:52:29+00:00" }, - { - "name": "prologue/alerts", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/prologuephp/alerts.git", - "reference": "b2880e28814b8dba8768e60e00511627381efe14" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/prologuephp/alerts/zipball/b2880e28814b8dba8768e60e00511627381efe14", - "reference": "b2880e28814b8dba8768e60e00511627381efe14", - "shasum": "" - }, - "require": { - "illuminate/config": "~9", - "illuminate/session": "~9", - "illuminate/support": "~9" - }, - "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Prologue\\Alerts\\AlertsServiceProvider" - ], - "aliases": { - "Alert": "Prologue\\Alerts\\Facades\\Alert" - } - } - }, - "autoload": { - "psr-4": { - "Prologue\\Alerts\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dries Vints", - "email": "dries.vints@gmail.com", - "homepage": "http://driesvints.com", - "role": "Creator" - }, - { - "name": "Cristian Tabacitu", - "email": "hello@tabacitu.ro", - "homepage": "http://tabacitu.ro", - "role": "Maintainer" - } - ], - "description": "Prologue Alerts is a package that handles global site messages.", - "keywords": [ - "alerts", - "laravel", - "messages" - ], - "support": { - "issues": "https://github.com/prologuephp/alerts/issues", - "source": "https://github.com/prologuephp/alerts/tree/1.0.0" - }, - "time": "2022-01-19T09:28:45+00:00" - }, { "name": "psr/cache", "version": "3.0.0", @@ -4785,16 +4716,16 @@ }, { "name": "spatie/laravel-query-builder", - "version": "5.0.3", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "2243e3d60fc184ef20ad2b19765bc7006fa9dbfc" + "reference": "14a6802cd513cfd2abf68044cca5fd7391eb543d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/2243e3d60fc184ef20ad2b19765bc7006fa9dbfc", - "reference": "2243e3d60fc184ef20ad2b19765bc7006fa9dbfc", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/14a6802cd513cfd2abf68044cca5fd7391eb543d", + "reference": "14a6802cd513cfd2abf68044cca5fd7391eb543d", "shasum": "" }, "require": { @@ -4853,7 +4784,7 @@ "type": "custom" } ], - "time": "2022-07-29T14:19:59+00:00" + "time": "2022-12-02T21:28:40+00:00" }, { "name": "staudenmeir/belongs-to-through", @@ -7499,28 +7430,27 @@ }, { "name": "symfony/yaml", - "version": "v5.4.16", + "version": "v6.0.16", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ebd37c71f62d5ec5f6e27de3e06fee492d4c6298" + "reference": "eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ebd37c71f62d5ec5f6e27de3e06fee492d4c6298", - "reference": "ebd37c71f62d5ec5f6e27de3e06fee492d4c6298", + "url": "https://api.github.com/repos/symfony/yaml/zipball/eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b", + "reference": "eb85bd1b0b297e976f3ada52ad239ef80b4dbd0b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.0.2", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" + "symfony/console": "^5.4|^6.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -7554,7 +7484,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.16" + "source": "https://github.com/symfony/yaml/tree/v6.0.16" }, "funding": [ { @@ -7570,7 +7500,7 @@ "type": "tidelift" } ], - "time": "2022-11-25T16:04:03+00:00" + "time": "2022-11-25T18:58:46+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8209,32 +8139,35 @@ }, { "name": "doctrine/annotations", - "version": "1.13.3", + "version": "1.14.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/9e034d7a70032d422169f27d8759e8d84abb4f51", + "reference": "9e034d7a70032d422169f27d8759e8d84abb4f51", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", + "doctrine/lexer": "^1 || ^2", "ext-tokenizer": "*", "php": "^7.1 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", "vimeo/psalm": "^4.10" }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, "type": "library", "autoload": { "psr-4": { @@ -8276,9 +8209,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.3" + "source": "https://github.com/doctrine/annotations/tree/1.14.1" }, - "time": "2022-07-02T10:48:51+00:00" + "time": "2022-12-12T12:46:12+00:00" }, { "name": "doctrine/instantiator", @@ -8352,20 +8285,20 @@ }, { "name": "fakerphp/faker", - "version": "v1.20.0", + "version": "v1.21.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b" + "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/37f751c67a5372d4e26353bd9384bc03744ec77b", - "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/92efad6a967f0b79c499705c69b662f738cc9e4d", + "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", + "php": "^7.4 || ^8.0", "psr/container": "^1.0 || ^2.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" }, @@ -8376,7 +8309,8 @@ "bamarni/composer-bin-plugin": "^1.4.1", "doctrine/persistence": "^1.3 || ^2.0", "ext-intl": "*", - "symfony/phpunit-bridge": "^4.4 || ^5.2" + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" }, "suggest": { "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", @@ -8388,7 +8322,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "v1.20-dev" + "dev-main": "v1.21-dev" } }, "autoload": { @@ -8413,9 +8347,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.20.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.21.0" }, - "time": "2022-07-20T13:12:54+00:00" + "time": "2022-12-13T13:54:32+00:00" }, { "name": "filp/whoops", @@ -8630,16 +8564,16 @@ }, { "name": "itsgoingd/clockwork", - "version": "v5.1.11", + "version": "v5.1.12", "source": { "type": "git", "url": "https://github.com/itsgoingd/clockwork.git", - "reference": "a790200347f0c6d07e2fca252ccb446df87520c6" + "reference": "c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/a790200347f0c6d07e2fca252ccb446df87520c6", - "reference": "a790200347f0c6d07e2fca252ccb446df87520c6", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b", + "reference": "c9dbdbb1f0efd19bb80f1080ef63f1b9b1bc3b1b", "shasum": "" }, "require": { @@ -8686,7 +8620,7 @@ ], "support": { "issues": "https://github.com/itsgoingd/clockwork/issues", - "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.11" + "source": "https://github.com/itsgoingd/clockwork/tree/v5.1.12" }, "funding": [ { @@ -8694,20 +8628,20 @@ "type": "github" } ], - "time": "2022-11-02T21:11:04+00:00" + "time": "2022-12-13T00:04:12+00:00" }, { "name": "laravel/sail", - "version": "v1.16.3", + "version": "v1.16.4", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "0dbee8802e17911afbe29a8506316343829b056e" + "reference": "72412b14d6f4e73b71b5f3068bdb064184fbb001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/0dbee8802e17911afbe29a8506316343829b056e", - "reference": "0dbee8802e17911afbe29a8506316343829b056e", + "url": "https://api.github.com/repos/laravel/sail/zipball/72412b14d6f4e73b71b5f3068bdb064184fbb001", + "reference": "72412b14d6f4e73b71b5f3068bdb064184fbb001", "shasum": "" }, "require": { @@ -8754,7 +8688,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2022-11-21T16:19:18+00:00" + "time": "2022-12-12T16:47:37+00:00" }, { "name": "mockery/mockery", @@ -9565,16 +9499,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.2", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa" + "reference": "709999b91448d4f2bb07daffffedc889b33e461c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/709999b91448d4f2bb07daffffedc889b33e461c", + "reference": "709999b91448d4f2bb07daffffedc889b33e461c", "shasum": "" }, "require": { @@ -9604,7 +9538,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.2" + "source": "https://github.com/phpstan/phpstan/tree/1.9.3" }, "funding": [ { @@ -9620,20 +9554,20 @@ "type": "tidelift" } ], - "time": "2022-11-10T09:56:11+00:00" + "time": "2022-12-13T10:28:10+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.19", + "version": "9.2.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559" + "reference": "3f893e19712bb0c8bc86665d1562e9fd509c4ef0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/3f893e19712bb0c8bc86665d1562e9fd509c4ef0", + "reference": "3f893e19712bb0c8bc86665d1562e9fd509c4ef0", "shasum": "" }, "require": { @@ -9689,7 +9623,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.21" }, "funding": [ { @@ -9697,7 +9631,7 @@ "type": "github" } ], - "time": "2022-11-18T07:47:47+00:00" + "time": "2022-12-14T13:26:54+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9942,16 +9876,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.26", + "version": "9.5.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", + "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", "shasum": "" }, "require": { @@ -10024,7 +9958,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27" }, "funding": [ { @@ -10040,7 +9974,7 @@ "type": "tidelift" } ], - "time": "2022-10-28T06:00:21+00:00" + "time": "2022-12-09T07:31:23+00:00" }, { "name": "sebastian/cli-parser", @@ -11214,16 +11148,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "2b79cf6ed40946b64ac6713d7d2da8a9d87f612b" + "reference": "d6e1e1ad93abe280abf41c33f8ea7647dfc0c233" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/2b79cf6ed40946b64ac6713d7d2da8a9d87f612b", - "reference": "2b79cf6ed40946b64ac6713d7d2da8a9d87f612b", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/d6e1e1ad93abe280abf41c33f8ea7647dfc0c233", + "reference": "d6e1e1ad93abe280abf41c33f8ea7647dfc0c233", "shasum": "" }, "require": { @@ -11300,7 +11234,7 @@ "type": "github" } ], - "time": "2022-10-26T17:39:54+00:00" + "time": "2022-12-08T15:31:38+00:00" }, { "name": "symfony/filesystem", diff --git a/config/app.php b/config/app.php index 36d89b711..68bb4132a 100644 --- a/config/app.php +++ b/config/app.php @@ -202,11 +202,6 @@ return [ Pterodactyl\Providers\RouteServiceProvider::class, Pterodactyl\Providers\RepositoryServiceProvider::class, Pterodactyl\Providers\ViewComposerServiceProvider::class, - - /* - * Additional Dependencies - */ - Prologue\Alerts\AlertsServiceProvider::class, ], /* @@ -221,7 +216,6 @@ return [ */ 'aliases' => Facade::defaultAliases()->merge([ - 'Alert' => Prologue\Alerts\Facades\Alert::class, 'Carbon' => Carbon\Carbon::class, 'JavaScript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, diff --git a/config/prologue/alerts.php b/config/prologue/alerts.php deleted file mode 100644 index 16397703a..000000000 --- a/config/prologue/alerts.php +++ /dev/null @@ -1,37 +0,0 @@ - [ - 'info', - 'warning', - 'danger', - 'success', - ], - - /* - |-------------------------------------------------------------------------- - | Session Key - |-------------------------------------------------------------------------- - | - | The session key which is used to store flashed messages into the current - | session. This can be changed if it conflicts with another key. - | - */ - - 'session_key' => 'alert_messages', -]; diff --git a/phpstan.neon b/phpstan.neon index 1a394a28f..9d13c6c6c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,9 +10,6 @@ parameters: level: 4 ignoreErrors: - # Ignore dynamic methods from 3rd Party Vendor - - '#Call to an undefined method Prologue\\Alerts\\AlertsMessageBag::(success|info|warning|danger)\(\)#' - # Ignore repository interface missing methods - '#Call to an undefined method Pterodactyl\\Repositories\\Wings\\DaemonRepository::(\w+)\(\)#' @@ -22,9 +19,6 @@ parameters: excludePaths: - app/Repositories - # Bug in Laravel Framework #44807 - - app/Console/Commands/Overrides/UpCommand.php - # More magic spatie to be replaced - app/Extensions/Spatie/Fractalistic/Fractal.php From 0c5416ee27d7836afa07b3ad7eb36267136b6fcf Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:43:20 -0700 Subject: [PATCH 076/106] cli: fix up command override --- app/Console/Commands/Overrides/UpCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/Overrides/UpCommand.php b/app/Console/Commands/Overrides/UpCommand.php index 225634dc2..0a7caaeb7 100644 --- a/app/Console/Commands/Overrides/UpCommand.php +++ b/app/Console/Commands/Overrides/UpCommand.php @@ -21,6 +21,6 @@ class UpCommand extends BaseUpCommand return 1; } - return parent::handle() ?? 0; + return parent::handle(); } } From 507a802dec76ffddf6e20f235166ddc3031d0cd5 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 18:53:31 -0700 Subject: [PATCH 077/106] database-host: reverse node relationship --- app/Models/DatabaseHost.php | 23 ++++++++----------- .../Application/DatabaseHostTransformer.php | 1 - .../Database/DatabaseAuthorizationTest.php | 2 +- .../DatabaseManagementServiceTest.php | 10 ++++---- .../DeployServerDatabaseServiceTest.php | 8 +++---- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index df56eb7c7..19a46333e 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -3,7 +3,7 @@ namespace Pterodactyl\Models; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int $id @@ -13,7 +13,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string $username * @property string $password * @property int|null $max_databases - * @property int|null $node_id * @property \Carbon\CarbonImmutable $created_at * @property \Carbon\CarbonImmutable $updated_at */ @@ -41,7 +40,7 @@ class DatabaseHost extends Model * Fields that are mass assignable. */ protected $fillable = [ - 'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id', + 'name', 'host', 'port', 'username', 'password', 'max_databases', ]; /** @@ -50,7 +49,6 @@ class DatabaseHost extends Model protected $casts = [ 'id' => 'integer', 'max_databases' => 'integer', - 'node_id' => 'integer', ]; /** @@ -62,17 +60,8 @@ class DatabaseHost extends Model 'port' => 'required|numeric|between:1,65535', 'username' => 'required|string|max:32', 'password' => 'nullable|string', - 'node_id' => 'sometimes|nullable|integer|exists:nodes,id', ]; - /** - * Gets the node associated with a database host. - */ - public function node(): BelongsTo - { - return $this->belongsTo(Node::class); - } - /** * Gets the databases associated with this host. */ @@ -80,4 +69,12 @@ class DatabaseHost extends Model { return $this->hasMany(Database::class); } + + /** + * Returns the nodes that a database host is assigned to. + */ + public function nodes(): BelongsToMany + { + return $this->belongsToMany(Node::class); + } } diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index c69f9dff3..dfe18c7c4 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -31,7 +31,6 @@ class DatabaseHostTransformer extends Transformer 'host' => $model->host, 'port' => $model->port, 'username' => $model->username, - 'node_id' => $model->node_id, 'created_at' => self::formatTimestamp($model->created_at), 'updated_at' => self::formatTimestamp($model->updated_at), ]; diff --git a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php index ba15c595c..b6f3924e0 100644 --- a/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php +++ b/tests/Integration/Api/Client/Server/Database/DatabaseAuthorizationTest.php @@ -24,7 +24,7 @@ class DatabaseAuthorizationTest extends ClientApiIntegrationTestCase // And as no access to $server3. $server3 = $this->createServerModel(); - $host = DatabaseHost::factory()->create([]); + $host = DatabaseHost::factory()->create(); // Set the API $user as a subuser of server 2, but with no permissions // to do anything with the databases for that server. diff --git a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php index 7e1319b22..1bb599ef9 100644 --- a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php +++ b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php @@ -58,7 +58,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase public function testDatabaseCannotBeCreatedIfServerHasReachedLimit() { $server = $this->createServerModel(['database_limit' => 2]); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); Database::factory()->times(2)->create(['server_id' => $server->id, 'database_host_id' => $host->id]); @@ -90,8 +90,8 @@ class DatabaseManagementServiceTest extends IntegrationTestCase $server = $this->createServerModel(); $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); - $host2 = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); + $host2 = DatabaseHost::factory()->create(); Database::factory()->create([ 'database' => $name, 'database_host_id' => $host->id, @@ -119,7 +119,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase $server = $this->createServerModel(); $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); $this->repository->expects('createDatabase')->with($name); @@ -177,7 +177,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase $server = $this->createServerModel(); $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + $host = DatabaseHost::factory()->create(); $this->repository->expects('createDatabase')->with($name)->andThrows(new \BadMethodCallException()); $this->repository->expects('dropDatabase')->with($name); diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index ec02c13c7..52b3b6554 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -62,7 +62,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase $server = $this->createServerModel(); $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(['node_id' => $node->id]); + DatabaseHost::factory()->create(); config()->set('pterodactyl.client_features.databases.allow_random', false); @@ -97,8 +97,8 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase $server = $this->createServerModel(); $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(['node_id' => $node->id]); - $host = DatabaseHost::factory()->create(['node_id' => $server->node_id]); + DatabaseHost::factory()->create(); + $host = DatabaseHost::factory()->create(); $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, @@ -124,7 +124,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase $server = $this->createServerModel(); $node = Node::factory()->create(['location_id' => $server->location->id]); - $host = DatabaseHost::factory()->create(['node_id' => $node->id]); + $host = DatabaseHost::factory()->create(); $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, From 8fff0846a0632905cd6a498624a682979ee72eef Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 19:13:00 -0700 Subject: [PATCH 078/106] Attempt to fix Fractal object type being null --- .../Serializers/PterodactylSerializer.php | 22 ++++---- .../Spatie/Fractalistic/Fractal.php | 9 ++-- .../{Nests => Eggs}/EggControllerTest.php | 52 ++++++++++++------- 3 files changed, 47 insertions(+), 36 deletions(-) rename tests/Integration/Api/Application/{Nests => Eggs}/EggControllerTest.php (67%) diff --git a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php index 5b53a5ad7..27b4724e1 100644 --- a/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php +++ b/app/Extensions/League/Fractal/Serializers/PterodactylSerializer.php @@ -6,17 +6,6 @@ use League\Fractal\Serializer\ArraySerializer; class PterodactylSerializer extends ArraySerializer { - /** - * Serialize an item. - */ - public function item(?string $resourceKey, array $data): array - { - return [ - 'object' => $resourceKey, - 'attributes' => $data, - ]; - } - /** * Serialize a collection. */ @@ -33,6 +22,17 @@ class PterodactylSerializer extends ArraySerializer ]; } + /** + * Serialize an item. + */ + public function item(?string $resourceKey, array $data): array + { + return [ + 'object' => $resourceKey, + 'attributes' => $data, + ]; + } + /** * Serialize a null resource. */ diff --git a/app/Extensions/Spatie/Fractalistic/Fractal.php b/app/Extensions/Spatie/Fractalistic/Fractal.php index 0c65d6e8e..7fcd91b46 100644 --- a/app/Extensions/Spatie/Fractalistic/Fractal.php +++ b/app/Extensions/Spatie/Fractalistic/Fractal.php @@ -3,8 +3,8 @@ namespace Pterodactyl\Extensions\Spatie\Fractalistic; use League\Fractal\Scope; -use League\Fractal\TransformerAbstract; use Spatie\Fractal\Fractal as SpatieFractal; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use Pterodactyl\Extensions\League\Fractal\Serializers\PterodactylSerializer; @@ -32,11 +32,8 @@ class Fractal extends SpatieFractal // If the resource name is not set attempt to pull it off the transformer // itself and set it automatically. - if ( - is_null($this->resourceName) - && $this->transformer instanceof TransformerAbstract - && method_exists($this->transformer, 'getResourceName') - ) { + $class = is_string($this->transformer) ? new $this->transformer() : $this->transformer; + if (is_null($this->resourceName) && $class instanceof Transformer) { $this->resourceName = $this->transformer->getResourceName(); } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Eggs/EggControllerTest.php similarity index 67% rename from tests/Integration/Api/Application/Nests/EggControllerTest.php rename to tests/Integration/Api/Application/Eggs/EggControllerTest.php index e41b20eb4..58755545c 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Eggs/EggControllerTest.php @@ -1,21 +1,33 @@ repository = $this->app->make(EggRepositoryInterface::class); + } + /** * Test that all the eggs belonging to a given nest can be returned. */ public function testListAllEggsInNest() { - $eggs = Egg::query()->where('nest_id', 1)->get(); + $eggs = $this->repository->findWhere([['nest_id', '=', 1]]); $response = $this->getJson('/api/application/nests/' . $eggs->first()->nest_id . '/eggs'); $response->assertStatus(Response::HTTP_OK); @@ -32,7 +44,6 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase 'files' => [], 'startup' => ['done'], 'stop', - 'logs' => [], 'extends', ], ], @@ -44,12 +55,12 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase $egg = $eggs->where('id', '=', $datum['attributes']['id'])->first(); $expected = json_encode(Arr::sortRecursive($datum['attributes'])); - $actual = json_encode(Arr::sortRecursive($this->getTransformer(EggTransformer::class)->transform($egg))); + $actual = json_encode(Arr::sortRecursive((new EggTransformer())->transform($egg))); - $this->assertSame( + $this->assertJsonStringEqualsJsonString( $expected, $actual, - 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[$expected]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[$actual]." + 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[{$expected}]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[{$actual}]." ); } } @@ -59,9 +70,9 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testReturnSingleEgg() { - $egg = Egg::query()->findOrFail(1); + $egg = $this->repository->find(1); - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id); + $response = $this->getJson('/api/application/eggs/' . $egg->id); $response->assertStatus(Response::HTTP_OK); $response->assertJsonStructure([ 'object', @@ -72,7 +83,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase $response->assertJson([ 'object' => 'egg', - 'attributes' => $this->getTransformer(EggTransformer::class)->transform($egg), + 'attributes' => json_decode(json_encode((new EggTransformer())->transform($egg)), true), ], true); } @@ -81,9 +92,9 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testReturnSingleEggWithRelationships() { - $egg = Egg::query()->findOrFail(1); + $egg = $this->repository->find(1); - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id . '?include=servers,variables,nest'); + $response = $this->getJson('/api/application/eggs/' . $egg->id . '?include=servers,variables,nest'); $response->assertStatus(Response::HTTP_OK); $response->assertJsonStructure([ 'object', @@ -102,9 +113,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetMissingEgg() { - $egg = Egg::query()->findOrFail(1); - - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/0'); + $response = $this->getJson('/api/application/eggs/nil'); $this->assertNotFoundJson($response); } @@ -114,10 +123,15 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $egg = Egg::query()->findOrFail(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); + } - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); - $this->assertAccessDeniedJson($response); + /** + * Test that a nests's existence is not exposed unless an API key has permission + * to access the resource. + */ + public function testResourceIsNotExposedWithoutPermissions() + { + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } From 10b2380e9edb83bfe91ed74ac3ff2ec18ceb323f Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 19:15:19 -0700 Subject: [PATCH 079/106] Make sure to actually update other references as well --- app/Extensions/Spatie/Fractalistic/Fractal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Extensions/Spatie/Fractalistic/Fractal.php b/app/Extensions/Spatie/Fractalistic/Fractal.php index 7fcd91b46..7af9b5436 100644 --- a/app/Extensions/Spatie/Fractalistic/Fractal.php +++ b/app/Extensions/Spatie/Fractalistic/Fractal.php @@ -34,7 +34,7 @@ class Fractal extends SpatieFractal // itself and set it automatically. $class = is_string($this->transformer) ? new $this->transformer() : $this->transformer; if (is_null($this->resourceName) && $class instanceof Transformer) { - $this->resourceName = $this->transformer->getResourceName(); + $this->resourceName = $class->getResourceName(); } return parent::createData(); From 7f669828c60de832618a7c7ae1edaa953e5e650d Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 14 Dec 2022 19:53:07 -0700 Subject: [PATCH 080/106] tests: more fixes, but stuff is still broken --- app/Models/User.php | 2 +- .../ApplicationApiIntegrationTestCase.php | 2 + .../Application/Eggs/EggControllerTest.php | 9 --- .../Location/LocationControllerTest.php | 6 +- .../Application/Nests/NestControllerTest.php | 8 +-- .../Users/ExternalUserControllerTest.php | 8 +-- .../Application/Users/UserControllerTest.php | 68 ++++++------------- .../Client/ClientApiIntegrationTestCase.php | 4 +- .../DatabaseManagementServiceTest.php | 6 +- .../DeployServerDatabaseServiceTest.php | 8 +-- 10 files changed, 36 insertions(+), 85 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index fa6ebffa1..afdbf6566 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -224,7 +224,7 @@ class User extends Model implements public function avatarURL(): string { - return 'https://www.gravatar.com/avatar/' . md5($this->email) . '.jpg'; + return 'https://www.gravatar.com/avatar/' . $this->md5 . '.jpg'; } /** diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 53d902e23..1c7e7e001 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -91,6 +91,8 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase /** * Return a transformer that can be used for testing purposes. + * + * @deprecated Instantiate the transformer directly. */ protected function getTransformer(string $abstract): Transformer { diff --git a/tests/Integration/Api/Application/Eggs/EggControllerTest.php b/tests/Integration/Api/Application/Eggs/EggControllerTest.php index 58755545c..0fe816127 100644 --- a/tests/Integration/Api/Application/Eggs/EggControllerTest.php +++ b/tests/Integration/Api/Application/Eggs/EggControllerTest.php @@ -125,13 +125,4 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase { $this->markTestSkipped('todo: implement proper admin api key permissions system'); } - - /** - * Test that a nests's existence is not exposed unless an API key has permission - * to access the resource. - */ - public function testResourceIsNotExposedWithoutPermissions() - { - $this->markTestSkipped('todo: implement proper admin api key permissions system'); - } } diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index 7a02092b1..92081b5e1 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -259,10 +259,6 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $location = Location::factory()->create(); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_locations' => 0]); - - $response = $this->getJson('/api/application/locations/' . $location->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index 5cbed783c..7b0e68fa4 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -45,7 +45,7 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase 'pagination' => [ 'total' => 4, 'count' => 4, - 'per_page' => 50, + 'per_page' => 10, 'current_page' => 1, 'total_pages' => 1, ], @@ -118,10 +118,6 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $nest = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); - - $response = $this->getJson('/api/application/nests/' . $nest->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index 053ba36ee..468544839 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -37,7 +37,7 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase 'email' => $user->email, 'language' => $user->language, 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->totp_enabled, + '2fa' => (bool) $user->use_totp, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -59,10 +59,6 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(['external_id' => Str::random()]); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/external/' . $user->external_id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 60f104201..43cc83f5a 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Users; use Pterodactyl\Models\User; use Illuminate\Http\Response; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Transformers\Api\Application\UserTransformer; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; @@ -24,8 +23,8 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonStructure([ 'object', 'data' => [ - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], - ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at']], + ['object', 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at']], ], 'meta' => ['pagination' => ['total', 'count', 'per_page', 'current_page', 'total_pages']], ]); @@ -53,8 +52,11 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'username' => $this->getApiUser()->username, 'email' => $this->getApiUser()->email, 'language' => $this->getApiUser()->language, + 'admin_role_id' => $this->getApiUser()->admin_role_id, 'root_admin' => $this->getApiUser()->root_admin, '2fa' => $this->getApiUser()->use_totp, + 'avatar_url' => $this->getApiUser()->avatarURL(), + 'role_name' => $this->getApiUser()->adminRoleName(), 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), ], @@ -68,8 +70,11 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'username' => $user->username, 'email' => $user->email, 'language' => $user->language, + 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->use_totp, + 'avatar_url' => $user->avatarURL(), + 'role_name' => $user->adminRoleName(), 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -88,7 +93,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at'], ]); $response->assertJson([ @@ -100,8 +105,11 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'username' => $user->username, 'email' => $user->email, 'language' => $user->language, + 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, - '2fa' => (bool) $user->totp_enabled, + '2fa' => (bool) $user->use_totp, + 'avatar_url' => $user->avatarURL(), + 'role_name' => $user->adminRoleName(), 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -122,7 +130,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonStructure([ 'object', 'attributes' => [ - 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at', + 'id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at', 'relationships' => ['servers' => ['object', 'data' => [['object', 'attributes' => []]]]], ], ]); @@ -144,33 +152,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testKeyWithoutPermissionCannotLoadRelationship() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_servers' => 0]); - - $user = User::factory()->create(); - $this->createServerModel(['user_id' => $user->id]); - - $response = $this->getJson('/api/application/users/' . $user->id . '?include=servers'); - $response->assertStatus(Response::HTTP_OK); - $response->assertJsonCount(2)->assertJsonCount(1, 'attributes.relationships'); - $response->assertJsonStructure([ - 'attributes' => [ - 'relationships' => [ - 'servers' => ['object', 'attributes'], - ], - ], - ]); - - // Just assert that we see the expected relationship IDs in the response. - $response->assertJson([ - 'attributes' => [ - 'relationships' => [ - 'servers' => [ - 'object' => 'null_resource', - 'attributes' => null, - ], - ], - ], - ]); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -188,11 +170,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/' . $user->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -209,7 +187,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonCount(3); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at'], 'meta' => ['resource'], ]); @@ -240,7 +218,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJsonCount(2); $response->assertJsonStructure([ 'object', - 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'root_admin', '2fa', 'created_at', 'updated_at'], + 'attributes' => ['id', 'external_id', 'uuid', 'username', 'email', 'language', 'admin_role_id', 'root_admin', '2fa', 'avatar_url', 'role_name', 'created_at', 'updated_at'], ]); $this->assertDatabaseHas('users', ['username' => 'new.test.name', 'email' => 'new@emailtest.com']); @@ -274,15 +252,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testApiKeyWithoutWritePermissions(string $method, string $url) { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => AdminAcl::READ]); - - if (str_contains($url, '{id}')) { - $user = User::factory()->create(); - $url = str_replace('{id}', $user->id, $url); - } - - $response = $this->$method($url); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index bc515386c..f5ec4703f 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -14,10 +14,10 @@ use Pterodactyl\Models\Schedule; use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; use Pterodactyl\Models\DatabaseHost; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Tests\Integration\TestResponse; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Illuminate\Database\Eloquent\Model as EloquentModel; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; abstract class ClientApiIntegrationTestCase extends IntegrationTestCase { @@ -89,7 +89,7 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase $transformer = sprintf('\\Pterodactyl\\Transformers\\Api\\Client\\%sTransformer', $reflect->getShortName()); $transformer = new $transformer(); - $this->assertInstanceOf(BaseClientTransformer::class, $transformer); + $this->assertInstanceOf(Transformer::class, $transformer); $this->assertSame( $transformer->transform($model), diff --git a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php index 1bb599ef9..2848adb0f 100644 --- a/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php +++ b/tests/Integration/Services/Databases/DatabaseManagementServiceTest.php @@ -88,7 +88,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase public function testCreatingDatabaseWithIdenticalNameTriggersAnException() { $server = $this->createServerModel(); - $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); + $name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id); $host = DatabaseHost::factory()->create(); $host2 = DatabaseHost::factory()->create(); @@ -117,7 +117,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase public function testServerDatabaseCanBeCreated() { $server = $this->createServerModel(); - $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); + $name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id); $host = DatabaseHost::factory()->create(); @@ -175,7 +175,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase public function testExceptionEncounteredWhileCreatingDatabaseAttemptsToCleanup() { $server = $this->createServerModel(); - $name = DatabaseManagementService::generateUniqueDatabaseName('soemthing', $server->id); + $name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id); $host = DatabaseHost::factory()->create(); diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index 52b3b6554..9dada9405 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -61,8 +61,8 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase { $server = $this->createServerModel(); + $host = DatabaseHost::factory()->create(); $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(); config()->set('pterodactyl.client_features.databases.allow_random', false); @@ -96,9 +96,9 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase { $server = $this->createServerModel(); - $node = Node::factory()->create(['location_id' => $server->location->id]); - DatabaseHost::factory()->create(); + $node = Node::factory()->create(['location_id' => $server->location->id, 'database_host_id' => DatabaseHost::factory()->create()->id]); $host = DatabaseHost::factory()->create(); + $server->node->database_host_id = $host->id; $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, @@ -123,8 +123,8 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase { $server = $this->createServerModel(); - $node = Node::factory()->create(['location_id' => $server->location->id]); $host = DatabaseHost::factory()->create(); + $node = Node::factory()->create(['location_id' => $server->location->id, 'database_host_id' => $host->id]); $this->managementService->expects('create')->with($server, [ 'database_host_id' => $host->id, From 926c8563d06386031e26a6be81f3b4d963f59e4a Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 15 Dec 2022 12:26:34 -0700 Subject: [PATCH 081/106] user: cleanup --- app/Models/User.php | 77 ++++++++----------- .../Api/Application/UserTransformer.php | 4 +- .../Api/Client/UserTransformer.php | 2 +- .../ApplicationApiIntegrationTestCase.php | 2 +- .../Application/Users/UserControllerTest.php | 12 +-- 5 files changed, 44 insertions(+), 53 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index afdbf6566..6ffe07091 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -41,6 +41,10 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @property bool $gravatar * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at + * @property string $avatar_url + * @property string|null $admin_role_name + * @property string $md5 + * @property \Pterodactyl\Models\AdminRole|null $adminRole * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $apiKeys * @property int|null $api_keys_count * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications @@ -127,10 +131,6 @@ class User extends Model implements 'root_admin', ]; - protected $appends = [ - 'md5', - ]; - /** * Cast values to correct type. */ @@ -192,11 +192,9 @@ class User extends Model implements */ public function toReactObject(): array { - $object = Collection::make($this->toArray())->except(['id', 'external_id'])->toArray(); - $object['avatar_url'] = $this->avatarURL(); - $object['role_name'] = $this->adminRoleName(); - - return $object; + return Collection::make($this->append(['avatar_url', 'admin_role_name'])->toArray()) + ->except(['id', 'external_id', 'admin_role', 'admin_role_id']) + ->toArray(); } /** @@ -222,22 +220,34 @@ class User extends Model implements $this->attributes['username'] = mb_strtolower($value); } - public function avatarURL(): string + public function avatarUrl(): Attribute { - return 'https://www.gravatar.com/avatar/' . $this->md5 . '.jpg'; + return Attribute::make( + get: fn () => 'https://www.gravatar.com/avatar/' . $this->md5 . '.jpg', + ); + } + + public function adminRoleName(): Attribute + { + return Attribute::make( + get: fn () => is_null($this->adminRole) ? ($this->root_admin ? 'None' : null) : $this->adminRole->name, + ); + } + + public function md5(): Attribute + { + return Attribute::make( + get: fn () => md5(strtolower($this->email)), + ); } /** - * Gets the name of the role assigned to a user. + * Returns all the activity logs where this user is the subject — not to + * be confused by activity logs where this user is the _actor_. */ - public function adminRoleName(): ?string + public function activity(): MorphToMany { - $role = $this->adminRole; - if (is_null($role)) { - return $this->root_admin ? 'None' : null; - } - - return $role->name; + return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); } public function adminRole(): HasOne @@ -245,14 +255,6 @@ class User extends Model implements return $this->hasOne(AdminRole::class, 'id', 'admin_role_id'); } - /** - * Returns all servers that a user owns. - */ - public function servers(): HasMany - { - return $this->hasMany(Server::class, 'owner_id'); - } - public function apiKeys(): HasMany { return $this->hasMany(ApiKey::class) @@ -264,27 +266,16 @@ class User extends Model implements return $this->hasMany(RecoveryToken::class); } + public function servers(): HasMany + { + return $this->hasMany(Server::class, 'owner_id'); + } + public function sshKeys(): HasMany { return $this->hasMany(UserSSHKey::class); } - /** - * Returns all the activity logs where this user is the subject — not to - * be confused by activity logs where this user is the _actor_. - */ - public function activity(): MorphToMany - { - return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); - } - - public function md5(): Attribute - { - return Attribute::make( - get: fn () => md5(strtolower($this->email)), - ); - } - /** * Returns all the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index c50a9d8c6..2a3065654 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -37,9 +37,9 @@ class UserTransformer extends Transformer 'language' => $model->language, 'root_admin' => (bool) $model->root_admin, '2fa' => (bool) $model->use_totp, - 'avatar_url' => $model->avatarURL(), + 'avatar_url' => $model->avatar_url, 'admin_role_id' => $model->admin_role_id, - 'role_name' => $model->adminRoleName(), + 'role_name' => $model->admin_role_name, 'created_at' => self::formatTimestamp($model->created_at), 'updated_at' => self::formatTimestamp($model->updated_at), ]; diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index 7a2f07754..c076e6b99 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -25,7 +25,7 @@ class UserTransformer extends Transformer 'uuid' => $model->uuid, 'username' => $model->username, 'email' => $model->email, - 'image' => $model->avatarURL(), + 'image' => $model->avatar_url, '2fa_enabled' => $model->use_totp, 'created_at' => self::formatTimestamp($model->created_at), ]; diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 1c7e7e001..a137a324d 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -92,7 +92,7 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase /** * Return a transformer that can be used for testing purposes. * - * @deprecated Instantiate the transformer directly. + * @deprecated instantiate the transformer directly */ protected function getTransformer(string $abstract): Transformer { diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 43cc83f5a..738f5843f 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -55,8 +55,8 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'admin_role_id' => $this->getApiUser()->admin_role_id, 'root_admin' => $this->getApiUser()->root_admin, '2fa' => $this->getApiUser()->use_totp, - 'avatar_url' => $this->getApiUser()->avatarURL(), - 'role_name' => $this->getApiUser()->adminRoleName(), + 'avatar_url' => $this->getApiUser()->avatar_url, + 'role_name' => $this->getApiUser()->admin_role_name, 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), ], @@ -73,8 +73,8 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->use_totp, - 'avatar_url' => $user->avatarURL(), - 'role_name' => $user->adminRoleName(), + 'avatar_url' => $user->avatar_url, + 'role_name' => $user->admin_role_name, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], @@ -108,8 +108,8 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'admin_role_id' => $user->admin_role_id, 'root_admin' => (bool) $user->root_admin, '2fa' => (bool) $user->use_totp, - 'avatar_url' => $user->avatarURL(), - 'role_name' => $user->adminRoleName(), + 'avatar_url' => $user->avatar_url, + 'role_name' => $user->admin_role_name, 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], From d1c7494933b7fa4a216eac2c374176546f4d07b9 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 15 Dec 2022 12:39:37 -0700 Subject: [PATCH 082/106] app: fix DeployServerDatabaseService --- .../Databases/DeployServerDatabaseService.php | 21 ++++++++++--------- .../DeployServerDatabaseServiceTest.php | 11 +++++----- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index e22eba51d..ec678e40a 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -27,21 +27,22 @@ class DeployServerDatabaseService Assert::notEmpty($data['database'] ?? null); Assert::notEmpty($data['remote'] ?? null); - $hosts = DatabaseHost::query()->get()->toBase(); - if ($hosts->isEmpty()) { - throw new NoSuitableDatabaseHostException(); - } else { - $nodeHosts = $hosts->where('node_id', $server->node_id)->toBase(); - - if ($nodeHosts->isEmpty() && !config('pterodactyl.client_features.databases.allow_random')) { + $databaseHostId = $server->node->database_host_id; + if (is_null($databaseHostId)) { + if (!config('pterodactyl.client_features.databases.allow_random')) { throw new NoSuitableDatabaseHostException(); } + + $hosts = DatabaseHost::query()->get()->toBase(); + if ($hosts->isEmpty()) { + throw new NoSuitableDatabaseHostException(); + } + + $databaseHostId = $hosts->random()->id; } return $this->managementService->create($server, [ - 'database_host_id' => $nodeHosts->isEmpty() - ? $hosts->random()->id - : $nodeHosts->random()->id, + 'database_host_id' => $databaseHostId, 'database' => DatabaseManagementService::generateUniqueDatabaseName($data['database'], $server->id), 'remote' => $data['remote'], ]); diff --git a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php index 9dada9405..f1511852b 100644 --- a/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php +++ b/tests/Integration/Services/Databases/DeployServerDatabaseServiceTest.php @@ -62,7 +62,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase $server = $this->createServerModel(); $host = DatabaseHost::factory()->create(); - $node = Node::factory()->create(['location_id' => $server->location->id]); + $node = Node::factory()->create(['database_host_id' => $host->id, 'location_id' => $server->location->id]); config()->set('pterodactyl.client_features.databases.allow_random', false); @@ -96,12 +96,13 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase { $server = $this->createServerModel(); - $node = Node::factory()->create(['location_id' => $server->location->id, 'database_host_id' => DatabaseHost::factory()->create()->id]); - $host = DatabaseHost::factory()->create(); - $server->node->database_host_id = $host->id; + $host1 = DatabaseHost::factory()->create(); + $host2 = DatabaseHost::factory()->create(); + $node = Node::factory()->create(['database_host_id' => $host2->id, 'location_id' => $server->location->id]); + $server->node->database_host_id = $host2->id; $this->managementService->expects('create')->with($server, [ - 'database_host_id' => $host->id, + 'database_host_id' => $host2->id, 'database' => "s{$server->id}_something", 'remote' => '%', ])->andReturns(new Database()); From 5402584508083bc016618ca0dbd7310ff27a52b6 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 15 Dec 2022 19:06:14 -0700 Subject: [PATCH 083/106] ui(admin): add "working" React admin ui --- app/Http/Kernel.php | 2 + .../SubstituteApplicationApiBindings.php | 66 ++++ package.json | 4 +- .../api/admin/databases/createDatabase.ts | 12 + .../api/admin/databases/deleteDatabase.ts | 9 + .../api/admin/databases/getDatabase.ts | 10 + .../api/admin/databases/getDatabases.ts | 64 ++++ .../api/admin/databases/searchDatabases.ts | 25 ++ .../api/admin/databases/updateDatabase.ts | 12 + resources/scripts/api/admin/egg.ts | 104 ++++++ resources/scripts/api/admin/eggs/createEgg.ts | 31 ++ .../api/admin/eggs/createEggVariable.ts | 22 ++ resources/scripts/api/admin/eggs/deleteEgg.ts | 9 + .../api/admin/eggs/deleteEggVariable.ts | 9 + resources/scripts/api/admin/eggs/getEgg.ts | 108 ++++++ resources/scripts/api/admin/eggs/updateEgg.ts | 31 ++ .../api/admin/eggs/updateEggVariables.ts | 21 ++ resources/scripts/api/admin/getVersion.ts | 22 ++ resources/scripts/api/admin/index.ts | 66 ++++ resources/scripts/api/admin/location.ts | 13 + .../api/admin/locations/createLocation.ts | 12 + .../api/admin/locations/deleteLocation.ts | 9 + .../api/admin/locations/getLocation.ts | 10 + .../api/admin/locations/getLocations.ts | 54 +++ .../api/admin/locations/searchLocations.ts | 25 ++ .../api/admin/locations/updateLocation.ts | 12 + .../scripts/api/admin/mounts/createMount.ts | 12 + .../scripts/api/admin/mounts/deleteMount.ts | 9 + .../scripts/api/admin/mounts/getMount.ts | 10 + .../scripts/api/admin/mounts/getMounts.ts | 80 ++++ .../scripts/api/admin/mounts/updateMount.ts | 12 + resources/scripts/api/admin/nest.ts | 25 ++ .../scripts/api/admin/nests/createNest.ts | 12 + .../scripts/api/admin/nests/deleteNest.ts | 9 + resources/scripts/api/admin/nests/getEggs.ts | 38 ++ resources/scripts/api/admin/nests/getNest.ts | 10 + resources/scripts/api/admin/nests/getNests.ts | 66 ++++ .../scripts/api/admin/nests/importEgg.ts | 17 + .../scripts/api/admin/nests/updateNest.ts | 12 + resources/scripts/api/admin/node.ts | 84 +++++ .../nodes/allocations/createAllocation.ts | 16 + .../nodes/allocations/deleteAllocation.ts | 9 + .../admin/nodes/allocations/getAllocations.ts | 39 ++ .../scripts/api/admin/nodes/createNode.ts | 42 +++ .../scripts/api/admin/nodes/deleteNode.ts | 9 + .../scripts/api/admin/nodes/getAllocations.ts | 61 +++ resources/scripts/api/admin/nodes/getNode.ts | 10 + .../api/admin/nodes/getNodeConfiguration.ts | 9 + .../api/admin/nodes/getNodeInformation.ts | 19 + resources/scripts/api/admin/nodes/getNodes.ts | 107 ++++++ .../scripts/api/admin/nodes/updateNode.ts | 21 ++ resources/scripts/api/admin/roles.ts | 103 ++++++ resources/scripts/api/admin/server.ts | 99 +++++ .../scripts/api/admin/servers/createServer.ts | 80 ++++ .../scripts/api/admin/servers/deleteServer.ts | 9 + .../scripts/api/admin/servers/getServer.ts | 10 + .../scripts/api/admin/servers/getServers.ts | 177 +++++++++ .../scripts/api/admin/servers/updateServer.ts | 64 ++++ .../api/admin/servers/updateServerStartup.ts | 28 ++ resources/scripts/api/admin/users.ts | 96 +++++ .../scripts/api/definitions/admin/index.ts | 2 + .../scripts/api/definitions/admin/models.d.ts | 29 ++ .../api/definitions/admin/transformers.ts | 212 +++++++++++ resources/scripts/components/App.tsx | 15 +- .../scripts/components/admin/AdminBox.tsx | 36 ++ .../components/admin/AdminCheckbox.tsx | 36 ++ .../components/admin/AdminContentBlock.tsx | 42 +++ .../scripts/components/admin/AdminTable.tsx | 348 ++++++++++++++++++ .../scripts/components/admin/Sidebar.tsx | 87 +++++ .../components/admin/SubNavigation.tsx | 42 +++ .../admin/databases/DatabaseDeleteButton.tsx | 73 ++++ .../admin/databases/DatabaseEditContainer.tsx | 235 ++++++++++++ .../admin/databases/DatabasesContainer.tsx | 194 ++++++++++ .../admin/databases/NewDatabaseContainer.tsx | 48 +++ .../admin/locations/LocationDeleteButton.tsx | 74 ++++ .../admin/locations/LocationEditContainer.tsx | 180 +++++++++ .../admin/locations/LocationsContainer.tsx | 186 ++++++++++ .../admin/locations/NewLocationButton.tsx | 112 ++++++ .../admin/mounts/MountDeleteButton.tsx | 73 ++++ .../admin/mounts/MountEditContainer.tsx | 142 +++++++ .../components/admin/mounts/MountForm.tsx | 133 +++++++ .../admin/mounts/MountsContainer.tsx | 241 ++++++++++++ .../admin/mounts/NewMountContainer.tsx | 51 +++ .../admin/nests/ImportEggButton.tsx | 82 +++++ .../admin/nests/NestDeleteButton.tsx | 73 ++++ .../admin/nests/NestEditContainer.tsx | 250 +++++++++++++ .../components/admin/nests/NestEggTable.tsx | 160 ++++++++ .../components/admin/nests/NestsContainer.tsx | 182 +++++++++ .../admin/nests/NewEggContainer.tsx | 115 ++++++ .../components/admin/nests/NewNestButton.tsx | 112 ++++++ .../admin/nests/eggs/EggDeleteButton.tsx | 73 ++++ .../admin/nests/eggs/EggExportButton.tsx | 85 +++++ .../admin/nests/eggs/EggInstallContainer.tsx | 110 ++++++ .../components/admin/nests/eggs/EggRouter.tsx | 90 +++++ .../admin/nests/eggs/EggSettingsContainer.tsx | 245 ++++++++++++ .../nests/eggs/EggVariablesContainer.tsx | 218 +++++++++++ .../admin/nests/eggs/NewVariableButton.tsx | 103 ++++++ .../components/admin/nodes/DatabaseSelect.tsx | 56 +++ .../components/admin/nodes/LocationSelect.tsx | 56 +++ .../admin/nodes/NewNodeContainer.tsx | 127 +++++++ .../admin/nodes/NodeAboutContainer.tsx | 96 +++++ .../admin/nodes/NodeAllocationContainer.tsx | 27 ++ .../nodes/NodeConfigurationContainer.tsx | 70 ++++ .../admin/nodes/NodeDeleteButton.tsx | 73 ++++ .../admin/nodes/NodeEditContainer.tsx | 134 +++++++ .../admin/nodes/NodeLimitContainer.tsx | 47 +++ .../admin/nodes/NodeListenContainer.tsx | 37 ++ .../components/admin/nodes/NodeRouter.tsx | 146 ++++++++ .../components/admin/nodes/NodeServers.tsx | 10 + .../admin/nodes/NodeSettingsContainer.tsx | 95 +++++ .../components/admin/nodes/NodesContainer.tsx | 271 ++++++++++++++ .../nodes/allocations/AllocationTable.tsx | 216 +++++++++++ .../allocations/CreateAllocationForm.tsx | 118 ++++++ .../allocations/DeleteAllocationButton.tsx | 77 ++++ .../admin/overview/OverviewContainer.tsx | 103 ++++++ .../components/admin/roles/NewRoleButton.tsx | 107 ++++++ .../admin/roles/RoleDeleteButton.tsx | 73 ++++ .../admin/roles/RoleEditContainer.tsx | 176 +++++++++ .../components/admin/roles/RolesContainer.tsx | 182 +++++++++ .../components/admin/servers/EggSelect.tsx | 75 ++++ .../components/admin/servers/NestSelector.tsx | 44 +++ .../admin/servers/NewServerContainer.tsx | 225 +++++++++++ .../components/admin/servers/NodeSelect.tsx | 46 +++ .../components/admin/servers/OwnerSelect.tsx | 47 +++ .../admin/servers/ServerDeleteButton.tsx | 66 ++++ .../admin/servers/ServerManageContainer.tsx | 60 +++ .../components/admin/servers/ServerRouter.tsx | 76 ++++ .../admin/servers/ServerSettingsContainer.tsx | 103 ++++++ .../admin/servers/ServerStartupContainer.tsx | 258 +++++++++++++ .../admin/servers/ServersContainer.tsx | 36 ++ .../components/admin/servers/ServersTable.tsx | 236 ++++++++++++ .../servers/settings/BaseSettingsBox.tsx | 31 ++ .../servers/settings/FeatureLimitsBox.tsx | 38 ++ .../admin/servers/settings/NetworkingBox.tsx | 68 ++++ .../servers/settings/ServerResourceBox.tsx | 73 ++++ .../admin/settings/GeneralSettings.tsx | 37 ++ .../admin/settings/MailSettings.tsx | 102 +++++ .../admin/settings/SettingsContainer.tsx | 52 +++ .../admin/users/NewUserContainer.tsx | 49 +++ .../components/admin/users/RoleSelect.tsx | 56 +++ .../admin/users/UserAboutContainer.tsx | 62 ++++ .../admin/users/UserDeleteButton.tsx | 73 ++++ .../components/admin/users/UserForm.tsx | 148 ++++++++ .../components/admin/users/UserRouter.tsx | 114 ++++++ .../components/admin/users/UserServers.tsx | 10 + .../components/admin/users/UserTableRow.tsx | 79 ++++ .../components/admin/users/UsersContainer.tsx | 119 ++++++ .../activity/ActivityLogContainer.tsx | 2 +- .../dashboard/forms/RecoveryTokensDialog.tsx | 6 +- .../dashboard/forms/SetupTOTPDialog.tsx | 4 +- .../scripts/components/elements/Code.tsx | 2 +- .../components/elements/CopyOnClick.tsx | 2 +- .../scripts/components/elements/Editor.tsx | 318 ++++++++++++++++ .../scripts/components/elements/Field.tsx | 51 ++- .../components/elements/SearchableSelect.tsx | 315 ++++++++++++++++ .../components/elements/SelectField.tsx | 347 +++++++++++++++++ .../elements/activity/ActivityLogEntry.tsx | 12 +- .../activity/ActivityLogMetaButton.tsx | 4 +- .../elements/activity/style.module.css | 6 +- .../components/elements/alert/Alert.tsx | 2 +- .../elements/button/style.module.css | 14 +- .../components/elements/dialog/Dialog.tsx | 2 +- .../elements/dialog/DialogFooter.tsx | 4 +- .../elements/dialog/style.module.css | 6 +- .../elements/table/TFootPaginated.tsx | 23 ++ .../components/elements/tooltip/Tooltip.tsx | 4 +- resources/scripts/components/helpers.ts | 12 + .../server/ServerActivityLogContainer.tsx | 4 +- .../server/backups/BackupContextMenu.tsx | 6 +- .../components/server/console/ChartBlock.tsx | 2 +- .../components/server/console/Console.tsx | 2 +- .../server/console/ServerConsoleContainer.tsx | 2 +- .../server/console/ServerDetailsBlock.tsx | 10 +- .../components/server/console/StatBlock.tsx | 14 +- .../components/server/console/chart.ts | 4 +- .../server/console/style.module.css | 6 +- .../server/files/FileDropdownMenu.tsx | 2 +- .../server/files/FileManagerStatus.tsx | 4 +- .../server/files/MassActionsBar.tsx | 2 +- .../server/network/AllocationRow.tsx | 2 +- .../helpers/extractSearchFilters.spec.ts | 80 ++++ .../scripts/helpers/extractSearchFilters.ts | 49 +++ .../helpers/splitStringWhitespace.spec.ts | 16 + .../scripts/helpers/splitStringWhitespace.ts | 27 ++ .../scripts/plugins/useDebouncedState.ts | 12 + resources/scripts/routers/AdminRouter.tsx | 180 +++++++++ resources/scripts/state/admin/allocations.ts | 29 ++ resources/scripts/state/admin/databases.ts | 29 ++ resources/scripts/state/admin/index.ts | 44 +++ resources/scripts/state/admin/locations.ts | 29 ++ resources/scripts/state/admin/mounts.ts | 29 ++ resources/scripts/state/admin/nests.ts | 29 ++ resources/scripts/state/admin/nodes.ts | 29 ++ resources/scripts/state/admin/roles.ts | 29 ++ resources/scripts/state/admin/servers.ts | 29 ++ resources/scripts/state/admin/users.ts | 29 ++ routes/api-application.php | 100 ++--- tailwind.config.js | 17 +- yarn.lock | 221 ++++++++++- 199 files changed, 13387 insertions(+), 151 deletions(-) create mode 100644 app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php create mode 100644 resources/scripts/api/admin/databases/createDatabase.ts create mode 100644 resources/scripts/api/admin/databases/deleteDatabase.ts create mode 100644 resources/scripts/api/admin/databases/getDatabase.ts create mode 100644 resources/scripts/api/admin/databases/getDatabases.ts create mode 100644 resources/scripts/api/admin/databases/searchDatabases.ts create mode 100644 resources/scripts/api/admin/databases/updateDatabase.ts create mode 100644 resources/scripts/api/admin/egg.ts create mode 100644 resources/scripts/api/admin/eggs/createEgg.ts create mode 100644 resources/scripts/api/admin/eggs/createEggVariable.ts create mode 100644 resources/scripts/api/admin/eggs/deleteEgg.ts create mode 100644 resources/scripts/api/admin/eggs/deleteEggVariable.ts create mode 100644 resources/scripts/api/admin/eggs/getEgg.ts create mode 100644 resources/scripts/api/admin/eggs/updateEgg.ts create mode 100644 resources/scripts/api/admin/eggs/updateEggVariables.ts create mode 100644 resources/scripts/api/admin/getVersion.ts create mode 100644 resources/scripts/api/admin/index.ts create mode 100644 resources/scripts/api/admin/location.ts create mode 100644 resources/scripts/api/admin/locations/createLocation.ts create mode 100644 resources/scripts/api/admin/locations/deleteLocation.ts create mode 100644 resources/scripts/api/admin/locations/getLocation.ts create mode 100644 resources/scripts/api/admin/locations/getLocations.ts create mode 100644 resources/scripts/api/admin/locations/searchLocations.ts create mode 100644 resources/scripts/api/admin/locations/updateLocation.ts create mode 100644 resources/scripts/api/admin/mounts/createMount.ts create mode 100644 resources/scripts/api/admin/mounts/deleteMount.ts create mode 100644 resources/scripts/api/admin/mounts/getMount.ts create mode 100644 resources/scripts/api/admin/mounts/getMounts.ts create mode 100644 resources/scripts/api/admin/mounts/updateMount.ts create mode 100644 resources/scripts/api/admin/nest.ts create mode 100644 resources/scripts/api/admin/nests/createNest.ts create mode 100644 resources/scripts/api/admin/nests/deleteNest.ts create mode 100644 resources/scripts/api/admin/nests/getEggs.ts create mode 100644 resources/scripts/api/admin/nests/getNest.ts create mode 100644 resources/scripts/api/admin/nests/getNests.ts create mode 100644 resources/scripts/api/admin/nests/importEgg.ts create mode 100644 resources/scripts/api/admin/nests/updateNest.ts create mode 100644 resources/scripts/api/admin/node.ts create mode 100644 resources/scripts/api/admin/nodes/allocations/createAllocation.ts create mode 100644 resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts create mode 100644 resources/scripts/api/admin/nodes/allocations/getAllocations.ts create mode 100644 resources/scripts/api/admin/nodes/createNode.ts create mode 100644 resources/scripts/api/admin/nodes/deleteNode.ts create mode 100644 resources/scripts/api/admin/nodes/getAllocations.ts create mode 100644 resources/scripts/api/admin/nodes/getNode.ts create mode 100644 resources/scripts/api/admin/nodes/getNodeConfiguration.ts create mode 100644 resources/scripts/api/admin/nodes/getNodeInformation.ts create mode 100644 resources/scripts/api/admin/nodes/getNodes.ts create mode 100644 resources/scripts/api/admin/nodes/updateNode.ts create mode 100644 resources/scripts/api/admin/roles.ts create mode 100644 resources/scripts/api/admin/server.ts create mode 100644 resources/scripts/api/admin/servers/createServer.ts create mode 100644 resources/scripts/api/admin/servers/deleteServer.ts create mode 100644 resources/scripts/api/admin/servers/getServer.ts create mode 100644 resources/scripts/api/admin/servers/getServers.ts create mode 100644 resources/scripts/api/admin/servers/updateServer.ts create mode 100644 resources/scripts/api/admin/servers/updateServerStartup.ts create mode 100644 resources/scripts/api/admin/users.ts create mode 100644 resources/scripts/api/definitions/admin/index.ts create mode 100644 resources/scripts/api/definitions/admin/models.d.ts create mode 100644 resources/scripts/api/definitions/admin/transformers.ts create mode 100644 resources/scripts/components/admin/AdminBox.tsx create mode 100644 resources/scripts/components/admin/AdminCheckbox.tsx create mode 100644 resources/scripts/components/admin/AdminContentBlock.tsx create mode 100644 resources/scripts/components/admin/AdminTable.tsx create mode 100644 resources/scripts/components/admin/Sidebar.tsx create mode 100644 resources/scripts/components/admin/SubNavigation.tsx create mode 100644 resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx create mode 100644 resources/scripts/components/admin/databases/DatabaseEditContainer.tsx create mode 100644 resources/scripts/components/admin/databases/DatabasesContainer.tsx create mode 100644 resources/scripts/components/admin/databases/NewDatabaseContainer.tsx create mode 100644 resources/scripts/components/admin/locations/LocationDeleteButton.tsx create mode 100644 resources/scripts/components/admin/locations/LocationEditContainer.tsx create mode 100644 resources/scripts/components/admin/locations/LocationsContainer.tsx create mode 100644 resources/scripts/components/admin/locations/NewLocationButton.tsx create mode 100644 resources/scripts/components/admin/mounts/MountDeleteButton.tsx create mode 100644 resources/scripts/components/admin/mounts/MountEditContainer.tsx create mode 100644 resources/scripts/components/admin/mounts/MountForm.tsx create mode 100644 resources/scripts/components/admin/mounts/MountsContainer.tsx create mode 100644 resources/scripts/components/admin/mounts/NewMountContainer.tsx create mode 100644 resources/scripts/components/admin/nests/ImportEggButton.tsx create mode 100644 resources/scripts/components/admin/nests/NestDeleteButton.tsx create mode 100644 resources/scripts/components/admin/nests/NestEditContainer.tsx create mode 100644 resources/scripts/components/admin/nests/NestEggTable.tsx create mode 100644 resources/scripts/components/admin/nests/NestsContainer.tsx create mode 100644 resources/scripts/components/admin/nests/NewEggContainer.tsx create mode 100644 resources/scripts/components/admin/nests/NewNestButton.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggExportButton.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggRouter.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx create mode 100644 resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx create mode 100644 resources/scripts/components/admin/nodes/DatabaseSelect.tsx create mode 100644 resources/scripts/components/admin/nodes/LocationSelect.tsx create mode 100644 resources/scripts/components/admin/nodes/NewNodeContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeAboutContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeDeleteButton.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeEditContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeLimitContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeListenContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeRouter.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeServers.tsx create mode 100644 resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/NodesContainer.tsx create mode 100644 resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx create mode 100644 resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx create mode 100644 resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx create mode 100644 resources/scripts/components/admin/overview/OverviewContainer.tsx create mode 100644 resources/scripts/components/admin/roles/NewRoleButton.tsx create mode 100644 resources/scripts/components/admin/roles/RoleDeleteButton.tsx create mode 100644 resources/scripts/components/admin/roles/RoleEditContainer.tsx create mode 100644 resources/scripts/components/admin/roles/RolesContainer.tsx create mode 100644 resources/scripts/components/admin/servers/EggSelect.tsx create mode 100644 resources/scripts/components/admin/servers/NestSelector.tsx create mode 100644 resources/scripts/components/admin/servers/NewServerContainer.tsx create mode 100644 resources/scripts/components/admin/servers/NodeSelect.tsx create mode 100644 resources/scripts/components/admin/servers/OwnerSelect.tsx create mode 100644 resources/scripts/components/admin/servers/ServerDeleteButton.tsx create mode 100644 resources/scripts/components/admin/servers/ServerManageContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServerRouter.tsx create mode 100644 resources/scripts/components/admin/servers/ServerSettingsContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServerStartupContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServersContainer.tsx create mode 100644 resources/scripts/components/admin/servers/ServersTable.tsx create mode 100644 resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx create mode 100644 resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx create mode 100644 resources/scripts/components/admin/servers/settings/NetworkingBox.tsx create mode 100644 resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx create mode 100644 resources/scripts/components/admin/settings/GeneralSettings.tsx create mode 100644 resources/scripts/components/admin/settings/MailSettings.tsx create mode 100644 resources/scripts/components/admin/settings/SettingsContainer.tsx create mode 100644 resources/scripts/components/admin/users/NewUserContainer.tsx create mode 100644 resources/scripts/components/admin/users/RoleSelect.tsx create mode 100644 resources/scripts/components/admin/users/UserAboutContainer.tsx create mode 100644 resources/scripts/components/admin/users/UserDeleteButton.tsx create mode 100644 resources/scripts/components/admin/users/UserForm.tsx create mode 100644 resources/scripts/components/admin/users/UserRouter.tsx create mode 100644 resources/scripts/components/admin/users/UserServers.tsx create mode 100644 resources/scripts/components/admin/users/UserTableRow.tsx create mode 100644 resources/scripts/components/admin/users/UsersContainer.tsx create mode 100644 resources/scripts/components/elements/Editor.tsx create mode 100644 resources/scripts/components/elements/SearchableSelect.tsx create mode 100644 resources/scripts/components/elements/SelectField.tsx create mode 100644 resources/scripts/components/elements/table/TFootPaginated.tsx create mode 100644 resources/scripts/components/helpers.ts create mode 100644 resources/scripts/helpers/extractSearchFilters.spec.ts create mode 100644 resources/scripts/helpers/extractSearchFilters.ts create mode 100644 resources/scripts/helpers/splitStringWhitespace.spec.ts create mode 100644 resources/scripts/helpers/splitStringWhitespace.ts create mode 100644 resources/scripts/plugins/useDebouncedState.ts create mode 100644 resources/scripts/routers/AdminRouter.tsx create mode 100644 resources/scripts/state/admin/allocations.ts create mode 100644 resources/scripts/state/admin/databases.ts create mode 100644 resources/scripts/state/admin/index.ts create mode 100644 resources/scripts/state/admin/locations.ts create mode 100644 resources/scripts/state/admin/mounts.ts create mode 100644 resources/scripts/state/admin/nests.ts create mode 100644 resources/scripts/state/admin/nodes.ts create mode 100644 resources/scripts/state/admin/roles.ts create mode 100644 resources/scripts/state/admin/servers.ts create mode 100644 resources/scripts/state/admin/users.ts diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 7df1ed2b1..ad0318720 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -33,6 +33,7 @@ use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; +use Pterodactyl\Http\Middleware\Api\Application\SubstituteApplicationApiBindings; class Kernel extends HttpKernel { @@ -70,6 +71,7 @@ class Kernel extends HttpKernel AuthenticateIPAccess::class, ], 'application-api' => [ +// SubstituteApplicationApiBindings::class, SubstituteBindings::class, AuthenticateApplicationUser::class, ], diff --git a/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php b/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php new file mode 100644 index 000000000..e629f6ca6 --- /dev/null +++ b/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php @@ -0,0 +1,66 @@ + Allocation::class, + 'database' => Database::class, + 'egg' => Egg::class, + 'location' => Location::class, + 'nest' => Nest::class, + 'node' => Node::class, + 'server' => Server::class, + 'user' => User::class, + ]; + + public function __construct(Registrar $router) + { + $this->router = $router; + } + + /** + * Perform substitution of route parameters without triggering + * a 404 error if a model is not found. + * + * @param \Illuminate\Http\Request $request + * + * @return mixed + */ + public function handle($request, Closure $next) + { + foreach (self::$mappings as $key => $class) { + $this->router->bind($key, $class); + } + + try { + $this->router->substituteImplicitBindings($route = $request->route()); + } catch (ModelNotFoundException $exception) { + if (!empty($route) && $route->getMissing()) { + $route->getMissing()($request); + } + + throw $exception; + } + + return $next($request); + } +} diff --git a/package.json b/package.json index 6641cd841..00b8764a1 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@codemirror/view": "^6.0.0", "@floating-ui/react-dom-interactions": "0.13.3", "@fortawesome/fontawesome-svg-core": "6.2.1", + "@fortawesome/free-brands-svg-icons": "6.2.1", "@fortawesome/free-solid-svg-icons": "6.2.1", "@fortawesome/react-fontawesome": "0.2.0", "@flyyer/use-fit-text": "3.0.1", @@ -72,6 +73,7 @@ "react-fast-compare": "3.2.0", "react-i18next": "12.1.1", "react-router-dom": "6.4.5", + "react-select": "5.7.0", "reaptcha": "1.12.1", "sockette": "2.0.6", "styled-components": "5.3.6", @@ -109,7 +111,7 @@ "eslint-plugin-react": "7.31.11", "eslint-plugin-react-hooks": "4.6.0", "happy-dom": "8.1.0", - "laravel-vite-plugin": "0.7.1", + "laravel-vite-plugin": "0.7.2", "pathe": "1.0.0", "postcss": "8.4.20", "postcss-nesting": "10.2.0", diff --git a/resources/scripts/api/admin/databases/createDatabase.ts b/resources/scripts/api/admin/databases/createDatabase.ts new file mode 100644 index 000000000..98d37bf1d --- /dev/null +++ b/resources/scripts/api/admin/databases/createDatabase.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +export default (name: string, host: string, port: number, username: string, password: string, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/databases', { + name, host, port, username, password, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToDatabase(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/deleteDatabase.ts b/resources/scripts/api/admin/databases/deleteDatabase.ts new file mode 100644 index 000000000..436aeaa85 --- /dev/null +++ b/resources/scripts/api/admin/databases/deleteDatabase.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/databases/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/getDatabase.ts b/resources/scripts/api/admin/databases/getDatabase.ts new file mode 100644 index 000000000..0af69fcd6 --- /dev/null +++ b/resources/scripts/api/admin/databases/getDatabase.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/databases/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToDatabase(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/getDatabases.ts b/resources/scripts/api/admin/databases/getDatabases.ts new file mode 100644 index 000000000..4ad5f4a3d --- /dev/null +++ b/resources/scripts/api/admin/databases/getDatabases.ts @@ -0,0 +1,64 @@ +import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; + +export interface Database { + id: number; + name: string; + host: string; + port: number; + username: string; + maxDatabases: number; + createdAt: Date; + updatedAt: Date; + + getAddress (): string; +} + +export const rawDataToDatabase = ({ attributes }: FractalResponseData): Database => ({ + id: attributes.id, + name: attributes.name, + host: attributes.host, + port: attributes.port, + username: attributes.username, + maxDatabases: attributes.max_databases, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + getAddress: () => `${attributes.host}:${attributes.port}`, +}); + +export interface Filters { + id?: string; + name?: string; + host?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'databases', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/databases', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToDatabase), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/databases/searchDatabases.ts b/resources/scripts/api/admin/databases/searchDatabases.ts new file mode 100644 index 000000000..99533b5dc --- /dev/null +++ b/resources/scripts/api/admin/databases/searchDatabases.ts @@ -0,0 +1,25 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +interface Filters { + name?: string; + host?: string; +} + +export default (filters?: Filters): Promise => { + const params = {}; + if (filters !== undefined) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/databases', { params }) + .then(response => resolve( + (response.data.data || []).map(rawDataToDatabase) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/databases/updateDatabase.ts b/resources/scripts/api/admin/databases/updateDatabase.ts new file mode 100644 index 000000000..7d01a9024 --- /dev/null +++ b/resources/scripts/api/admin/databases/updateDatabase.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; + +export default (id: number, name: string, host: string, port: number, username: string, password: string | undefined, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/databases/${id}`, { + name, host, port, username, password, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToDatabase(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/egg.ts b/resources/scripts/api/admin/egg.ts new file mode 100644 index 000000000..874fd7ad7 --- /dev/null +++ b/resources/scripts/api/admin/egg.ts @@ -0,0 +1,104 @@ +import type { AxiosError } from 'axios'; +import { useParams } from 'react-router-dom'; +import type { SWRResponse } from 'swr'; +import useSWR from 'swr'; + +import type { Model, UUID, WithRelationships } from '@/api/admin/index'; +import { withRelationships } from '@/api/admin/index'; +import type { Nest } from '@/api/admin/nest'; +import type { QueryBuilderParams } from '@/api/http'; +import http, { withQueryBuilderParams } from '@/api/http'; +import { Transformers } from '@definitions/admin'; + +export interface Egg extends Model { + id: number; + uuid: UUID; + nestId: number; + author: string; + name: string; + description: string | null; + features: string[] | null; + dockerImages: Record; + configFiles: Record | null; + configStartup: Record | null; + configStop: string | null; + configFrom: number | null; + startup: string; + scriptContainer: string; + copyScriptFrom: number | null; + scriptEntry: string; + scriptIsPrivileged: boolean; + scriptInstall: string | null; + createdAt: Date; + updatedAt: Date; + relationships: { + nest?: Nest; + variables?: EggVariable[]; + }; +} + +export interface EggVariable extends Model { + id: number; + eggId: number; + name: string; + description: string; + environmentVariable: string; + defaultValue: string; + isUserViewable: boolean; + isUserEditable: boolean; + // isRequired: boolean; + rules: string; + createdAt: Date; + updatedAt: Date; +} + +/** + * A standard API response with the minimum viable details for the frontend + * to correctly render a egg. + */ +type LoadedEgg = WithRelationships; + +/** + * Gets a single egg from the database and returns it. + */ +export const getEgg = async (id: number | string): Promise => { + const { data } = await http.get(`/api/application/eggs/${id}`, { + params: { + include: ['nest', 'variables'], + }, + }); + + return withRelationships(Transformers.toEgg(data), 'nest', 'variables'); +}; + +export const searchEggs = async ( + nestId: number, + params: QueryBuilderParams<'name'>, +): Promise[]> => { + const { data } = await http.get(`/api/application/nests/${nestId}/eggs`, { + params: { + ...withQueryBuilderParams(params), + include: ['variables'], + }, + }); + + return data.data.map(Transformers.toEgg); +}; + +export const exportEgg = async (eggId: number): Promise> => { + const { data } = await http.get(`/api/application/eggs/${eggId}/export`); + return data; +}; + +/** + * Returns an SWR instance by automatically loading in the server for the currently + * loaded route match in the admin area. + */ +export const useEggFromRoute = (): SWRResponse => { + const params = useParams<'id'>(); + + return useSWR(`/api/application/eggs/${params.id}`, async () => getEgg(Number(params.id)), { + revalidateOnMount: false, + revalidateOnFocus: false, + }); +}; diff --git a/resources/scripts/api/admin/eggs/createEgg.ts b/resources/scripts/api/admin/eggs/createEgg.ts new file mode 100644 index 000000000..0ad08d9ee --- /dev/null +++ b/resources/scripts/api/admin/eggs/createEgg.ts @@ -0,0 +1,31 @@ +import http from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +type Egg2 = Omit, 'configFiles'>, 'configStartup'> & { configFiles: string, configStartup: string }; + +export default (egg: Partial): Promise => { + return new Promise((resolve, reject) => { + http.post( + '/api/application/eggs', + { + nest_id: egg.nestId, + name: egg.name, + description: egg.description, + features: egg.features, + docker_images: egg.dockerImages, + config_files: egg.configFiles, + config_startup: egg.configStartup, + config_stop: egg.configStop, + config_from: egg.configFrom, + startup: egg.startup, + script_container: egg.scriptContainer, + copy_script_from: egg.copyScriptFrom, + script_entry: egg.scriptEntry, + script_is_privileged: egg.scriptIsPrivileged, + script_install: egg.scriptInstall, + }, + ) + .then(({ data }) => resolve(rawDataToEgg(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/createEggVariable.ts b/resources/scripts/api/admin/eggs/createEggVariable.ts new file mode 100644 index 000000000..375283d2c --- /dev/null +++ b/resources/scripts/api/admin/eggs/createEggVariable.ts @@ -0,0 +1,22 @@ +import http from '@/api/http'; +import { EggVariable } from '@/api/admin/egg'; +import { Transformers } from '@definitions/admin'; + +export type CreateEggVariable = Omit; + +export default async (eggId: number, variable: CreateEggVariable): Promise => { + const { data } = await http.post( + `/api/application/eggs/${eggId}/variables`, + { + name: variable.name, + description: variable.description, + env_variable: variable.environmentVariable, + default_value: variable.defaultValue, + user_viewable: variable.isUserViewable, + user_editable: variable.isUserEditable, + rules: variable.rules, + }, + ); + + return Transformers.toEggVariable(data); +}; diff --git a/resources/scripts/api/admin/eggs/deleteEgg.ts b/resources/scripts/api/admin/eggs/deleteEgg.ts new file mode 100644 index 000000000..635f3d6c2 --- /dev/null +++ b/resources/scripts/api/admin/eggs/deleteEgg.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/eggs/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/deleteEggVariable.ts b/resources/scripts/api/admin/eggs/deleteEggVariable.ts new file mode 100644 index 000000000..967798f55 --- /dev/null +++ b/resources/scripts/api/admin/eggs/deleteEggVariable.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (eggId: number, variableId: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/eggs/${eggId}/variables/${variableId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/getEgg.ts b/resources/scripts/api/admin/eggs/getEgg.ts new file mode 100644 index 000000000..2a6bb0633 --- /dev/null +++ b/resources/scripts/api/admin/eggs/getEgg.ts @@ -0,0 +1,108 @@ +import { Nest } from '@/api/admin/nests/getNests'; +import { rawDataToServer, Server } from '@/api/admin/servers/getServers'; +import http, { FractalResponseData, FractalResponseList } from '@/api/http'; +import useSWR from 'swr'; + +export interface EggVariable { + id: number; + eggId: number; + name: string; + description: string; + envVariable: string; + defaultValue: string; + userViewable: boolean; + userEditable: boolean; + rules: string; + createdAt: Date; + updatedAt: Date; +} + +export const rawDataToEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({ + id: attributes.id, + eggId: attributes.egg_id, + name: attributes.name, + description: attributes.description, + envVariable: attributes.env_variable, + defaultValue: attributes.default_value, + userViewable: attributes.user_viewable, + userEditable: attributes.user_editable, + rules: attributes.rules, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), +}); + +export interface Egg { + id: number; + uuid: string; + nestId: number; + author: string; + name: string; + description: string | null; + features: string[] | null; + dockerImages: Record; + configFiles: Record | null; + configStartup: Record | null; + configStop: string | null; + configFrom: number | null; + startup: string; + scriptContainer: string; + copyScriptFrom: number | null; + scriptEntry: string; + scriptIsPrivileged: boolean; + scriptInstall: string | null; + createdAt: Date; + updatedAt: Date; + + relations: { + nest?: Nest; + servers?: Server[]; + variables?: EggVariable[]; + }; +} + +export const rawDataToEgg = ({ attributes }: FractalResponseData): Egg => ({ + id: attributes.id, + uuid: attributes.uuid, + nestId: attributes.nest_id, + author: attributes.author, + name: attributes.name, + description: attributes.description, + features: attributes.features, + dockerImages: attributes.docker_images, + configFiles: attributes.config?.files, + configStartup: attributes.config?.startup, + configStop: attributes.config?.stop, + configFrom: attributes.config?.extends, + startup: attributes.startup, + copyScriptFrom: attributes.copy_script_from, + scriptContainer: attributes.script?.container, + scriptEntry: attributes.script?.entry, + scriptIsPrivileged: attributes.script?.privileged, + scriptInstall: attributes.script?.install, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + nest: undefined, + servers: ((attributes.relationships?.servers as FractalResponseList | undefined)?.data || []).map( + rawDataToServer, + ), + variables: ((attributes.relationships?.variables as FractalResponseList | undefined)?.data || []).map( + rawDataToEggVariable, + ), + }, +}); + +export const getEgg = async (id: number): Promise => { + const { data } = await http.get(`/api/application/eggs/${id}`, { params: { include: ['variables'] } }); + + return rawDataToEgg(data); +}; + +export default (id: number) => { + return useSWR(`egg:${id}`, async () => { + const { data } = await http.get(`/api/application/eggs/${id}`, { params: { include: ['variables'] } }); + + return rawDataToEgg(data); + }); +}; diff --git a/resources/scripts/api/admin/eggs/updateEgg.ts b/resources/scripts/api/admin/eggs/updateEgg.ts new file mode 100644 index 000000000..2500ba6b2 --- /dev/null +++ b/resources/scripts/api/admin/eggs/updateEgg.ts @@ -0,0 +1,31 @@ +import http from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +type Egg2 = Omit, 'configFiles'>, 'configStartup'> & { configFiles?: string, configStartup?: string }; + +export default (id: number, egg: Partial): Promise => { + return new Promise((resolve, reject) => { + http.patch( + `/api/application/eggs/${id}`, + { + nest_id: egg.nestId, + name: egg.name, + description: egg.description, + features: egg.features, + docker_images: egg.dockerImages, + config_files: egg.configFiles, + config_startup: egg.configStartup, + config_stop: egg.configStop, + config_from: egg.configFrom, + startup: egg.startup, + script_container: egg.scriptContainer, + copy_script_from: egg.copyScriptFrom, + script_entry: egg.scriptEntry, + script_is_privileged: egg.scriptIsPrivileged, + script_install: egg.scriptInstall, + }, + ) + .then(({ data }) => resolve(rawDataToEgg(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/eggs/updateEggVariables.ts b/resources/scripts/api/admin/eggs/updateEggVariables.ts new file mode 100644 index 000000000..b5d97b952 --- /dev/null +++ b/resources/scripts/api/admin/eggs/updateEggVariables.ts @@ -0,0 +1,21 @@ +import http from '@/api/http'; +import { EggVariable } from '@/api/admin/egg'; +import { Transformers } from '@definitions/admin'; + +export default async (eggId: number, variables: Omit[]): Promise => { + const { data } = await http.patch( + `/api/application/eggs/${eggId}/variables`, + variables.map(variable => ({ + id: variable.id, + name: variable.name, + description: variable.description, + env_variable: variable.environmentVariable, + default_value: variable.defaultValue, + user_viewable: variable.isUserViewable, + user_editable: variable.isUserEditable, + rules: variable.rules, + })), + ); + + return data.data.map(Transformers.toEggVariable); +}; diff --git a/resources/scripts/api/admin/getVersion.ts b/resources/scripts/api/admin/getVersion.ts new file mode 100644 index 000000000..3f24911e4 --- /dev/null +++ b/resources/scripts/api/admin/getVersion.ts @@ -0,0 +1,22 @@ +import http from '@/api/http'; + +export interface VersionData { + panel: { + current: string; + latest: string; + } + + wings: { + latest: string; + } + + git: string | null; +} + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/api/application/version') + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/index.ts b/resources/scripts/api/admin/index.ts new file mode 100644 index 000000000..014a207a7 --- /dev/null +++ b/resources/scripts/api/admin/index.ts @@ -0,0 +1,66 @@ +import { createContext } from 'react'; + +export interface Model { + relationships: Record; +} + +export type UUID = string; + +/** + * Marks the provided relationships keys as present in the given model + * rather than being optional to improve typing responses. + */ +export type WithRelationships = Omit & { + relationships: Omit & { + [K in R]: NonNullable; + } +} + +/** + * Helper type that allows you to infer the type of an object by giving + * it the specific API request function with a return type. For example: + * + * type EggT = InferModel; + */ +export type InferModel any> = ReturnType extends Promise ? U : T; + +/** + * Helper function that just returns the model you pass in, but types the model + * such that TypeScript understands the relationships on it. This is just to help + * reduce the amount of duplicated type casting all over the codebase. + */ +export const withRelationships = (model: M, ..._keys: R[]) => { + return model as unknown as WithRelationships; +}; + +export interface ListContext { + page: number; + setPage: (page: ((p: number) => number) | number) => void; + + filters: T | null; + setFilters: (filters: ((f: T | null) => T | null) | T | null) => void; + + sort: string | null; + setSort: (sort: string | null) => void; + + sortDirection: boolean; + setSortDirection: (direction: ((p: boolean) => boolean) | boolean) => void; +} + +function create () { + return createContext>({ + page: 1, + setPage: () => 1, + + filters: null, + setFilters: () => null, + + sort: null, + setSort: () => null, + + sortDirection: false, + setSortDirection: () => false, + }); +} + +export { create as createContext }; diff --git a/resources/scripts/api/admin/location.ts b/resources/scripts/api/admin/location.ts new file mode 100644 index 000000000..82ff394f8 --- /dev/null +++ b/resources/scripts/api/admin/location.ts @@ -0,0 +1,13 @@ +import { Model } from '@/api/admin/index'; +import { Node } from '@/api/admin/node'; + +export interface Location extends Model { + id: number; + short: string; + long: string; + createdAt: Date; + updatedAt: Date; + relationships: { + nodes?: Node[]; + }; +} diff --git a/resources/scripts/api/admin/locations/createLocation.ts b/resources/scripts/api/admin/locations/createLocation.ts new file mode 100644 index 000000000..053148fc4 --- /dev/null +++ b/resources/scripts/api/admin/locations/createLocation.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export default (short: string, long: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/locations', { + short, long, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToLocation(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/deleteLocation.ts b/resources/scripts/api/admin/locations/deleteLocation.ts new file mode 100644 index 000000000..85b42d60e --- /dev/null +++ b/resources/scripts/api/admin/locations/deleteLocation.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/locations/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/getLocation.ts b/resources/scripts/api/admin/locations/getLocation.ts new file mode 100644 index 000000000..9a1aad4bc --- /dev/null +++ b/resources/scripts/api/admin/locations/getLocation.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/locations/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToLocation(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/getLocations.ts b/resources/scripts/api/admin/locations/getLocations.ts new file mode 100644 index 000000000..f43567e51 --- /dev/null +++ b/resources/scripts/api/admin/locations/getLocations.ts @@ -0,0 +1,54 @@ +import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; + +export interface Location { + id: number; + short: string; + long: string; + createdAt: Date; + updatedAt: Date; +} + +export const rawDataToLocation = ({ attributes }: FractalResponseData): Location => ({ + id: attributes.id, + short: attributes.short, + long: attributes.long, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), +}); + +export interface Filters { + id?: string; + short?: string; + long?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'locations', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/locations', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToLocation), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/locations/searchLocations.ts b/resources/scripts/api/admin/locations/searchLocations.ts new file mode 100644 index 000000000..6edc50e62 --- /dev/null +++ b/resources/scripts/api/admin/locations/searchLocations.ts @@ -0,0 +1,25 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +interface Filters { + short?: string; + long?: string; +} + +export default (filters?: Filters): Promise => { + const params = {}; + if (filters !== undefined) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/locations', { params }) + .then(response => resolve( + (response.data.data || []).map(rawDataToLocation) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/locations/updateLocation.ts b/resources/scripts/api/admin/locations/updateLocation.ts new file mode 100644 index 000000000..bbb5f8f4c --- /dev/null +++ b/resources/scripts/api/admin/locations/updateLocation.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export default (id: number, short: string, long: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/locations/${id}`, { + short, long, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToLocation(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/createMount.ts b/resources/scripts/api/admin/mounts/createMount.ts new file mode 100644 index 000000000..63538c821 --- /dev/null +++ b/resources/scripts/api/admin/mounts/createMount.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Mount, rawDataToMount } from '@/api/admin/mounts/getMounts'; + +export default (name: string, description: string, source: string, target: string, readOnly: boolean, userMountable: boolean, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/mounts', { + name, description, source, target, read_only: readOnly, user_mountable: userMountable, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToMount(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/deleteMount.ts b/resources/scripts/api/admin/mounts/deleteMount.ts new file mode 100644 index 000000000..e4ec1d113 --- /dev/null +++ b/resources/scripts/api/admin/mounts/deleteMount.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/mounts/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/getMount.ts b/resources/scripts/api/admin/mounts/getMount.ts new file mode 100644 index 000000000..9bd8a56e9 --- /dev/null +++ b/resources/scripts/api/admin/mounts/getMount.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Mount, rawDataToMount } from '@/api/admin/mounts/getMounts'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/mounts/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToMount(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/mounts/getMounts.ts b/resources/scripts/api/admin/mounts/getMounts.ts new file mode 100644 index 000000000..449308119 --- /dev/null +++ b/resources/scripts/api/admin/mounts/getMounts.ts @@ -0,0 +1,80 @@ +import http, { FractalResponseData, FractalResponseList, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface Mount { + id: number; + uuid: string; + name: string; + description?: string; + source: string; + target: string; + readOnly: boolean; + userMountable: boolean; + createdAt: Date; + updatedAt: Date; + + relations: { + eggs: Egg[] | undefined; + nodes: Node[] | undefined; + servers: Server[] | undefined; + }; +} + +export const rawDataToMount = ({ attributes }: FractalResponseData): Mount => ({ + id: attributes.id, + uuid: attributes.uuid, + name: attributes.name, + description: attributes.description, + source: attributes.source, + target: attributes.target, + readOnly: attributes.read_only, + userMountable: attributes.user_mountable, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + eggs: ((attributes.relationships?.eggs as FractalResponseList | undefined)?.data || []).map(rawDataToEgg), + nodes: ((attributes.relationships?.nodes as FractalResponseList | undefined)?.data || []).map(rawDataToNode), + servers: ((attributes.relationships?.servers as FractalResponseList | undefined)?.data || []).map(rawDataToServer), + }, +}); + +export interface Filters { + id?: string; + name?: string; + source?: string; + target?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'mounts', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/mounts', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToMount), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/mounts/updateMount.ts b/resources/scripts/api/admin/mounts/updateMount.ts new file mode 100644 index 000000000..c2485777a --- /dev/null +++ b/resources/scripts/api/admin/mounts/updateMount.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Mount, rawDataToMount } from '@/api/admin/mounts/getMounts'; + +export default (id: number, name: string, description: string | null, source: string, target: string, readOnly: boolean, userMountable: boolean, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/mounts/${id}`, { + name, description, source, target, read_only: readOnly, user_mountable: userMountable, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToMount(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nest.ts b/resources/scripts/api/admin/nest.ts new file mode 100644 index 000000000..697b15bed --- /dev/null +++ b/resources/scripts/api/admin/nest.ts @@ -0,0 +1,25 @@ +import { Model, UUID } from '@/api/admin/index'; +import { Egg } from '@/api/admin/egg'; +import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import { Transformers } from '@definitions/admin'; + +export interface Nest extends Model { + id: number; + uuid: UUID; + author: string; + name: string; + description?: string; + createdAt: Date; + updatedAt: Date; + relationships: { + eggs?: Egg[]; + }; +} + +export const searchNests = async (params: QueryBuilderParams<'name'>): Promise => { + const { data } = await http.get('/api/application/nests', { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toNest); +}; diff --git a/resources/scripts/api/admin/nests/createNest.ts b/resources/scripts/api/admin/nests/createNest.ts new file mode 100644 index 000000000..6f8d045fa --- /dev/null +++ b/resources/scripts/api/admin/nests/createNest.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; + +export default (name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/nests', { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNest(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/deleteNest.ts b/resources/scripts/api/admin/nests/deleteNest.ts new file mode 100644 index 000000000..d6d4ae3a5 --- /dev/null +++ b/resources/scripts/api/admin/nests/deleteNest.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/nests/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/getEggs.ts b/resources/scripts/api/admin/nests/getEggs.ts new file mode 100644 index 000000000..6a406dc96 --- /dev/null +++ b/resources/scripts/api/admin/nests/getEggs.ts @@ -0,0 +1,38 @@ +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +export interface Filters { + id?: string; + name?: string; +} + +export const Context = createContext(); + +export default (nestId: number, include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ nestId, 'eggs', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get(`/api/application/nests/${nestId}/eggs`, { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToEgg), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nests/getNest.ts b/resources/scripts/api/admin/nests/getNest.ts new file mode 100644 index 000000000..23f1cf782 --- /dev/null +++ b/resources/scripts/api/admin/nests/getNest.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; + +export default (id: number, include: string[]): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nests/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNest(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/getNests.ts b/resources/scripts/api/admin/nests/getNests.ts new file mode 100644 index 000000000..712a2a29b --- /dev/null +++ b/resources/scripts/api/admin/nests/getNests.ts @@ -0,0 +1,66 @@ +import http, { FractalResponseData, FractalResponseList, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +export interface Nest { + id: number; + uuid: string; + author: string; + name: string; + description?: string; + createdAt: Date; + updatedAt: Date; + + relations: { + eggs: Egg[] | undefined; + }, +} + +export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({ + id: attributes.id, + uuid: attributes.uuid, + author: attributes.author, + name: attributes.name, + description: attributes.description, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + eggs: ((attributes.relationships?.eggs as FractalResponseList | undefined)?.data || []).map(rawDataToEgg), + }, +}); + +export interface Filters { + id?: string; + name?: string; +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'nests', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/nests', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToNest), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nests/importEgg.ts b/resources/scripts/api/admin/nests/importEgg.ts new file mode 100644 index 000000000..2163386ca --- /dev/null +++ b/resources/scripts/api/admin/nests/importEgg.ts @@ -0,0 +1,17 @@ +import http from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +export default (id: number, content: any, type = 'application/json', include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/application/nests/${id}/import`, content, { + headers: { + 'Content-Type': type, + }, + params: { + include: include.join(','), + }, + }) + .then(({ data }) => resolve(rawDataToEgg(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/updateNest.ts b/resources/scripts/api/admin/nests/updateNest.ts new file mode 100644 index 000000000..869f0532b --- /dev/null +++ b/resources/scripts/api/admin/nests/updateNest.ts @@ -0,0 +1,12 @@ +import http from '@/api/http'; +import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; + +export default (id: number, name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/nests/${id}`, { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNest(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/node.ts b/resources/scripts/api/admin/node.ts new file mode 100644 index 000000000..dae7a09cc --- /dev/null +++ b/resources/scripts/api/admin/node.ts @@ -0,0 +1,84 @@ +import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index'; +import { Location } from '@/api/admin/location'; +import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; +import { Transformers } from '@definitions/admin'; +import { Server } from '@/api/admin/server'; + +interface NodePorts { + http: { + listen: number; + public: number; + }; + sftp: { + listen: number; + public: number; + }; +} + +export interface Allocation extends Model { + id: number; + ip: string; + port: number; + alias: string | null; + isAssigned: boolean; + relationships: { + node?: Node; + server?: Server | null; + }; + getDisplayText(): string; +} + +export interface Node extends Model { + id: number; + uuid: UUID; + isPublic: boolean; + locationId: number; + databaseHostId: number; + name: string; + description: string | null; + fqdn: string; + ports: NodePorts; + scheme: 'http' | 'https'; + isBehindProxy: boolean; + isMaintenanceMode: boolean; + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + uploadSize: number; + daemonBase: string; + createdAt: Date; + updatedAt: Date; + relationships: { + location?: Location; + }; +} + +/** + * Gets a single node and returns it. + */ +export const getNode = async (id: string | number): Promise> => { + const { data } = await http.get(`/api/application/nodes/${id}`, { + params: { + include: [ 'location' ], + }, + }); + + return withRelationships(Transformers.toNode(data.data), 'location'); +}; + +export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise => { + const { data } = await http.get('/api/application/nodes', { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toNode); +}; + +export const getAllocations = async (id: string | number, params?: QueryBuilderParams<'ip' | 'server_id'>): Promise => { + const { data } = await http.get(`/api/application/nodes/${id}/allocations`, { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toAllocation); +}; diff --git a/resources/scripts/api/admin/nodes/allocations/createAllocation.ts b/resources/scripts/api/admin/nodes/allocations/createAllocation.ts new file mode 100644 index 000000000..89bacbd4d --- /dev/null +++ b/resources/scripts/api/admin/nodes/allocations/createAllocation.ts @@ -0,0 +1,16 @@ +import http from '@/api/http'; +import { Allocation, rawDataToAllocation } from '@/api/admin/nodes/getAllocations'; + +export interface Values { + ip: string; + ports: number[]; + alias?: string; +} + +export default (id: string | number, values: Values, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/application/nodes/${id}/allocations`, values, { params: { include: include.join(',') } }) + .then(({ data }) => resolve((data || []).map(rawDataToAllocation))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts b/resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts new file mode 100644 index 000000000..f4a775183 --- /dev/null +++ b/resources/scripts/api/admin/nodes/allocations/deleteAllocation.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (nodeId: number, allocationId: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/nodes/${nodeId}/allocations/${allocationId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/allocations/getAllocations.ts b/resources/scripts/api/admin/nodes/allocations/getAllocations.ts new file mode 100644 index 000000000..14c99c655 --- /dev/null +++ b/resources/scripts/api/admin/nodes/allocations/getAllocations.ts @@ -0,0 +1,39 @@ +import { Allocation, rawDataToAllocation } from '@/api/admin/nodes/getAllocations'; +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; + +export interface Filters { + id?: string; + ip?: string; + port?: string; +} + +export const Context = createContext(); + +export default (id: number, include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'allocations', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get(`/api/application/nodes/${id}/allocations`, { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToAllocation), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nodes/createNode.ts b/resources/scripts/api/admin/nodes/createNode.ts new file mode 100644 index 000000000..8143a92c3 --- /dev/null +++ b/resources/scripts/api/admin/nodes/createNode.ts @@ -0,0 +1,42 @@ +import http from '@/api/http'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; + +export interface Values { + name: string; + locationId: number; + databaseHostId: number | null; + fqdn: string; + scheme: string; + behindProxy: boolean; + public: boolean; + daemonBase: string; + + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + + listenPortHTTP: number; + publicPortHTTP: number; + listenPortSFTP: number; + publicPortSFTP: number; +} + +export default (values: Values, include: string[] = []): Promise => { + const data = {}; + + Object.keys(values).forEach((key) => { + const key2 = key + .replace('HTTP', 'Http') + .replace('SFTP', 'Sftp') + .replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); + // @ts-ignore + data[key2] = values[key]; + }); + + return new Promise((resolve, reject) => { + http.post('/api/application/nodes', data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNode(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/deleteNode.ts b/resources/scripts/api/admin/nodes/deleteNode.ts new file mode 100644 index 000000000..56a426111 --- /dev/null +++ b/resources/scripts/api/admin/nodes/deleteNode.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/nodes/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getAllocations.ts b/resources/scripts/api/admin/nodes/getAllocations.ts new file mode 100644 index 000000000..e6f773dfe --- /dev/null +++ b/resources/scripts/api/admin/nodes/getAllocations.ts @@ -0,0 +1,61 @@ +import http, { FractalResponseData } from '@/api/http'; +import { rawDataToServer, Server } from '@/api/admin/servers/getServers'; + +export interface Allocation { + id: number; + ip: string; + port: number; + alias: string | null; + serverId: number | null; + assigned: boolean; + + relations: { + server?: Server; + } + + getDisplayText (): string; +} + +export const rawDataToAllocation = ({ attributes }: FractalResponseData): Allocation => ({ + id: attributes.id, + ip: attributes.ip, + port: attributes.port, + alias: attributes.alias || null, + serverId: attributes.server_id, + assigned: attributes.assigned, + + relations: { + server: attributes.relationships?.server?.object === 'server' ? rawDataToServer(attributes.relationships.server as FractalResponseData) : undefined, + }, + + // TODO: If IP is an IPv6, wrap IP in []. + getDisplayText (): string { + if (attributes.alias !== null) { + return `${attributes.ip}:${attributes.port} (${attributes.alias})`; + } + return `${attributes.ip}:${attributes.port}`; + }, +}); + +export interface Filters { + ip?: string + /* eslint-disable camelcase */ + server_id?: string; + /* eslint-enable camelcase */ +} + +export default (id: string | number, filters: Filters = {}, include: string[] = []): Promise => { + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}/allocations`, { params: { include: include.join(','), ...params } }) + .then(({ data }) => resolve((data.data || []).map(rawDataToAllocation))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNode.ts b/resources/scripts/api/admin/nodes/getNode.ts new file mode 100644 index 000000000..b940c8e2e --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNode.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; + +export default (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNode(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNodeConfiguration.ts b/resources/scripts/api/admin/nodes/getNodeConfiguration.ts new file mode 100644 index 000000000..f439b1e7e --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNodeConfiguration.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}/configuration?format=yaml`) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNodeInformation.ts b/resources/scripts/api/admin/nodes/getNodeInformation.ts new file mode 100644 index 000000000..771433a5e --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNodeInformation.ts @@ -0,0 +1,19 @@ +import http from '@/api/http'; + +export interface NodeInformation { + version: string; + system: { + type: string; + arch: string; + release: string; + cpus: number; + }; +} + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nodes/${id}/information`) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nodes/getNodes.ts b/resources/scripts/api/admin/nodes/getNodes.ts new file mode 100644 index 000000000..59e87ceb4 --- /dev/null +++ b/resources/scripts/api/admin/nodes/getNodes.ts @@ -0,0 +1,107 @@ +import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import { Database, rawDataToDatabase } from '@/api/admin/databases/getDatabases'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export interface Node { + id: number; + uuid: string; + public: boolean; + name: string; + description: string | null; + locationId: number; + databaseHostId: number | null; + fqdn: string; + listenPortHTTP: number; + publicPortHTTP: number; + listenPortSFTP: number; + publicPortSFTP: number; + scheme: string; + behindProxy: boolean; + maintenanceMode: boolean; + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + uploadSize: number; + daemonBase: string; + createdAt: Date; + updatedAt: Date; + + relations: { + databaseHost: Database | undefined; + location: Location | undefined; + } +} + +export const rawDataToNode = ({ attributes }: FractalResponseData): Node => ({ + id: attributes.id, + uuid: attributes.uuid, + public: attributes.public, + name: attributes.name, + description: attributes.description, + locationId: attributes.location_id, + databaseHostId: attributes.database_host_id, + fqdn: attributes.fqdn, + listenPortHTTP: attributes.listen_port_http, + publicPortHTTP: attributes.public_port_http, + listenPortSFTP: attributes.listen_port_sftp, + publicPortSFTP: attributes.public_port_sftp, + scheme: attributes.scheme, + behindProxy: attributes.behind_proxy, + maintenanceMode: attributes.maintenance_mode, + memory: attributes.memory, + memoryOverallocate: attributes.memory_overallocate, + disk: attributes.disk, + diskOverallocate: attributes.disk_overallocate, + uploadSize: attributes.upload_size, + daemonBase: attributes.daemon_base, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + // eslint-disable-next-line camelcase + databaseHost: attributes.relationships?.database_host !== undefined && attributes.relationships?.database_host.object !== 'null_resource' ? rawDataToDatabase(attributes.relationships.database_host as FractalResponseData) : undefined, + location: attributes.relationships?.location !== undefined ? rawDataToLocation(attributes.relationships.location as FractalResponseData) : undefined, + }, +}); + +export interface Filters { + id?: string; + uuid?: string; + name?: string; + image?: string; + /* eslint-disable camelcase */ + external_id?: string; + /* eslint-enable camelcase */ +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'nodes', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/nodes', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToNode), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/nodes/updateNode.ts b/resources/scripts/api/admin/nodes/updateNode.ts new file mode 100644 index 000000000..623f66931 --- /dev/null +++ b/resources/scripts/api/admin/nodes/updateNode.ts @@ -0,0 +1,21 @@ +import http from '@/api/http'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; + +export default (id: number, node: Partial, include: string[] = []): Promise => { + const data = {}; + + Object.keys(node).forEach((key) => { + const key2 = key + .replace('HTTP', 'Http') + .replace('SFTP', 'Sftp') + .replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); + // @ts-ignore + data[key2] = node[key]; + }); + + return new Promise((resolve, reject) => { + http.patch(`/api/application/nodes/${id}`, data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToNode(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/roles.ts b/resources/scripts/api/admin/roles.ts new file mode 100644 index 000000000..8fb5c4c85 --- /dev/null +++ b/resources/scripts/api/admin/roles.ts @@ -0,0 +1,103 @@ +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; +import { Transformers, UserRole } from '@definitions/admin'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin/index'; + +export interface Filters { + id?: string; + name?: string; +} + +export const Context = createContext(); + +const createRole = (name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/roles', { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUserRole(data))) + .catch(reject); + }); +}; + +const deleteRole = (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/roles/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; + +const getRole = (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/roles/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUserRole(data))) + .catch(reject); + }); +}; + +const searchRoles = (filters?: { name?: string }): Promise => { + const params = {}; + if (filters !== undefined) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/roles', { params }) + .then(response => resolve( + (response.data.data || []).map(Transformers.toUserRole) + )) + .catch(reject); + }); +}; + +const updateRole = (id: number, name: string, description: string | null, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch(`/api/application/roles/${id}`, { + name, description, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUserRole(data))) + .catch(reject); + }); +}; + +const getRoles = (include: string[] = []) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + return useSWR>([ 'roles', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/roles', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(Transformers.toUserRole), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; + +export { + createRole, + deleteRole, + getRole, + searchRoles, + updateRole, + getRoles, +}; diff --git a/resources/scripts/api/admin/server.ts b/resources/scripts/api/admin/server.ts new file mode 100644 index 000000000..26f9ba6b5 --- /dev/null +++ b/resources/scripts/api/admin/server.ts @@ -0,0 +1,99 @@ +import useSWR, { SWRResponse } from 'swr'; +import { AxiosError } from 'axios'; +import { useParams } from 'react-router-dom'; +import http from '@/api/http'; +import { Model, UUID, withRelationships, WithRelationships } from '@/api/admin/index'; +import { Allocation, Node } from '@/api/admin/node'; +import { Transformers, User } from '@definitions/admin'; +import { Egg, EggVariable } from '@/api/admin/egg'; +import { Nest } from '@/api/admin/nest'; + +/** + * Defines the limits for a server that exists on the Panel. + */ +interface ServerLimits { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string | null; + oomDisabled: boolean; +} + +export interface ServerVariable extends EggVariable { + serverValue: string; +} + +/** + * Defines a single server instance that is returned from the Panel's admin + * API endpoints. + */ +export interface Server extends Model { + id: number; + uuid: UUID; + externalId: string | null; + identifier: string; + name: string; + description: string; + status: string; + userId: number; + nodeId: number; + allocationId: number; + eggId: number; + nestId: number; + limits: ServerLimits; + featureLimits: { + databases: number; + allocations: number; + backups: number; + }; + container: { + startup: string | null; + image: string; + environment: Record; + }; + createdAt: Date; + updatedAt: Date; + relationships: { + allocations?: Allocation[]; + nest?: Nest; + egg?: Egg; + node?: Node; + user?: User; + variables?: ServerVariable[]; + }; +} + +/** + * A standard API response with the minimum viable details for the frontend + * to correctly render a server. + */ +type LoadedServer = WithRelationships; + +/** + * Fetches a server from the API and ensures that the allocations, user, and + * node data is loaded. + */ +export const getServer = async (id: number | string): Promise => { + const { data } = await http.get(`/api/application/servers/${id}`, { + params: { + include: ['allocations', 'user', 'node', 'variables'], + }, + }); + + return withRelationships(Transformers.toServer(data), 'allocations', 'user', 'node', 'variables'); +}; + +/** + * Returns an SWR instance by automatically loading in the server for the currently + * loaded route match in the admin area. + */ +export const useServerFromRoute = (): SWRResponse => { + const params = useParams<'id'>(); + + return useSWR(`/api/application/servers/${params.id}`, async () => getServer(Number(params.id)), { + revalidateOnMount: false, + revalidateOnFocus: false, + }); +}; diff --git a/resources/scripts/api/admin/servers/createServer.ts b/resources/scripts/api/admin/servers/createServer.ts new file mode 100644 index 000000000..3fd94ca62 --- /dev/null +++ b/resources/scripts/api/admin/servers/createServer.ts @@ -0,0 +1,80 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface CreateServerRequest { + externalId: string; + name: string; + description: string | null; + ownerId: number; + nodeId: number; + + limits: { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string; + oomDisabled: boolean; + } + + featureLimits: { + allocations: number; + backups: number; + databases: number; + }; + + allocation: { + default: number; + additional: number[]; + }; + + startup: string; + environment: Record; + eggId: number; + image: string; + skipScripts: boolean; + startOnCompletion: boolean; +} + +export default (r: CreateServerRequest, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/application/servers', { + externalId: r.externalId, + name: r.name, + description: r.description, + owner_id: r.ownerId, + node_id: r.nodeId, + + limits: { + cpu: r.limits.cpu, + disk: r.limits.disk, + io: r.limits.io, + memory: r.limits.memory, + swap: r.limits.swap, + threads: r.limits.threads, + oom_killer: r.limits.oomDisabled, + }, + + feature_limits: { + allocations: r.featureLimits.allocations, + backups: r.featureLimits.backups, + databases: r.featureLimits.databases, + }, + + allocation: { + default: r.allocation.default, + additional: r.allocation.additional, + }, + + startup: r.startup, + environment: r.environment, + egg_id: r.eggId, + image: r.image, + skip_scripts: r.skipScripts, + start_on_completion: r.startOnCompletion, + }, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/deleteServer.ts b/resources/scripts/api/admin/servers/deleteServer.ts new file mode 100644 index 000000000..579559518 --- /dev/null +++ b/resources/scripts/api/admin/servers/deleteServer.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/servers/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/getServer.ts b/resources/scripts/api/admin/servers/getServer.ts new file mode 100644 index 000000000..be10f0216 --- /dev/null +++ b/resources/scripts/api/admin/servers/getServer.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export default (id: number, include: string[]): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/servers/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/getServers.ts b/resources/scripts/api/admin/servers/getServers.ts new file mode 100644 index 000000000..6d73f070b --- /dev/null +++ b/resources/scripts/api/admin/servers/getServers.ts @@ -0,0 +1,177 @@ +import { Allocation, rawDataToAllocation } from '@/api/admin/nodes/getAllocations'; +import { useContext } from 'react'; +import useSWR from 'swr'; +import { createContext } from '@/api/admin'; +import http, { FractalResponseData, FractalResponseList, getPaginationSet, PaginatedResult } from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; +import { Node, rawDataToNode } from '@/api/admin/nodes/getNodes'; +import { Transformers, User } from '@definitions/admin'; + +export interface ServerVariable { + id: number; + eggId: number; + name: string; + description: string; + envVariable: string; + defaultValue: string; + userViewable: boolean; + userEditable: boolean; + rules: string; + required: boolean; + serverValue: string; + createdAt: Date; + updatedAt: Date; +} + +export const rawDataToServerVariable = ({ attributes }: FractalResponseData): ServerVariable => ({ + id: attributes.id, + eggId: attributes.egg_id, + name: attributes.name, + description: attributes.description, + envVariable: attributes.env_variable, + defaultValue: attributes.default_value, + userViewable: attributes.user_viewable, + userEditable: attributes.user_editable, + rules: attributes.rules, + required: attributes.required, + serverValue: attributes.server_value, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), +}); + +export interface Server { + id: number; + externalId: string | null + uuid: string; + identifier: string; + name: string; + description: string; + status: string; + + limits: { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string | null; + oomDisabled: boolean; + } + + featureLimits: { + databases: number; + allocations: number; + backups: number; + } + + ownerId: number; + nodeId: number; + allocationId: number; + nestId: number; + eggId: number; + + container: { + startup: string; + image: string; + environment: Map; + } + + createdAt: Date; + updatedAt: Date; + + relations: { + allocations?: Allocation[]; + egg?: Egg; + node?: Node; + user?: User; + variables: ServerVariable[]; + } +} + +export const rawDataToServer = ({ attributes }: FractalResponseData): Server => ({ + id: attributes.id, + externalId: attributes.external_id, + uuid: attributes.uuid, + identifier: attributes.identifier, + name: attributes.name, + description: attributes.description, + status: attributes.status, + + limits: { + memory: attributes.limits.memory, + swap: attributes.limits.swap, + disk: attributes.limits.disk, + io: attributes.limits.io, + cpu: attributes.limits.cpu, + threads: attributes.limits.threads, + oomDisabled: attributes.limits.oom_disabled, + }, + + featureLimits: { + databases: attributes.feature_limits.databases, + allocations: attributes.feature_limits.allocations, + backups: attributes.feature_limits.backups, + }, + + ownerId: attributes.owner_id, + nodeId: attributes.node_id, + allocationId: attributes.allocation_id, + nestId: attributes.nest_id, + eggId: attributes.egg_id, + + container: { + startup: attributes.container.startup, + image: attributes.container.image, + environment: attributes.container.environment, + }, + + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + + relations: { + allocations: ((attributes.relationships?.allocations as FractalResponseList | undefined)?.data || []).map(rawDataToAllocation), + egg: attributes.relationships?.egg?.object === 'egg' ? rawDataToEgg(attributes.relationships.egg as FractalResponseData) : undefined, + node: attributes.relationships?.node?.object === 'node' ? rawDataToNode(attributes.relationships.node as FractalResponseData) : undefined, + user: attributes.relationships?.user?.object === 'user' ? Transformers.toUser(attributes.relationships.user as FractalResponseData) : undefined, + variables: ((attributes.relationships?.variables as FractalResponseList | undefined)?.data || []).map(rawDataToServerVariable), + }, +}) as Server; + +export interface Filters { + id?: string; + uuid?: string; + name?: string; + /* eslint-disable camelcase */ + owner_id?: string; + node_id?: string; + external_id?: string; + /* eslint-enable camelcase */ +} + +export const Context = createContext(); + +export default (include: string[] = []) => { + const { page, filters, sort, sortDirection } = useContext(Context); + + const params = {}; + if (filters !== null) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + if (sort !== null) { + // @ts-ignore + params.sort = (sortDirection ? '-' : '') + sort; + } + + return useSWR>([ 'servers', page, filters, sort, sortDirection ], async () => { + const { data } = await http.get('/api/application/servers', { params: { include: include.join(','), page, ...params } }); + + return ({ + items: (data.data || []).map(rawDataToServer), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/admin/servers/updateServer.ts b/resources/scripts/api/admin/servers/updateServer.ts new file mode 100644 index 000000000..e74b7422a --- /dev/null +++ b/resources/scripts/api/admin/servers/updateServer.ts @@ -0,0 +1,64 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface Values { + externalId: string; + name: string; + ownerId: number; + + limits: { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string; + oomDisabled: boolean; + } + + featureLimits: { + allocations: number; + backups: number; + databases: number; + } + + allocationId: number; + addAllocations: number[]; + removeAllocations: number[]; +} + +export default (id: number, server: Partial, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch( + `/api/application/servers/${id}`, + { + external_id: server.externalId, + name: server.name, + owner_id: server.ownerId, + + limits: { + memory: server.limits?.memory, + swap: server.limits?.swap, + disk: server.limits?.disk, + io: server.limits?.io, + cpu: server.limits?.cpu, + threads: server.limits?.threads, + oom_killer: server.limits?.oomDisabled, + }, + + feature_limits: { + allocations: server.featureLimits?.allocations, + backups: server.featureLimits?.backups, + databases: server.featureLimits?.databases, + }, + + allocation_id: server.allocationId, + add_allocations: server.addAllocations, + remove_allocations: server.removeAllocations, + }, + { params: { include: include.join(',') } } + ) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/servers/updateServerStartup.ts b/resources/scripts/api/admin/servers/updateServerStartup.ts new file mode 100644 index 000000000..7dec26e30 --- /dev/null +++ b/resources/scripts/api/admin/servers/updateServerStartup.ts @@ -0,0 +1,28 @@ +import http from '@/api/http'; +import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; + +export interface Values { + startup: string; + environment: Record; + eggId: number; + image: string; + skipScripts: boolean; +} + +export default (id: number, values: Partial, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.patch( + `/api/application/servers/${id}/startup`, + { + startup: values.startup !== '' ? values.startup : null, + environment: values.environment, + egg_id: values.eggId, + image: values.image, + skip_scripts: values.skipScripts, + }, + { params: { include: include.join(',') } } + ) + .then(({ data }) => resolve(rawDataToServer(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/users.ts b/resources/scripts/api/admin/users.ts new file mode 100644 index 000000000..817721cb3 --- /dev/null +++ b/resources/scripts/api/admin/users.ts @@ -0,0 +1,96 @@ +import http, { + FractalPaginatedResponse, + PaginatedResult, + QueryBuilderParams, + getPaginationSet, + withQueryBuilderParams, +} from '@/api/http'; +import { Transformers, User } from '@definitions/admin'; +import useSWR, { SWRConfiguration, SWRResponse } from 'swr'; +import { AxiosError } from 'axios'; + +export interface UpdateUserValues { + externalId: string; + username: string; + email: string; + password: string; + adminRoleId: number | null; + rootAdmin: boolean; +} + +const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const; +type Filters = typeof filters[number]; + +const useGetUsers = ( + params?: QueryBuilderParams, + config?: SWRConfiguration, +): SWRResponse, AxiosError> => { + return useSWR>( + ['/api/application/users', JSON.stringify(params)], + async () => { + const { data } = await http.get('/api/application/users', { + params: withQueryBuilderParams(params), + }); + + return getPaginationSet(data, Transformers.toUser); + }, + config || { revalidateOnMount: true, revalidateOnFocus: false }, + ); +}; + +const getUser = (id: number, include: string[] = []): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/users/${id}`, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUser(data))) + .catch(reject); + }); +}; + +const searchUserAccounts = async (params: QueryBuilderParams<'username' | 'email'>): Promise => { + const { data } = await http.get('/api/application/users', { + params: withQueryBuilderParams(params), + }); + + return data.data.map(Transformers.toUser); +}; + +const createUser = (values: UpdateUserValues, include: string[] = []): Promise => { + const data = {}; + Object.keys(values).forEach(k => { + // @ts-ignore + data[k.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`)] = values[k]; + }); + + return new Promise((resolve, reject) => { + http.post('/api/application/users', data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUser(data))) + .catch(reject); + }); +}; + +const updateUser = (id: number, values: Partial, include: string[] = []): Promise => { + const data = {}; + Object.keys(values).forEach(k => { + // Don't set password if it is empty. + if (k === 'password' && values[k] === '') { + return; + } + // @ts-ignore + data[k.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`)] = values[k]; + }); + return new Promise((resolve, reject) => { + http.patch(`/api/application/users/${id}`, data, { params: { include: include.join(',') } }) + .then(({ data }) => resolve(Transformers.toUser(data))) + .catch(reject); + }); +}; + +const deleteUser = (id: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/application/users/${id}`) + .then(() => resolve()) + .catch(reject); + }); +}; + +export { useGetUsers, getUser, searchUserAccounts, createUser, updateUser, deleteUser }; diff --git a/resources/scripts/api/definitions/admin/index.ts b/resources/scripts/api/definitions/admin/index.ts new file mode 100644 index 000000000..39ac1f45a --- /dev/null +++ b/resources/scripts/api/definitions/admin/index.ts @@ -0,0 +1,2 @@ +export * from './models.d'; +export { default as Transformers } from './transformers'; diff --git a/resources/scripts/api/definitions/admin/models.d.ts b/resources/scripts/api/definitions/admin/models.d.ts new file mode 100644 index 000000000..627a448f9 --- /dev/null +++ b/resources/scripts/api/definitions/admin/models.d.ts @@ -0,0 +1,29 @@ +import { ModelWithRelationships, UUID } from '@/api/definitions'; +import { Server } from '@/api/admin/server'; + +interface User extends ModelWithRelationships { + id: number; + uuid: UUID; + externalId: string; + username: string; + email: string; + language: string; + adminRoleId: number | null; + roleName: string; + isRootAdmin: boolean; + isUsingTwoFactor: boolean; + avatarUrl: string; + createdAt: Date; + updatedAt: Date; + relationships: { + role: UserRole | null; + // TODO: just use an API call, this is probably a bad idea for performance. + servers?: Server[]; + }; +} + +interface UserRole extends ModelWithRelationships { + id: number; + name: string; + description: string; +} diff --git a/resources/scripts/api/definitions/admin/transformers.ts b/resources/scripts/api/definitions/admin/transformers.ts new file mode 100644 index 000000000..07095ab07 --- /dev/null +++ b/resources/scripts/api/definitions/admin/transformers.ts @@ -0,0 +1,212 @@ +/* eslint-disable camelcase */ +import { Allocation, Node } from '@/api/admin/node'; +import { Server, ServerVariable } from '@/api/admin/server'; +import { FractalResponseData, FractalResponseList } from '@/api/http'; +import * as Models from '@definitions/admin/models'; +import { Location } from '@/api/admin/location'; +import { Egg, EggVariable } from '@/api/admin/egg'; +import { Nest } from '@/api/admin/nest'; + +const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list'; + +function transform (data: undefined, transformer: (callback: FractalResponseData) => T, missing?: M): undefined; +function transform (data: FractalResponseData | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T | M | undefined; +function transform (data: FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T[] | undefined; +function transform (data: FractalResponseData | FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing = undefined) { + if (data === undefined) return undefined; + + if (isList(data)) { + return data.data.map(transformer); + } + + return !data ? missing : transformer(data); +} + +export default class Transformers { + static toServer = ({ attributes }: FractalResponseData): Server => { + const { oom_disabled, ...limits } = attributes.limits; + const { allocations, egg, nest, node, user, variables } = attributes.relationships || {}; + + return { + id: attributes.id, + uuid: attributes.uuid, + externalId: attributes.external_id, + identifier: attributes.identifier, + name: attributes.name, + description: attributes.description, + status: attributes.status, + userId: attributes.owner_id, + nodeId: attributes.node_id, + allocationId: attributes.allocation_id, + eggId: attributes.egg_id, + nestId: attributes.nest_id, + limits: { ...limits, oomDisabled: oom_disabled }, + featureLimits: attributes.feature_limits, + container: attributes.container, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + allocations: transform(allocations as FractalResponseList | undefined, this.toAllocation), + nest: transform(nest as FractalResponseData | undefined, this.toNest), + egg: transform(egg as FractalResponseData | undefined, this.toEgg), + node: transform(node as FractalResponseData | undefined, this.toNode), + user: transform(user as FractalResponseData | undefined, this.toUser), + variables: transform(variables as FractalResponseList | undefined, this.toServerEggVariable), + }, + }; + }; + + static toNode = ({ attributes }: FractalResponseData): Node => { + return { + id: attributes.id, + uuid: attributes.uuid, + isPublic: attributes.public, + locationId: attributes.location_id, + databaseHostId: attributes.database_host_id, + name: attributes.name, + description: attributes.description, + fqdn: attributes.fqdn, + ports: { + http: { + public: attributes.publicPortHttp, + listen: attributes.listenPortHttp, + }, + sftp: { + public: attributes.publicPortSftp, + listen: attributes.listenPortSftp, + }, + }, + scheme: attributes.scheme, + isBehindProxy: attributes.behindProxy, + isMaintenanceMode: attributes.maintenance_mode, + memory: attributes.memory, + memoryOverallocate: attributes.memory_overallocate, + disk: attributes.disk, + diskOverallocate: attributes.disk_overallocate, + uploadSize: attributes.upload_size, + daemonBase: attributes.daemonBase, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + location: transform(attributes.relationships?.location as FractalResponseData, this.toLocation), + }, + }; + }; + + static toUserRole = ({ attributes }: FractalResponseData): Models.UserRole => ({ + id: attributes.id, + name: attributes.name, + description: attributes.description, + relationships: {}, + }); + + static toUser = ({ attributes }: FractalResponseData): Models.User => { + return { + id: attributes.id, + uuid: attributes.uuid, + externalId: attributes.external_id, + username: attributes.username, + email: attributes.email, + language: attributes.language, + adminRoleId: attributes.adminRoleId || null, + roleName: attributes.role_name, + isRootAdmin: attributes.root_admin, + isUsingTwoFactor: attributes['2fa'] || false, + avatarUrl: attributes.avatar_url, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + role: transform(attributes.relationships?.role as FractalResponseData, this.toUserRole) || null, + }, + }; + }; + + static toLocation = ({ attributes }: FractalResponseData): Location => ({ + id: attributes.id, + short: attributes.short, + long: attributes.long, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + nodes: transform(attributes.relationships?.node as FractalResponseList, this.toNode), + }, + }); + + static toEgg = ({ attributes }: FractalResponseData): Egg => ({ + id: attributes.id, + uuid: attributes.uuid, + nestId: attributes.nest_id, + author: attributes.author, + name: attributes.name, + description: attributes.description, + features: attributes.features, + dockerImages: attributes.docker_images, + configFiles: attributes.config?.files, + configStartup: attributes.config?.startup, + configStop: attributes.config?.stop, + configFrom: attributes.config?.extends, + startup: attributes.startup, + copyScriptFrom: attributes.copy_script_from, + scriptContainer: attributes.script?.container, + scriptEntry: attributes.script?.entry, + scriptIsPrivileged: attributes.script?.privileged, + scriptInstall: attributes.script?.install, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + nest: transform(attributes.relationships?.nest as FractalResponseData, this.toNest), + variables: transform(attributes.relationships?.variables as FractalResponseList, this.toEggVariable), + }, + }); + + static toEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({ + id: attributes.id, + eggId: attributes.egg_id, + name: attributes.name, + description: attributes.description, + environmentVariable: attributes.env_variable, + defaultValue: attributes.default_value, + isUserViewable: attributes.user_viewable, + isUserEditable: attributes.user_editable, + // isRequired: attributes.required, + rules: attributes.rules, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: {}, + }); + + static toServerEggVariable = (data: FractalResponseData): ServerVariable => ({ + ...this.toEggVariable(data), + serverValue: data.attributes.server_value, + }); + + static toAllocation = ({ attributes }: FractalResponseData): Allocation => ({ + id: attributes.id, + ip: attributes.ip, + port: attributes.port, + alias: attributes.alias || null, + isAssigned: attributes.assigned, + relationships: { + node: transform(attributes.relationships?.node as FractalResponseData, this.toNode), + server: transform(attributes.relationships?.server as FractalResponseData, this.toServer), + }, + getDisplayText (): string { + const raw = `${this.ip}:${this.port}`; + + return !this.alias ? raw : `${this.alias} (${raw})`; + }, + }); + + static toNest = ({ attributes }: FractalResponseData): Nest => ({ + id: attributes.id, + uuid: attributes.uuid, + author: attributes.author, + name: attributes.name, + description: attributes.description, + createdAt: new Date(attributes.created_at), + updatedAt: new Date(attributes.updated_at), + relationships: { + eggs: transform(attributes.relationships?.eggs as FractalResponseList, this.toEgg), + }, + }); +} diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx index c72ce426f..ff53fb80e 100644 --- a/resources/scripts/components/App.tsx +++ b/resources/scripts/components/App.tsx @@ -11,10 +11,12 @@ import Spinner from '@/components/elements/Spinner'; import { store } from '@/state'; import { ServerContext } from '@/state/server'; import { SiteSettings } from '@/state/settings'; +import { AdminContext } from '@/state/admin'; +const AdminRouter = lazy(() => import('@/routers/AdminRouter')); +const AuthenticationRouter = lazy(() => import('@/routers/AuthenticationRouter')); const DashboardRouter = lazy(() => import('@/routers/DashboardRouter')); const ServerRouter = lazy(() => import('@/routers/ServerRouter')); -const AuthenticationRouter = lazy(() => import('@/routers/AuthenticationRouter')); interface ExtendedWindow extends Window { SiteConfiguration?: SiteSettings; @@ -86,6 +88,17 @@ function App() { } /> + + + + + + } + /> + ( +
    + +
    + {typeof title === 'string' ? ( +

    + {icon && } + {title} +

    + ) : ( + title + )} + {button} +
    +
    {children}
    +
    +); + +export default AdminBox; diff --git a/resources/scripts/components/admin/AdminCheckbox.tsx b/resources/scripts/components/admin/AdminCheckbox.tsx new file mode 100644 index 000000000..32ba2b00e --- /dev/null +++ b/resources/scripts/components/admin/AdminCheckbox.tsx @@ -0,0 +1,36 @@ +import type { ChangeEvent } from 'react'; +import tw, { styled } from 'twin.macro'; + +import Input from '@/components/elements/Input'; + +export const TableCheckbox = styled(Input)` + && { + ${tw`border-neutral-500 bg-transparent`}; + + &:not(:checked) { + ${tw`hover:border-neutral-300`}; + } + } +`; + +export default ({ + name, + checked, + onChange, +}: { + name: string; + checked: boolean; + onChange(e: ChangeEvent): void; +}) => { + return ( +
    + +
    + ); +}; diff --git a/resources/scripts/components/admin/AdminContentBlock.tsx b/resources/scripts/components/admin/AdminContentBlock.tsx new file mode 100644 index 000000000..e182026e8 --- /dev/null +++ b/resources/scripts/components/admin/AdminContentBlock.tsx @@ -0,0 +1,42 @@ +import type { ReactNode } from 'react'; +import { useEffect } from 'react'; +// import { CSSTransition } from 'react-transition-group'; +import tw from 'twin.macro'; +import FlashMessageRender from '@/components/FlashMessageRender'; + +const AdminContentBlock: React.FC<{ + children: ReactNode; + title?: string; + showFlashKey?: string; + className?: string; +}> = ({ children, title, showFlashKey }) => { + useEffect(() => { + if (!title) { + return; + } + + document.title = `Admin | ${title}`; + }, [title]); + + return ( + // + <> + {showFlashKey && } + {children} + {/*

    + © 2015 - 2021  + + Pterodactyl Software + +

    */} + + //
    + ); +}; + +export default AdminContentBlock; diff --git a/resources/scripts/components/admin/AdminTable.tsx b/resources/scripts/components/admin/AdminTable.tsx new file mode 100644 index 000000000..60c544a0a --- /dev/null +++ b/resources/scripts/components/admin/AdminTable.tsx @@ -0,0 +1,348 @@ +import { debounce } from 'debounce'; +import type { ChangeEvent, MouseEvent, ReactNode } from 'react'; +import { useCallback, useState } from 'react'; +import tw, { styled } from 'twin.macro'; + +import type { ListContext as TableHooks } from '@/api/admin'; +import type { PaginatedResult, PaginationDataSet } from '@/api/http'; +import { TableCheckbox } from '@/components/admin/AdminCheckbox'; +import Input from '@/components/elements/Input'; +import InputSpinner from '@/components/elements/InputSpinner'; +import Spinner from '@/components/elements/Spinner'; + +export function useTableHooks(initialState?: T | (() => T)): TableHooks { + const [page, setPage] = useState(1); + const [filters, setFilters] = useState(initialState || null); + const [sort, setSortState] = useState(null); + const [sortDirection, setSortDirection] = useState(false); + + const setSort = (newSort: string | null) => { + if (sort === newSort) { + setSortDirection(!sortDirection); + } else { + setSortState(newSort); + setSortDirection(false); + } + }; + + return { page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }; +} + +export const TableHeader = ({ + name, + onClick, + direction, +}: { + name?: string; + onClick?: (e: MouseEvent) => void; + direction?: number | null; +}) => { + if (!name) { + return ; + } + + return ( + + + + {name} + + + {direction !== undefined ? ( +
    + + {direction === null || direction === 1 ? ( + + ) : null} + {direction === null || direction === 2 ? ( + + ) : null} + +
    + ) : null} +
    + + ); +}; + +export const TableHead = ({ children }: { children: ReactNode }) => { + return ( + + + + {children} + + + ); +}; + +export const TableBody = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +export const TableRow = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +interface Props { + data?: PaginatedResult; + onPageSelect: (page: number) => void; + + children: ReactNode; +} + +const PaginationButton = styled.button<{ active?: boolean }>` + ${tw`relative items-center px-3 py-1 -ml-px text-sm font-normal leading-5 transition duration-150 ease-in-out border border-neutral-500 focus:z-10 focus:outline-none focus:border-primary-300 inline-flex`}; + + ${props => + props.active ? tw`bg-neutral-500 text-neutral-50` : tw`bg-neutral-600 text-neutral-200 hover:text-neutral-50`}; +`; + +const PaginationArrow = styled.button` + ${tw`relative inline-flex items-center px-1 py-1 text-sm font-medium leading-5 transition duration-150 ease-in-out border border-neutral-500 bg-neutral-600 text-neutral-400 hover:text-neutral-50 focus:z-10 focus:outline-none focus:border-primary-300`}; + + &:disabled { + ${tw`bg-neutral-700`} + } + + &:hover:disabled { + ${tw`text-neutral-400 cursor-default`}; + } +`; + +export function Pagination({ data, onPageSelect, children }: Props) { + let pagination: PaginationDataSet; + if (data === undefined) { + pagination = { + total: 0, + count: 0, + perPage: 0, + currentPage: 1, + totalPages: 1, + }; + } else { + pagination = data.pagination; + } + + const setPage = (page: number) => { + if (page < 1 || page > pagination.totalPages) { + return; + } + + onPageSelect(page); + }; + + const isFirstPage = pagination.currentPage === 1; + const isLastPage = pagination.currentPage >= pagination.totalPages; + + const pages = []; + + if (pagination.totalPages < 7) { + for (let i = 1; i <= pagination.totalPages; i++) { + pages.push(i); + } + } else { + // Don't ask me how this works, all I know is that this code will always have 7 items in the pagination, + // and keeps the current page centered if it is not too close to the start or end. + let start = Math.max(pagination.currentPage - 3, 1); + const end = Math.min( + pagination.totalPages, + pagination.currentPage + (pagination.currentPage < 4 ? 7 - pagination.currentPage : 3), + ); + + while (start !== 1 && end - start !== 6) { + start--; + } + + for (let i = start; i <= end; i++) { + pages.push(i); + } + } + + return ( + <> + {children} + +
    +

    + Showing{' '} + + {(pagination.currentPage - 1) * pagination.perPage + (pagination.total > 0 ? 1 : 0)} + {' '} + to{' '} + + {(pagination.currentPage - 1) * pagination.perPage + pagination.count} + {' '} + of {pagination.total} results +

    + + {isFirstPage && isLastPage ? null : ( +
    + +
    + )} +
    + + ); +} + +export const Loading = () => { + return ( +
    + +
    + ); +}; + +export const NoItems = ({ className }: { className?: string }) => { + return ( +
    +
    + {'No +
    + +

    + No items could be found, it's almost like they are hiding. +

    +
    + ); +}; + +interface Params { + checked: boolean; + onSelectAllClick: (e: ChangeEvent) => void; + onSearch?: (query: string) => Promise; + + children: ReactNode; +} + +export const ContentWrapper = ({ checked, onSelectAllClick, onSearch, children }: Params) => { + const [loading, setLoading] = useState(false); + const [inputText, setInputText] = useState(''); + + const search = useCallback( + debounce((query: string) => { + if (onSearch === undefined) { + return; + } + + setLoading(true); + onSearch(query).then(() => setLoading(false)); + }, 200), + [], + ); + + return ( + <> +
    +
    + + + + + +
    + +
    + + { + setInputText(e.currentTarget.value); + search(e.currentTarget.value); + }} + /> + +
    +
    + + {children} + + ); +}; + +export default ({ children }: { children: ReactNode }) => { + return ( +
    +
    {children}
    +
    + ); +}; diff --git a/resources/scripts/components/admin/Sidebar.tsx b/resources/scripts/components/admin/Sidebar.tsx new file mode 100644 index 000000000..7ff60cad2 --- /dev/null +++ b/resources/scripts/components/admin/Sidebar.tsx @@ -0,0 +1,87 @@ +import tw, { css, styled } from 'twin.macro'; + +import { withSubComponents } from '@/components/helpers'; + +const Wrapper = styled.div` + ${tw`w-full flex flex-col px-4`}; + + & > a { + ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none px-4`}; + ${tw`hover:text-neutral-50`}; + + & > svg { + ${tw`h-6 w-6 flex flex-shrink-0`}; + } + + & > span { + ${tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; + } + + &:active, + &.active { + ${tw`text-neutral-50 bg-neutral-800 rounded`}; + } + } +`; + +const Section = styled.div` + ${tw`h-[18px] font-header font-medium text-xs text-neutral-300 whitespace-nowrap uppercase ml-4 mb-1 select-none`}; + + &:not(:first-of-type) { + ${tw`mt-4`}; + } +`; + +const User = styled.div` + ${tw`h-16 w-full flex items-center bg-neutral-700 justify-center`}; +`; + +const Sidebar = styled.div<{ $collapsed?: boolean }>` + ${tw`h-screen hidden md:flex flex-col items-center flex-shrink-0 bg-neutral-900 overflow-x-hidden ease-linear`}; + ${tw`transition-[width] duration-150 ease-in`}; + ${tw`w-[17.5rem]`}; + + & > a { + ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none px-8`}; + ${tw`hover:text-neutral-50`}; + + & > svg { + ${tw`transition-none h-6 w-6 flex flex-shrink-0`}; + } + + & > span { + ${tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; + } + } + + ${props => + props.$collapsed && + css` + ${tw`w-20`}; + + ${Section} { + ${tw`invisible`}; + } + + ${Wrapper} { + ${tw`px-5`}; + + & > a { + ${tw`justify-center px-0`}; + } + } + + & > a { + ${tw`justify-center px-4`}; + } + + & > a > span, + ${User} > div, + ${User} > a, + ${Wrapper} > a > span { + ${tw`hidden`}; + } + `}; +`; + +export default withSubComponents(Sidebar, { Section, Wrapper, User }); diff --git a/resources/scripts/components/admin/SubNavigation.tsx b/resources/scripts/components/admin/SubNavigation.tsx new file mode 100644 index 000000000..5af3f28f0 --- /dev/null +++ b/resources/scripts/components/admin/SubNavigation.tsx @@ -0,0 +1,42 @@ +import type { ComponentType, ReactNode } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw, { styled } from 'twin.macro'; + +export const SubNavigation = styled.div` + ${tw`flex flex-row items-center flex-shrink-0 h-12 mb-4 border-b border-neutral-700`}; + + & > a { + ${tw`flex flex-row items-center h-full px-4 border-b text-neutral-300 text-base whitespace-nowrap border-transparent`}; + + & > svg { + ${tw`w-6 h-6 mr-2`}; + } + + &:active, + &.active { + ${tw`text-primary-300 border-primary-300`}; + } + } +`; + +interface Props { + to: string; + name: string; +} + +interface PropsWithIcon extends Props { + icon: ComponentType; + children?: never; +} + +interface PropsWithoutIcon extends Props { + icon?: never; + children: ReactNode; +} + +export const SubNavigationLink = ({ to, name, icon: IconComponent, children }: PropsWithIcon | PropsWithoutIcon) => ( + + {IconComponent ? : children} + {name} + +); diff --git a/resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx b/resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx new file mode 100644 index 000000000..be08b56fe --- /dev/null +++ b/resources/scripts/components/admin/databases/DatabaseDeleteButton.tsx @@ -0,0 +1,73 @@ +import { Actions, useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteDatabase from '@/api/admin/databases/deleteDatabase'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + databaseId: number; + onDeleted: () => void; +} + +export default ({ databaseId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('database'); + + deleteDatabase(databaseId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this database host? This action will delete all knowledge of databases + created on this host but not the databases themselves. + + + + + ); +}; diff --git a/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx b/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx new file mode 100644 index 000000000..89d8df46f --- /dev/null +++ b/resources/scripts/components/admin/databases/DatabaseEditContainer.tsx @@ -0,0 +1,235 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { number, object, string } from 'yup'; + +import type { Database } from '@/api/admin/databases/getDatabases'; +import getDatabase from '@/api/admin/databases/getDatabase'; +import updateDatabase from '@/api/admin/databases/updateDatabase'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import DatabaseDeleteButton from '@/components/admin/databases/DatabaseDeleteButton'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + database: Database | undefined; + setDatabase: Action; +} + +export const Context = createContextStore({ + database: undefined, + + setDatabase: action((state, payload) => { + state.database = payload; + }), +}); + +export interface Values { + name: string; + host: string; + port: number; + username: string; + password: string; +} + +export interface Params { + title: string; + initialValues?: Values; + children?: React.ReactNode; + + onSubmit: (values: Values, helpers: FormikHelpers) => void; +} + +export const InformationContainer = ({ title, initialValues, children, onSubmit }: Params) => { + const submit = (values: Values, helpers: FormikHelpers) => { + onSubmit(values, helpers); + }; + + if (!initialValues) { + initialValues = { + name: '', + host: '', + port: 3306, + username: '', + password: '', + }; + } + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + {children} +
    + +
    +
    +
    +
    + + )} +
    + ); +}; + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const database = Context.useStoreState(state => state.database); + const setDatabase = Context.useStoreActions(actions => actions.setDatabase); + + if (database === undefined) { + return <>; + } + + const submit = ({ name, host, port, username, password }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('database'); + + updateDatabase(database.id, name, host, port, username, password || undefined) + .then(() => setDatabase({ ...database, name, host, port, username })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    + navigate('/admin/databases')} /> +
    +
    + ); +}; + +const DatabaseEditContainer = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const database = Context.useStoreState(state => state.database); + const setDatabase = Context.useStoreActions(actions => actions.setDatabase); + + useEffect(() => { + clearFlashes('database'); + + getDatabase(Number(params.id)) + .then(database => setDatabase(database)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || database === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {database.name}

    +

    + {database.getAddress()} +

    +
    +
    + + + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/databases/DatabasesContainer.tsx b/resources/scripts/components/admin/databases/DatabasesContainer.tsx new file mode 100644 index 000000000..73ff3027c --- /dev/null +++ b/resources/scripts/components/admin/databases/DatabasesContainer.tsx @@ -0,0 +1,194 @@ +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/databases/getDatabases'; +import getDatabases, { Context as DatabasesContext } from '@/api/admin/databases/getDatabases'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import Button from '@/components/elements/Button'; +import CopyOnClick from '@/components/elements/CopyOnClick'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.databases.selectedDatabases.indexOf(id) >= 0); + const appendSelectedDatabase = AdminContext.useStoreActions(actions => actions.databases.appendSelectedDatabase); + const removeSelectedDatabase = AdminContext.useStoreActions(actions => actions.databases.removeSelectedDatabase); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedDatabase(id); + } else { + removeSelectedDatabase(id); + } + }} + /> + ); +}; + +const DatabasesContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(DatabasesContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: databases, error, isValidating } = getDatabases(); + + useEffect(() => { + if (!error) { + clearFlashes('databases'); + return; + } + + clearAndAddHttpError({ key: 'databases', error }); + }, [error]); + + const length = databases?.items?.length || 0; + + const setSelectedDatabases = AdminContext.useStoreActions(actions => actions.databases.setSelectedDatabases); + const selectedDatabasesLength = AdminContext.useStoreState(state => state.databases.selectedDatabases.length); + + const onSelectAllClick = (e: React.ChangeEvent) => { + setSelectedDatabases(e.currentTarget.checked ? databases?.items?.map(database => database.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedDatabases([]); + }, [page]); + + return ( + +
    +
    +

    Database Hosts

    +

    + Database hosts that servers can have databases created on. +

    +
    + +
    + + + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + + {databases !== undefined && + !error && + !isValidating && + length > 0 && + databases.items.map(database => ( + + + + + + + + + + + + ))} + +
    + + + + + {database.id} + + + + + {database.name} + + + + + {database.getAddress()} + + + + {database.username} +
    + + {databases === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/databases/NewDatabaseContainer.tsx b/resources/scripts/components/admin/databases/NewDatabaseContainer.tsx new file mode 100644 index 000000000..3bc0afa94 --- /dev/null +++ b/resources/scripts/components/admin/databases/NewDatabaseContainer.tsx @@ -0,0 +1,48 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import createDatabase from '@/api/admin/databases/createDatabase'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import { InformationContainer, Values } from '@/components/admin/databases/DatabaseEditContainer'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = ({ name, host, port, username, password }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('database:create'); + + createDatabase(name, host, port, username, password) + .then(database => navigate(`/admin/databases/${database.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'database:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Database Host

    +

    + Add a new database host to the panel. +

    +
    +
    + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/locations/LocationDeleteButton.tsx b/resources/scripts/components/admin/locations/LocationDeleteButton.tsx new file mode 100644 index 000000000..9fb80954e --- /dev/null +++ b/resources/scripts/components/admin/locations/LocationDeleteButton.tsx @@ -0,0 +1,74 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteLocation from '@/api/admin/locations/deleteLocation'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + locationId: number; + onDeleted: () => void; +} + +export default ({ locationId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('location'); + + deleteLocation(locationId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'location', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this location? You may only delete a location if no nodes are assigned + to it. + + + + + ); +}; diff --git a/resources/scripts/components/admin/locations/LocationEditContainer.tsx b/resources/scripts/components/admin/locations/LocationEditContainer.tsx new file mode 100644 index 000000000..6c0e74c6f --- /dev/null +++ b/resources/scripts/components/admin/locations/LocationEditContainer.tsx @@ -0,0 +1,180 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import type { Location } from '@/api/admin/locations/getLocations'; +import getLocation from '@/api/admin/locations/getLocation'; +import updateLocation from '@/api/admin/locations/updateLocation'; +import AdminBox from '@/components/admin/AdminBox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import LocationDeleteButton from '@/components/admin/locations/LocationDeleteButton'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Spinner from '@/components/elements/Spinner'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + location: Location | undefined; + setLocation: Action; +} + +export const Context = createContextStore({ + location: undefined, + + setLocation: action((state, payload) => { + state.location = payload; + }), +}); + +interface Values { + short: string; + long: string; +} + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const location = Context.useStoreState(state => state.location); + const setLocation = Context.useStoreActions(actions => actions.setLocation); + + if (location === undefined) { + return <>; + } + + const submit = ({ short, long }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('location'); + + updateLocation(location.id, short, long) + .then(() => setLocation({ ...location, short, long })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'location', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    +
    + +
    + +
    + +
    + +
    +
    + navigate('/admin/locations')} + /> +
    + +
    + +
    +
    +
    +
    + + )} +
    + ); +}; + +const LocationEditContainer = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const location = Context.useStoreState(state => state.location); + const setLocation = Context.useStoreActions(actions => actions.setLocation); + + useEffect(() => { + clearFlashes('location'); + + getLocation(Number(params.id)) + .then(location => setLocation(location)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'location', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || location === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {location.short}

    + {(location.long || '').length < 1 ? ( +

    + No long name +

    + ) : ( +

    + {location.long} +

    + )} +
    +
    + + + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/locations/LocationsContainer.tsx b/resources/scripts/components/admin/locations/LocationsContainer.tsx new file mode 100644 index 000000000..81ddd710a --- /dev/null +++ b/resources/scripts/components/admin/locations/LocationsContainer.tsx @@ -0,0 +1,186 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/locations/getLocations'; +import getLocations, { Context as LocationsContext } from '@/api/admin/locations/getLocations'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import NewLocationButton from '@/components/admin/locations/NewLocationButton'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.locations.selectedLocations.indexOf(id) >= 0); + const appendSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.appendSelectedLocation); + const removeSelectedLocation = AdminContext.useStoreActions(actions => actions.locations.removeSelectedLocation); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedLocation(id); + } else { + removeSelectedLocation(id); + } + }} + /> + ); +}; + +const LocationsContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(LocationsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: locations, error, isValidating } = getLocations(); + + useEffect(() => { + if (!error) { + clearFlashes('locations'); + return; + } + + clearAndAddHttpError({ key: 'locations', error }); + }, [error]); + + const length = locations?.items?.length || 0; + + const setSelectedLocations = AdminContext.useStoreActions(actions => actions.locations.setSelectedLocations); + const selectedLocationsLength = AdminContext.useStoreState(state => state.locations.selectedLocations.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedLocations(e.currentTarget.checked ? locations?.items?.map(location => location.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ short: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedLocations([]); + }, [page]); + + return ( + +
    +
    +

    Locations

    +

    + All locations that nodes can be assigned to for easier categorization. +

    +
    + +
    + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('short')} + /> + setSort('long')} + /> + + + + {locations !== undefined && + !error && + !isValidating && + length > 0 && + locations.items.map(location => ( + + + + + + + + + + ))} + +
    + + + + + {location.id} + + + + + {location.short} + + + {location.long} +
    + + {locations === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/locations/NewLocationButton.tsx b/resources/scripts/components/admin/locations/NewLocationButton.tsx new file mode 100644 index 000000000..f7f688e9b --- /dev/null +++ b/resources/scripts/components/admin/locations/NewLocationButton.tsx @@ -0,0 +1,112 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import createLocation from '@/api/admin/locations/createLocation'; +import getLocations from '@/api/admin/locations/getLocations'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + short: string; + long: string; +} + +const schema = object().shape({ + short: string() + .required('A location short name must be provided.') + .max(32, 'Location short name must not exceed 32 characters.'), + long: string().max(255, 'Location long name must not exceed 255 characters.'), +}); + +export default () => { + const [visible, setVisible] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { mutate } = getLocations(); + + const submit = ({ short, long }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('location:create'); + setSubmitting(true); + + createLocation(short, long) + .then(async location => { + await mutate(data => ({ ...data!, items: data!.items.concat(location) }), false); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'location:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + {({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + + +

    New Location

    + +
    + + +
    + +
    + +
    + + +
    + +
    + )} +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/MountDeleteButton.tsx b/resources/scripts/components/admin/mounts/MountDeleteButton.tsx new file mode 100644 index 000000000..e515e50e2 --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteMount from '@/api/admin/mounts/deleteMount'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + mountId: number; + onDeleted: () => void; +} + +export default ({ mountId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('mount'); + + deleteMount(mountId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this mount? Deleting a mount will not delete files on any nodes. + + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/MountEditContainer.tsx b/resources/scripts/components/admin/mounts/MountEditContainer.tsx new file mode 100644 index 000000000..a9282cf17 --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountEditContainer.tsx @@ -0,0 +1,142 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Mount } from '@/api/admin/mounts/getMounts'; +import getMount from '@/api/admin/mounts/getMount'; +import updateMount from '@/api/admin/mounts/updateMount'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import MountDeleteButton from '@/components/admin/mounts/MountDeleteButton'; +import MountForm from '@/components/admin/mounts/MountForm'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + mount: Mount | undefined; + setMount: Action; +} + +export const Context = createContextStore({ + mount: undefined, + + setMount: action((state, payload) => { + state.mount = payload; + }), +}); + +const MountEditContainer = () => { + const navigate = useNavigate(); + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const mount = Context.useStoreState(state => state.mount); + const setMount = Context.useStoreActions(actions => actions.setMount); + + const submit = ( + { name, description, source, target, readOnly, userMountable }: any, + { setSubmitting }: FormikHelpers, + ) => { + if (mount === undefined) { + return; + } + + clearFlashes('mount'); + + updateMount(mount.id, name, description, source, target, readOnly === '1', userMountable === '1') + .then(() => + setMount({ + ...mount, + name, + description, + source, + target, + readOnly: readOnly === '1', + userMountable: userMountable === '1', + }), + ) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount', error }); + }) + .then(() => setSubmitting(false)); + }; + + useEffect(() => { + clearFlashes('mount'); + + getMount(Number(params.id)) + .then(mount => setMount(mount)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || mount === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {mount.name}

    + {(mount.description || '').length < 1 ? ( +

    + No description +

    + ) : ( +

    + {mount.description} +

    + )} +
    +
    + + + + +
    + navigate('/admin/mounts')} /> +
    +
    +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/MountForm.tsx b/resources/scripts/components/admin/mounts/MountForm.tsx new file mode 100644 index 000000000..a86b947a8 --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountForm.tsx @@ -0,0 +1,133 @@ +import type { FormikHelpers } from 'formik'; +import { Field as FormikField, Form, Formik } from 'formik'; +import tw from 'twin.macro'; +import { boolean, object, string } from 'yup'; + +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Label from '@/components/elements/Label'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +interface Values { + name: string; + description: string; + source: string; + target: string; + readOnly: string; + userMountable: string; +} + +interface Props { + action: string; + title: string; + initialValues?: Values; + + onSubmit: (values: Values, helpers: FormikHelpers) => void; + + children?: React.ReactNode; +} + +function MountForm({ action, title, initialValues, children, onSubmit }: Props) { + const submit = (values: Values, helpers: FormikHelpers) => { + onSubmit(values, helpers); + }; + + if (!initialValues) { + initialValues = { + name: '', + description: '', + source: '', + target: '', + readOnly: '0', + userMountable: '0', + }; + } + + return ( + + {({ isSubmitting, isValid }) => ( + + + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + + +
    + + + +
    +
    + +
    + + +
    + + + +
    +
    +
    + +
    + {children} + +
    + +
    +
    +
    +
    + )} +
    + ); +} + +export default MountForm; diff --git a/resources/scripts/components/admin/mounts/MountsContainer.tsx b/resources/scripts/components/admin/mounts/MountsContainer.tsx new file mode 100644 index 000000000..bc3e0054d --- /dev/null +++ b/resources/scripts/components/admin/mounts/MountsContainer.tsx @@ -0,0 +1,241 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/mounts/getMounts'; +import getMounts, { Context as MountsContext } from '@/api/admin/mounts/getMounts'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import Button from '@/components/elements/Button'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.mounts.selectedMounts.indexOf(id) >= 0); + const appendSelectedMount = AdminContext.useStoreActions(actions => actions.mounts.appendSelectedMount); + const removeSelectedMount = AdminContext.useStoreActions(actions => actions.mounts.removeSelectedMount); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedMount(id); + } else { + removeSelectedMount(id); + } + }} + /> + ); +}; + +const MountsContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(MountsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: mounts, error, isValidating } = getMounts(); + + useEffect(() => { + if (!error) { + clearFlashes('mounts'); + return; + } + + clearAndAddHttpError({ key: 'mounts', error }); + }, [error]); + + const length = mounts?.items?.length || 0; + + const setSelectedMounts = AdminContext.useStoreActions(actions => actions.mounts.setSelectedMounts); + const selectedMountsLength = AdminContext.useStoreState(state => state.mounts.selectedMounts.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedMounts(e.currentTarget.checked ? mounts?.items?.map(mount => mount.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedMounts([]); + }, [page]); + + return ( + +
    +
    +

    Mounts

    +

    + Configure and manage additional mount points for servers. +

    +
    + +
    + + + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + setSort('source')} + /> + setSort('target')} + /> + + + + + + + + + + + + + + + ))} + +
    + + + + + {mounts !== undefined && + !error && + !isValidating && + length > 0 && + mounts.items.map(mount => ( + + + + + + + {mount.id} + + + + + {mount.name} + + + + + {mount.source} + + + + + + {mount.target} + + + + {mount.readOnly ? ( + + Read Only + + ) : ( + + Writable + + )} + + {mount.userMountable ? ( + + Mountable + + ) : ( + + Admin Only + + )} +
    + + {mounts === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/NewMountContainer.tsx b/resources/scripts/components/admin/mounts/NewMountContainer.tsx new file mode 100644 index 000000000..064b0942d --- /dev/null +++ b/resources/scripts/components/admin/mounts/NewMountContainer.tsx @@ -0,0 +1,51 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import MountForm from '@/components/admin/mounts/MountForm'; +import createMount from '@/api/admin/mounts/createMount'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = ( + { name, description, source, target, readOnly, userMountable }: any, + { setSubmitting }: FormikHelpers, + ) => { + clearFlashes('mount:create'); + + createMount(name, description, source, target, readOnly === '1', userMountable === '1') + .then(mount => navigate(`/admin/mounts/${mount.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'mount:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Mount

    +

    + Add a new mount to the panel. +

    +
    +
    + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/nests/ImportEggButton.tsx b/resources/scripts/components/admin/nests/ImportEggButton.tsx new file mode 100644 index 000000000..1dd6d2dd2 --- /dev/null +++ b/resources/scripts/components/admin/nests/ImportEggButton.tsx @@ -0,0 +1,82 @@ +import getEggs from '@/api/admin/nests/getEggs'; +import importEgg from '@/api/admin/nests/importEgg'; +import useFlash from '@/plugins/useFlash'; +// import { Editor } from '@/components/elements/editor'; +import { useState } from 'react'; +import Button from '@/components/elements/Button'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +export default ({ className }: { className?: string }) => { + const [visible, setVisible] = useState(false); + + const { clearFlashes } = useFlash(); + + const params = useParams<'nestId'>(); + const { mutate } = getEggs(Number(params.nestId)); + + let fetchFileContent: (() => Promise) | null = null; + + const submit = async () => { + clearFlashes('egg:import'); + + if (fetchFileContent === null) { + return; + } + + const egg = await importEgg(Number(params.nestId), await fetchFileContent()); + await mutate(data => ({ ...data!, items: [...data!.items!, egg] })); + setVisible(false); + }; + + return ( + <> + { + setVisible(false); + }} + > + + +

    Import Egg

    + + {/* {*/} + {/* fetchFileContent = value;*/} + {/* }}*/} + {/*/>*/} + +
    + + +
    +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestDeleteButton.tsx b/resources/scripts/components/admin/nests/NestDeleteButton.tsx new file mode 100644 index 000000000..09d573377 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteNest from '@/api/admin/nests/deleteNest'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + nestId: number; + onDeleted: () => void; +} + +export default ({ nestId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('nest'); + + deleteNest(nestId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'nest', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this nest? Deleting a nest will delete all eggs assigned to it. + + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestEditContainer.tsx b/resources/scripts/components/admin/nests/NestEditContainer.tsx new file mode 100644 index 000000000..dabbead60 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestEditContainer.tsx @@ -0,0 +1,250 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { NavLink, useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import ImportEggButton from '@/components/admin/nests/ImportEggButton'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { Nest } from '@/api/admin/nests/getNests'; +import getNest from '@/api/admin/nests/getNest'; +import updateNest from '@/api/admin/nests/updateNest'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import AdminBox from '@/components/admin/AdminBox'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import NestDeleteButton from '@/components/admin/nests/NestDeleteButton'; +import NestEggTable from '@/components/admin/nests/NestEggTable'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + nest: Nest | undefined; + setNest: Action; + + selectedEggs: number[]; + + setSelectedEggs: Action; + appendSelectedEggs: Action; + removeSelectedEggs: Action; +} + +export const Context = createContextStore({ + nest: undefined, + + setNest: action((state, payload) => { + state.nest = payload; + }), + + selectedEggs: [], + + setSelectedEggs: action((state, payload) => { + state.selectedEggs = payload; + }), + + appendSelectedEggs: action((state, payload) => { + state.selectedEggs = state.selectedEggs.filter(id => id !== payload).concat(payload); + }), + + removeSelectedEggs: action((state, payload) => { + state.selectedEggs = state.selectedEggs.filter(id => id !== payload); + }), +}); + +interface Values { + name: string; + description: string; +} + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const nest = Context.useStoreState(state => state.nest); + const setNest = Context.useStoreActions(actions => actions.setNest); + + if (nest === undefined) { + return <>; + } + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('nest'); + + updateNest(nest.id, name, description) + .then(() => setNest({ ...nest, name, description })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'nest', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    + + + + +
    +
    + navigate('/admin/nests')} /> +
    + +
    + +
    +
    + +
    + + )} +
    + ); +}; + +const ViewDetailsContainer = () => { + const nest = Context.useStoreState(state => state.nest); + + if (nest === undefined) { + return <>; + } + + return ( + +
    +
    +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    +
    +
    +
    + ); +}; + +const NestEditContainer = () => { + const params = useParams<'nestId'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const nest = Context.useStoreState(state => state.nest); + const setNest = Context.useStoreActions(actions => actions.setNest); + + useEffect(() => { + clearFlashes('nest'); + + getNest(Number(params.nestId), ['eggs']) + .then(nest => setNest(nest)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'nest', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || nest === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {nest.name}

    + {(nest.description || '').length < 1 ? ( +

    + No description +

    + ) : ( +

    + {nest.description} +

    + )} +
    + +
    + + + + + +
    +
    + + + +
    + + +
    + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestEggTable.tsx b/resources/scripts/components/admin/nests/NestEggTable.tsx new file mode 100644 index 000000000..704dec150 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestEggTable.tsx @@ -0,0 +1,160 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/nests/getEggs'; +import getEggs, { Context as EggsContext } from '@/api/admin/nests/getEggs'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import { Context } from '@/components/admin/nests/NestEditContainer'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import useFlash from '@/plugins/useFlash'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = Context.useStoreState(state => state.selectedEggs.indexOf(id) >= 0); + const appendSelectedEggs = Context.useStoreActions(actions => actions.appendSelectedEggs); + const removeSelectedEggs = Context.useStoreActions(actions => actions.removeSelectedEggs); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedEggs(id); + } else { + removeSelectedEggs(id); + } + }} + /> + ); +}; + +const EggsTable = () => { + const params = useParams<'nestId' | 'id'>(); + + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(EggsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: eggs, error, isValidating } = getEggs(Number(params.nestId)); + + useEffect(() => { + if (!error) { + clearFlashes('nests'); + return; + } + + clearAndAddHttpError({ key: 'nests', error }); + }, [error]); + + const length = eggs?.items?.length || 0; + + const setSelectedEggs = Context.useStoreActions(actions => actions.setSelectedEggs); + const selectedEggsLength = Context.useStoreState(state => state.selectedEggs.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedEggs(e.currentTarget.checked ? eggs?.items?.map(nest => nest.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedEggs([]); + }, [page]); + + return ( + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + {eggs !== undefined && + !error && + !isValidating && + length > 0 && + eggs.items.map(egg => ( + + + + + + + + + + ))} + +
    + + + + + {egg.id} + + + + + {egg.name} + + + {egg.description} +
    + + {eggs === undefined || (error && isValidating) ? : length < 1 ? : null} +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NestsContainer.tsx b/resources/scripts/components/admin/nests/NestsContainer.tsx new file mode 100644 index 000000000..2f937da80 --- /dev/null +++ b/resources/scripts/components/admin/nests/NestsContainer.tsx @@ -0,0 +1,182 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/nests/getNests'; +import getNests, { Context as NestsContext } from '@/api/admin/nests/getNests'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import NewNestButton from '@/components/admin/nests/NewNestButton'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.nests.selectedNests.indexOf(id) >= 0); + const appendSelectedNest = AdminContext.useStoreActions(actions => actions.nests.appendSelectedNest); + const removeSelectedNest = AdminContext.useStoreActions(actions => actions.nests.removeSelectedNest); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedNest(id); + } else { + removeSelectedNest(id); + } + }} + /> + ); +}; + +const NestsContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(NestsContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: nests, error, isValidating } = getNests(); + + useEffect(() => { + if (!error) { + clearFlashes('nests'); + return; + } + + clearAndAddHttpError({ key: 'nests', error }); + }, [error]); + + const length = nests?.items?.length || 0; + + const setSelectedNests = AdminContext.useStoreActions(actions => actions.nests.setSelectedNests); + const selectedNestsLength = AdminContext.useStoreState(state => state.nests.selectedNests.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedNests(e.currentTarget.checked ? nests?.items?.map(nest => nest.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedNests([]); + }, [page]); + + return ( + +
    +
    +

    Nests

    +

    + All nests currently available on this system. +

    +
    + +
    + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + {nests !== undefined && + !error && + !isValidating && + length > 0 && + nests.items.map(nest => ( + + + + + + + + + + ))} + +
    + + + + + {nest.id} + + + + + {nest.name} + + + {nest.description} +
    + + {nests === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/NewEggContainer.tsx b/resources/scripts/components/admin/nests/NewEggContainer.tsx new file mode 100644 index 000000000..1e79eb4ee --- /dev/null +++ b/resources/scripts/components/admin/nests/NewEggContainer.tsx @@ -0,0 +1,115 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useRef } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import createEgg from '@/api/admin/eggs/createEgg'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import { + EggImageContainer, + EggInformationContainer, + EggLifecycleContainer, + EggProcessContainer, + EggProcessContainerRef, + EggStartupContainer, +} from '@/components/admin/nests/eggs/EggSettingsContainer'; +import Button from '@/components/elements/Button'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + name: string; + description: string; + startup: string; + dockerImages: string; + configStop: string; + configStartup: string; + configFiles: string; +} + +export default () => { + const navigate = useNavigate(); + const params = useParams<{ nestId: string }>(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const ref = useRef(); + + const submit = async (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('egg:create'); + + const nestId = Number(params.nestId); + + values.configStartup = (await ref.current?.getStartupConfiguration()) || ''; + values.configFiles = (await ref.current?.getFilesConfiguration()) || ''; + + createEgg({ ...values, dockerImages: values.dockerImages.split('\n'), nestId }) + .then(egg => navigate(`/admin/nests/${nestId}/eggs/${egg.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Egg

    +

    + Add a new egg to the panel. +

    +
    +
    + + + + + {({ isSubmitting, isValid }) => ( +
    +
    + +
    + + + +
    + + +
    + + + +
    +
    + +
    +
    + + )} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nests/NewNestButton.tsx b/resources/scripts/components/admin/nests/NewNestButton.tsx new file mode 100644 index 000000000..60a613840 --- /dev/null +++ b/resources/scripts/components/admin/nests/NewNestButton.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import createNest from '@/api/admin/nests/createNest'; +import getNests from '@/api/admin/nests/getNests'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { Form, Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import tw from 'twin.macro'; + +interface Values { + name: string, + description: string, +} + +const schema = object().shape({ + name: string() + .required('A nest name must be provided.') + .max(32, 'Nest name must not exceed 32 characters.'), + description: string() + .max(255, 'Nest description must not exceed 255 characters.'), +}); + +export default () => { + const [ visible, setVisible ] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { mutate } = getNests(); + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('nest:create'); + setSubmitting(true); + + createNest(name, description) + .then(async (nest) => { + await mutate(data => ({ ...data!, items: data!.items.concat(nest) }), false); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'nest:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + { + ({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + + +

    New Nest

    + +
    + + +
    + +
    + +
    + + +
    + +
    + ) + } +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx b/resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx new file mode 100644 index 000000000..11a141693 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteEgg from '@/api/admin/eggs/deleteEgg'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + eggId: number; + onDeleted: () => void; +} + +export default ({ eggId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('egg'); + + deleteEgg(eggId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this egg? You may only delete an egg with no servers using it. + + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggExportButton.tsx b/resources/scripts/components/admin/nests/eggs/EggExportButton.tsx new file mode 100644 index 000000000..513bc972d --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggExportButton.tsx @@ -0,0 +1,85 @@ +import { exportEgg } from '@/api/admin/egg'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import useFlash from '@/plugins/useFlash'; +// import { jsonLanguage } from '@codemirror/lang-json'; +// import Editor from '@/components/elements/Editor'; +import { useEffect, useState } from 'react'; +import Button from '@/components/elements/Button'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +export default ({ className }: { className?: string }) => { + const params = useParams<'id'>(); + const { clearAndAddHttpError, clearFlashes } = useFlash(); + + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(true); + const [_content, setContent] = useState | null>(null); + + useEffect(() => { + if (!visible) { + return; + } + + clearFlashes('egg:export'); + setLoading(true); + + exportEgg(Number(params.id)) + .then(setContent) + .catch(error => clearAndAddHttpError({ key: 'egg:export', error })) + .then(() => setLoading(false)); + }, [visible]); + + return ( + <> + { + setVisible(false); + }} + css={tw`relative`} + > + +

    Export Egg

    + + + {/**/} + +
    + + +
    +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx b/resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx new file mode 100644 index 000000000..3a9626c38 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggInstallContainer.tsx @@ -0,0 +1,110 @@ +import { useEggFromRoute } from '@/api/admin/egg'; +import updateEgg from '@/api/admin/eggs/updateEgg'; +import Field from '@/components/elements/Field'; +import useFlash from '@/plugins/useFlash'; +// import { shell } from '@codemirror/legacy-modes/mode/shell'; +import { faScroll } from '@fortawesome/free-solid-svg-icons'; +import { Form, Formik, FormikHelpers } from 'formik'; +import tw from 'twin.macro'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +// import Editor from '@/components/elements/Editor'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +interface Values { + scriptContainer: string; + scriptEntry: string; + scriptInstall: string; +} + +export default function EggInstallContainer() { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { data: egg } = useEggFromRoute(); + + if (!egg) { + return null; + } + + let fetchFileContent: (() => Promise) | null = null; + + const submit = async (values: Values, { setSubmitting }: FormikHelpers) => { + if (fetchFileContent === null) { + return; + } + + values.scriptInstall = await fetchFileContent(); + + clearFlashes('egg'); + + updateEgg(egg.id, values) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + +
    + + +
    + {/* {*/} + {/* fetchFileContent = value;*/} + {/* }}*/} + {/*/>*/} + +
    +
    + + + +
    +
    + +
    + +
    + +
    +
    + )} +
    + ); +} diff --git a/resources/scripts/components/admin/nests/eggs/EggRouter.tsx b/resources/scripts/components/admin/nests/eggs/EggRouter.tsx new file mode 100644 index 000000000..9bf84ac3c --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggRouter.tsx @@ -0,0 +1,90 @@ +import { useEffect } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import { useEggFromRoute } from '@/api/admin/egg'; +import EggInstallContainer from '@/components/admin/nests/eggs/EggInstallContainer'; +import EggVariablesContainer from '@/components/admin/nests/eggs/EggVariablesContainer'; +import useFlash from '@/plugins/useFlash'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import EggSettingsContainer from '@/components/admin/nests/eggs/EggSettingsContainer'; + +const EggRouter = () => { + const { id, nestId } = useParams<'nestId' | 'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: egg, error, isValidating, mutate } = useEggFromRoute(); + + useEffect(() => { + mutate(); + }, []); + + useEffect(() => { + if (!error) clearFlashes('egg'); + if (error) clearAndAddHttpError({ key: 'egg', error }); + }, [error]); + + if (!egg || (error && isValidating)) { + return ( + + + + ); + } + + return ( + +
    +
    +

    {egg.name}

    +

    + {egg.uuid} +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + } /> + } /> + } /> + +
    + ); +}; + +export default () => { + return ; +}; diff --git a/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx b/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx new file mode 100644 index 000000000..314b02cba --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx @@ -0,0 +1,245 @@ +import { useEggFromRoute } from '@/api/admin/egg'; +import updateEgg from '@/api/admin/eggs/updateEgg'; +import EggDeleteButton from '@/components/admin/nests/eggs/EggDeleteButton'; +import EggExportButton from '@/components/admin/nests/eggs/EggExportButton'; +import Button from '@/components/elements/Button'; +// import Editor from '@/components/elements/Editor'; +import Field, { TextareaField } from '@/components/elements/Field'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import useFlash from '@/plugins/useFlash'; +// import { jsonLanguage } from '@codemirror/lang-json'; +import { faDocker } from '@fortawesome/free-brands-svg-icons'; +import { faEgg, faFireAlt, faMicrochip, faTerminal } from '@fortawesome/free-solid-svg-icons'; +import { forwardRef, useImperativeHandle, useRef } from 'react'; +import AdminBox from '@/components/admin/AdminBox'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object } from 'yup'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; + +export function EggInformationContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + + + ); +} + +function EggDetailsContainer() { + const { data: egg } = useEggFromRoute(); + + if (!egg) { + return null; + } + + return ( + +
    + + +
    + +
    + + +
    +
    + ); +} + +export function EggStartupContainer({ className }: { className?: string }) { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + ); +} + +export function EggImageContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + ); +} + +export function EggLifecycleContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + + + + ); +} + +interface EggProcessContainerProps { + className?: string; +} + +export interface EggProcessContainerRef { + getStartupConfiguration: () => Promise; + getFilesConfiguration: () => Promise; +} + +export const EggProcessContainer = forwardRef(function EggProcessContainer( + { className }, + ref, +) { + // const { isSubmitting, values } = useFormikContext(); + const { isSubmitting } = useFormikContext(); + + let fetchStartupConfiguration: (() => Promise) | null = null; + let fetchFilesConfiguration: (() => Promise) | null = null; + + useImperativeHandle(ref, () => ({ + getStartupConfiguration: async () => { + if (fetchStartupConfiguration === null) { + return new Promise(resolve => resolve(null)); + } + return await fetchStartupConfiguration(); + }, + + getFilesConfiguration: async () => { + if (fetchFilesConfiguration === null) { + return new Promise(resolve => resolve(null)); + } + return await fetchFilesConfiguration(); + }, + })); + + return ( + + + +
    + + {/* {*/} + {/* fetchStartupConfiguration = value;*/} + {/* }}*/} + {/*/>*/} +
    + +
    + + {/* {*/} + {/* fetchFilesConfiguration = value;*/} + {/* }}*/} + {/*/>*/} +
    +
    + ); +}); + +interface Values { + name: string; + description: string; + startup: string; + dockerImages: string; + configStop: string; + configStartup: string; + configFiles: string; +} + +export default function EggSettingsContainer() { + const navigate = useNavigate(); + + const ref = useRef(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { data: egg } = useEggFromRoute(); + + if (!egg) { + return null; + } + + const submit = async (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('egg'); + + values.configStartup = (await ref.current?.getStartupConfiguration()) || ''; + values.configFiles = (await ref.current?.getFilesConfiguration()) || ''; + + updateEgg(egg.id, { + ...values, + // TODO + dockerImages: {}, + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    + + +
    + + + +
    + + +
    + + + +
    +
    + navigate('/admin/nests')} /> + + +
    +
    + + )} +
    + ); +} diff --git a/resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx b/resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx new file mode 100644 index 000000000..c6cbfe925 --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/EggVariablesContainer.tsx @@ -0,0 +1,218 @@ +import { TrashIcon } from '@heroicons/react/outline'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useFormikContext } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; +import { array, boolean, object, string } from 'yup'; + +import deleteEggVariable from '@/api/admin/eggs/deleteEggVariable'; +import updateEggVariables from '@/api/admin/eggs/updateEggVariables'; +import { NoItems } from '@/components/admin/AdminTable'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { EggVariable } from '@/api/admin/egg'; +import { useEggFromRoute } from '@/api/admin/egg'; +import NewVariableButton from '@/components/admin/nests/eggs/NewVariableButton'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Checkbox from '@/components/elements/Checkbox'; +import Field, { FieldRow, TextareaField } from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import useFlash from '@/plugins/useFlash'; + +export const validationSchema = object().shape({ + name: string().required().min(1).max(191), + description: string(), + environmentVariable: string().required().min(1).max(191), + defaultValue: string(), + isUserViewable: boolean().required(), + isUserEditable: boolean().required(), + rules: string().required(), +}); + +export function EggVariableForm({ prefix }: { prefix: string }) { + return ( + <> + + + + + + + + + + +
    + + + +
    + + + + ); +} + +function EggVariableDeleteButton({ onClick }: { onClick: (success: () => void) => void }) { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const onDelete = () => { + setLoading(true); + + onClick(() => { + //setLoading(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this variable? Deleting this variable will delete it from every server + using this egg. + + + + + ); +} + +function EggVariableBox({ + onDeleteClick, + variable, + prefix, +}: { + onDeleteClick: (success: () => void) => void; + variable: EggVariable; + prefix: string; +}) { + const { isSubmitting } = useFormikContext(); + + return ( + {variable.name}

    } + button={} + > + + + +
    + ); +} + +export default function EggVariablesContainer() { + const { clearAndAddHttpError } = useFlash(); + + const { data: egg, mutate } = useEggFromRoute(); + + if (!egg) { + return null; + } + + const submit = (values: EggVariable[], { setSubmitting }: FormikHelpers) => { + updateEggVariables(egg.id, values) + .then(async () => await mutate()) + .catch(error => clearAndAddHttpError({ key: 'egg', error })) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    + {egg.relationships.variables?.length === 0 ? ( + + ) : ( +
    + {egg.relationships.variables.map((v, i) => ( + { + deleteEggVariable(egg.id, v.id) + .then(async () => { + await mutate(egg => ({ + ...egg!, + relationships: { + ...egg!.relationships, + variables: egg!.relationships.variables!.filter( + v2 => v.id === v2.id, + ), + }, + })); + success(); + }) + .catch(error => clearAndAddHttpError({ key: 'egg', error })); + }} + /> + ))} +
    + )} + +
    +
    + + + +
    +
    +
    +
    + )} +
    + ); +} diff --git a/resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx b/resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx new file mode 100644 index 000000000..fd9057f3c --- /dev/null +++ b/resources/scripts/components/admin/nests/eggs/NewVariableButton.tsx @@ -0,0 +1,103 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useFormikContext } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import type { CreateEggVariable } from '@/api/admin/eggs/createEggVariable'; +import createEggVariable from '@/api/admin/eggs/createEggVariable'; +import { useEggFromRoute } from '@/api/admin/egg'; +import { EggVariableForm, validationSchema } from '@/components/admin/nests/eggs/EggVariablesContainer'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Button from '@/components/elements/Button'; +import useFlash from '@/plugins/useFlash'; + +export default function NewVariableButton() { + const { setValues } = useFormikContext(); + const [visible, setVisible] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { data: egg, mutate } = useEggFromRoute(); + + if (!egg) { + return null; + } + + const submit = (values: CreateEggVariable, { setSubmitting }: FormikHelpers) => { + clearFlashes('variable:create'); + + createEggVariable(egg.id, values) + .then(async variable => { + setValues([...egg.relationships.variables, variable]); + await mutate(egg => ({ + ...egg!, + relationships: { ...egg!.relationships, variables: [...egg!.relationships.variables, variable] }, + })); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'variable:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + {({ isSubmitting, isValid, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + + +

    New Variable

    + +
    + + +
    + + +
    + +
    + )} +
    + + + + ); +} diff --git a/resources/scripts/components/admin/nodes/DatabaseSelect.tsx b/resources/scripts/components/admin/nodes/DatabaseSelect.tsx new file mode 100644 index 000000000..e032ec16c --- /dev/null +++ b/resources/scripts/components/admin/nodes/DatabaseSelect.tsx @@ -0,0 +1,56 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import type { Database } from '@/api/admin/databases/getDatabases'; +import searchDatabases from '@/api/admin/databases/searchDatabases'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; + +export default ({ selected }: { selected: Database | null }) => { + const context = useFormikContext(); + + const [database, setDatabase] = useState(selected); + const [databases, setDatabases] = useState(null); + + const onSearch = (query: string): Promise => { + return new Promise((resolve, reject) => { + searchDatabases({ name: query }) + .then(databases => { + setDatabases(databases); + return resolve(); + }) + .catch(reject); + }); + }; + + const onSelect = (database: Database | null) => { + setDatabase(database); + context.setFieldValue('databaseHostId', database?.id || null); + }; + + const getSelectedText = (database: Database | null): string | undefined => { + return database?.name; + }; + + return ( + + {databases?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/nodes/LocationSelect.tsx b/resources/scripts/components/admin/nodes/LocationSelect.tsx new file mode 100644 index 000000000..0bf38f46e --- /dev/null +++ b/resources/scripts/components/admin/nodes/LocationSelect.tsx @@ -0,0 +1,56 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import type { Location } from '@/api/admin/locations/getLocations'; +import searchLocations from '@/api/admin/locations/searchLocations'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; + +export default ({ selected }: { selected: Location | null }) => { + const context = useFormikContext(); + + const [location, setLocation] = useState(selected); + const [locations, setLocations] = useState(null); + + const onSearch = (query: string): Promise => { + return new Promise((resolve, reject) => { + searchLocations({ short: query }) + .then(locations => { + setLocations(locations); + return resolve(); + }) + .catch(reject); + }); + }; + + const onSelect = (location: Location | null) => { + setLocation(location); + context.setFieldValue('locationId', location?.id || null); + }; + + const getSelectedText = (location: Location | null): string | undefined => { + return location?.short; + }; + + return ( + + {locations?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NewNodeContainer.tsx b/resources/scripts/components/admin/nodes/NewNodeContainer.tsx new file mode 100644 index 000000000..e98ba4bd8 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NewNodeContainer.tsx @@ -0,0 +1,127 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { number, object, string } from 'yup'; + +import type { Values } from '@/api/admin/nodes/createNode'; +import createNode from '@/api/admin/nodes/createNode'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import NodeLimitContainer from '@/components/admin/nodes/NodeLimitContainer'; +import NodeListenContainer from '@/components/admin/nodes/NodeListenContainer'; +import NodeSettingsContainer from '@/components/admin/nodes/NodeSettingsContainer'; +import Button from '@/components/elements/Button'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +type Values2 = Omit, 'public'> & { behindProxy: string; public: string }; + +const initialValues: Values2 = { + name: '', + locationId: 0, + databaseHostId: null, + fqdn: '', + scheme: 'https', + behindProxy: 'false', + public: 'true', + daemonBase: '/var/lib/pterodactyl/volumes', + + listenPortHTTP: 8080, + publicPortHTTP: 8080, + listenPortSFTP: 2022, + publicPortSFTP: 2022, + + memory: 0, + memoryOverallocate: 0, + disk: 0, + diskOverallocate: 0, +}; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = (values2: Values2, { setSubmitting }: FormikHelpers) => { + clearFlashes('node:create'); + + const values: Values = { + ...values2, + behindProxy: values2.behindProxy === 'true', + public: values2.public === 'true', + }; + + createNode(values) + .then(node => navigate(`/admin/nodes/${node.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Node

    +

    + Add a new node to the panel. +

    +
    +
    + + + + + {({ isSubmitting, isValid }) => ( +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    + )} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx b/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx new file mode 100644 index 000000000..bda7c8d77 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeAboutContainer.tsx @@ -0,0 +1,96 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { ReactNode } from 'react'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + +import type { NodeInformation } from '@/api/admin/nodes/getNodeInformation'; +import getNodeInformation from '@/api/admin/nodes/getNodeInformation'; +import AdminBox from '@/components/admin/AdminBox'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import { Context } from '@/components/admin/nodes/NodeRouter'; +import type { ApplicationStore } from '@/state'; + +const Code = ({ className, children }: { className?: string; children: ReactNode }) => { + return ( + + {children} + + ); +}; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const [loading, setLoading] = useState(true); + const [info, setInfo] = useState(null); + + const node = Context.useStoreState(state => state.node); + + if (node === undefined) { + return <>; + } + + useEffect(() => { + clearFlashes('node'); + + getNodeInformation(node.id) + .then(info => setInfo(info)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading) { + return ( + + + + ); + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + + +
    Wings Version + {info?.version} +
    Operating System + {info?.system.type} +
    Architecture + {info?.system.arch} +
    Kernel + {info?.system.release} +
    CPU Threads + {info?.system.cpus} +
    + + {/* TODO: Description code-block with edit option */} +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx b/resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx new file mode 100644 index 000000000..b8c47e3d4 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeAllocationContainer.tsx @@ -0,0 +1,27 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import { useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import AllocationTable from '@/components/admin/nodes/allocations/AllocationTable'; +import CreateAllocationForm from '@/components/admin/nodes/allocations/CreateAllocationForm'; + +export default () => { + const params = useParams<'id'>(); + + return ( + <> +
    +
    + +
    + +
    + + + +
    +
    + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx b/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx new file mode 100644 index 000000000..e8ca1d978 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeConfigurationContainer.tsx @@ -0,0 +1,70 @@ +import { faCode, faDragon } from '@fortawesome/free-solid-svg-icons'; +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + +import getNodeConfiguration from '@/api/admin/nodes/getNodeConfiguration'; +import AdminBox from '@/components/admin/AdminBox'; +import { Context } from '@/components/admin/nodes/NodeRouter'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const [configuration, setConfiguration] = useState(''); + + const node = Context.useStoreState(state => state.node); + + if (node === undefined) { + return <>; + } + + useEffect(() => { + clearFlashes('node'); + + getNodeConfiguration(node.id) + .then(configuration => setConfiguration(configuration)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }); + }, []); + + return ( + <> + +
    +
    + + + + + +
    +
    +                        {configuration}
    +                    
    +
    +
    + + + Never™ + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeDeleteButton.tsx b/resources/scripts/components/admin/nodes/NodeDeleteButton.tsx new file mode 100644 index 000000000..aafad3ef2 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteNode from '@/api/admin/nodes/deleteNode'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + nodeId: number; + onDeleted: () => void; +} + +export default ({ nodeId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('node'); + + deleteNode(nodeId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this node? + + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeEditContainer.tsx b/resources/scripts/components/admin/nodes/NodeEditContainer.tsx new file mode 100644 index 000000000..dc7280fef --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeEditContainer.tsx @@ -0,0 +1,134 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { number, object, string } from 'yup'; + +import updateNode from '@/api/admin/nodes/updateNode'; +import NodeDeleteButton from '@/components/admin/nodes/NodeDeleteButton'; +import NodeLimitContainer from '@/components/admin/nodes/NodeLimitContainer'; +import NodeListenContainer from '@/components/admin/nodes/NodeListenContainer'; +import { Context } from '@/components/admin/nodes/NodeRouter'; +import NodeSettingsContainer from '@/components/admin/nodes/NodeSettingsContainer'; +import Button from '@/components/elements/Button'; +import type { ApplicationStore } from '@/state'; + +interface Values { + name: string; + locationId: number; + databaseHostId: number | null; + fqdn: string; + scheme: string; + behindProxy: string; // Yes, this is technically a boolean. + public: string; // Yes, this is technically a boolean. + daemonBase: string; // This value cannot be updated once a node has been created. + + memory: number; + memoryOverallocate: number; + disk: number; + diskOverallocate: number; + + listenPortHTTP: number; + publicPortHTTP: number; + listenPortSFTP: number; + publicPortSFTP: number; +} + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const node = Context.useStoreState(state => state.node); + const setNode = Context.useStoreActions(actions => actions.setNode); + + if (node === undefined) { + return <>; + } + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('node'); + + const v = { ...values, behindProxy: values.behindProxy === 'true', public: values.public === 'true' }; + + updateNode(node.id, v) + .then(() => setNode({ ...node, ...v })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    +
    + navigate('/admin/nodes')} /> + +
    +
    +
    +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx b/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx new file mode 100644 index 000000000..4ef5c3a1f --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeLimitContainer.tsx @@ -0,0 +1,47 @@ +import { faMicrochip } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeListenContainer.tsx b/resources/scripts/components/admin/nodes/NodeListenContainer.tsx new file mode 100644 index 000000000..7c1baa27d --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeListenContainer.tsx @@ -0,0 +1,37 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeRouter.tsx b/resources/scripts/components/admin/nodes/NodeRouter.tsx new file mode 100644 index 000000000..a3ee6b2d9 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeRouter.tsx @@ -0,0 +1,146 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import { useEffect, useState } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Node } from '@/api/admin/nodes/getNodes'; +import getNode from '@/api/admin/nodes/getNode'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import NodeEditContainer from '@/components/admin/nodes/NodeEditContainer'; +import Spinner from '@/components/elements/Spinner'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import NodeAboutContainer from '@/components/admin/nodes/NodeAboutContainer'; +import NodeConfigurationContainer from '@/components/admin/nodes/NodeConfigurationContainer'; +import NodeAllocationContainer from '@/components/admin/nodes/NodeAllocationContainer'; +import NodeServers from '@/components/admin/nodes/NodeServers'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + node: Node | undefined; + setNode: Action; +} + +export const Context = createContextStore({ + node: undefined, + + setNode: action((state, payload) => { + state.node = payload; + }), +}); + +const NodeRouter = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const node = Context.useStoreState(state => state.node); + const setNode = Context.useStoreActions(actions => actions.setNode); + + useEffect(() => { + clearFlashes('node'); + + getNode(Number(params.id), ['database_host', 'location']) + .then(node => setNode(node)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'node', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || node === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {node.name}

    +

    + {node.uuid} +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } /> + } /> + } /> + } /> + } /> + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeServers.tsx b/resources/scripts/components/admin/nodes/NodeServers.tsx new file mode 100644 index 000000000..7b11274dd --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeServers.tsx @@ -0,0 +1,10 @@ +import { Context } from '@/components/admin/nodes/NodeRouter'; +import ServersTable from '@/components/admin/servers/ServersTable'; + +function NodeServers() { + const node = Context.useStoreState(state => state.node); + + return ; +} + +export default NodeServers; diff --git a/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx new file mode 100644 index 000000000..6dfa443c5 --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx @@ -0,0 +1,95 @@ +import { faDatabase } from '@fortawesome/free-solid-svg-icons'; +import { Field as FormikField, useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import type { Node } from '@/api/admin/nodes/getNodes'; +import AdminBox from '@/components/admin/AdminBox'; +import DatabaseSelect from '@/components/admin/nodes/DatabaseSelect'; +import LocationSelect from '@/components/admin/nodes/LocationSelect'; +import Label from '@/components/elements/Label'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; + +export default function NodeSettingsContainer({ node }: { node?: Node }) { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + +
    + + + +
    +
    + +
    + + +
    + + + +
    +
    + +
    + + +
    + + + +
    +
    +
    + ); +} diff --git a/resources/scripts/components/admin/nodes/NodesContainer.tsx b/resources/scripts/components/admin/nodes/NodesContainer.tsx new file mode 100644 index 000000000..2ad97ad7f --- /dev/null +++ b/resources/scripts/components/admin/nodes/NodesContainer.tsx @@ -0,0 +1,271 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import type { Filters } from '@/api/admin/servers/getServers'; +import getNodes, { Context as NodesContext } from '@/api/admin/nodes/getNodes'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import Button from '@/components/elements/Button'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import { bytesToString, mbToBytes } from '@/lib/formatters'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.nodes.selectedNodes.indexOf(id) >= 0); + const appendSelectedNode = AdminContext.useStoreActions(actions => actions.nodes.appendSelectedNode); + const removeSelectedNode = AdminContext.useStoreActions(actions => actions.nodes.removeSelectedNode); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedNode(id); + } else { + removeSelectedNode(id); + } + }} + /> + ); +}; + +const NodesContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(NodesContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: nodes, error, isValidating } = getNodes(['location']); + + useEffect(() => { + if (!error) { + clearFlashes('nodes'); + return; + } + + clearAndAddHttpError({ key: 'nodes', error }); + }, [error]); + + const length = nodes?.items?.length || 0; + + const setSelectedNodes = AdminContext.useStoreActions(actions => actions.nodes.setSelectedNodes); + const selectedNodesLength = AdminContext.useStoreState(state => state.nodes.selectedNodes.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedNodes(e.currentTarget.checked ? nodes?.items?.map(node => node.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedNodes([]); + }, [page]); + + return ( + +
    +
    +

    Nodes

    +

    + All nodes available on the system. +

    +
    + +
    + + + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + setSort('location_id')} + /> + setSort('fqdn')} + /> + setSort('memory')} + /> + setSort('disk')} + /> + + + + + + {nodes !== undefined && + !error && + !isValidating && + length > 0 && + nodes.items.map(node => ( + + + + + + + + {/* TODO: Have permission check for displaying location information. */} + + + + + + + + + + + + ))} + +
    + + + + + {node.id} + + + + + {node.name} + + + +
    + {node.relations.location?.short} +
    + +
    + {node.relations.location?.long} +
    +
    +
    + + + {node.fqdn} + + + + {bytesToString(mbToBytes(node.memory))} + + {bytesToString(mbToBytes(node.disk))} + + {node.scheme === 'https' ? ( + + Secure + + ) : ( + + Non-Secure + + )} + + {/* TODO: Change color based off of online/offline status */} + + + +
    + + {nodes === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx b/resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx new file mode 100644 index 000000000..6ada3e9ec --- /dev/null +++ b/resources/scripts/components/admin/nodes/allocations/AllocationTable.tsx @@ -0,0 +1,216 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/nodes/allocations/getAllocations'; +import getAllocations, { Context as AllocationsContext } from '@/api/admin/nodes/allocations/getAllocations'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + ContentWrapper, + Loading, + NoItems, + Pagination, + TableBody, + TableHead, + TableHeader, + useTableHooks, +} from '@/components/admin/AdminTable'; +import DeleteAllocationButton from '@/components/admin/nodes/allocations/DeleteAllocationButton'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import useFlash from '@/plugins/useFlash'; +import { AdminContext } from '@/state/admin'; + +function RowCheckbox({ id }: { id: number }) { + const isChecked = AdminContext.useStoreState(state => state.allocations.selectedAllocations.indexOf(id) >= 0); + const appendSelectedAllocation = AdminContext.useStoreActions( + actions => actions.allocations.appendSelectedAllocation, + ); + const removeSelectedAllocation = AdminContext.useStoreActions( + actions => actions.allocations.removeSelectedAllocation, + ); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedAllocation(id); + } else { + removeSelectedAllocation(id); + } + }} + /> + ); +} + +interface Props { + nodeId: number; + filters?: Filters; +} + +function AllocationsTable({ nodeId, filters }: Props) { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(AllocationsContext); + const { data: allocations, error, isValidating, mutate } = getAllocations(nodeId, ['server']); + + const length = allocations?.items?.length || 0; + + const setSelectedAllocations = AdminContext.useStoreActions(actions => actions.allocations.setSelectedAllocations); + const selectedAllocationLength = AdminContext.useStoreState(state => state.allocations.selectedAllocations.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedAllocations( + e.currentTarget.checked ? allocations?.items?.map?.(allocation => allocation.id) || [] : [], + ); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(filters || null); + } else { + setFilters({ ...filters, ip: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedAllocations([]); + }, [page]); + + useEffect(() => { + if (!error) { + clearFlashes('allocations'); + return; + } + + clearAndAddHttpError({ key: 'allocations', error }); + }, [error]); + + return ( + + + +
    + + + setSort('ip')} + /> + + setSort('port')} + /> + + + + + + {allocations !== undefined && + !error && + !isValidating && + length > 0 && + allocations.items.map(allocation => ( + + + + + + {allocation.alias !== null ? ( + + ) : ( + + + {allocation.relations.server !== undefined ? ( + + ) : ( + + + ))} + +
    + + + + + {allocation.ip} + + + + + + {allocation.alias} + + + + )} + + + + + {allocation.port} + + + + + {allocation.relations.server.name} + + + )} + + + { + await mutate(allocations => ({ + pagination: allocations!.pagination, + items: allocations!.items.filter( + a => a.id === allocation.id, + ), + })); + + // Go back a page if no more items will exist on the current page. + if (allocations?.items.length - (1 % 10) === 0) { + setPage(p => p - 1); + } + }} + /> +
    + + {allocations === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    + ); +} + +export default (props: Props) => { + const hooks = useTableHooks(props.filters); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx b/resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx new file mode 100644 index 000000000..4c2df4ba6 --- /dev/null +++ b/resources/scripts/components/admin/nodes/allocations/CreateAllocationForm.tsx @@ -0,0 +1,118 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; +import { array, number, object, string } from 'yup'; + +import createAllocation from '@/api/admin/nodes/allocations/createAllocation'; +import getAllocations from '@/api/admin/nodes/getAllocations'; +import getAllocations2 from '@/api/admin/nodes/allocations/getAllocations'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import type { Option } from '@/components/elements/SelectField'; +import SelectField from '@/components/elements/SelectField'; + +interface Values { + ips: string[]; + ports: number[]; + alias: string; +} + +const distinct = (value: any, index: any, self: any) => { + return self.indexOf(value) === index; +}; + +function CreateAllocationForm({ nodeId }: { nodeId: number }) { + const [ips, setIPs] = useState([]); + const [ports] = useState([]); + + const { mutate } = getAllocations2(nodeId, ['server']); + + useEffect(() => { + getAllocations(nodeId).then(allocations => { + setIPs( + allocations + .map(a => a.ip) + .filter(distinct) + .map(ip => { + return { value: ip, label: ip }; + }), + ); + }); + }, [nodeId]); + + const isValidIP = (inputValue: string): boolean => { + // TODO: Better way of checking for a valid ip (and CIDR) + return inputValue.match(/^([0-9a-f.:/]+)$/) !== null; + }; + + const isValidPort = (inputValue: string): boolean => { + // TODO: Better way of checking for a valid port (and port range) + return inputValue.match(/^([0-9-]+)$/) !== null; + }; + + const submit = ({ ips, ports, alias }: Values, { setSubmitting }: FormikHelpers) => { + setSubmitting(false); + + ips.forEach(async ip => { + const allocations = await createAllocation(nodeId, { ip, ports, alias }, ['server']); + await mutate(data => ({ ...data!, items: { ...data!.items!, ...allocations } })); + }); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    + + + + +
    + +
    + +
    +
    + +
    +
    + + )} +
    + ); +} + +export default CreateAllocationForm; diff --git a/resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx b/resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx new file mode 100644 index 000000000..2096303fd --- /dev/null +++ b/resources/scripts/components/admin/nodes/allocations/DeleteAllocationButton.tsx @@ -0,0 +1,77 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import deleteAllocation from '@/api/admin/nodes/allocations/deleteAllocation'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + nodeId: number; + allocationId: number; + onDeleted?: () => void; +} + +export default ({ nodeId, allocationId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('allocation'); + + deleteAllocation(nodeId, allocationId) + .then(() => { + setLoading(false); + setVisible(false); + if (onDeleted !== undefined) { + onDeleted(); + } + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'allocation', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this allocation? + + + + + ); +}; diff --git a/resources/scripts/components/admin/overview/OverviewContainer.tsx b/resources/scripts/components/admin/overview/OverviewContainer.tsx new file mode 100644 index 000000000..92850a0f5 --- /dev/null +++ b/resources/scripts/components/admin/overview/OverviewContainer.tsx @@ -0,0 +1,103 @@ +import type { ReactNode } from 'react'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; + +import type { VersionData } from '@/api/admin/getVersion'; +import getVersion from '@/api/admin/getVersion'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Spinner from '@/components/elements/Spinner'; +import useFlash from '@/plugins/useFlash'; + +const Code = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); +}; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const [loading, setLoading] = useState(true); + const [versionData, setVersionData] = useState(undefined); + + useEffect(() => { + clearFlashes('overview'); + + getVersion() + .then(versionData => setVersionData(versionData)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'overview', error }); + }) + .then(() => setLoading(false)); + }, []); + + return ( + +
    +
    +

    Overview

    +

    + A quick glance at your system. +

    +
    +
    + + + +
    + {loading ? ( +
    + +
    + ) : ( +
    +
    +

    + + + + System Information +

    +
    + +
    + {versionData?.panel.current === 'canary' ? ( +

    + I hope you enjoy living on the edge because you are running a{' '} + {versionData?.panel.current} version of Pterodactyl. +

    + ) : versionData?.panel.latest === versionData?.panel.current ? ( +

    + Your panel is up-to-date. The latest version + is {versionData?.panel.latest} and you are running version{' '} + {versionData?.panel.current}. +

    + ) : ( +

    + Your panel is not up-to-date. The latest + version is {versionData?.panel.latest} and you are running version{' '} + {versionData?.panel.current}. +

    + )} +
    +
    + )} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/roles/NewRoleButton.tsx b/resources/scripts/components/admin/roles/NewRoleButton.tsx new file mode 100644 index 000000000..210b84492 --- /dev/null +++ b/resources/scripts/components/admin/roles/NewRoleButton.tsx @@ -0,0 +1,107 @@ +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useState } from 'react'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import { getRoles, createRole } from '@/api/admin/roles'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Modal from '@/components/elements/Modal'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + name: string; + description: string; +} + +const schema = object().shape({ + name: string().required('A role name must be provided.').max(32, 'Role name must not exceed 32 characters.'), + description: string().max(255, 'Role description must not exceed 255 characters.'), +}); + +export default () => { + const [visible, setVisible] = useState(false); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { mutate } = getRoles(); + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('role:create'); + setSubmitting(true); + + createRole(name, description) + .then(async role => { + await mutate(data => ({ ...data!, items: data!.items.concat(role) }), false); + setVisible(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'role:create', error }); + setSubmitting(false); + }); + }; + + return ( + <> + + {({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + +

    New Role

    +
    + + +
    + +
    + +
    + + +
    + +
    + )} +
    + + + + ); +}; diff --git a/resources/scripts/components/admin/roles/RoleDeleteButton.tsx b/resources/scripts/components/admin/roles/RoleDeleteButton.tsx new file mode 100644 index 000000000..e312823c8 --- /dev/null +++ b/resources/scripts/components/admin/roles/RoleDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import { deleteRole } from '@/api/admin/roles'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + roleId: number; + onDeleted: () => void; +} + +export default ({ roleId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('role'); + + deleteRole(roleId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'role', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this role? + + + + + ); +}; diff --git a/resources/scripts/components/admin/roles/RoleEditContainer.tsx b/resources/scripts/components/admin/roles/RoleEditContainer.tsx new file mode 100644 index 000000000..dc3a135d2 --- /dev/null +++ b/resources/scripts/components/admin/roles/RoleEditContainer.tsx @@ -0,0 +1,176 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object, string } from 'yup'; + +import { getRole, updateRole } from '@/api/admin/roles'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminBox from '@/components/admin/AdminBox'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import RoleDeleteButton from '@/components/admin/roles/RoleDeleteButton'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Spinner from '@/components/elements/Spinner'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import type { UserRole } from '@definitions/admin'; +import type { ApplicationStore } from '@/state'; + +interface ctx { + role: UserRole | undefined; + setRole: Action; +} + +export const Context = createContextStore({ + role: undefined, + + setRole: action((state, payload) => { + state.role = payload; + }), +}); + +interface Values { + name: string; + description: string; +} + +const EditInformationContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const role = Context.useStoreState(state => state.role); + const setRole = Context.useStoreActions(actions => actions.setRole); + + if (role === undefined) { + return <>; + } + + const submit = ({ name, description }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('role'); + + updateRole(role.id, name, description) + .then(() => setRole({ ...role, name, description })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'role', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    +
    + +
    + +
    + +
    + +
    +
    + navigate('/admin/roles')} /> +
    + +
    + +
    +
    +
    +
    + + )} +
    + ); +}; + +const RoleEditContainer = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const role = Context.useStoreState(state => state.role); + const setRole = Context.useStoreActions(actions => actions.setRole); + + useEffect(() => { + clearFlashes('role'); + + getRole(Number(params.id)) + .then(role => setRole(role)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'role', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || role === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {role.name}

    + {(role.description || '').length < 1 ? ( +

    + No description +

    + ) : ( +

    + {role.description} +

    + )} +
    +
    + + + + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/roles/RolesContainer.tsx b/resources/scripts/components/admin/roles/RolesContainer.tsx new file mode 100644 index 000000000..7563edd67 --- /dev/null +++ b/resources/scripts/components/admin/roles/RolesContainer.tsx @@ -0,0 +1,182 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/roles'; +import { getRoles, Context as RolesContext } from '@/api/admin/roles'; +import { AdminContext } from '@/state/admin'; +import NewRoleButton from '@/components/admin/roles/NewRoleButton'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + TableBody, + TableHead, + TableHeader, + TableRow, + Pagination, + Loading, + NoItems, + ContentWrapper, + useTableHooks, +} from '@/components/admin/AdminTable'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import useFlash from '@/plugins/useFlash'; + +const RowCheckbox = ({ id }: { id: number }) => { + const isChecked = AdminContext.useStoreState(state => state.roles.selectedRoles.indexOf(id) >= 0); + const appendSelectedRole = AdminContext.useStoreActions(actions => actions.roles.appendSelectedRole); + const removeSelectedRole = AdminContext.useStoreActions(actions => actions.roles.removeSelectedRole); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedRole(id); + } else { + removeSelectedRole(id); + } + }} + /> + ); +}; + +const RolesContainer = () => { + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(RolesContext); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: roles, error, isValidating } = getRoles(); + + useEffect(() => { + if (!error) { + clearFlashes('roles'); + return; + } + + clearAndAddHttpError({ key: 'roles', error }); + }, [error]); + + const length = roles?.items?.length || 0; + + const setSelectedRoles = AdminContext.useStoreActions(actions => actions.roles.setSelectedRoles); + const selectedRolesLength = AdminContext.useStoreState(state => state.roles.selectedRoles.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedRoles(e.currentTarget.checked ? roles?.items?.map(role => role.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(null); + } else { + setFilters({ name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedRoles([]); + }, [page]); + + return ( + +
    +
    +

    Roles

    +

    + Soon™ +

    +
    + +
    + +
    +
    + + + + + + +
    + + + setSort('id')} + /> + setSort('name')} + /> + + + + + {roles !== undefined && + !error && + !isValidating && + length > 0 && + roles.items.map(role => ( + + + + + + + + + + ))} + +
    + + + + + {role.id} + + + + + {role.name} + + + {role.description} +
    + + {roles === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    +
    + ); +}; + +export default () => { + const hooks = useTableHooks(); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/EggSelect.tsx b/resources/scripts/components/admin/servers/EggSelect.tsx new file mode 100644 index 000000000..c6b788274 --- /dev/null +++ b/resources/scripts/components/admin/servers/EggSelect.tsx @@ -0,0 +1,75 @@ +import { useField } from 'formik'; +import type { ChangeEvent } from 'react'; +import { useEffect, useState } from 'react'; + +import type { WithRelationships } from '@/api/admin'; +import type { Egg } from '@/api/admin/egg'; +import { searchEggs } from '@/api/admin/egg'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; + +interface Props { + nestId?: number; + selectedEggId?: number; + onEggSelect: (egg: Egg | null) => void; +} + +export default ({ nestId, selectedEggId, onEggSelect }: Props) => { + const [, , { setValue, setTouched }] = useField>('environment'); + const [eggs, setEggs] = useState[] | null>(null); + + const selectEgg = (egg: Egg | null) => { + if (egg === null) { + onEggSelect(null); + return; + } + + // Clear values + setValue({}); + setTouched(true); + + onEggSelect(egg); + + const values: Record = {}; + egg.relationships.variables?.forEach(v => { + values[v.environmentVariable] = v.defaultValue; + }); + setValue(values); + setTouched(true); + }; + + useEffect(() => { + if (!nestId) { + setEggs(null); + return; + } + + searchEggs(nestId, {}) + .then(eggs => { + setEggs(eggs); + selectEgg(eggs[0] || null); + }) + .catch(error => console.error(error)); + }, [nestId]); + + const onSelectChange = (e: ChangeEvent) => { + selectEgg(eggs?.find(egg => egg.id.toString() === e.currentTarget.value) || null); + }; + + return ( + <> + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/NestSelector.tsx b/resources/scripts/components/admin/servers/NestSelector.tsx new file mode 100644 index 000000000..762bf39a5 --- /dev/null +++ b/resources/scripts/components/admin/servers/NestSelector.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from 'react'; + +import type { Nest } from '@/api/admin/nest'; +import { searchNests } from '@/api/admin/nest'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; + +interface Props { + selectedNestId?: number; + onNestSelect: (nest: number) => void; +} + +export default ({ selectedNestId, onNestSelect }: Props) => { + const [nests, setNests] = useState(null); + + useEffect(() => { + searchNests({}) + .then(nests => { + setNests(nests); + if (selectedNestId === 0 && nests.length > 0) { + // @ts-expect-error go away + onNestSelect(nests[0].id); + } + }) + .catch(error => console.error(error)); + }, []); + + return ( + <> + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/NewServerContainer.tsx b/resources/scripts/components/admin/servers/NewServerContainer.tsx new file mode 100644 index 000000000..1970d4806 --- /dev/null +++ b/resources/scripts/components/admin/servers/NewServerContainer.tsx @@ -0,0 +1,225 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useFormikContext } from 'formik'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import type { Egg } from '@/api/admin/egg'; +import type { CreateServerRequest } from '@/api/admin/servers/createServer'; +import createServer from '@/api/admin/servers/createServer'; +import type { Allocation, Node } from '@/api/admin/node'; +import { getAllocations } from '@/api/admin/node'; +import AdminBox from '@/components/admin/AdminBox'; +import NodeSelect from '@/components/admin/servers/NodeSelect'; +import { + ServerImageContainer, + ServerServiceContainer, + ServerVariableContainer, +} from '@/components/admin/servers/ServerStartupContainer'; +import BaseSettingsBox from '@/components/admin/servers/settings/BaseSettingsBox'; +import FeatureLimitsBox from '@/components/admin/servers/settings/FeatureLimitsBox'; +import ServerResourceBox from '@/components/admin/servers/settings/ServerResourceBox'; +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import FormikSwitch from '@/components/elements/FormikSwitch'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; + +function InternalForm() { + const { + isSubmitting, + isValid, + setFieldValue, + values: { environment }, + } = useFormikContext(); + + const [egg, setEgg] = useState(null); + const [node, setNode] = useState(null); + const [allocations, setAllocations] = useState(null); + + useEffect(() => { + if (egg === null) { + return; + } + + setFieldValue('eggId', egg.id); + setFieldValue('startup', ''); + setFieldValue('image', egg.dockerImages.length > 0 ? egg.dockerImages[0] : ''); + }, [egg]); + + useEffect(() => { + if (node === null) { + return; + } + + // server_id: 0 filters out assigned allocations + getAllocations(node.id, { filters: { server_id: '0' } }).then(setAllocations); + }, [node]); + + return ( +
    +
    +
    + + +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    + {/*
    */} + {/* /!* TODO: Multi-select *!/*/} + {/* */} + {/* */} + {/*
    */} +
    +
    + + +
    + + + + + + + +
    + {/* This ensures that no variables are rendered unless the environment has a value for the variable. */} + {egg?.relationships.variables + ?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined) + .map((v, i) => ( + + ))} +
    + +
    +
    + +
    +
    +
    +
    + ); +} + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const submit = (r: CreateServerRequest, { setSubmitting }: FormikHelpers) => { + clearFlashes('server:create'); + + createServer(r) + .then(s => navigate(`/admin/servers/${s.id}`)) + .catch(error => clearAndAddHttpError({ key: 'server:create', error })) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New Server

    +

    + Add a new server to the panel. +

    +
    +
    + + + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/NodeSelect.tsx b/resources/scripts/components/admin/servers/NodeSelect.tsx new file mode 100644 index 000000000..bd1e3a675 --- /dev/null +++ b/resources/scripts/components/admin/servers/NodeSelect.tsx @@ -0,0 +1,46 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import type { Node } from '@/api/admin/node'; +import { searchNodes } from '@/api/admin/node'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; + +export default ({ node, setNode }: { node: Node | null; setNode: (_: Node | null) => void }) => { + const { setFieldValue } = useFormikContext(); + + const [nodes, setNodes] = useState(null); + + const onSearch = async (query: string) => { + setNodes(await searchNodes({ filters: { name: query } })); + }; + + const onSelect = (node: Node | null) => { + setNode(node); + setFieldValue('nodeId', node?.id || null); + }; + + const getSelectedText = (node: Node | null): string => node?.name || ''; + + return ( + + {nodes?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/servers/OwnerSelect.tsx b/resources/scripts/components/admin/servers/OwnerSelect.tsx new file mode 100644 index 000000000..25de1133a --- /dev/null +++ b/resources/scripts/components/admin/servers/OwnerSelect.tsx @@ -0,0 +1,47 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import { searchUserAccounts } from '@/api/admin/users'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; +import type { User } from '@definitions/admin'; + +export default ({ selected }: { selected?: User }) => { + const { setFieldValue } = useFormikContext(); + + const [user, setUser] = useState(selected || null); + const [users, setUsers] = useState(null); + + const onSearch = async (query: string) => { + setUsers(await searchUserAccounts({ filters: { username: query, email: query } })); + }; + + const onSelect = (user: User | null) => { + setUser(user); + setFieldValue('ownerId', user?.id || null); + }; + + const getSelectedText = (user: User | null): string => user?.email || ''; + + return ( + + {users?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerDeleteButton.tsx b/resources/scripts/components/admin/servers/ServerDeleteButton.tsx new file mode 100644 index 000000000..27acf2e2c --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerDeleteButton.tsx @@ -0,0 +1,66 @@ +import { TrashIcon } from '@heroicons/react/outline'; +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import deleteServer from '@/api/admin/servers/deleteServer'; +import { useServerFromRoute } from '@/api/admin/server'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + const { data: server } = useServerFromRoute(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + if (!server) return; + + setLoading(true); + clearFlashes('server'); + + deleteServer(server.id) + .then(() => navigate('/admin/servers')) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'server', error }); + + setLoading(false); + setVisible(false); + }); + }; + + if (!server) return null; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this server? + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerManageContainer.tsx b/resources/scripts/components/admin/servers/ServerManageContainer.tsx new file mode 100644 index 000000000..1fda0d5de --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerManageContainer.tsx @@ -0,0 +1,60 @@ +import tw from 'twin.macro'; + +import { useServerFromRoute } from '@/api/admin/server'; +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; + +export default () => { + const { data: server } = useServerFromRoute(); + + if (!server) return null; + + return ( +
    +
    + +
    +
    + + + +
    +

    Danger! This could overwrite server data.

    +
    + +

    + This will reinstall the server with the assigned service scripts. +

    +
    +
    +
    + + +

    + If you need to change the install status from uninstalled to installed, or vice versa, you may + do so with the button below. +

    +
    +
    +
    + + +

    + This will suspend the server, stop any running processes, and immediately block the user from + being able to access their files or otherwise manage the server through the panel or API. +

    +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerRouter.tsx b/resources/scripts/components/admin/servers/ServerRouter.tsx new file mode 100644 index 000000000..f21fc3795 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerRouter.tsx @@ -0,0 +1,76 @@ +import { useEffect } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import ServerManageContainer from '@/components/admin/servers/ServerManageContainer'; +import ServerStartupContainer from '@/components/admin/servers/ServerStartupContainer'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import ServerSettingsContainer from '@/components/admin/servers/ServerSettingsContainer'; +import useFlash from '@/plugins/useFlash'; +import { useServerFromRoute } from '@/api/admin/server'; +import { AdjustmentsIcon, CogIcon, DatabaseIcon, FolderIcon, ShieldExclamationIcon } from '@heroicons/react/outline'; + +export default () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { data: server, error, isValidating, mutate } = useServerFromRoute(); + + useEffect(() => { + mutate(); + }, []); + + useEffect(() => { + if (!error) clearFlashes('server'); + if (error) clearAndAddHttpError({ key: 'server', error }); + }, [error]); + + if (!server || (error && isValidating)) { + return ( + + + + ); + } + + return ( + + +
    +
    +

    {server.name}

    +

    + {server.uuid} +

    +
    +
    + + + + + + + + + + + + + } /> + } /> + } /> + +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx b/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx new file mode 100644 index 000000000..db094f884 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx @@ -0,0 +1,103 @@ +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import { useServerFromRoute } from '@/api/admin/server'; +import type { Values } from '@/api/admin/servers/updateServer'; +import updateServer from '@/api/admin/servers/updateServer'; +import ServerDeleteButton from '@/components/admin/servers/ServerDeleteButton'; +import BaseSettingsBox from '@/components/admin/servers/settings/BaseSettingsBox'; +import FeatureLimitsBox from '@/components/admin/servers/settings/FeatureLimitsBox'; +import NetworkingBox from '@/components/admin/servers/settings/NetworkingBox'; +import ServerResourceBox from '@/components/admin/servers/settings/ServerResourceBox'; +import Button from '@/components/elements/Button'; + +export default () => { + const { data: server } = useServerFromRoute(); + const { clearFlashes, clearAndAddHttpError } = useStoreActions(actions => actions.flashes); + + if (!server) return null; + + const submit = (values: Values, { setSubmitting, setFieldValue }: FormikHelpers) => { + clearFlashes('server'); + + // This value is inverted to have the switch be on when the + // OOM Killer is enabled, rather than when disabled. + values.limits.oomDisabled = !values.limits.oomDisabled; + + updateServer(server.id, values) + .then(() => { + // setServer({ ...server, ...s }); + + // TODO: Figure out how to properly clear react-selects for allocations. + setFieldValue('addAllocations', []); + setFieldValue('removeAllocations', []); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'server', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerStartupContainer.tsx b/resources/scripts/components/admin/servers/ServerStartupContainer.tsx new file mode 100644 index 000000000..d672c57e0 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerStartupContainer.tsx @@ -0,0 +1,258 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik, useField, useFormikContext } from 'formik'; +import { useEffect, useState } from 'react'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +import type { InferModel } from '@/api/admin'; +import type { Egg, EggVariable } from '@/api/admin/egg'; +import { getEgg } from '@/api/admin/egg'; +import type { Server } from '@/api/admin/server'; +import { useServerFromRoute } from '@/api/admin/server'; +import type { Values } from '@/api/admin/servers/updateServerStartup'; +import updateServerStartup from '@/api/admin/servers/updateServerStartup'; +import EggSelect from '@/components/admin/servers/EggSelect'; +import NestSelector from '@/components/admin/servers/NestSelector'; +import FormikSwitch from '@/components/elements/FormikSwitch'; +import Button from '@/components/elements/Button'; +import Input from '@/components/elements/Input'; +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import Label from '@/components/elements/Label'; +import type { ApplicationStore } from '@/state'; + +function ServerStartupLineContainer({ egg, server }: { egg: Egg | null; server: Server }) { + const { isSubmitting, setFieldValue } = useFormikContext(); + + useEffect(() => { + if (egg === null) { + return; + } + + if (server.eggId === egg.id) { + setFieldValue('image', server.container.image); + setFieldValue('startup', server.container.startup || ''); + return; + } + + // Whenever the egg is changed, set the server's startup command to the egg's default. + setFieldValue('image', egg.dockerImages.length > 0 ? egg.dockerImages[0] : ''); + setFieldValue('startup', ''); + }, [egg]); + + return ( + + + +
    + +
    + +
    + + +
    +
    + ); +} + +export function ServerServiceContainer({ + egg, + setEgg, + nestId: _nestId, +}: { + egg: Egg | null; + setEgg: (value: Egg | null) => void; + nestId: number; +}) { + const { isSubmitting } = useFormikContext(); + + const [nestId, setNestId] = useState(_nestId); + + return ( + +
    + +
    +
    + +
    +
    + +
    +
    + ); +} + +export function ServerImageContainer() { + const { isSubmitting } = useFormikContext(); + + return ( + + + +
    +
    + +
    +
    +
    + ); +} + +export function ServerVariableContainer({ variable, value }: { variable: EggVariable; value?: string }) { + const key = 'environment.' + variable.environmentVariable; + + const [, , { setValue, setTouched }] = useField(key); + + const { isSubmitting } = useFormikContext(); + + useEffect(() => { + if (value === undefined) { + return; + } + + setValue(value); + setTouched(true); + }, [value]); + + return ( + {variable.name}

    }> + + + +
    + ); +} + +function ServerStartupForm({ + egg, + setEgg, + server, +}: { + egg: Egg | null; + setEgg: (value: Egg | null) => void; + server: Server; +}) { + const { + isSubmitting, + isValid, + values: { environment }, + } = useFormikContext(); + + return ( +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + {/* This ensures that no variables are rendered unless the environment has a value for the variable. */} + {egg?.relationships.variables + ?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined) + .map((v, i) => ( + v.eggId === v2.eggId && v.environmentVariable === v2.environmentVariable, + )?.serverValue + } + /> + ))} +
    + +
    +
    + +
    +
    +
    +
    + ); +} + +export default () => { + const { data: server } = useServerFromRoute(); + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [egg, setEgg] = useState | null>(null); + + useEffect(() => { + if (!server) return; + + getEgg(server.eggId) + .then(egg => setEgg(egg)) + .catch(error => console.error(error)); + }, [server?.eggId]); + + if (!server) return null; + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('server'); + + updateServerStartup(server.id, values) + // .then(s => { + // mutate(data => { ...data, ...s }); + // }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'server', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + , + image: server.container.image, + eggId: server.eggId, + skipScripts: false, + }} + validationSchema={object().shape({})} + > + + + ); +}; diff --git a/resources/scripts/components/admin/servers/ServersContainer.tsx b/resources/scripts/components/admin/servers/ServersContainer.tsx new file mode 100644 index 000000000..3533ceff8 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServersContainer.tsx @@ -0,0 +1,36 @@ +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import FlashMessageRender from '@/components/FlashMessageRender'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import ServersTable from '@/components/admin/servers/ServersTable'; +import Button from '@/components/elements/Button'; + +function ServersContainer() { + return ( + +
    +
    +

    Servers

    +

    + All servers available on the system. +

    +
    + +
    + + + +
    +
    + + + + +
    + ); +} + +export default ServersContainer; diff --git a/resources/scripts/components/admin/servers/ServersTable.tsx b/resources/scripts/components/admin/servers/ServersTable.tsx new file mode 100644 index 000000000..c41f0b527 --- /dev/null +++ b/resources/scripts/components/admin/servers/ServersTable.tsx @@ -0,0 +1,236 @@ +import type { ChangeEvent } from 'react'; +import { useContext, useEffect } from 'react'; +import { NavLink } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { Filters } from '@/api/admin/servers/getServers'; +import getServers, { Context as ServersContext } from '@/api/admin/servers/getServers'; +import AdminCheckbox from '@/components/admin/AdminCheckbox'; +import AdminTable, { + ContentWrapper, + Loading, + NoItems, + Pagination, + TableBody, + TableHead, + TableHeader, + useTableHooks, +} from '@/components/admin/AdminTable'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import { AdminContext } from '@/state/admin'; +import useFlash from '@/plugins/useFlash'; + +function RowCheckbox({ id }: { id: number }) { + const isChecked = AdminContext.useStoreState(state => state.servers.selectedServers.indexOf(id) >= 0); + const appendSelectedServer = AdminContext.useStoreActions(actions => actions.servers.appendSelectedServer); + const removeSelectedServer = AdminContext.useStoreActions(actions => actions.servers.removeSelectedServer); + + return ( + ) => { + if (e.currentTarget.checked) { + appendSelectedServer(id); + } else { + removeSelectedServer(id); + } + }} + /> + ); +} + +interface Props { + filters?: Filters; +} + +function ServersTable({ filters }: Props) { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const { page, setPage, setFilters, sort, setSort, sortDirection } = useContext(ServersContext); + const { data: servers, error, isValidating } = getServers(['node', 'user']); + + const length = servers?.items?.length || 0; + + const setSelectedServers = AdminContext.useStoreActions(actions => actions.servers.setSelectedServers); + const selectedServerLength = AdminContext.useStoreState(state => state.servers.selectedServers.length); + + const onSelectAllClick = (e: ChangeEvent) => { + setSelectedServers(e.currentTarget.checked ? servers?.items?.map(server => server.id) || [] : []); + }; + + const onSearch = (query: string): Promise => { + return new Promise(resolve => { + if (query.length < 2) { + setFilters(filters || null); + } else { + setFilters({ ...filters, name: query }); + } + return resolve(); + }); + }; + + useEffect(() => { + setSelectedServers([]); + }, [page]); + + useEffect(() => { + if (!error) { + clearFlashes('servers'); + return; + } + + clearAndAddHttpError({ key: 'servers', error }); + }, [error]); + + return ( + + + +
    + + + setSort('uuidShort')} + /> + setSort('name')} + /> + setSort('owner_id')} + /> + setSort('node_id')} + /> + setSort('status')} + /> + + + + {servers !== undefined && + !error && + !isValidating && + length > 0 && + servers.items.map(server => ( + + + + + + + + {/* TODO: Have permission check for displaying user information. */} + + + {/* TODO: Have permission check for displaying node information. */} + + + + + ))} + +
    + + + + + {server.identifier} + + + + + {server.name} + + + +
    + {server.relations.user?.email} +
    + +
    + {server.relations.user?.uuid.split('-')[0]} +
    +
    +
    + +
    + {server.relations.node?.name} +
    + +
    + {server.relations.node?.fqdn} +
    +
    +
    + {server.status === 'installing' ? ( + + Installing + + ) : server.status === 'transferring' ? ( + + Transferring + + ) : server.status === 'suspended' ? ( + + Suspended + + ) : ( + + Active + + )} +
    + + {servers === undefined || (error && isValidating) ? ( + + ) : length < 1 ? ( + + ) : null} +
    +
    +
    +
    + ); +} + +export default ({ filters }: Props) => { + const hooks = useTableHooks(filters); + + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx b/resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx new file mode 100644 index 000000000..3d3b8911a --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/BaseSettingsBox.tsx @@ -0,0 +1,31 @@ +import { faCogs } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import type { ReactNode } from 'react'; +import tw from 'twin.macro'; + +import { useServerFromRoute } from '@/api/admin/server'; +import AdminBox from '@/components/admin/AdminBox'; +import OwnerSelect from '@/components/admin/servers/OwnerSelect'; +import Field from '@/components/elements/Field'; + +export default ({ children }: { children?: ReactNode }) => { + const { data: server } = useServerFromRoute(); + const { isSubmitting } = useFormikContext(); + + return ( + +
    + + + + {children} +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx b/resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx new file mode 100644 index 000000000..46219af0a --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/FeatureLimitsBox.tsx @@ -0,0 +1,38 @@ +import { faConciergeBell } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + +
    + + + +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/NetworkingBox.tsx b/resources/scripts/components/admin/servers/settings/NetworkingBox.tsx new file mode 100644 index 000000000..d3adcf060 --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/NetworkingBox.tsx @@ -0,0 +1,68 @@ +import { faNetworkWired } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import getAllocations from '@/api/admin/nodes/getAllocations'; +import { useServerFromRoute } from '@/api/admin/server'; +import AdminBox from '@/components/admin/AdminBox'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; +import type { Option } from '@/components/elements/SelectField'; +import SelectField, { AsyncSelectField } from '@/components/elements/SelectField'; + +export default () => { + const { isSubmitting } = useFormikContext(); + const { data: server } = useServerFromRoute(); + + const loadOptions = async (inputValue: string, callback: (options: Option[]) => void) => { + if (!server) { + // eslint-disable-next-line node/no-callback-literal + callback([] as Option[]); + return; + } + + const allocations = await getAllocations(server.nodeId, { ip: inputValue, server_id: '0' }); + + callback( + allocations.map(a => { + return { value: a.id.toString(), label: a.getDisplayText() }; + }), + ); + }; + + return ( + +
    +
    + + +
    + + { + return { value: a.id.toString(), label: a.getDisplayText() }; + }) || [] + } + isMulti + isSearchable + /> +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx b/resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx new file mode 100644 index 000000000..69312ec3e --- /dev/null +++ b/resources/scripts/components/admin/servers/settings/ServerResourceBox.tsx @@ -0,0 +1,73 @@ +import { faBalanceScale } from '@fortawesome/free-solid-svg-icons'; +import { useFormikContext } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field from '@/components/elements/Field'; +import FormikSwitch from '@/components/elements/FormikSwitch'; + +export default () => { + const { isSubmitting } = useFormikContext(); + + return ( + +
    + + + + + + +
    + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/settings/GeneralSettings.tsx b/resources/scripts/components/admin/settings/GeneralSettings.tsx new file mode 100644 index 000000000..eb4ece2c8 --- /dev/null +++ b/resources/scripts/components/admin/settings/GeneralSettings.tsx @@ -0,0 +1,37 @@ +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Field, { FieldRow } from '@/components/elements/Field'; + +export default () => { + const submit = () => { + // + }; + + return ( + +
    +
    + + + + + + + + + + + +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/settings/MailSettings.tsx b/resources/scripts/components/admin/settings/MailSettings.tsx new file mode 100644 index 000000000..1e63eecc2 --- /dev/null +++ b/resources/scripts/components/admin/settings/MailSettings.tsx @@ -0,0 +1,102 @@ +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; + +import AdminBox from '@/components/admin/AdminBox'; +import Button from '@/components/elements/Button'; +import Field, { FieldRow } from '@/components/elements/Field'; +import Label from '@/components/elements/Label'; +import Select from '@/components/elements/Select'; + +export default () => { + const submit = () => { + // + }; + + return ( + + {({ isSubmitting, isValid }) => ( +
    + + + + +
    + + +
    +
    + + + + + + + + + + +
    + +
    +
    + +
    +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/admin/settings/SettingsContainer.tsx b/resources/scripts/components/admin/settings/SettingsContainer.tsx new file mode 100644 index 000000000..3fab06956 --- /dev/null +++ b/resources/scripts/components/admin/settings/SettingsContainer.tsx @@ -0,0 +1,52 @@ +import { AdjustmentsIcon, ChipIcon, CodeIcon, MailIcon, ShieldCheckIcon } from '@heroicons/react/outline'; +import { Route, Routes } from 'react-router-dom'; +import tw from 'twin.macro'; + +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import MailSettings from '@/components/admin/settings/MailSettings'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import GeneralSettings from '@/components/admin/settings/GeneralSettings'; + +export default () => { + return ( + +
    +
    +

    Settings

    +

    + Configure and manage settings for Pterodactyl. +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + } /> + } /> + Security

    } /> + Features

    } /> + Advanced

    } /> +
    +
    + ); +}; diff --git a/resources/scripts/components/admin/users/NewUserContainer.tsx b/resources/scripts/components/admin/users/NewUserContainer.tsx new file mode 100644 index 000000000..36fdf29f2 --- /dev/null +++ b/resources/scripts/components/admin/users/NewUserContainer.tsx @@ -0,0 +1,49 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; +import tw from 'twin.macro'; + +import type { UpdateUserValues } from '@/api/admin/users'; +import { createUser } from '@/api/admin/users'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import UserForm from '@/components/admin/users/UserForm'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; + +export default () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const submit = (values: UpdateUserValues, { setSubmitting }: FormikHelpers) => { + clearFlashes('user:create'); + + createUser(values) + .then(user => navigate(`/admin/users/${user.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user:create', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    +
    +

    New User

    +

    + Add a new user to the panel. +

    +
    +
    + + + + +
    + ); +}; diff --git a/resources/scripts/components/admin/users/RoleSelect.tsx b/resources/scripts/components/admin/users/RoleSelect.tsx new file mode 100644 index 000000000..11ab7aa16 --- /dev/null +++ b/resources/scripts/components/admin/users/RoleSelect.tsx @@ -0,0 +1,56 @@ +import { useFormikContext } from 'formik'; +import { useState } from 'react'; + +import { searchRoles } from '@/api/admin/roles'; +import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; +import type { UserRole } from '@definitions/admin'; + +export default ({ selected }: { selected: UserRole | null }) => { + const context = useFormikContext(); + + const [role, setRole] = useState(selected); + const [roles, setRoles] = useState(null); + + const onSearch = (query: string): Promise => { + return new Promise((resolve, reject) => { + searchRoles({ name: query }) + .then(roles => { + setRoles(roles); + return resolve(); + }) + .catch(reject); + }); + }; + + const onSelect = (role: UserRole | null) => { + setRole(role); + context.setFieldValue('adminRoleId', role?.id || null); + }; + + const getSelectedText = (role: UserRole | null): string | undefined => { + return role?.name; + }; + + return ( + + {roles?.map(d => ( + + ))} + + ); +}; diff --git a/resources/scripts/components/admin/users/UserAboutContainer.tsx b/resources/scripts/components/admin/users/UserAboutContainer.tsx new file mode 100644 index 000000000..623e6b87c --- /dev/null +++ b/resources/scripts/components/admin/users/UserAboutContainer.tsx @@ -0,0 +1,62 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { useNavigate } from 'react-router-dom'; + +import type { UpdateUserValues } from '@/api/admin/users'; +import { updateUser } from '@/api/admin/users'; +import UserDeleteButton from '@/components/admin/users/UserDeleteButton'; +import UserForm from '@/components/admin/users/UserForm'; +import { Context } from '@/components/admin/users/UserRouter'; +import type { ApplicationStore } from '@/state'; +import tw from 'twin.macro'; + +const UserAboutContainer = () => { + const navigate = useNavigate(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const user = Context.useStoreState(state => state.user); + const setUser = Context.useStoreActions(actions => actions.setUser); + + if (user === undefined) { + return <>; + } + + const submit = (values: UpdateUserValues, { setSubmitting }: FormikHelpers) => { + clearFlashes('user'); + + updateUser(user.id, values) + .then(() => setUser({ ...user, ...values })) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user', error }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + +
    + navigate('/admin/users')} /> +
    +
    + ); +}; + +export default UserAboutContainer; diff --git a/resources/scripts/components/admin/users/UserDeleteButton.tsx b/resources/scripts/components/admin/users/UserDeleteButton.tsx new file mode 100644 index 000000000..8911a5638 --- /dev/null +++ b/resources/scripts/components/admin/users/UserDeleteButton.tsx @@ -0,0 +1,73 @@ +import type { Actions } from 'easy-peasy'; +import { useStoreActions } from 'easy-peasy'; +import { useState } from 'react'; +import tw from 'twin.macro'; + +import { deleteUser } from '@/api/admin/users'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import type { ApplicationStore } from '@/state'; + +interface Props { + userId: number; + onDeleted: () => void; +} + +export default ({ userId, onDeleted }: Props) => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + + const onDelete = () => { + setLoading(true); + clearFlashes('user'); + + deleteUser(userId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this user? + + + + + ); +}; diff --git a/resources/scripts/components/admin/users/UserForm.tsx b/resources/scripts/components/admin/users/UserForm.tsx new file mode 100644 index 000000000..1c8be4b38 --- /dev/null +++ b/resources/scripts/components/admin/users/UserForm.tsx @@ -0,0 +1,148 @@ +import type { Action } from 'easy-peasy'; +import { action, createContextStore } from 'easy-peasy'; +import type { FormikHelpers } from 'formik'; +import { Form, Formik } from 'formik'; +import tw from 'twin.macro'; +import { bool, object, string } from 'yup'; + +import type { UpdateUserValues } from '@/api/admin/users'; +import AdminBox from '@/components/admin/AdminBox'; +import RoleSelect from '@/components/admin/users/RoleSelect'; +import CopyOnClick from '@/components/elements/CopyOnClick'; +import FormikSwitch from '@/components/elements/FormikSwitch'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import Button from '@/components/elements/Button'; +import Field, { FieldRow } from '@/components/elements/Field'; +import type { User, UserRole } from '@definitions/admin'; + +interface ctx { + user: User | undefined; + setUser: Action; +} + +export const Context = createContextStore({ + user: undefined, + + setUser: action((state, payload) => { + state.user = payload; + }), +}); + +export interface Params { + title: string; + initialValues?: UpdateUserValues; + children?: React.ReactNode; + + onSubmit: (values: UpdateUserValues, helpers: FormikHelpers) => void; + + uuid?: string; + role: UserRole | null; +} + +export default function UserForm({ title, initialValues, children, onSubmit, uuid, role }: Params) { + const submit = (values: UpdateUserValues, helpers: FormikHelpers) => { + onSubmit(values, helpers); + }; + + if (!initialValues) { + initialValues = { + externalId: '', + username: '', + email: '', + password: '', + adminRoleId: null, + rootAdmin: false, + }; + } + + return ( + + {({ isSubmitting, isValid }) => ( + <> + + + +
    + + {uuid && ( +
    + + + + +
    + )} + + + + + +
    + + {/* TODO: Remove toggle once role permissions are implemented. */} +
    +
    + +
    +
    + +
    + {children} +
    + +
    +
    +
    +
    + + )} +
    + ); +} diff --git a/resources/scripts/components/admin/users/UserRouter.tsx b/resources/scripts/components/admin/users/UserRouter.tsx new file mode 100644 index 000000000..b6f5554bf --- /dev/null +++ b/resources/scripts/components/admin/users/UserRouter.tsx @@ -0,0 +1,114 @@ +import type { Action, Actions } from 'easy-peasy'; +import { action, createContextStore, useStoreActions } from 'easy-peasy'; +import { useEffect, useState } from 'react'; +import { Route, Routes, useParams } from 'react-router-dom'; +import tw from 'twin.macro'; + +import { getUser } from '@/api/admin/users'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation'; +import UserAboutContainer from '@/components/admin/users/UserAboutContainer'; +import UserServers from '@/components/admin/users/UserServers'; +import Spinner from '@/components/elements/Spinner'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import type { ApplicationStore } from '@/state'; +import type { User } from '@definitions/admin'; + +interface ctx { + user: User | undefined; + setUser: Action; +} + +export const Context = createContextStore({ + user: undefined, + + setUser: action((state, payload) => { + state.user = payload; + }), +}); + +const UserRouter = () => { + const params = useParams<'id'>(); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions( + (actions: Actions) => actions.flashes, + ); + const [loading, setLoading] = useState(true); + + const user = Context.useStoreState(state => state.user); + const setUser = Context.useStoreActions(actions => actions.setUser); + + useEffect(() => { + clearFlashes('user'); + + getUser(Number(params.id), ['role']) + .then(user => setUser(user)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'user', error }); + }) + .then(() => setLoading(false)); + }, []); + + if (loading || user === undefined) { + return ( + + + +
    + +
    +
    + ); + } + + return ( + +
    +
    +

    {user.email}

    +

    + {user.uuid} +

    +
    +
    + + + + + + + + + + + + + + + + + + + } /> + } /> + +
    + ); +}; + +export default () => { + return ( + + + + ); +}; diff --git a/resources/scripts/components/admin/users/UserServers.tsx b/resources/scripts/components/admin/users/UserServers.tsx new file mode 100644 index 000000000..e4d3157f4 --- /dev/null +++ b/resources/scripts/components/admin/users/UserServers.tsx @@ -0,0 +1,10 @@ +import ServersTable from '@/components/admin/servers/ServersTable'; +import { Context } from '@/components/admin/users/UserRouter'; + +function UserServers() { + const user = Context.useStoreState(state => state.user); + + return ; +} + +export default UserServers; diff --git a/resources/scripts/components/admin/users/UserTableRow.tsx b/resources/scripts/components/admin/users/UserTableRow.tsx new file mode 100644 index 000000000..ee8baf729 --- /dev/null +++ b/resources/scripts/components/admin/users/UserTableRow.tsx @@ -0,0 +1,79 @@ +import { BanIcon, DotsVerticalIcon, LockOpenIcon, PencilIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid'; +import { useState } from 'react'; + +import Checkbox from '@/components/elements/inputs/Checkbox'; +import { Dropdown } from '@/components/elements/dropdown'; +import { Dialog } from '@/components/elements/dialog'; +import { Button } from '@/components/elements/button'; +import { User } from '@definitions/admin'; + +interface Props { + user: User; + selected?: boolean; + onRowChange: (user: User, selected: boolean) => void; +} + +const UserTableRow = ({ user, selected, onRowChange }: Props) => { + const [visible, setVisible] = useState(false); + + return ( + <> + setVisible(false)}> + + This account will be permanently deleted. + + setVisible(false)}>Cancel + Delete + + + + +
    + onRowChange(user, e.currentTarget.checked)} /> +
    + + +
    +
    + {'User +
    +
    +

    {user.email}

    +

    {user.uuid}

    +
    +
    + + + {user.isUsingTwoFactor && ( + + 2-FA Enabled + + )} + + + + + + + }>Edit + }>Reset Password + } disabled={!user.isUsingTwoFactor}> + Disable 2-FA + + }>Suspend + + } onClick={() => setVisible(true)} danger> + Delete Account + + + + + + ); +}; + +export default UserTableRow; diff --git a/resources/scripts/components/admin/users/UsersContainer.tsx b/resources/scripts/components/admin/users/UsersContainer.tsx new file mode 100644 index 000000000..b361722dd --- /dev/null +++ b/resources/scripts/components/admin/users/UsersContainer.tsx @@ -0,0 +1,119 @@ +import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid'; +import { Fragment, useEffect, useState } from 'react'; + +import { useGetUsers } from '@/api/admin/users'; +import type { UUID } from '@/api/definitions'; +import { Transition } from '@/components/elements/transitions'; +import { Button } from '@/components/elements/button/index'; +import Checkbox from '@/components/elements/inputs/Checkbox'; +import InputField from '@/components/elements/inputs/InputField'; +import UserTableRow from '@/components/admin/users/UserTableRow'; +import TFootPaginated from '@/components/elements/table/TFootPaginated'; +import type { User } from '@definitions/admin'; +import extractSearchFilters from '@/helpers/extractSearchFilters'; +import useDebouncedState from '@/plugins/useDebouncedState'; + +const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const; + +const UsersContainer = () => { + const [search, setSearch] = useDebouncedState('', 500); + const [selected, setSelected] = useState([]); + const { data: users } = useGetUsers( + extractSearchFilters(search, filters, { + splitUnmatched: true, + returnUnmatched: true, + }), + ); + + useEffect(() => { + document.title = 'Admin | Users'; + }, []); + + const onRowChange = (user: User, checked: boolean) => { + setSelected(state => { + return checked ? [...state, user.uuid] : selected.filter(uuid => uuid !== user.uuid); + }); + }; + + const selectAllChecked = users && users.items.length > 0 && selected.length > 0; + const onSelectAll = () => + setSelected(state => (state.length > 0 ? [] : users?.items.map(({ uuid }) => uuid) || [])); + + return ( +
    +
    + +
    +
    +
    + +
    +
    + setSearch(e.currentTarget.value)} + /> +
    + 0} duration={'duration-75'}> +
    +
    + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + {users?.items.map(user => ( + + ))} + + {users && } +
    + + Email + + +
    +
    + ); +}; + +export default UsersContainer; diff --git a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx index aaf8ccd64..bc5995284 100644 --- a/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx +++ b/resources/scripts/components/dashboard/activity/ActivityLogContainer.tsx @@ -47,7 +47,7 @@ export default () => { {!data && isValidating ? ( ) : ( -
    +
    {data?.items.map(activity => ( {typeof activity.properties.useragent === 'string' && ( diff --git a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx index cddd5d55a..13208670a 100644 --- a/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx +++ b/resources/scripts/components/dashboard/forms/RecoveryTokensDialog.tsx @@ -28,13 +28,13 @@ export default ({ tokens, open, onClose }: RecoveryTokenDialogProps) => { > -
    +                
                         {grouped.map(value => (
                             
                                 {value[0]}
    -                             
    +                             
                                 {value[1]}
    -                             
    +                             
                             
                         ))}
                     
    diff --git a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx index f10b1036d..5f5616d5e 100644 --- a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx @@ -62,7 +62,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => { return (
    -
    +
    {!token ? ( ) : ( @@ -70,7 +70,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => { )}
    -

    +

    {token?.secret.match(/.{1,4}/g)!.join(' ') || 'Loading...'}

    diff --git a/resources/scripts/components/elements/Code.tsx b/resources/scripts/components/elements/Code.tsx index 02640d699..90c6c7835 100644 --- a/resources/scripts/components/elements/Code.tsx +++ b/resources/scripts/components/elements/Code.tsx @@ -11,7 +11,7 @@ export default ({ dark, className, children }: CodeProps) => ( {children} diff --git a/resources/scripts/components/elements/CopyOnClick.tsx b/resources/scripts/components/elements/CopyOnClick.tsx index 80c01277c..50ed4fcb3 100644 --- a/resources/scripts/components/elements/CopyOnClick.tsx +++ b/resources/scripts/components/elements/CopyOnClick.tsx @@ -51,7 +51,7 @@ const CopyOnClick = ({ text, showInNotification = true, children }: CopyOnClickP
    -
    +

    {showInNotification ? `Copied "${String(text)}" to clipboard.` diff --git a/resources/scripts/components/elements/Editor.tsx b/resources/scripts/components/elements/Editor.tsx new file mode 100644 index 000000000..c3a77b9c6 --- /dev/null +++ b/resources/scripts/components/elements/Editor.tsx @@ -0,0 +1,318 @@ +import { autocompletion, completionKeymap } from '@codemirror/autocomplete'; +import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets'; +import { defaultKeymap, indentWithTab } from '@codemirror/commands'; +import { commentKeymap } from '@codemirror/comment'; +import { foldGutter, foldKeymap } from '@codemirror/fold'; +import { lineNumbers, highlightActiveLineGutter } from '@codemirror/gutter'; +import { defaultHighlightStyle } from '@codemirror/highlight'; +import { history, historyKeymap } from '@codemirror/history'; +import { indentOnInput, LanguageSupport, LRLanguage, indentUnit } from '@codemirror/language'; +import { lintKeymap } from '@codemirror/lint'; +import { bracketMatching } from '@codemirror/matchbrackets'; +import { rectangularSelection } from '@codemirror/rectangular-selection'; +import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; +import { Compartment, Extension, EditorState } from '@codemirror/state'; +import { StreamLanguage, StreamParser } from '@codemirror/stream-parser'; +import { keymap, highlightSpecialChars, drawSelection, highlightActiveLine, EditorView } from '@codemirror/view'; +import { clike } from '@codemirror/legacy-modes/mode/clike'; +import { cpp } from '@codemirror/lang-cpp'; +import { css } from '@codemirror/lang-css'; +import { Cassandra, MariaSQL, MSSQL, MySQL, PostgreSQL, sql, SQLite, StandardSQL } from '@codemirror/lang-sql'; +import { diff } from '@codemirror/legacy-modes/mode/diff'; +import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'; +import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; +import { go } from '@codemirror/legacy-modes/mode/go'; +import { html } from '@codemirror/lang-html'; +import { http } from '@codemirror/legacy-modes/mode/http'; +import { javascript, typescriptLanguage } from '@codemirror/lang-javascript'; +import { json } from '@codemirror/lang-json'; +import { lua } from '@codemirror/legacy-modes/mode/lua'; +import { properties } from '@codemirror/legacy-modes/mode/properties'; +import { python } from '@codemirror/legacy-modes/mode/python'; +import { ruby } from '@codemirror/legacy-modes/mode/ruby'; +import { rust } from '@codemirror/lang-rust'; +import { shell } from '@codemirror/legacy-modes/mode/shell'; +import { toml } from '@codemirror/legacy-modes/mode/toml'; +import { xml } from '@codemirror/lang-xml'; +import { yaml } from '@codemirror/legacy-modes/mode/yaml'; +import React, { useCallback, useEffect, useState } from 'react'; +import tw, { styled, TwStyle } from 'twin.macro'; +import { ayuMirage } from '@/components/elements/EditorTheme'; + +type EditorMode = LanguageSupport | LRLanguage | StreamParser; + +export interface Mode { + name: string; + mime: string; + mimes?: string[]; + mode?: EditorMode; + ext?: string[]; + alias?: string[]; + file?: RegExp; +} + +export const modes: Mode[] = [ + { name: 'C', mime: 'text/x-csrc', mode: clike({}), ext: [ 'c', 'h', 'ino' ] }, + { name: 'C++', mime: 'text/x-c++src', mode: cpp(), ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ], alias: [ 'cpp' ] }, + { name: 'C#', mime: 'text/x-csharp', mode: clike({}), ext: [ 'cs' ], alias: [ 'csharp', 'cs' ] }, + { name: 'CSS', mime: 'text/css', mode: css(), ext: [ 'css' ] }, + { name: 'CQL', mime: 'text/x-cassandra', mode: sql({ dialect: Cassandra }), ext: [ 'cql' ] }, + { name: 'Diff', mime: 'text/x-diff', mode: diff, ext: [ 'diff', 'patch' ] }, + { name: 'Dockerfile', mime: 'text/x-dockerfile', mode: dockerFile, file: /^Dockerfile$/ }, + { name: 'Git Markdown', mime: 'text/x-gfm', mode: markdown({ defaultCodeLanguage: markdownLanguage }), file: /^(readme|contributing|history|license).md$/i }, + { name: 'Golang', mime: 'text/x-go', mode: go, ext: [ 'go' ] }, + { name: 'HTML', mime: 'text/html', mode: html(), ext: [ 'html', 'htm', 'handlebars', 'hbs' ], alias: [ 'xhtml' ] }, + { name: 'HTTP', mime: 'message/http', mode: http }, + { name: 'JavaScript', mime: 'text/javascript', mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ], mode: javascript(), ext: [ 'js' ], alias: [ 'ecmascript', 'js', 'node' ] }, + { name: 'JSON', mime: 'application/json', mimes: [ 'application/json', 'application/x-json' ], mode: json(), ext: [ 'json', 'json5', 'map' ], alias: [ 'json5' ] }, + { name: 'Lua', mime: 'text/x-lua', mode: lua, ext: [ 'lua' ] }, + { name: 'Markdown', mime: 'text/x-markdown', mode: markdown({ defaultCodeLanguage: markdownLanguage }), ext: [ 'markdown', 'md', 'mkd' ] }, + { name: 'MariaDB', mime: 'text/x-mariadb', mode: sql({ dialect: MariaSQL }) }, + { name: 'MS SQL', mime: 'text/x-mssql', mode: sql({ dialect: MSSQL }) }, + { name: 'MySQL', mime: 'text/x-mysql', mode: sql({ dialect: MySQL }) }, + { name: 'Plain Text', mime: 'text/plain', mode: undefined, ext: [ 'txt', 'text', 'conf', 'def', 'list', 'log' ] }, + { name: 'PostgreSQL', mime: 'text/x-pgsql', mode: sql({ dialect: PostgreSQL }) }, + { name: 'Properties', mime: 'text/x-properties', mode: properties, ext: [ 'properties', 'ini', 'in' ], alias: [ 'ini', 'properties' ] }, + { name: 'Python', mime: 'text/x-python', mode: python, ext: [ 'BUILD', 'bzl', 'py', 'pyw' ], file: /^(BUCK|BUILD)$/ }, + { name: 'Ruby', mime: 'text/x-ruby', mode: ruby, ext: [ 'rb' ], alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ] }, + { name: 'Rust', mime: 'text/x-rustsrc', mode: rust(), ext: [ 'rs' ] }, + { name: 'Sass', mime: 'text/x-sass', mode: css(), ext: [ 'sass' ] }, + { name: 'SCSS', mime: 'text/x-scss', mode: css(), ext: [ 'scss' ] }, + { name: 'Shell', mime: 'text/x-sh', mimes: [ 'text/x-sh', 'application/x-sh' ], mode: shell, ext: [ 'sh', 'ksh', 'bash' ], alias: [ 'bash', 'sh', 'zsh' ], file: /^PKGBUILD$/ }, + { name: 'SQL', mime: 'text/x-sql', mode: sql({ dialect: StandardSQL }), ext: [ 'sql' ] }, + { name: 'SQLite', mime: 'text/x-sqlite', mode: sql({ dialect: SQLite }) }, + { name: 'TOML', mime: 'text/x-toml', mode: toml, ext: [ 'toml' ] }, + { name: 'TypeScript', mime: 'application/typescript', mode: typescriptLanguage, ext: [ 'ts' ], alias: [ 'ts' ] }, + { name: 'XML', mime: 'application/xml', mimes: [ 'application/xml', 'text/xml' ], mode: xml(), ext: [ 'xml', 'xsl', 'xsd', 'svg' ], alias: [ 'rss', 'wsdl', 'xsd' ] }, + { name: 'YAML', mime: 'text/x-yaml', mimes: [ 'text/x-yaml', 'text/yaml' ], mode: yaml, ext: [ 'yaml', 'yml' ], alias: [ 'yml' ] }, +]; + +export const modeToExtension = (m: EditorMode): Extension => { + if (m instanceof LanguageSupport) { + return m; + } + + if (m instanceof LRLanguage) { + return m; + } + + return StreamLanguage.define(m); +}; + +const findModeByFilename = (filename: string): Mode => { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + + if (info.file && info.file.test(filename)) { + return info; + } + } + + const dot = filename.lastIndexOf('.'); + const ext = dot > -1 && filename.substring(dot + 1, filename.length); + + if (ext) { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + if (info.ext) { + for (let j = 0; j < info.ext.length; j++) { + if (info.ext[j] === ext) { + return info; + } + } + } + } + } + + const plainText = modes.find(m => m.mime === 'text/plain'); + if (plainText === undefined) { + throw new Error('failed to find \'text/plain\' mode'); + } + return plainText; +}; + +const findLanguageExtensionByMode = (mode: Mode): Extension => { + if (mode.mode === undefined) { + return []; + } + return modeToExtension(mode.mode); +}; + +const defaultExtensions: Extension = [ + ayuMirage, + + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + defaultHighlightStyle.fallback, + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + highlightActiveLine(), + highlightSelectionMatches(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + ...commentKeymap, + ...completionKeymap, + ...lintKeymap, + indentWithTab, + ]), + EditorState.tabSize.of(4), + // This is gonna piss people off, but that isn't my problem. + indentUnit.of('\t'), +]; + +const EditorContainer = styled.div<{ overrides?: TwStyle }>` + //min-height: 12rem; + ${tw`relative`}; + + & > div { + ${props => props.overrides}; + + &.cm-focused { + outline: none; + } + } +`; + +export interface Props { + className?: string; + style?: React.CSSProperties; + overrides?: TwStyle; + + initialContent?: string; + extensions?: Extension[]; + mode?: EditorMode; + + filename?: string; + onModeChanged?: (mode: Mode) => void; + fetchContent?: (callback: () => Promise) => void; + onContentSaved?: () => void; +} + +export default ({ className, style, overrides, initialContent, extensions, mode, filename, onModeChanged, fetchContent, onContentSaved }: Props) => { + const [ languageConfig ] = useState(new Compartment()); + const [ keybinds ] = useState(new Compartment()); + const [ view, setView ] = useState(); + + const createEditorState = () => { + return EditorState.create({ + doc: initialContent, + extensions: [ + ...defaultExtensions, + ...(extensions !== undefined ? extensions : []), + + languageConfig.of(mode !== undefined ? modeToExtension(mode) : findLanguageExtensionByMode(findModeByFilename(filename || ''))), + keybinds.of([]), + ], + }); + }; + + const ref = useCallback((node) => { + if (!node) { + return; + } + + const view = new EditorView({ + state: createEditorState(), + parent: node, + }); + setView(view); + }, []); + + // This useEffect is required to send the proper mode back to the parent element + // due to the initial language being set with EditorState#create, rather than in + // an useEffect like this one, or one watching `filename`. + useEffect(() => { + if (onModeChanged === undefined) { + return; + } + + onModeChanged(findModeByFilename(filename || '')); + }, []); + + useEffect(() => { + if (view === undefined) { + return; + } + + if (mode === undefined) { + return; + } + + view.dispatch({ + effects: languageConfig.reconfigure(modeToExtension(mode)), + }); + }, [ mode ]); + + useEffect(() => { + if (view === undefined) { + return; + } + + if (filename === undefined) { + return; + } + + const mode = findModeByFilename(filename || ''); + + view.dispatch({ + effects: languageConfig.reconfigure(findLanguageExtensionByMode(mode)), + }); + + if (onModeChanged !== undefined) { + onModeChanged(mode); + } + }, [ filename ]); + + useEffect(() => { + if (view === undefined) { + return; + } + + // We could dispatch a view update to replace the content, but this would keep the edit history, + // and previously would duplicate the content of the editor. + view.setState(createEditorState()); + }, [ initialContent ]); + + useEffect(() => { + if (fetchContent === undefined) { + return; + } + + if (!view) { + fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); + return; + } + + if (onContentSaved !== undefined) { + view.dispatch({ + effects: keybinds.reconfigure(keymap.of([ + { + key: 'Mod-s', + run: () => { + onContentSaved(); + return true; + }, + }, + ])), + }); + } + + fetchContent(() => Promise.resolve(view.state.doc.toString())); + }, [ view, fetchContent, onContentSaved ]); + + return ( + + ); +}; diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index 3ce78349a..907689cb7 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -1,8 +1,12 @@ +import type { FieldProps } from 'formik'; +import { Field as FormikField } from 'formik'; +import type { InputHTMLAttributes, TextareaHTMLAttributes } from 'react'; import { forwardRef } from 'react'; -import * as React from 'react'; -import { Field as FormikField, FieldProps } from 'formik'; -import Input from '@/components/elements/Input'; +import tw, { styled } from 'twin.macro'; + import Label from '@/components/elements/Label'; +import Input, { Textarea } from '@/components/elements/Input'; +import InputError from '@/components/elements/InputError'; interface OwnProps { name: string; @@ -12,7 +16,7 @@ interface OwnProps { validate?: (value: any) => undefined | string | Promise; } -type Props = OwnProps & Omit, 'name'>; +type Props = OwnProps & Omit, 'name'>; const Field = forwardRef( ({ id, name, light = false, label, description, validate, ...props }, ref) => ( @@ -47,3 +51,42 @@ const Field = forwardRef( Field.displayName = 'Field'; export default Field; + +type TextareaProps = OwnProps & Omit, 'name'>; + +export const TextareaField = forwardRef(function TextareaField( + { id, name, light = false, label, description, validate, className, ...props }, + ref, +) { + return ( + + {({ field, form: { errors, touched } }: FieldProps) => ( +

    + {label && ( + + )} +