From fb9c10644881cc8fda10955b9f18857ec137e555 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 17 Aug 2019 16:03:10 -0700 Subject: [PATCH] Update server listing and associated logic to pull from the panel dynamiacally --- .../Application/ApplicationApiController.php | 6 +- .../Api/Client/ClientController.php | 2 +- .../Servers/ResourceUtilizationController.php | 28 +++- app/Http/Controllers/Base/IndexController.php | 59 +------- app/Models/Server.php | 44 ++++++ app/Providers/RepositoryServiceProvider.php | 2 +- .../Wings/WingsServerRepository.php | 28 ++++ .../Api/Client/StatsTransformer.php | 79 ++-------- resources/scripts/api/getServers.ts | 13 ++ resources/scripts/api/http.ts | 27 +++- resources/scripts/api/server/getServer.ts | 2 + .../api/server/getServerResourceUsage.ts | 29 ++++ .../dashboard/DashboardContainer.tsx | 116 ++++----------- .../components/dashboard/ServerRow.tsx | 135 ++++++++++++++++++ .../forms/UpdateEmailAddressForm.tsx | 2 +- .../dashboard/forms/UpdatePasswordForm.tsx | 2 +- .../scripts/components/elements/Modal.tsx | 2 +- .../scripts/components/elements/Spinner.tsx | 16 ++- .../components/elements/SpinnerOverlay.tsx | 15 +- .../scripts/components/server/Console.tsx | 2 +- .../server/databases/DatabasesContainer.tsx | 2 +- .../server/files/FileDropdownMenu.tsx | 2 +- .../server/files/FileManagerContainer.tsx | 2 +- resources/scripts/helpers.ts | 4 + resources/scripts/routers/ServerRouter.tsx | 2 +- routes/api-client.php | 2 +- 26 files changed, 384 insertions(+), 239 deletions(-) create mode 100644 app/Repositories/Wings/WingsServerRepository.php create mode 100644 resources/scripts/api/getServers.ts create mode 100644 resources/scripts/api/server/getServerResourceUsage.ts create mode 100644 resources/scripts/components/dashboard/ServerRow.tsx diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index bdd5f9e7b..da37a1a73 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application; use Illuminate\Http\Request; use Webmozart\Assert\Assert; use Illuminate\Http\Response; +use Illuminate\Support\Collection; use Illuminate\Container\Container; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal; @@ -30,7 +31,10 @@ abstract class ApplicationApiController extends Controller Container::getInstance()->call([$this, 'loadDependencies']); // Parse all of the includes to use on this request. - $includes = collect(explode(',', $this->request->input('include', '')))->map(function ($value) { + $input = $this->request->input('include', []); + $input = is_array($input) ? $input : explode(',', $input); + + $includes = (new Collection($input))->map(function ($value) { return trim($value); })->filter()->toArray(); diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index 35a084aae..d271c4cd7 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -52,7 +52,7 @@ class ClientController extends ClientApiController break; } - $servers = $this->repository-> + $servers = $this->repository ->setSearchTerm($request->input('query')) ->filterUserAccessServers( $request->user(), $filter, config('pterodactyl.paginate.frontend.servers') diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index 75645e9af..0c7c7b025 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -3,21 +3,45 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; +use Pterodactyl\Repositories\Wings\WingsServerRepository; use Pterodactyl\Transformers\Api\Client\StatsTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\GetServerRequest; class ResourceUtilizationController extends ClientApiController { + /** + * @var \Pterodactyl\Repositories\Wings\WingsServerRepository + */ + private $repository; + + /** + * ResourceUtilizationController constructor. + * + * @param \Pterodactyl\Repositories\Wings\WingsServerRepository $repository + */ + public function __construct(WingsServerRepository $repository) + { + parent::__construct(); + + $this->repository = $repository; + } + /** * Return the current resource utilization for a server. * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\GetServerRequest $request * @return array + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function index(GetServerRequest $request): array + public function __invoke(GetServerRequest $request): array { - return $this->fractal->item($request->getModel(Server::class)) + $stats = $this->repository + ->setServer($request->getModel(Server::class)) + ->getDetails(); + + return $this->fractal->item($stats) ->transformWith($this->getTransformer(StatsTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 625c9b23e..f62de118e 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -4,27 +4,11 @@ namespace Pterodactyl\Http\Controllers\Base; use Illuminate\Http\Request; use Pterodactyl\Models\User; -use Illuminate\Http\Response; -use GuzzleHttp\Exception\ConnectException; -use GuzzleHttp\Exception\RequestException; use Pterodactyl\Http\Controllers\Controller; -use Symfony\Component\HttpKernel\Exception\HttpException; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class IndexController extends Controller { - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - protected $daemonRepository; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService - */ - protected $keyProviderService; - /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ @@ -33,17 +17,10 @@ class IndexController extends Controller /** * IndexController constructor. * - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ - public function __construct( - DaemonKeyProviderService $keyProviderService, - DaemonServerRepositoryInterface $daemonRepository, - ServerRepositoryInterface $repository - ) { - $this->daemonRepository = $daemonRepository; - $this->keyProviderService = $keyProviderService; + public function __construct(ServerRepositoryInterface $repository) + { $this->repository = $repository; } @@ -61,34 +38,4 @@ class IndexController extends Controller return view('templates/base.core', ['servers' => $servers]); } - - /** - * Returns status of the server in a JSON response used for populating active status list. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - * @throws \Exception - */ - public function status(Request $request, $uuid) - { - $server = $this->repository->findFirstWhere([['uuidShort', '=', $uuid]]); - $token = $this->keyProviderService->handle($server, $request->user()); - - if (! $server->installed) { - return response()->json(['status' => 20]); - } elseif ($server->suspended) { - return response()->json(['status' => 30]); - } - - try { - $response = $this->daemonRepository->setServer($server)->setToken($token)->details(); - } catch (ConnectException $exception) { - throw new HttpException(Response::HTTP_GATEWAY_TIMEOUT, $exception->getMessage()); - } catch (RequestException $exception) { - throw new HttpException(500, $exception->getMessage()); - } - - return response()->json(json_decode($response->getBody())); - } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 30a3975e3..2f4477bd0 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -11,6 +11,50 @@ use Znck\Eloquent\Traits\BelongsToThrough; use Sofa\Eloquence\Contracts\CleansAttributes; use Sofa\Eloquence\Contracts\Validable as ValidableContract; +/** + * @property int $id + * @property string|null $external_id + * @property string $uuid + * @property string $uuidShort + * @property int $node_id + * @property string $name + * @property string $description + * @property bool $skip_scripts + * @property bool $suspended + * @property int $owner_id + * @property int $memory + * @property int $swap + * @property int $disk + * @property int $io + * @property int $cpu + * @property bool $oom_disabled + * @property int $allocation_id + * @property int $nest_id + * @property int $egg_id + * @property int|null $pack_id + * @property string $startup + * @property string $image + * @property int $installed + * @property int $allocation_limit + * @property int $database_limit + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\User $user + * @property \Pterodactyl\Models\User[]|\Illuminate\Support\Collection $subusers + * @property \Pterodactyl\Models\Allocation $allocation + * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Support\Collection $allocations + * @property \Pterodactyl\Models\Pack|null $pack + * @property \Pterodactyl\Models\Node $node + * @property \Pterodactyl\Models\Nest $nest + * @property \Pterodactyl\Models\Egg $egg + * @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Support\Collection $variables + * @property \Pterodactyl\Models\Schedule[]|\Illuminate\Support\Collection $schedule + * @property \Pterodactyl\Models\Database[]|\Illuminate\Support\Collection $databases + * @property \Pterodactyl\Models\Location $location + * @property \Pterodactyl\Models\DaemonKey $key + * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Support\Collection $keys + */ class Server extends Model implements CleansAttributes, ValidableContract { use BelongsToThrough, Eloquence, Notifiable, Validable; diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 9813baa2e..85d779af1 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -51,7 +51,7 @@ use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; -use Pterodactyl\Repositories\Daemon\ServerRepository as DaemonServerRepository; +use Pterodactyl\Repositories\Wings\WingsServerRepository as DaemonServerRepository; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class RepositoryServiceProvider extends ServiceProvider diff --git a/app/Repositories/Wings/WingsServerRepository.php b/app/Repositories/Wings/WingsServerRepository.php new file mode 100644 index 000000000..512a050f2 --- /dev/null +++ b/app/Repositories/Wings/WingsServerRepository.php @@ -0,0 +1,28 @@ +getHttpClient()->get( + sprintf('/api/servers/%s', $this->getServer()->uuid) + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + + return json_decode($response->getBody()->__toString(), true); + } +} diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index d3e66eb9a..0fc1563a0 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -2,28 +2,10 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface; +use Illuminate\Support\Arr; class StatsTransformer extends BaseClientTransformer { - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - private $repository; - - /** - * Perform dependency injection. - * - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $repository - */ - public function handle(ServerRepositoryInterface $repository) - { - $this->repository = $repository; - } - /** * @return string */ @@ -36,60 +18,21 @@ class StatsTransformer extends BaseClientTransformer * Transform stats from the daemon into a result set that can be used in * the client API. * - * @param \Pterodactyl\Models\Server $model + * @param array $data * @return array - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function transform(Server $model) + public function transform(array $data) { - try { - $stats = $this->repository->setServer($model)->details(); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } - - $object = json_decode($stats->getBody()->getContents()); - return [ - 'state' => $this->transformState(object_get($object, 'status', 0)), - 'memory' => [ - 'current' => round(object_get($object, 'proc.memory.total', 0) / 1024 / 1024), - 'limit' => floatval($model->memory), + 'current_state' => Arr::get($data, 'state', 'stopped'), + 'is_suspended' => Arr::get($data, 'suspended', false), + 'resources' => [ + 'memory_bytes' => Arr::get($data, 'resources.memory_bytes', 0), + 'cpu_absolute' => Arr::get($data, 'resources.cpu_absolute', 0), + 'disk_bytes' => Arr::get($data, 'resources.disk_bytes', 0), + 'network_rx_bytes' => Arr::get($data, 'resources.network.rx_bytes', 0), + 'network_tx_bytes' => Arr::get($data, 'resources.network.tx_bytes', 0), ], - 'cpu' => [ - 'current' => object_get($object, 'proc.cpu.total', 0), - 'cores' => object_get($object, 'proc.cpu.cores', []), - 'limit' => floatval($model->cpu), - ], - 'disk' => [ - 'current' => round(object_get($object, 'proc.disk.used', 0)), - 'limit' => floatval($model->disk), - 'io' => $model->io, - ], - 'installed' => $model->installed === 1, - 'suspended' => (bool) $model->suspended, ]; } - - /** - * Transform the state returned by the daemon into a human readable string. - * - * @param int $state - * @return string - */ - private function transformState(int $state): string - { - switch ($state) { - case 1: - return 'on'; - case 2: - return 'starting'; - case 3: - return 'stopping'; - case 0: - default: - return 'off'; - } - } } diff --git a/resources/scripts/api/getServers.ts b/resources/scripts/api/getServers.ts new file mode 100644 index 000000000..b77440da2 --- /dev/null +++ b/resources/scripts/api/getServers.ts @@ -0,0 +1,13 @@ +import { rawDataToServerObject, Server } from '@/api/server/getServer'; +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; + +export default (): Promise> => { + return new Promise((resolve, reject) => { + http.get(`/api/client`, { params: { include: [ 'allocation' ] } }) + .then(({ data }) => resolve({ + items: (data.data || []).map((datum: any) => rawDataToServerObject(datum.attributes)), + pagination: getPaginationSet(data.meta.pagination), + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index 676a735c7..ba0a9e230 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -1,9 +1,5 @@ import axios, { AxiosInstance } from 'axios'; -// This token is set in the bootstrap.js file at the beginning of the request -// and is carried through from there. -// const token: string = ''; - const http: AxiosInstance = axios.create({ headers: { 'X-Requested-With': 'XMLHttpRequest', @@ -41,3 +37,26 @@ export function httpErrorToHuman (error: any): string { return error.message; } + +export interface PaginatedResult { + items: T[]; + pagination: PaginationDataSet; +} + +interface PaginationDataSet { + total: number; + count: number; + perPage: number; + currentPage: number; + totalPages: number; +} + +export function getPaginationSet (data: any): PaginationDataSet { + return { + total: data.total, + count: data.count, + perPage: data.per_page, + currentPage: data.current_page, + totalPages: data.total_pages, + }; +} diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index e56a0f780..7d1149f34 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -4,6 +4,7 @@ export interface Allocation { ip: string; alias: string | null; port: number; + default: boolean; } export interface Server { @@ -36,6 +37,7 @@ export const rawDataToServerObject = (data: any): Server => ({ ip: data.allocation.ip, alias: null, port: data.allocation.port, + default: true, }], limits: { ...data.limits }, featureLimits: { ...data.feature_limits }, diff --git a/resources/scripts/api/server/getServerResourceUsage.ts b/resources/scripts/api/server/getServerResourceUsage.ts new file mode 100644 index 000000000..6b71dcf54 --- /dev/null +++ b/resources/scripts/api/server/getServerResourceUsage.ts @@ -0,0 +1,29 @@ +import http from '@/api/http'; + +export type ServerPowerState = 'offline' | 'starting' | 'running' | 'stopping'; + +export interface ServerStats { + status: ServerPowerState; + isSuspended: boolean; + memoryUsageInBytes: number; + cpuUsagePercent: number; + diskUsageInBytes: number; + networkRxInBytes: number; + networkTxInBytes: number; +} + +export default (server: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${server}/resources`) + .then(({ data: { attributes } }) => resolve({ + status: attributes.current_state, + isSuspended: attributes.is_suspended, + memoryUsageInBytes: attributes.resources.memory_bytes, + cpuUsagePercent: attributes.resources.cpu_absolute, + diskUsageInBytes: attributes.resources.disk_bytes, + networkRxInBytes: attributes.resources.network_rx_bytes, + networkTxInBytes: attributes.resources.network_tx_bytes, + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index a9c59a679..fba47f528 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -1,97 +1,35 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faServer } from '@fortawesome/free-solid-svg-icons/faServer'; import { faMicrochip } from '@fortawesome/free-solid-svg-icons/faMicrochip'; import { faMemory } from '@fortawesome/free-solid-svg-icons/faMemory'; import { faHdd } from '@fortawesome/free-solid-svg-icons/faHdd'; import { faEthernet } from '@fortawesome/free-solid-svg-icons/faEthernet'; -import { Link } from 'react-router-dom'; +import { Server } from '@/api/server/getServer'; +import getServers from '@/api/getServers'; +import ServerRow from '@/components/dashboard/ServerRow'; +import Spinner from '@/components/elements/Spinner'; -export default () => ( -
- -
- -
-
-

Party Parrots

-
-
-
- -

- 192.168.100.100:25565 -

-
-
- -

- 34.6% -

-
-
-
- -

- 2094 MB -

-
-

of 4096 MB

-
-
-
- -

- 278 MB -

-
-

of 16 GB

-
-
- -
-
- -
-
-

My Factions Server

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore - et dolore magna aliqua. -

-
-
-
- -

- 192.168.202.10:34556 -

-
-
- -

- 98.2 % -

-
-
-
- -

- 376 MB -

-
-

of 1024 MB

-
-
-
- -

- 187 MB -

-
-

of 32 GB

-
-
+export default () => { + const [ servers, setServers ] = useState(null); + + const loadServers = () => getServers().then(data => setServers(data.items)); + + useEffect(() => { + loadServers(); + }, []); + + if (servers === null) { + return ; + } + + return ( +
+ { + servers.map(server => ( + + )) + }
-
-); + ); +}; diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx new file mode 100644 index 000000000..7789f8125 --- /dev/null +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -0,0 +1,135 @@ +import React, { useEffect, useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faServer } from '@fortawesome/free-solid-svg-icons/faServer'; +import { faEthernet } from '@fortawesome/free-solid-svg-icons/faEthernet'; +import { faMicrochip } from '@fortawesome/free-solid-svg-icons/faMicrochip'; +import { faMemory } from '@fortawesome/free-solid-svg-icons/faMemory'; +import { faHdd } from '@fortawesome/free-solid-svg-icons/faHdd'; +import { Link } from 'react-router-dom'; +import { Server } from '@/api/server/getServer'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import getServerResourceUsage, { ServerStats } from '@/api/server/getServerResourceUsage'; +import { bytesToHuman } from '@/helpers'; +import classNames from 'classnames'; + +// Determines if the current value is in an alarm threshold so we can show it in red rather +// than the more faded default style. +const isAlarmState = (current: number, limit: number): boolean => { + const limitInBytes = limit * 1000 * 1000; + + return current / limitInBytes >= 0.90; +}; + +export default ({ server, className }: { server: Server; className: string | undefined }) => { + const [ stats, setStats ] = useState(null); + + const getStats = () => getServerResourceUsage(server.uuid).then(data => setStats(data)); + + useEffect(() => { + let interval: any = null; + getStats().then(() => { + interval = setInterval(() => getStats(), 20000); + }); + + return () => { + interval && clearInterval(interval); + }; + }, []); + + const alarms = { cpu: false, memory: false, disk: false }; + if (stats) { + alarms.cpu = server.limits.cpu === 0 ? false : (stats.cpuUsagePercent >= (server.limits.cpu * 0.9)); + alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory); + alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk); + } + + return ( + +
+ +
+
+

{server.name}

+
+
+
+ +

+ { + server.allocations.filter(alloc => alloc.default).map(allocation => ( + {allocation.alias || allocation.ip}:{allocation.port} + )) + } +

+
+
+
+ {!stats ? + + : + +
+ +

+ {stats.cpuUsagePercent} % +

+
+
+
+ +

+ {bytesToHuman(stats.memoryUsageInBytes)} +

+
+

of {bytesToHuman(server.limits.memory * 1000 * 1000)}

+
+
+
+ +

+ {bytesToHuman(stats.diskUsageInBytes)} +

+
+

+ of {bytesToHuman(server.limits.disk * 1000 * 1000)} +

+
+
+ } +
+ + ); +}; diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx index 1ee88a9ca..41f8bcac6 100644 --- a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx @@ -53,7 +53,7 @@ export default () => { { ({ isSubmitting, isValid }) => ( - +
{ { ({ isSubmitting, isValid }) => ( - + { className={'absolute w-full h-full rounded flex items-center justify-center'} style={{ background: 'hsla(211, 10%, 53%, 0.25)' }} > - +
}
diff --git a/resources/scripts/components/elements/Spinner.tsx b/resources/scripts/components/elements/Spinner.tsx index d1704916f..83ee202be 100644 --- a/resources/scripts/components/elements/Spinner.tsx +++ b/resources/scripts/components/elements/Spinner.tsx @@ -1,11 +1,19 @@ import React from 'react'; import classNames from 'classnames'; -export default ({ large, centered }: { large?: boolean; centered?: boolean }) => ( +export type SpinnerSize = 'large' | 'normal' | 'tiny'; + +export default ({ size, centered }: { size?: SpinnerSize; centered?: boolean }) => ( centered ? -
-
+
+
: -
+
); diff --git a/resources/scripts/components/elements/SpinnerOverlay.tsx b/resources/scripts/components/elements/SpinnerOverlay.tsx index 78ba2e050..21ea56b5d 100644 --- a/resources/scripts/components/elements/SpinnerOverlay.tsx +++ b/resources/scripts/components/elements/SpinnerOverlay.tsx @@ -1,18 +1,25 @@ import React from 'react'; import classNames from 'classnames'; import { CSSTransition } from 'react-transition-group'; -import Spinner from '@/components/elements/Spinner'; +import Spinner, { SpinnerSize } from '@/components/elements/Spinner'; -export default ({ large, fixed, visible }: { visible: boolean; fixed?: boolean; large?: boolean }) => ( +interface Props { + visible: boolean; + fixed?: boolean; + size?: SpinnerSize; + backgroundOpacity?: number; +} + +export default ({ size, fixed, visible, backgroundOpacity }: Props) => (
- +
); diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index 66f0b76bd..5b1bcc1dd 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -91,7 +91,7 @@ class Console extends React.PureComponent> { render () { return (
- +
{
{loading ? - + : diff --git a/resources/scripts/components/server/files/FileDropdownMenu.tsx b/resources/scripts/components/server/files/FileDropdownMenu.tsx index 75192e6dc..db0ded5c9 100644 --- a/resources/scripts/components/server/files/FileDropdownMenu.tsx +++ b/resources/scripts/components/server/files/FileDropdownMenu.tsx @@ -109,7 +109,7 @@ export default ({ uuid }: { uuid: string }) => { setMenuVisible(false); }} /> - +
{
{ loading ? - + : !files.length ?

diff --git a/resources/scripts/helpers.ts b/resources/scripts/helpers.ts index 9cec33a01..020fe6020 100644 --- a/resources/scripts/helpers.ts +++ b/resources/scripts/helpers.ts @@ -1,4 +1,8 @@ export function bytesToHuman (bytes: number): string { + if (bytes === 0) { + return '0 kB'; + } + const i = Math.floor(Math.log(bytes) / Math.log(1000)); // @ts-ignore diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 62f6ae0ff..bd5e691a4 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -43,7 +43,7 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)

{!server ?
- +
: diff --git a/routes/api-client.php b/routes/api-client.php index ed5286935..05366c44c 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -29,7 +29,7 @@ Route::group(['prefix' => '/account'], function () { */ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class]], function () { Route::get('/', 'Servers\ServerController@index')->name('api.client.servers.view'); - Route::get('/utilization', 'Servers\ResourceUtilizationController@index') + Route::get('/resources', 'Servers\ResourceUtilizationController') ->name('api.client.servers.resources'); Route::post('/command', 'Servers\CommandController@index')->name('api.client.servers.command');