From 326d346f92435fed4ed6e8433a8347e548319289 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 28 Sep 2019 13:09:47 -0700 Subject: [PATCH 01/98] Handle errors sent back over the sockt --- .../Client/Servers/WebsocketController.php | 6 ++++- .../scripts/components/server/Console.tsx | 8 +++++- .../components/server/WebsocketHandler.tsx | 25 +++++++++++-------- resources/scripts/plugins/Websocket.ts | 6 +++++ 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index c597ab8a0..7d68b5a31 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -63,7 +63,11 @@ class WebsocketController extends ClientApiController ->expiresAt($now->addMinutes(15)->getTimestamp()) ->withClaim('user_id', $request->user()->id) ->withClaim('server_uuid', $server->uuid) - ->withClaim('permissions', ['connect', 'send-command', 'send-power']) + ->withClaim('permissions', array_merge([ + 'connect', + 'send-command', + 'send-power', + ], $request->user()->root_admin ? ['receive-errors'] : [])) ->getToken($signer, new Key($server->node->daemonSecret)); $socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $server->node->getConnectionAddress()); diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index f35ded898..a789a2682 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -45,6 +45,10 @@ export default () => { line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m', ); + const handleDaemonErrorOutput = (line: string) => terminal.writeln( + '\u001b[1m\u001b[41m[Internal] ' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m', + ); + const handleCommandKeydown = (e: React.KeyboardEvent) => { if (e.key !== 'Enter' || (e.key === 'Enter' && e.currentTarget.value.length < 1)) { return; @@ -69,11 +73,13 @@ export default () => { terminal.clear(); instance.addListener('console output', handleConsoleOutput); + instance.addListener('daemon error', handleDaemonErrorOutput); instance.send('send logs'); } return () => { - instance && instance.removeListener('console output', handleConsoleOutput); + instance && instance.removeListener('console output', handleConsoleOutput) + .removeListener('daemon error', handleDaemonErrorOutput); }; }, [ connected, instance ]); diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index a46b3fd1c..d84c39413 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -5,10 +5,16 @@ import getWebsocketToken from '@/api/server/getWebsocketToken'; export default () => { const server = ServerContext.useStoreState(state => state.server.data); - const { instance, connected } = ServerContext.useStoreState(state => state.socket); + const { instance } = ServerContext.useStoreState(state => state.socket); const setServerStatus = ServerContext.useStoreActions(actions => actions.status.setServerStatus); const { setInstance, setConnectionState } = ServerContext.useStoreActions(actions => actions.socket); + const updateToken = (uuid: string, socket: Websocket) => { + getWebsocketToken(uuid) + .then(data => socket.setToken(data.token)) + .catch(error => console.error(error)); + }; + useEffect(() => { // If there is already an instance or there is no server, just exit out of this process // since we don't need to make a new connection. @@ -23,6 +29,13 @@ export default () => { socket.on('SOCKET_ERROR', () => setConnectionState(false)); socket.on('status', (status) => setServerStatus(status)); + socket.on('daemon error', message => { + console.warn('Got error message from daemon socket:', message); + }); + + socket.on('token expiring', () => updateToken(server.uuid, socket)); + socket.on('token expired', () => updateToken(server.uuid, socket)); + getWebsocketToken(server.uuid) .then(data => { socket.setToken(data.token).connect(data.socket); @@ -36,15 +49,5 @@ export default () => { }; }, [ server ]); - // Prevent issues with HMR in development environments. This might need to also - // exist outside of dev? Will need to see how things go. - if (process.env.NODE_ENV === 'development') { - useEffect(() => { - if (!connected && instance && instance.getToken() && instance.getSocketUrl()) { - instance.connect(instance.getSocketUrl()!); - } - }, [ connected ]); - } - return null; }; diff --git a/resources/scripts/plugins/Websocket.ts b/resources/scripts/plugins/Websocket.ts index 09aa2a118..0bf393e87 100644 --- a/resources/scripts/plugins/Websocket.ts +++ b/resources/scripts/plugins/Websocket.ts @@ -51,6 +51,10 @@ export class Websocket extends EventEmitter { setToken (token: string): this { this.token = token; + if (this.url) { + this.send('auth', token); + } + return this; } @@ -60,6 +64,8 @@ export class Websocket extends EventEmitter { } close (code?: number, reason?: string) { + this.url = null; + this.token = ''; this.socket && this.socket.close(code, reason); } From 79571e1928422bfad97c55ee859c33477ad66ce9 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 28 Sep 2019 13:17:51 -0700 Subject: [PATCH 02/98] Add button to toggle kill option after pressing stop once --- .../components/server/ServerConsole.tsx | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 854644f2e..344862743 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -16,6 +16,29 @@ const GreyBox = styled.div` const ChunkedConsole = lazy(() => import('@/components/server/Console')); +const StopOrKillButton = ({ onPress }: { onPress: (action: string) => void }) => { + const [ clicked, setClicked ] = useState(false); + const status = ServerContext.useStoreState(state => state.status.value); + + useEffect(() => { + setClicked(state => ['stopping'].indexOf(status) < 0 ? false : state); + }, [status]); + + return ( + + ); +}; + export default () => { const [ memory, setMemory ] = useState(0); const [ cpu, setCpu ] = useState(0); @@ -114,16 +137,7 @@ export default () => { > Restart - + sendPowerCommand(action)}/> Date: Sat, 28 Sep 2019 13:29:49 -0700 Subject: [PATCH 03/98] Fix some typescript typing issues --- resources/scripts/components/server/ServerConsole.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 344862743..2fe11e42a 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -10,13 +10,15 @@ import { faMicrochip } from '@fortawesome/free-solid-svg-icons/faMicrochip'; import { bytesToHuman } from '@/helpers'; import Spinner from '@/components/elements/Spinner'; +type PowerAction = 'start' | 'stop' | 'restart' | 'kill'; + const GreyBox = styled.div` ${tw`mt-4 shadow-md bg-neutral-700 rounded p-3 flex text-xs`} `; const ChunkedConsole = lazy(() => import('@/components/server/Console')); -const StopOrKillButton = ({ onPress }: { onPress: (action: string) => void }) => { +const StopOrKillButton = ({ onPress }: { onPress: (action: PowerAction) => void }) => { const [ clicked, setClicked ] = useState(false); const status = ServerContext.useStoreState(state => state.status.value); @@ -60,7 +62,7 @@ export default () => { setCpu(stats.cpu_absolute); }; - const sendPowerCommand = (command: 'start' | 'stop' | 'restart' | 'kill') => { + const sendPowerCommand = (command: PowerAction) => { instance && instance.send('set state', command); }; From 8599e2c64ba0223726f4c9087fb6fb5df47017a7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 28 Sep 2019 13:45:09 -0700 Subject: [PATCH 04/98] Add server state change events into the console log --- resources/scripts/components/server/Console.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index a789a2682..148e04cb7 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -49,6 +49,10 @@ export default () => { '\u001b[1m\u001b[41m[Internal] ' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m', ); + const handlePowerChangeEvent = (state: string) => terminal.writeln( + '\u001b[1m\u001b[33m[Status Change] Server marked as ' + state + '...\u001b[0m', + ); + const handleCommandKeydown = (e: React.KeyboardEvent) => { if (e.key !== 'Enter' || (e.key === 'Enter' && e.currentTarget.value.length < 1)) { return; @@ -72,6 +76,7 @@ export default () => { if (connected && instance) { terminal.clear(); + instance.addListener('status', handlePowerChangeEvent); instance.addListener('console output', handleConsoleOutput); instance.addListener('daemon error', handleDaemonErrorOutput); instance.send('send logs'); @@ -79,7 +84,8 @@ export default () => { return () => { instance && instance.removeListener('console output', handleConsoleOutput) - .removeListener('daemon error', handleDaemonErrorOutput); + .removeListener('daemon error', handleDaemonErrorOutput) + .removeListener('status', handlePowerChangeEvent); }; }, [ connected, instance ]); From c66d2cd123abfa27960bba978669af689d12f32d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 28 Sep 2019 14:59:05 -0700 Subject: [PATCH 05/98] A few adjustments for chunking the new file edit page --- app/Models/Server.php | 2 +- .../api/server/files/getFileContents.ts | 9 ++++++++ .../components/elements/SuspenseSpinner.tsx | 14 +++++++++++ .../components/server/ServerConsole.tsx | 16 ++++--------- .../server/files/FileEditContainer.tsx | 23 +++++++++++++------ .../components/server/files/FileObjectRow.tsx | 13 +++++++---- resources/scripts/routers/ServerRouter.tsx | 18 ++++++++++++--- resources/styles/components/typography.css | 2 +- tailwind.js | 3 +++ 9 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 resources/scripts/api/server/files/getFileContents.ts create mode 100644 resources/scripts/components/elements/SuspenseSpinner.tsx diff --git a/app/Models/Server.php b/app/Models/Server.php index 64ab336b0..4092c4357 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -44,7 +44,7 @@ use Znck\Eloquent\Traits\BelongsToThrough; * @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\ServerVariable[]|\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 diff --git a/resources/scripts/api/server/files/getFileContents.ts b/resources/scripts/api/server/files/getFileContents.ts new file mode 100644 index 000000000..d35f0575d --- /dev/null +++ b/resources/scripts/api/server/files/getFileContents.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (server: string, file: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${server}/files/contents`, { params: { file } }) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/elements/SuspenseSpinner.tsx b/resources/scripts/components/elements/SuspenseSpinner.tsx new file mode 100644 index 000000000..6e8424126 --- /dev/null +++ b/resources/scripts/components/elements/SuspenseSpinner.tsx @@ -0,0 +1,14 @@ +import React, { Suspense } from 'react'; +import Spinner from '@/components/elements/Spinner'; + +export default ({ children }: { children?: React.ReactNode }) => ( + + + + } + > + {children} + +); diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 2fe11e42a..672d654a6 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -8,7 +8,7 @@ import styled from 'styled-components'; import { faMemory } from '@fortawesome/free-solid-svg-icons/faMemory'; import { faMicrochip } from '@fortawesome/free-solid-svg-icons/faMicrochip'; import { bytesToHuman } from '@/helpers'; -import Spinner from '@/components/elements/Spinner'; +import SuspenseSpinner from '@/components/elements/SuspenseSpinner'; type PowerAction = 'start' | 'stop' | 'restart' | 'kill'; @@ -23,8 +23,8 @@ const StopOrKillButton = ({ onPress }: { onPress: (action: PowerAction) => void const status = ServerContext.useStoreState(state => state.status.value); useEffect(() => { - setClicked(state => ['stopping'].indexOf(status) < 0 ? false : state); - }, [status]); + setClicked(state => [ 'stopping' ].indexOf(status) < 0 ? false : state); + }, [ status ]); return ( - - - - - -@endsection diff --git a/resources/views/base/index.blade.php b/resources/views/base/index.blade.php deleted file mode 100644 index 8b44b48df..000000000 --- a/resources/views/base/index.blade.php +++ /dev/null @@ -1,108 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('base.index.header') -@endsection - -@section('content-header') -

@lang('base.index.header')@lang('base.index.header_sub')

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

@lang('base.index.list')

-
-
-
- -
- -
-
-
-
-
-
- - - - - - - - - - - - - - @foreach($servers as $server) - - - - - - - - - - @if($server->node->maintenance_mode) - - @else - - @endif - - @if (! empty($server->description)) - - - - @endif - @endforeach - -
@lang('strings.id')@lang('strings.name')@lang('strings.node')@lang('strings.connection')@lang('strings.relation')@lang('strings.status')
description)) rowspan="2" @endif>{{ $server->uuidShort }}{{ $server->name }}{{ $server->getRelation('node')->name }}{{ $server->getRelation('allocation')->alias }}:{{ $server->getRelation('allocation')->port }} - @if($server->user->id === Auth::user()->id) - @lang('strings.owner') - @elseif(Auth::user()->root_admin) - @lang('strings.admin') - @else - @lang('strings.subuser') - @endif - - @lang('strings.under_maintenance') - - -

{{ str_limit($server->description, 400) }}

-
- @if($servers->hasPages()) - - @endif -
-
-
-@endsection - -@section('footer-scripts') - @parent - - {!! Theme::js('js/frontend/serverlist.js') !!} -@endsection diff --git a/resources/views/base/security.blade.php b/resources/views/base/security.blade.php deleted file mode 100644 index 7c4693dd4..000000000 --- a/resources/views/base/security.blade.php +++ /dev/null @@ -1,135 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('base.security.header') -@endsection - -@section('content-header') -

@lang('base.security.header')@lang('base.security.header_sub')

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

@lang('base.security.sessions')

-
- @if(!is_null($sessions)) -
- - - - - - - - - @foreach($sessions as $session) - - - - - - - @endforeach - -
@lang('strings.id')@lang('strings.ip')@lang('strings.last_activity')
{{ substr($session->id, 0, 6) }}{{ $session->ip_address }}{{ Carbon::createFromTimestamp($session->last_activity)->diffForHumans() }} - - - -
-
- @else -
-

@lang('base.security.session_mgmt_disabled')

-
- @endif -
-
-
-
-
-

@lang('base.security.2fa_header')

-
- @if(Auth::user()->use_totp) -
-
-

@lang('base.security.2fa_enabled')

-
- -
- -

@lang('base.security.2fa_token_help')

-
-
-
- -
- @else -
-
- @lang('base.security.2fa_disabled') -
- -
- @endif -
-
-
- -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/2fa-modal.js') !!} -@endsection diff --git a/resources/views/server/console.blade.php b/resources/views/server/console.blade.php deleted file mode 100644 index d1dea1d53..000000000 --- a/resources/views/server/console.blade.php +++ /dev/null @@ -1,45 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} - - - - {{ config('app.name', 'Pterodactyl') }} - Console → {{ $server->name }} - @include('layouts.scripts') - {!! Theme::css('vendor/bootstrap/bootstrap.min.css') !!} - {!! Theme::css('css/terminal.css') !!} - - - -
-
-
-
container:~/$
- -
-
- - - - {!! Theme::js('js/laroute.js') !!} - {!! Theme::js('vendor/ansi/ansi_up.js') !!} - {!! Theme::js('vendor/jquery/jquery.min.js') !!} - {!! Theme::js('vendor/socketio/socket.io.v203.min.js') !!} - {!! Theme::js('vendor/bootstrap-notify/bootstrap-notify.min.js') !!} - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/mousewheel/jquery.mousewheel-min.js') !!} - {!! Theme::js('js/frontend/console.js') !!} - - diff --git a/resources/views/server/databases/index.blade.php b/resources/views/server/databases/index.blade.php deleted file mode 100644 index 99b1e096b..000000000 --- a/resources/views/server/databases/index.blade.php +++ /dev/null @@ -1,198 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.database.header') -@endsection - -@section('content-header') -

@lang('server.config.database.header')@lang('server.config.database.header_sub')

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

@lang('server.config.database.your_dbs')

-
- @if(count($databases) > 0) -
- - - - - - - - @can('reset-db-password', $server)@endcan - - @foreach($databases as $database) - - - - - - @if(Gate::allows('reset-db-password', $server) || Gate::allows('delete-database', $server)) - - @endif - - @endforeach - -
@lang('strings.database')@lang('strings.username')@lang('strings.password')@lang('server.config.database.host')
{{ $database->database }}{{ $database->username }} - - •••••••• - - - {{ $database->host->host }}:{{ $database->host->port }} - @can('delete-database', $server) - - @endcan - @can('reset-db-password', $server) - - @endcan -
-
- @else -
-
- @lang('server.config.database.no_dbs') -
-
- @endif -
-
- @if($allowCreation && Gate::allows('create-database', $server)) -
-
-
-

Create New Database

-
- @if($overLimit) -
-
- You are currently using {{ count($databases) }} of your {{ $server->database_limit ?? '∞' }} allowed databases. -
-
- @else -
-
-
- -
- s{{ $server->id }}_ - -
-
-
- - -

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

-
-
- -
- @endif -
-
- @endif -
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/resources/views/server/files/add.blade.php b/resources/views/server/files/add.blade.php deleted file mode 100644 index fd6bf7362..000000000 --- a/resources/views/server/files/add.blade.php +++ /dev/null @@ -1,98 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.files.add.header') -@endsection - -@section('scripts') - {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} - {!! Theme::css('vendor/select2/select2.min.css') !!} - @parent -@endsection - -@section('content-header') -

@lang('server.files.add.header')@lang('server.files.add.header_sub')

- -@endsection - -@section('content') -
-
-
-
-
- /home/container/ - -
-
-
- -
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/select2/select2.full.min.js') !!} - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/ace/ace.js') !!} - {!! Theme::js('vendor/ace/ext-modelist.js') !!} - {!! Theme::js('vendor/ace/ext-whitespace.js') !!} - {!! Theme::js('vendor/lodash/lodash.js') !!} - {!! Theme::js('js/frontend/files/editor.js') !!} - -@endsection diff --git a/resources/views/server/files/edit.blade.php b/resources/views/server/files/edit.blade.php deleted file mode 100644 index 6716a164d..000000000 --- a/resources/views/server/files/edit.blade.php +++ /dev/null @@ -1,59 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.files.edit.header') -@endsection - -@section('content-header') -

@lang('server.files.edit.header')@lang('server.files.edit.header_sub')

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

{{ $file }}

- -
- - -
-
- -
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/ace/ace.js') !!} - {!! Theme::js('vendor/ace/ext-modelist.js') !!} - {!! Theme::js('vendor/ace/ext-whitespace.js') !!} - {!! Theme::js('js/frontend/files/editor.js') !!} - -@endsection diff --git a/resources/views/server/files/index.blade.php b/resources/views/server/files/index.blade.php deleted file mode 100644 index 4dd0c0901..000000000 --- a/resources/views/server/files/index.blade.php +++ /dev/null @@ -1,54 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.files.header') -@endsection - -@section('content-header') -

@lang('server.files.header')@lang('server.files.header_sub')

- -@endsection - -@section('content') -
-
-
-
-
-
-
@lang('server.files.loading')
-
-
- -
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/async/async.min.js') !!} - {!! Theme::js('vendor/lodash/lodash.js') !!} - {!! Theme::js('vendor/siofu/client.min.js') !!} - @if(App::environment('production')) - {!! Theme::js('js/frontend/files/filemanager.min.js?hash=cd7ec731dc633e23ec36144929a237d18c07d2f0') !!} - @else - {!! Theme::js('js/frontend/files/src/index.js') !!} - {!! Theme::js('js/frontend/files/src/contextmenu.js') !!} - {!! Theme::js('js/frontend/files/src/actions.js') !!} - @endif - {!! Theme::js('js/frontend/files/upload.js') !!} -@endsection diff --git a/resources/views/server/files/list.blade.php b/resources/views/server/files/list.blade.php deleted file mode 100644 index f8abc5a7b..000000000 --- a/resources/views/server/files/list.blade.php +++ /dev/null @@ -1,170 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} - -
-

/home/container{{ $directory['header'] }}

-
- - - - - - -
-
-
- - - - - - - - - - - - @if (isset($directory['first']) && $directory['first'] === true) - - - - - - - - @endif - @if (isset($directory['show']) && $directory['show'] === true) - - - - - - - - @endif - @foreach ($folders as $folder) - - - - - - - - @endforeach - @foreach ($files as $file) - - - - - - - - @endforeach - -
- - @lang('server.files.file_name')
- ← {{ $directory['link_show'] }} -
- - - {{ $folder['entry'] }} - - -
- {{-- oh boy --}} - @if(in_array($file['mime'], [ - 'application/x-7z-compressed', - 'application/zip', - 'application/x-compressed-zip', - 'application/x-tar', - 'application/x-gzip', - 'application/x-bzip', - 'application/x-bzip2', - 'application/java-archive' - ])) - - @elseif(in_array($file['mime'], [ - 'application/json', - 'application/javascript', - 'application/xml', - 'application/xhtml+xml', - 'text/xml', - 'text/css', - 'text/html', - 'text/x-perl', - 'text/x-shellscript' - ])) - - @elseif(starts_with($file['mime'], 'image')) - - @elseif(starts_with($file['mime'], 'video')) - - @elseif(starts_with($file['mime'], 'video')) - - @elseif(starts_with($file['mime'], 'application/vnd.ms-powerpoint')) - - @elseif(in_array($file['mime'], [ - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'application/msword' - ]) || starts_with($file['mime'], 'application/vnd.ms-word')) - - @elseif(in_array($file['mime'], [ - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - ]) || starts_with($file['mime'], 'application/vnd.ms-excel')) - - @elseif($file['mime'] === 'application/pdf') - - @else - - @endif - - @if(in_array($file['mime'], $editableMime)) - @can('edit-files', $server) - {{ $file['entry'] }} - @else - {{ $file['entry'] }} - @endcan - @else - {{ $file['entry'] }} - @endif - - -
-
diff --git a/resources/views/server/index.blade.php b/resources/views/server/index.blade.php deleted file mode 100644 index 9cb4ba4a9..000000000 --- a/resources/views/server/index.blade.php +++ /dev/null @@ -1,85 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - {{ trans('server.index.title', [ 'name' => $server->name]) }} -@endsection - -@section('scripts') - @parent - {!! Theme::css('css/terminal.css') !!} -@endsection - -@section('content-header') -

@lang('server.index.header')@lang('server.index.header_sub')

- -@endsection - -@section('content') -
-
-
-
-
-
-
-
container:~/$
- -
-
- -
- -
-
-
-
-
-
-
-

Memory Usage

-
-
- -
-
-
-
-
-
-

CPU Usage

-
-
- -
-
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/ansi/ansi_up.js') !!} - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/mousewheel/jquery.mousewheel-min.js') !!} - {!! Theme::js('js/frontend/console.js') !!} - {!! Theme::js('vendor/chartjs/chart.min.js') !!} - {!! Theme::js('vendor/jquery/date-format.min.js') !!} - @if($server->nest->name === 'Minecraft' && $server->nest->author === 'support@pterodactyl.io') - {!! Theme::js('js/plugins/minecraft/eula.js') !!} - @endif -@endsection diff --git a/resources/views/server/schedules/index.blade.php b/resources/views/server/schedules/index.blade.php deleted file mode 100644 index aefcef5de..000000000 --- a/resources/views/server/schedules/index.blade.php +++ /dev/null @@ -1,98 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.schedule.header') -@endsection - -@section('content-header') -

@lang('server.schedule.header')@lang('server.schedule.header_sub')

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

@lang('server.schedule.current')

- -
-
- - - - - - - - - - - @foreach($schedules as $schedule) - is_active)class="muted muted-hover"@endif> - - - - - - - - @endforeach - -
@lang('strings.name')@lang('strings.queued')@lang('strings.tasks')@lang('strings.last_run')@lang('strings.next_run')
- @can('edit-schedule', $server) - - {{ $schedule->name ?? trans('server.schedule.unnamed') }} - - @else - {{ $schedule->name ?? trans('server.schedule.unnamed') }} - @endcan - - @if ($schedule->is_processing) - @lang('strings.yes') - @else - @lang('strings.no') - @endif - {{ $schedule->tasks_count }} - @if($schedule->last_run_at) - {{ Carbon::parse($schedule->last_run_at)->toDayDateTimeString() }}
({{ Carbon::parse($schedule->last_run_at)->diffForHumans() }}) - @else - @lang('strings.not_run_yet') - @endif -
- @if($schedule->is_active) - {{ Carbon::parse($schedule->next_run_at)->toDayDateTimeString() }}
({{ Carbon::parse($schedule->next_run_at)->diffForHumans() }}) - @else - n/a - @endif -
- @can('delete-schedule', $server) - - @endcan - @can('toggle-schedule', $server) - - - @endcan -
-
-
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('js/frontend/tasks/management-actions.js') !!} -@endsection diff --git a/resources/views/server/schedules/new.blade.php b/resources/views/server/schedules/new.blade.php deleted file mode 100644 index bb925c259..000000000 --- a/resources/views/server/schedules/new.blade.php +++ /dev/null @@ -1,145 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.schedules.new.header') -@endsection - -@section('scripts') - {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} - {!! Theme::css('vendor/select2/select2.min.css') !!} - @parent -@endsection - -@section('content-header') -

@lang('server.schedule.new.header')@lang('server.schedule.new.header_sub')

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

@lang('server.schedule.setup')

-
-
-
-
- -
- -
-
-
-
-
-
- -
- -
-
-
- -
-
-
-
- -
- -
-
-
- -
-
-
-
- -
- -
-
-
- -
-
-
-
- -
- -
-
-
- -
-
-
-
- -
-
-
-
-
-
- @include('partials.schedules.task-template') - -
-
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/select2/select2.full.min.js') !!} - {!! Theme::js('js/frontend/tasks/view-actions.js') !!} -@endsection diff --git a/resources/views/server/schedules/view.blade.php b/resources/views/server/schedules/view.blade.php deleted file mode 100644 index 370b4f0b0..000000000 --- a/resources/views/server/schedules/view.blade.php +++ /dev/null @@ -1,163 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.schedules.edit.header') -@endsection - -@section('scripts') - {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} - {!! Theme::css('vendor/select2/select2.min.css') !!} - @parent -@endsection - -@section('content-header') -

@lang('server.schedule.manage.header'){{ $schedule->name }}

- -@endsection - -@section('content') -
-
-
-
-
-
-
- -
- -
-
-
-
-
-
- -
- -
-
-
- -
-
-
-
- -
- -
-
-
- -
-
-
-
- -
- -
-
-
- -
-
-
-
- -
- -
-
-
- -
-
-
-
- -
-
-
-
-
-
- @include('partials.schedules.task-template') - -
-
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/select2/select2.full.min.js') !!} - {!! Theme::js('js/frontend/tasks/view-actions.js') !!} - -@endsection diff --git a/resources/views/server/settings/allocation.blade.php b/resources/views/server/settings/allocation.blade.php deleted file mode 100644 index cc1952401..000000000 --- a/resources/views/server/settings/allocation.blade.php +++ /dev/null @@ -1,120 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.allocation.header') -@endsection - -@section('content-header') -

@lang('server.config.allocation.header')@lang('server.config.allocation.header_sub')

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

@lang('server.config.allocation.available')

-
-
- - - - - - - - - @foreach ($allocations as $allocation) - - - - - - - @endforeach - -
@lang('strings.ip')@lang('strings.alias')@lang('strings.port')
- {{ $allocation->ip }} - - @if(is_null($allocation->ip_alias)) - @lang('strings.none') - @else - {{ $allocation->ip_alias }} - @endif - {{ $allocation->port }} - @if($allocation->id === $server->allocation_id) - @lang('strings.primary') - @else - @lang('strings.make_primary') - @endif -
-
- -
-
-
-
-
-

@lang('server.config.allocation.help')

-
-
-

@lang('server.config.allocation.help_text')

-
-
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/resources/views/server/settings/name.blade.php b/resources/views/server/settings/name.blade.php deleted file mode 100644 index 6d59162d0..000000000 --- a/resources/views/server/settings/name.blade.php +++ /dev/null @@ -1,50 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.name.header') -@endsection - -@section('content-header') -

@lang('server.config.name.header')@lang('server.config.name.header_sub')

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

@lang('server.config.name.details')

-
-
-
- -
-
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} -@endsection diff --git a/resources/views/server/settings/sftp.blade.php b/resources/views/server/settings/sftp.blade.php deleted file mode 100644 index 5da21ef77..000000000 --- a/resources/views/server/settings/sftp.blade.php +++ /dev/null @@ -1,54 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.sftp.header') -@endsection - -@section('content-header') -

@lang('server.config.sftp.header')@lang('server.config.sftp.header_sub')

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

@lang('server.config.sftp.details')

-
-
-
- -
- -
-
-
- -
- -
-
-
- -
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} -@endsection diff --git a/resources/views/server/settings/startup.blade.php b/resources/views/server/settings/startup.blade.php deleted file mode 100644 index 594dae4d2..000000000 --- a/resources/views/server/settings/startup.blade.php +++ /dev/null @@ -1,87 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.startup.header') -@endsection - -@section('content-header') -

@lang('server.config.startup.header')@lang('server.config.startup.header_sub')

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

@lang('server.config.startup.command')

-
-
-
- -
-
-
-
- @can('edit-startup', $server) -
- @foreach($variables as $v) -
-
-
-

{{ $v->name }}

-
-
- user_editable) - name="environment[{{ $v->env_variable }}]" - @else - readonly - @endif - class="form-control" type="text" value="{{ old('environment.' . $v->env_variable, $server_values[$v->env_variable]) }}" /> -

{{ $v->description }}

-

- @if($v->required && $v->user_editable ) - @lang('strings.required') - @elseif(! $v->required && $v->user_editable) - @lang('strings.optional') - @endif - @if(! $v->user_editable) - @lang('strings.read_only') - @endif -

-
- -
-
- @endforeach -
-
- -
-
-
- @endcan -
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} -@endsection diff --git a/resources/views/server/users/index.blade.php b/resources/views/server/users/index.blade.php deleted file mode 100644 index 632e79f8b..000000000 --- a/resources/views/server/users/index.blade.php +++ /dev/null @@ -1,132 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.users.header') -@endsection - -@section('content-header') -

@lang('server.users.header')@lang('server.users.header_sub')

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

@lang('server.users.list')

- @can('create-subuser', $server) - - @endcan -
-
- - - - - - - - - @can('view-subuser', $server)@endcan - @can('delete-subuser', $server)@endcan - - @foreach($subusers as $subuser) - - - - - - @can('view-subuser', $server) - - @endcan - @can('delete-subuser', $server) - - @endcan - - @endforeach - -
@lang('strings.username')@lang('strings.email')@lang('strings.2fa')
User Image{{ $subuser->user->username }} - {{ $subuser->user->email }} - @if($subuser->user->use_totp) - - @else - - @endif - - - - - - - - -
-
-
-
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - - -@endsection diff --git a/resources/views/server/users/new.blade.php b/resources/views/server/users/new.blade.php deleted file mode 100644 index 81231a704..000000000 --- a/resources/views/server/users/new.blade.php +++ /dev/null @@ -1,91 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.users.new.header') -@endsection - -@section('content-header') -

@lang('server.users.new.header')@lang('server.users.new.header_sub')

- -@endsection - -@section('content') - -
-
-
-
-
-
- -
- {!! csrf_field() !!} - -

@lang('server.users.new.email_help')

-
-
-
- -
-
-
-
- @foreach($permissions as $block => $perms) -
-
-
-

@lang('server.users.new.' . $block . '_header')

-
-
- @foreach($perms as $permission => $daemon) -
-
- - -
-

@lang('server.users.new.' . str_replace('-', '_', $permission) . '.description')

-
- @endforeach -
-
-
- @if ($loop->iteration % 2 === 0) -
- @endif - @endforeach -
-
-@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/resources/views/server/users/view.blade.php b/resources/views/server/users/view.blade.php deleted file mode 100644 index f175bb44e..000000000 --- a/resources/views/server/users/view.blade.php +++ /dev/null @@ -1,96 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.users.new.header') -@endsection - -@section('content-header') -

@lang('server.users.edit.header')@lang('server.users.edit.header_sub')

- -@endsection - -@section('content') -@can('edit-subuser', $server) -
-@endcan -
-
-
-
-
- -
- {!! csrf_field() !!} - -
-
-
- @can('edit-subuser', $server) -
- - {!! method_field('PATCH') !!} - -
- @endcan -
-
-
-
- @foreach($permlist as $block => $perms) -
-
-
-

@lang('server.users.new.' . $block . '_header')

-
-
- @foreach($perms as $permission => $daemon) -
-
- - -
-

@lang('server.users.new.' . str_replace('-', '_', $permission) . '.description')

-
- @endforeach -
-
-
- @if ($loop->iteration % 2 === 0) -
- @endif - @endforeach -
-@can('edit-subuser', $server) -
-@endcan -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/routes/base.php b/routes/base.php index 517aa5223..4d43235c4 100644 --- a/routes/base.php +++ b/routes/base.php @@ -6,34 +6,5 @@ Route::get('/account', 'IndexController@index')->name('account'); Route::get('/locales/{locale}/{namespace}.json', 'LocaleController') ->where('namespace', '.*'); -/* -|-------------------------------------------------------------------------- -| Account API Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /account/api -| -*/ -Route::group(['prefix' => 'account/api'], function () { - Route::get('/', 'ClientApiController@index')->name('account.api'); - Route::get('/new', 'ClientApiController@create')->name('account.api.new'); - Route::post('/new', 'ClientApiController@store'); - Route::delete('/revoke/{identifier}', 'ClientApiController@delete')->name('account.api.revoke'); -}); - -/* -|-------------------------------------------------------------------------- -| Account Security Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /account/security -| -*/ -Route::group(['prefix' => 'account/two_factor'], function () { - Route::get('/', 'SecurityController@index')->name('account.two_factor'); - Route::post('/totp', 'SecurityController@store')->name('account.two_factor.enable'); - Route::post('/totp/disable', 'SecurityController@delete')->name('account.two_factor.disable'); -}); - Route::get('/{react}', 'IndexController@index') ->where('react', '^(?!(\/)?(api|auth|admin|daemon)).+'); From 447c4291ad4cd6abca523eb16b70b979a448f9d1 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 15 Dec 2019 18:19:35 -0800 Subject: [PATCH 55/98] Update all places in the code referencing named routes using JS that doesn't exist now --- .../themes/pterodactyl/js/admin/new-server.js | 2 +- .../pterodactyl/js/frontend/2fa-modal.js | 89 --- .../themes/pterodactyl/js/frontend/console.js | 392 ------------- .../pterodactyl/js/frontend/files/editor.js | 131 ----- .../js/frontend/files/filemanager.min.js | 5 - .../js/frontend/files/filemanager.min.js.map | 1 - .../js/frontend/files/src/actions.js | 549 ------------------ .../js/frontend/files/src/contextmenu.js | 203 ------- .../js/frontend/files/src/index.js | 139 ----- .../pterodactyl/js/frontend/files/upload.js | 162 ------ .../pterodactyl/js/frontend/server.socket.js | 131 ----- .../pterodactyl/js/frontend/serverlist.js | 94 --- .../js/frontend/tasks/management-actions.js | 144 ----- .../js/frontend/tasks/view-actions.js | 61 -- resources/views/admin/api/index.blade.php | 2 +- .../admin/nodes/view/allocation.blade.php | 8 +- resources/views/admin/packs/new.blade.php | 2 +- .../admin/servers/view/database.blade.php | 4 +- .../admin/servers/view/details.blade.php | 2 +- resources/views/admin/settings/mail.blade.php | 6 +- 20 files changed, 12 insertions(+), 2115 deletions(-) delete mode 100644 public/themes/pterodactyl/js/frontend/2fa-modal.js delete mode 100644 public/themes/pterodactyl/js/frontend/console.js delete mode 100644 public/themes/pterodactyl/js/frontend/files/editor.js delete mode 100644 public/themes/pterodactyl/js/frontend/files/filemanager.min.js delete mode 100644 public/themes/pterodactyl/js/frontend/files/filemanager.min.js.map delete mode 100644 public/themes/pterodactyl/js/frontend/files/src/actions.js delete mode 100644 public/themes/pterodactyl/js/frontend/files/src/contextmenu.js delete mode 100644 public/themes/pterodactyl/js/frontend/files/src/index.js delete mode 100644 public/themes/pterodactyl/js/frontend/files/upload.js delete mode 100644 public/themes/pterodactyl/js/frontend/server.socket.js delete mode 100644 public/themes/pterodactyl/js/frontend/serverlist.js delete mode 100644 public/themes/pterodactyl/js/frontend/tasks/management-actions.js delete mode 100644 public/themes/pterodactyl/js/frontend/tasks/view-actions.js diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index 97f05487b..5a7393b4f 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -39,7 +39,7 @@ $(document).ready(function() { $('#pUserId').select2({ ajax: { - url: Router.route('admin.users.json'), + url: '/admin/users/accounts.json', dataType: 'json', delay: 250, data: function (params) { diff --git a/public/themes/pterodactyl/js/frontend/2fa-modal.js b/public/themes/pterodactyl/js/frontend/2fa-modal.js deleted file mode 100644 index 8de4ee539..000000000 --- a/public/themes/pterodactyl/js/frontend/2fa-modal.js +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -var TwoFactorModal = (function () { - - function bindListeners() { - $(document).ready(function () { - $('#close_reload').click(function () { - location.reload(); - }); - $('#do_2fa').submit(function (event) { - event.preventDefault(); - - $.ajax({ - type: 'PUT', - url: Router.route('account.security.totp'), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - var image = new Image(); - image.src = data.qrImage; - $(image).on('load', function () { - $('#hide_img_load').slideUp(function () { - $('#qr_image_insert').attr('src', image.src).slideDown(); - }); - }); - $('#open2fa').modal('show'); - }).fail(function (jqXHR) { - alert('An error occurred while attempting to load the 2FA setup modal. Please try again.'); - console.error(jqXHR); - }); - - }); - $('#2fa_token_verify').submit(function (event) { - event.preventDefault(); - $('#submit_action').html(' Submit').addClass('disabled'); - - $.ajax({ - type: 'POST', - url: Router.route('account.security.totp'), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - data: { - token: $('#2fa_token').val() - } - }).done(function (data) { - $('#notice_box_2fa').hide(); - if (data === 'true') { - $('#notice_box_2fa').html('
2-Factor Authentication has been enabled on your account. Press \'Close\' below to reload the page.
').slideDown(); - } else { - $('#notice_box_2fa').html('
The token provided was invalid.
').slideDown(); - } - }).fail(function (jqXHR) { - $('#notice_box_2fa').html('
There was an error while attempting to enable 2-Factor Authentication on this account.
').slideDown(); - console.error(jqXHR); - }).always(function () { - $('#submit_action').html('Submit').removeClass('disabled'); - }); - }); - }); - } - - return { - init: function () { - bindListeners(); - } - } -})(); - -TwoFactorModal.init(); diff --git a/public/themes/pterodactyl/js/frontend/console.js b/public/themes/pterodactyl/js/frontend/console.js deleted file mode 100644 index fc62ac024..000000000 --- a/public/themes/pterodactyl/js/frontend/console.js +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -var CONSOLE_PUSH_COUNT = Pterodactyl.config.console_count || 50; -var CONSOLE_PUSH_FREQ = Pterodactyl.config.console_freq || 200; -var CONSOLE_OUTPUT_LIMIT = Pterodactyl.config.console_limit || 2000; - -var KEYCODE_UP_ARROW = 38; -var KEYCODE_DOWN_ARROW = 40; - -var AnsiUp = new AnsiUp; -AnsiUp.use_classes = true; - -var $terminal = $('#terminal'); -var $terminalInput = $('.terminal_input--input'); -var $scrollNotify = $('#terminalNotify'); - -$(document).ready(function () { - var storage = window.localStorage; - var activeHx = []; - var currentHxIndex = 0; - var currentKeyCount = 0; - - var storedConsoleHistory = storage.getItem('console_hx_' + Pterodactyl.server.uuid); - try { - activeHx = JSON.parse(storedConsoleHistory) || []; - currentKeyCount = activeHx.length - 1; - } catch (ex) { - // - } - - $terminalInput.focus(); - $('.terminal_input--prompt, #terminal_input, #terminalNotify').on('click', function () { - $terminalInput.focus(); - }); - - $terminalInput.on('keyup', function (e) { - if (e.which === KEYCODE_DOWN_ARROW || e.which === KEYCODE_UP_ARROW) { - var value = consoleHistory(e.which); - - if (value !== false) { - $terminalInput.val(value); - } - } - - if (e.which === 27) { - $(this).val(''); - } - - if (e.which === 13) { - saveToHistory($(this).val()); - Socket.emit((ConsoleServerStatus !== 0) ? 'send command' : 'set state', $(this).val()); - - $(this).val(''); - } - }); - - function consoleHistory(key) { - // Get previous - if (key === KEYCODE_UP_ARROW) { - // currentHxIndex++; - var index = activeHx.length - (currentHxIndex + 1); - - if (typeof activeHx[index - 1] === 'undefined') { - return activeHx[index]; - } - - currentHxIndex++; - return activeHx[index]; - } - - // Get more recent - if (key === KEYCODE_DOWN_ARROW) { - var index = activeHx.length - currentHxIndex; - - if (typeof activeHx[index + 1] === 'undefined') { - return activeHx[index]; - } - - currentHxIndex--; - return activeHx[index]; - } - } - - function saveToHistory(command) { - if (command.length === 0) { - return; - } - - if (activeHx.length >= 50) { - activeHx.pop(); - } - - currentHxIndex = 0; - currentKeyCount++; - activeHx[currentKeyCount] = command; - - storage.setItem('console_hx_' + Pterodactyl.server.uuid, JSON.stringify(activeHx)); - } -}); - -$terminal.on('scroll', function () { - if (isTerminalScrolledDown()) { - $scrollNotify.addClass('hidden'); - } -}); - -function isTerminalScrolledDown() { - return $terminal.scrollTop() + $terminal.innerHeight() + 50 > $terminal[0].scrollHeight; -} - -window.scrollToBottom = function () { - $terminal.scrollTop($terminal[0].scrollHeight); -}; - -function pushToTerminal(string) { - $terminal.append('
' + AnsiUp.ansi_to_html(string + '\u001b[0m') + '
'); -} - -(function initConsole() { - window.TerminalQueue = []; - window.ConsoleServerStatus = 0; - window.ConsoleElements = 0; - - $scrollNotify.on('click', function () { - window.scrollToBottom(); - $scrollNotify.addClass('hidden'); - }); -})(); - -(function pushOutputQueue() { - if (TerminalQueue.length > CONSOLE_PUSH_COUNT) { - // console throttled warning show - } - - if (TerminalQueue.length > 0) { - var scrolledDown = isTerminalScrolledDown(); - - for (var i = 0; i < CONSOLE_PUSH_COUNT && TerminalQueue.length > 0; i++) { - pushToTerminal(TerminalQueue[0]); - - window.ConsoleElements++; - TerminalQueue.shift(); - } - - if (scrolledDown) { - window.scrollToBottom(); - } else if ($scrollNotify.hasClass('hidden')) { - $scrollNotify.removeClass('hidden'); - } - - var removeElements = window.ConsoleElements - CONSOLE_OUTPUT_LIMIT; - if (removeElements > 0) { - $('#terminal').find('.cmd').slice(0, removeElements).remove(); - window.ConsoleElements = window.ConsoleElements - removeElements; - } - } - - window.setTimeout(pushOutputQueue, CONSOLE_PUSH_FREQ); -})(); - -(function setupSocketListeners() { - // Update Listings on Initial Status - Socket.on('initial status', function (data) { - ConsoleServerStatus = data.status; - updateServerPowerControls(data.status); - - if (data.status === 1 || data.status === 2) { - Socket.emit('send server log'); - } - }); - - // Update Listings on Status - Socket.on('status', function (data) { - ConsoleServerStatus = data.status; - updateServerPowerControls(data.status); - }); - - // Skips the queue so we don't wait - // 10 minutes to load the log... - Socket.on('server log', function (data) { - $('#terminal').html(''); - data.split(/\n/g).forEach(function (item) { - pushToTerminal(item); - }); - window.scrollToBottom(); - }); - - Socket.on('console', function (data) { - if(data.line) { - data.line.split(/\n/g).forEach(function (item) { - TerminalQueue.push(item); - }); - } - }); -})(); - -function updateServerPowerControls (data) { - // Server is On or Starting - if(data == 1 || data == 2) { - $('[data-attr="power"][data-action="start"]').addClass('disabled'); - $('[data-attr="power"][data-action="stop"], [data-attr="power"][data-action="restart"]').removeClass('disabled'); - } else { - if (data == 0) { - $('[data-attr="power"][data-action="start"]').removeClass('disabled'); - } - $('[data-attr="power"][data-action="stop"], [data-attr="power"][data-action="restart"]').addClass('disabled'); - } - - if(data !== 0) { - $('[data-attr="power"][data-action="kill"]').removeClass('disabled'); - } else { - $('[data-attr="power"][data-action="kill"]').addClass('disabled'); - } -} - -$(document).ready(function () { - $('[data-attr="power"]').click(function (event) { - if (! $(this).hasClass('disabled')) { - Socket.emit('set state', $(this).data('action')); - } - }); - - (function setupChartElements() { - if (typeof SkipConsoleCharts !== 'undefined') { - return; - } - - Socket.on('proc', function (proc) { - if (CPUData.length > 10) { - CPUData.shift(); - MemoryData.shift(); - TimeLabels.shift(); - } - - var cpuUse = (Pterodactyl.server.cpu > 0) ? parseFloat(((proc.data.cpu.total / Pterodactyl.server.cpu) * 100).toFixed(3).toString()) : proc.data.cpu.total; - CPUData.push(cpuUse); - MemoryData.push(parseInt(proc.data.memory.total / (1024 * 1024))); - - TimeLabels.push($.format.date(new Date(), 'HH:mm:ss')); - - - // memory.cmax is the maximum given by the container - // memory.amax is given by the json config - // use the maximum of both - // with no limit memory.cmax will always be higher - // but with limit memory.amax is sometimes still smaller than memory.total - MemoryChart.config.options.scales.yAxes[0].ticks.max = Math.max(proc.data.memory.cmax, proc.data.memory.amax) / (1000 * 1000); - - if (Pterodactyl.server.cpu > 0) { - // if there is a cpu limit defined use 100% as maximum - CPUChart.config.options.scales.yAxes[0].ticks.max = 100; - } else { - // if there is no cpu limit defined use linux percentage - // and find maximum in all values - var maxCpu = 1; - for(var i = 0; i < CPUData.length; i++) { - maxCpu = Math.max(maxCpu, parseFloat(CPUData[i])) - } - - maxCpu = Math.ceil(maxCpu / 100) * 100; - CPUChart.config.options.scales.yAxes[0].ticks.max = maxCpu; - } - - - - CPUChart.update(); - MemoryChart.update(); - }); - - var ctc = $('#chart_cpu'); - var TimeLabels = []; - var CPUData = []; - var CPUChart = new Chart(ctc, { - type: 'line', - data: { - labels: TimeLabels, - datasets: [ - { - label: "Percent Use", - fill: false, - lineTension: 0.03, - backgroundColor: "#3c8dbc", - borderColor: "#3c8dbc", - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - pointBorderColor: "#3c8dbc", - pointBackgroundColor: "#fff", - pointBorderWidth: 1, - pointHoverRadius: 5, - pointHoverBackgroundColor: "#3c8dbc", - pointHoverBorderColor: "rgba(220,220,220,1)", - pointHoverBorderWidth: 2, - pointRadius: 1, - pointHitRadius: 10, - data: CPUData, - spanGaps: false, - } - ] - }, - options: { - title: { - display: true, - text: 'CPU Usage (as Percent Total)' - }, - legend: { - display: false, - }, - animation: { - duration: 1, - }, - scales: { - yAxes: [{ - ticks: { - beginAtZero: true - } - }] - } - } - }); - - var ctm = $('#chart_memory'); - MemoryData = []; - MemoryChart = new Chart(ctm, { - type: 'line', - data: { - labels: TimeLabels, - datasets: [ - { - label: "Memory Use", - fill: false, - lineTension: 0.03, - backgroundColor: "#3c8dbc", - borderColor: "#3c8dbc", - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - pointBorderColor: "#3c8dbc", - pointBackgroundColor: "#fff", - pointBorderWidth: 1, - pointHoverRadius: 5, - pointHoverBackgroundColor: "#3c8dbc", - pointHoverBorderColor: "rgba(220,220,220,1)", - pointHoverBorderWidth: 2, - pointRadius: 1, - pointHitRadius: 10, - data: MemoryData, - spanGaps: false, - } - ] - }, - options: { - title: { - display: true, - text: 'Memory Usage (in Megabytes)' - }, - legend: { - display: false, - }, - animation: { - duration: 1, - }, - scales: { - yAxes: [{ - ticks: { - beginAtZero: true - } - }] - } - } - }); - })(); -}); diff --git a/public/themes/pterodactyl/js/frontend/files/editor.js b/public/themes/pterodactyl/js/frontend/files/editor.js deleted file mode 100644 index 0ab7f9f7a..000000000 --- a/public/themes/pterodactyl/js/frontend/files/editor.js +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -(function () { - window.Editor = ace.edit('editor'); - var Whitespace = ace.require('ace/ext/whitespace'); - var Modelist = ace.require('ace/ext/modelist'); - - Editor.setTheme('ace/theme/chrome'); - Editor.getSession().setUseWrapMode(true); - Editor.setShowPrintMargin(false); - - if (typeof Pterodactyl !== 'undefined') { - if(typeof Pterodactyl.stat !== 'undefined') { - Editor.getSession().setMode(Modelist.getModeForPath(Pterodactyl.stat.name).mode); - } - } - - Editor.commands.addCommand({ - name: 'save', - bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, - exec: function(editor) { - if ($('#save_file').length) { - save(); - } else if ($('#create_file').length) { - create(); - } - }, - readOnly: false - }); - - Editor.commands.addCommands(Whitespace.commands); - - Whitespace.detectIndentation(Editor.session); - - $('#save_file').on('click', function (e) { - e.preventDefault(); - save(); - }); - - $('#create_file').on('click', function (e) { - e.preventDefault(); - create(); - }); - - $('#aceMode').on('change', event => { - Editor.getSession().setMode('ace/mode/' + $('#aceMode').val()); - }); - - function create() { - if (_.isEmpty($('#file_name').val())) { - $.notify({ - message: 'No filename was passed.' - }, { - type: 'danger' - }); - return; - } - $('#create_file').html(' Creating File').addClass('disabled'); - $.ajax({ - type: 'POST', - url: Router.route('server.files.save', { server: Pterodactyl.server.uuidShort }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - data: { - file: $('#file_name').val(), - contents: Editor.getValue() - } - }).done(function (data) { - window.location.replace(Router.route('server.files.edit', { - server: Pterodactyl.server.uuidShort, - file: $('#file_name').val(), - })); - }).fail(function (jqXHR) { - $.notify({ - message: jqXHR.responseText - }, { - type: 'danger' - }); - }).always(function () { - $('#create_file').html('Create File').removeClass('disabled'); - }); - } - - function save() { - var fileName = $('input[name="file"]').val(); - $('#save_file').html(' Saving File').addClass('disabled'); - $.ajax({ - type: 'POST', - url: Router.route('server.files.save', { server: Pterodactyl.server.uuidShort }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - data: { - file: fileName, - contents: Editor.getValue() - } - }).done(function (data) { - $.notify({ - message: 'File was successfully saved.' - }, { - type: 'success' - }); - }).fail(function (jqXHR) { - $.notify({ - message: jqXHR.responseText - }, { - type: 'danger' - }); - }).always(function () { - $('#save_file').html('  Save File').removeClass('disabled'); - }); - } -})(); diff --git a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js deleted file mode 100644 index 5fe4e9f35..000000000 --- a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i').text(value).html()}},{key:'folder',value:function folder(path){var inputValue=void 0;if(path){inputValue=path}else{var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.data('name'));var currentPath=decodeURIComponent(nameBlock.data('path'));if($(this.element).data('type')==='file'){inputValue=currentPath}else{inputValue=''+currentPath+currentName+'/'}}swal({type:'input',title:'Create Folder',text:'Please enter the path and folder name below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:inputValue},function(val){if(val===false){return false}$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/folder',timeout:10000,data:JSON.stringify({path:val})}).done(function(data){swal.close();Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'move',value:function move(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Move File',text:'Please enter the new path for the file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){if(val===false){return false}$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/move',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal.close()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'rename',value:function rename(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentLink=nameBlock.find('a');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var attachEditor='\n \n \n ';nameBlock.html(attachEditor);var inputField=nameBlock.find('input');var inputLoader=nameBlock.find('.input-loader');inputField.focus();inputField.on('blur keydown',function(e){if(e.type==='keydown'&&e.which===27||e.type==='blur'||e.type==='keydown'&&e.which===13&¤tName===inputField.val()){if(!_.isEmpty(currentLink)){nameBlock.html(currentLink)}else{nameBlock.html(currentName)}inputField.remove();ContextMenu.unbind().run();return}if(e.type==='keydown'&&e.which!==13)return;inputLoader.show();var currentPath=decodeURIComponent(nameBlock.data('path'));$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/rename',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+currentPath+inputField.val()})}).done(function(data){nameBlock.attr('data-name',inputField.val());if(!_.isEmpty(currentLink)){var newLink=currentLink.attr('href');if(nameBlock.parent().data('type')!=='folder'){newLink=newLink.substr(0,newLink.lastIndexOf('/'))+'/'+inputField.val()}currentLink.attr('href',newLink);nameBlock.html(currentLink.html(inputField.val()))}else{nameBlock.html(inputField.val())}inputField.remove()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}nameBlock.addClass('has-error').delay(2000).queue(function(){nameBlock.removeClass('has-error').dequeue()});inputField.popover({animation:true,placement:'top',content:error,title:'Save Error'}).popover('show')}).always(function(){inputLoader.remove();ContextMenu.unbind().run()})})}},{key:'copy',value:function copy(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Copy File',text:'Please enter the new path for the copied file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){if(val===false){return false}$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/copy',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){swal({type:'success',title:'',text:'File successfully copied.'});Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'download',value:function download(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var fileName=decodeURIComponent(nameBlock.attr('data-name'));var filePath=decodeURIComponent(nameBlock.data('path'));window.location='/server/'+Pterodactyl.server.uuidShort+'/files/download/'+filePath+fileName}},{key:'delete',value:function _delete(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var delPath=decodeURIComponent(nameBlock.data('path'));var delName=decodeURIComponent(nameBlock.data('name'));swal({type:'warning',title:'',text:'Are you sure you want to delete '+this.sanitizedString(delName)+'?',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:[''+delPath+delName]})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal({type:'success',title:'File Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occurred while attempting to delete this file. Please try again.'})})})}},{key:'toggleMassActions',value:function toggleMassActions(){if($('#file_listing input[type="checkbox"]:checked').length){$('#mass_actions').removeClass('disabled')}else{$('#mass_actions').addClass('disabled')}}},{key:'toggleHighlight',value:function toggleHighlight(event){var parent=$(event.currentTarget);var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$(item).prop('checked',false);parent.removeClass('warning').delay(200)}else{$(item).prop('checked',true);parent.addClass('warning').delay(200)}}},{key:'highlightAll',value:function highlightAll(event){var parent=void 0;var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$('#file_listing input[type=checkbox]').prop('checked',false);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.removeClass('warning').delay(200)})}else{$('#file_listing input[type=checkbox]').prop('checked',true);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.addClass('warning').delay(200)})}}},{key:'deleteSelected',value:function deleteSelected(){var selectedItems=[];var selectedItemsElements=[];var parent=void 0;var nameBlock=void 0;var delLocation=void 0;$('#file_listing input[data-action="addSelection"]:checked').each(function(){parent=$(this).closest('tr');nameBlock=$(parent).find('td[data-identifier="name"]');delLocation=decodeURIComponent(nameBlock.data('path'))+decodeURIComponent(nameBlock.data('name'));selectedItems.push(delLocation);selectedItemsElements.push(parent)});if(selectedItems.length!=0){var formattedItems='';var i=0;var self=this;$.each(selectedItems,function(key,value){formattedItems+=''+self.sanitizedString(value)+', ';i++;return i<5});formattedItems=formattedItems.slice(0,-2);if(selectedItems.length>5){formattedItems+=', and '+(selectedItems.length-5)+' other(s)'}swal({type:'warning',title:'',text:'Are you sure you want to delete the following files: '+formattedItems+'?',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:selectedItems})}).done(function(data){$('#file_listing input:checked').each(function(){$(this).prop('checked',false)});$.each(selectedItemsElements,function(){$(this).addClass('warning').delay(200).fadeOut()});swal({type:'success',title:'Files Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occurred while attempting to delete these files. Please try again.'})})})}else{swal({type:'warning',title:'',text:'Please select files/folders to delete.'})}}},{key:'decompress',value:function decompress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));swal({title:' Decompressing...',text:'This might take a few seconds to complete.',html:true,allowOutsideClick:false,allowEscapeKey:false,showConfirmButton:false});$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/decompress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName})}).done(function(data){swal.close();Files.list(compPath)}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}},{key:'compress',value:function compress(){var _this=this;var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/compress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName,to:compPath.toString()})}).done(function(data){Files.list(compPath,function(err){if(err)return;var fileListing=$('#file_listing').find('[data-name="'+data.saved_as+'"]').parent();fileListing.addClass('success pulsate').delay(3000).queue(function(){fileListing.removeClass('success pulsate').dequeue()})})}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:_this.sanitizedString(error)})})}}]);return ActionsClass}(); -'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i').text(newFilePath).html()+'" class="text-muted"> New File
  • New Folder
  • '}if(Pterodactyl.permissions.downloadFiles||Pterodactyl.permissions.deleteFiles){buildMenu+='
  • '}if(Pterodactyl.permissions.downloadFiles){buildMenu+=''}if(Pterodactyl.permissions.deleteFiles){buildMenu+='
  • Delete
  • '}buildMenu+='';return buildMenu}},{key:'rightClick',value:function rightClick(){var _this=this;$('[data-action="toggleMenu"]').on('mousedown',function(event){event.preventDefault();if($(document).find('#fileOptionMenu').is(':visible')){$('body').trigger('click');return}_this.showMenu(event)});$('#file_listing > tbody td').on('contextmenu',function(event){_this.showMenu(event)})}},{key:'showMenu',value:function showMenu(event){var _this2=this;var parent=$(event.target).closest('tr');var menu=$(this.makeMenu(parent));if(parent.data('type')==='disabled')return;event.preventDefault();$(menu).appendTo('body');$(menu).data('invokedOn',$(event.target)).show().css({position:'absolute',left:event.pageX-150,top:event.pageY});this.activeLine=parent;this.activeLine.addClass('active');var Actions=new ActionsClass(parent,menu);if(Pterodactyl.permissions.moveFiles){$(menu).find('li[data-action="move"]').unbind().on('click',function(e){e.preventDefault();Actions.move()});$(menu).find('li[data-action="rename"]').unbind().on('click',function(e){e.preventDefault();Actions.rename()})}if(Pterodactyl.permissions.copyFiles){$(menu).find('li[data-action="copy"]').unbind().on('click',function(e){e.preventDefault();Actions.copy()})}if(Pterodactyl.permissions.compressFiles){if(parent.data('type')==='folder'){$(menu).find('li[data-action="compress"]').removeClass('hidden')}$(menu).find('li[data-action="compress"]').unbind().on('click',function(e){e.preventDefault();Actions.compress()})}if(Pterodactyl.permissions.decompressFiles){if(_.without(['application/zip','application/gzip','application/x-gzip'],parent.data('mime')).length<3){$(menu).find('li[data-action="decompress"]').removeClass('hidden')}$(menu).find('li[data-action="decompress"]').unbind().on('click',function(e){e.preventDefault();Actions.decompress()})}if(Pterodactyl.permissions.createFiles){$(menu).find('li[data-action="folder"]').unbind().on('click',function(e){e.preventDefault();Actions.folder()})}if(Pterodactyl.permissions.downloadFiles){if(parent.data('type')==='file'){$(menu).find('li[data-action="download"]').removeClass('hidden')}$(menu).find('li[data-action="download"]').unbind().on('click',function(e){e.preventDefault();Actions.download()})}if(Pterodactyl.permissions.deleteFiles){$(menu).find('li[data-action="delete"]').unbind().on('click',function(e){e.preventDefault();Actions.delete()})}$(window).unbind().on('click',function(event){if($(event.target).is('.disable-menu-hide')){event.preventDefault();return}$(menu).unbind().remove();if(!_.isNull(_this2.activeLine))_this2.activeLine.removeClass('active')})}},{key:'directoryClick',value:function directoryClick(){$('a[data-action="directory-view"]').on('click',function(event){event.preventDefault();var path=$(this).parent().data('path')||'';var name=$(this).parent().data('name')||'';window.location.hash=encodeURIComponent(path+name);Files.list()})}}]);return ContextMenuClass}();window.ContextMenu=new ContextMenuClass; -'use strict';var _typeof=typeof Symbol==='function'&&typeof Symbol.iterator==='symbol'?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==='function'&&obj.constructor===Symbol&&obj!==Symbol.prototype?'symbol':typeof obj};var _createClass=function(){function defineProperties(target,props){for(var i=0;i\r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all\r\n// copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\nclass ActionsClass {\r\n constructor(element, menu) {\r\n this.element = element;\r\n this.menu = menu;\r\n }\r\n\r\n destroy() {\r\n this.element = undefined;\r\n }\r\n\r\n sanitizedString(value) {\r\n return $('
    ').text(value).html();\r\n }\r\n\r\n folder(path) {\r\n let inputValue\r\n if (path) {\r\n inputValue = path\r\n } else {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.data('name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n if ($(this.element).data('type') === 'file') {\r\n inputValue = currentPath;\r\n } else {\r\n inputValue = `${currentPath}${currentName}/`;\r\n }\r\n }\r\n\r\n swal({\r\n type: 'input',\r\n title: 'Create Folder',\r\n text: 'Please enter the path and folder name below.',\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true,\r\n inputValue: inputValue\r\n }, (val) => {\r\n if (val === false) {\r\n return false;\r\n }\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n path: val,\r\n }),\r\n }).done(data => {\r\n swal.close();\r\n Files.list();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: '',\r\n text: error,\r\n });\r\n });\r\n });\r\n }\r\n\r\n move() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n swal({\r\n type: 'input',\r\n title: 'Move File',\r\n text: 'Please enter the new path for the file below.',\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true,\r\n inputValue: `${currentPath}${currentName}`,\r\n }, (val) => {\r\n if (val === false) {\r\n return false;\r\n }\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n from: `${currentPath}${currentName}`,\r\n to: `${val}`,\r\n }),\r\n }).done(data => {\r\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\r\n swal.close();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: '',\r\n text: error,\r\n });\r\n });\r\n });\r\n\r\n }\r\n\r\n rename() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentLink = nameBlock.find('a');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const attachEditor = `\r\n \r\n \r\n `;\r\n\r\n nameBlock.html(attachEditor);\r\n const inputField = nameBlock.find('input');\r\n const inputLoader = nameBlock.find('.input-loader');\r\n\r\n inputField.focus();\r\n inputField.on('blur keydown', e => {\r\n // Save Field\r\n if (\r\n (e.type === 'keydown' && e.which === 27)\r\n || e.type === 'blur'\r\n || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val())\r\n ) {\r\n if (!_.isEmpty(currentLink)) {\r\n nameBlock.html(currentLink);\r\n } else {\r\n nameBlock.html(currentName);\r\n }\r\n inputField.remove();\r\n ContextMenu.unbind().run();\r\n return;\r\n }\r\n\r\n if (e.type === 'keydown' && e.which !== 13) return;\r\n\r\n inputLoader.show();\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n from: `${currentPath}${currentName}`,\r\n to: `${currentPath}${inputField.val()}`,\r\n }),\r\n }).done(data => {\r\n nameBlock.attr('data-name', inputField.val());\r\n if (!_.isEmpty(currentLink)) {\r\n let newLink = currentLink.attr('href');\r\n if (nameBlock.parent().data('type') !== 'folder') {\r\n newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val();\r\n }\r\n currentLink.attr('href', newLink);\r\n nameBlock.html(\r\n currentLink.html(inputField.val())\r\n );\r\n } else {\r\n nameBlock.html(inputField.val());\r\n }\r\n inputField.remove();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n nameBlock.addClass('has-error').delay(2000).queue(() => {\r\n nameBlock.removeClass('has-error').dequeue();\r\n });\r\n inputField.popover({\r\n animation: true,\r\n placement: 'top',\r\n content: error,\r\n title: 'Save Error'\r\n }).popover('show');\r\n }).always(() => {\r\n inputLoader.remove();\r\n ContextMenu.unbind().run();\r\n });\r\n });\r\n }\r\n\r\n copy() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n swal({\r\n type: 'input',\r\n title: 'Copy File',\r\n text: 'Please enter the new path for the copied file below.',\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true,\r\n inputValue: `${currentPath}${currentName}`,\r\n }, (val) => {\r\n if (val === false) {\r\n return false;\r\n }\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n from: `${currentPath}${currentName}`,\r\n to: `${val}`,\r\n }),\r\n }).done(data => {\r\n swal({\r\n type: 'success',\r\n title: '',\r\n text: 'File successfully copied.'\r\n });\r\n Files.list();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: '',\r\n text: error,\r\n });\r\n });\r\n });\r\n }\r\n\r\n download() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const fileName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const filePath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`;\r\n }\r\n\r\n delete() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const delPath = decodeURIComponent(nameBlock.data('path'));\r\n const delName = decodeURIComponent(nameBlock.data('name'));\r\n\r\n swal({\r\n type: 'warning',\r\n title: '',\r\n text: 'Are you sure you want to delete ' + this.sanitizedString(delName) + '?',\r\n html: true,\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true\r\n }, () => {\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n items: [`${delPath}${delName}`]\r\n }),\r\n }).done(data => {\r\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\r\n swal({\r\n type: 'success',\r\n title: 'File Deleted'\r\n });\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: 'An error occurred while attempting to delete this file. Please try again.',\r\n });\r\n });\r\n });\r\n }\r\n\r\n toggleMassActions() {\r\n if ($('#file_listing input[type=\"checkbox\"]:checked').length) {\r\n $('#mass_actions').removeClass('disabled');\r\n } else {\r\n $('#mass_actions').addClass('disabled');\r\n }\r\n }\r\n\r\n toggleHighlight(event) {\r\n const parent = $(event.currentTarget);\r\n const item = $(event.currentTarget).find('input');\r\n\r\n if($(item).is(':checked')) {\r\n $(item).prop('checked', false);\r\n parent.removeClass('warning').delay(200);\r\n } else {\r\n $(item).prop('checked', true);\r\n parent.addClass('warning').delay(200);\r\n }\r\n }\r\n\r\n highlightAll(event) {\r\n let parent;\r\n const item = $(event.currentTarget).find('input');\r\n\r\n if($(item).is(':checked')) {\r\n $('#file_listing input[type=checkbox]').prop('checked', false);\r\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\r\n parent = $(this).closest('tr');\r\n parent.removeClass('warning').delay(200);\r\n });\r\n } else {\r\n $('#file_listing input[type=checkbox]').prop('checked', true);\r\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\r\n parent = $(this).closest('tr');\r\n parent.addClass('warning').delay(200);\r\n });\r\n }\r\n }\r\n\r\n deleteSelected() {\r\n let selectedItems = [];\r\n let selectedItemsElements = [];\r\n let parent;\r\n let nameBlock;\r\n let delLocation;\r\n\r\n $('#file_listing input[data-action=\"addSelection\"]:checked').each(function() {\r\n parent = $(this).closest('tr');\r\n nameBlock = $(parent).find('td[data-identifier=\"name\"]');\r\n delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name'));\r\n\r\n selectedItems.push(delLocation);\r\n selectedItemsElements.push(parent);\r\n });\r\n\r\n if (selectedItems.length != 0)\r\n {\r\n let formattedItems = \"\";\r\n let i = 0;\r\n let self = this;\r\n\r\n $.each(selectedItems, function(key, value) {\r\n formattedItems += (\"\" + self.sanitizedString(value) + \", \");\r\n i++;\r\n return i < 5;\r\n });\r\n\r\n formattedItems = formattedItems.slice(0, -2);\r\n if (selectedItems.length > 5) {\r\n formattedItems += ', and ' + (selectedItems.length - 5) + ' other(s)';\r\n }\r\n\r\n swal({\r\n type: 'warning',\r\n title: '',\r\n text: 'Are you sure you want to delete the following files: ' + formattedItems + '?',\r\n html: true,\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true\r\n }, () => {\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n items: selectedItems\r\n }),\r\n }).done(data => {\r\n $('#file_listing input:checked').each(function() {\r\n $(this).prop('checked', false);\r\n });\r\n\r\n $.each(selectedItemsElements, function() {\r\n $(this).addClass('warning').delay(200).fadeOut();\r\n })\r\n\r\n swal({\r\n type: 'success',\r\n title: 'Files Deleted'\r\n });\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: 'An error occurred while attempting to delete these files. Please try again.',\r\n });\r\n });\r\n });\r\n } else {\r\n swal({\r\n type: 'warning',\r\n title: '',\r\n text: 'Please select files/folders to delete.',\r\n });\r\n }\r\n }\r\n\r\n decompress() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const compPath = decodeURIComponent(nameBlock.data('path'));\r\n const compName = decodeURIComponent(nameBlock.data('name'));\r\n\r\n swal({\r\n title: ' Decompressing...',\r\n text: 'This might take a few seconds to complete.',\r\n html: true,\r\n allowOutsideClick: false,\r\n allowEscapeKey: false,\r\n showConfirmButton: false,\r\n });\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`,\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n data: JSON.stringify({\r\n files: `${compPath}${compName}`\r\n })\r\n }).done(data => {\r\n swal.close();\r\n Files.list(compPath);\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: error\r\n });\r\n });\r\n }\r\n\r\n compress() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const compPath = decodeURIComponent(nameBlock.data('path'));\r\n const compName = decodeURIComponent(nameBlock.data('name'));\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`,\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n data: JSON.stringify({\r\n files: `${compPath}${compName}`,\r\n to: compPath.toString()\r\n })\r\n }).done(data => {\r\n Files.list(compPath, err => {\r\n if (err) return;\r\n const fileListing = $('#file_listing').find(`[data-name=\"${data.saved_as}\"]`).parent();\r\n fileListing.addClass('success pulsate').delay(3000).queue(() => {\r\n fileListing.removeClass('success pulsate').dequeue();\r\n });\r\n });\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: this.sanitizedString(error)\r\n });\r\n });\r\n }\r\n}\r\n","\"use strict\";\r\n\r\n// Copyright (c) 2015 - 2017 Dane Everitt \r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all\r\n// copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\nclass ContextMenuClass {\r\n constructor() {\r\n this.activeLine = null;\r\n }\r\n\r\n run() {\r\n this.directoryClick();\r\n this.rightClick();\r\n }\r\n\r\n makeMenu(parent) {\r\n $(document).find('#fileOptionMenu').remove();\r\n if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\r\n\r\n let newFilePath = $('#file_listing').data('current-dir');\r\n if (parent.data('type') === 'folder') {\r\n const nameBlock = parent.find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n newFilePath = `${currentPath}${currentName}`;\r\n }\r\n\r\n let buildMenu = '
      ';\r\n\r\n if (Pterodactyl.permissions.moveFiles) {\r\n buildMenu += '
    • Rename
    • \\\r\n
    • Move
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.copyFiles) {\r\n buildMenu += '
    • Copy
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.compressFiles) {\r\n buildMenu += '
    • Compress
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.decompressFiles) {\r\n buildMenu += '
    • Decompress
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.createFiles) {\r\n buildMenu += '
    • \\\r\n
    • ').text(newFilePath).html() + '\" class=\"text-muted\"> New File
    • \\\r\n
    • New Folder
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.downloadFiles || Pterodactyl.permissions.deleteFiles) {\r\n buildMenu += '
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.downloadFiles) {\r\n buildMenu += '
    • Download
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.deleteFiles) {\r\n buildMenu += '
    • Delete
    • ';\r\n }\r\n\r\n buildMenu += '
    ';\r\n return buildMenu;\r\n }\r\n\r\n rightClick() {\r\n $('[data-action=\"toggleMenu\"]').on('mousedown', event => {\r\n event.preventDefault();\r\n if ($(document).find('#fileOptionMenu').is(':visible')) {\r\n $('body').trigger('click');\r\n return;\r\n }\r\n this.showMenu(event);\r\n });\r\n $('#file_listing > tbody td').on('contextmenu', event => {\r\n this.showMenu(event);\r\n });\r\n }\r\n\r\n showMenu(event) {\r\n const parent = $(event.target).closest('tr');\r\n const menu = $(this.makeMenu(parent));\r\n\r\n if (parent.data('type') === 'disabled') return;\r\n event.preventDefault();\r\n\r\n $(menu).appendTo('body');\r\n $(menu).data('invokedOn', $(event.target)).show().css({\r\n position: 'absolute',\r\n left: event.pageX - 150,\r\n top: event.pageY,\r\n });\r\n\r\n this.activeLine = parent;\r\n this.activeLine.addClass('active');\r\n\r\n // Handle Events\r\n const Actions = new ActionsClass(parent, menu);\r\n if (Pterodactyl.permissions.moveFiles) {\r\n $(menu).find('li[data-action=\"move\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.move();\r\n });\r\n $(menu).find('li[data-action=\"rename\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.rename();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.copyFiles) {\r\n $(menu).find('li[data-action=\"copy\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.copy();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.compressFiles) {\r\n if (parent.data('type') === 'folder') {\r\n $(menu).find('li[data-action=\"compress\"]').removeClass('hidden');\r\n }\r\n $(menu).find('li[data-action=\"compress\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.compress();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.decompressFiles) {\r\n if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {\r\n $(menu).find('li[data-action=\"decompress\"]').removeClass('hidden');\r\n }\r\n $(menu).find('li[data-action=\"decompress\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.decompress();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.createFiles) {\r\n $(menu).find('li[data-action=\"folder\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.folder();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.downloadFiles) {\r\n if (parent.data('type') === 'file') {\r\n $(menu).find('li[data-action=\"download\"]').removeClass('hidden');\r\n }\r\n $(menu).find('li[data-action=\"download\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.download();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.deleteFiles) {\r\n $(menu).find('li[data-action=\"delete\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.delete();\r\n });\r\n }\r\n\r\n $(window).unbind().on('click', event => {\r\n if($(event.target).is('.disable-menu-hide')) {\r\n event.preventDefault();\r\n return;\r\n }\r\n $(menu).unbind().remove();\r\n if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\r\n });\r\n }\r\n\r\n directoryClick() {\r\n $('a[data-action=\"directory-view\"]').on('click', function (event) {\r\n event.preventDefault();\r\n\r\n const path = $(this).parent().data('path') || '';\r\n const name = $(this).parent().data('name') || '';\r\n\r\n window.location.hash = encodeURIComponent(path + name);\r\n Files.list();\r\n });\r\n }\r\n}\r\n\r\nwindow.ContextMenu = new ContextMenuClass;\r\n","\"use strict\";\r\n\r\n// Copyright (c) 2015 - 2017 Dane Everitt \r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all\r\n// copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\nclass FileManager {\r\n constructor() {\r\n this.list(this.decodeHash());\r\n }\r\n\r\n list(path, next) {\r\n if (_.isUndefined(path)) {\r\n path = this.decodeHash();\r\n }\r\n\r\n this.loader(true);\r\n $.ajax({\r\n type: 'POST',\r\n url: Pterodactyl.meta.directoryList,\r\n headers: {\r\n 'X-CSRF-Token': Pterodactyl.meta.csrftoken,\r\n },\r\n data: {\r\n directory: path,\r\n },\r\n }).done(data => {\r\n this.loader(false);\r\n $('#load_files').slideUp(10).html(data).slideDown(10, () => {\r\n ContextMenu.run();\r\n this.reloadFilesButton();\r\n this.addFolderButton();\r\n this.selectItem();\r\n this.selectAll();\r\n this.selectiveDeletion();\r\n this.selectRow();\r\n if (_.isFunction(next)) {\r\n return next();\r\n }\r\n });\r\n $('#internal_alert').slideUp();\r\n\r\n if (typeof Siofu === 'object') {\r\n Siofu.listenOnInput(document.getElementById(\"files_touch_target\"));\r\n }\r\n }).fail(jqXHR => {\r\n this.loader(false);\r\n if (_.isFunction(next)) {\r\n return next(new Error('Failed to load file listing.'));\r\n }\r\n\r\n if ((path !== '' && path !== '/') && jqXHR.status === 404) {\r\n return this.list('', next);\r\n }\r\n\r\n swal({\r\n type: 'error',\r\n title: 'File Error',\r\n text: jqXHR.responseJSON.errors[0].detail || 'An error occurred while attempting to process this request. Please try again.',\r\n });\r\n console.error(jqXHR);\r\n });\r\n }\r\n\r\n loader(show) {\r\n if (show){\r\n $('.file-overlay').fadeIn(100);\r\n } else {\r\n $('.file-overlay').fadeOut(100);\r\n }\r\n }\r\n\r\n reloadFilesButton() {\r\n $('i[data-action=\"reload-files\"]').unbind().on('click', () => {\r\n $('i[data-action=\"reload-files\"]').addClass('fa-spin');\r\n this.list();\r\n });\r\n }\r\n\r\n selectItem() {\r\n $('[data-action=\"addSelection\"]').on('click', event => {\r\n event.preventDefault();\r\n });\r\n }\r\n\r\n selectAll() {\r\n $('[data-action=\"selectAll\"]').on('click', event => {\r\n event.preventDefault();\r\n });\r\n }\r\n\r\n selectiveDeletion() {\r\n $('[data-action=\"selective-deletion\"]').on('mousedown', event => {\r\n new ActionsClass().deleteSelected();\r\n });\r\n }\r\n\r\n addFolderButton() {\r\n $('[data-action=\"add-folder\"]').unbind().on('click', () => {\r\n new ActionsClass().folder($('#file_listing').data('current-dir') || '/');\r\n });\r\n }\r\n\r\n selectRow() {\r\n $('#file_listing tr').on('mousedown', event => {\r\n if (event.which === 1) {\r\n if ($(event.target).is('th') || $(event.target).is('input[data-action=\"selectAll\"]')) {\r\n new ActionsClass().highlightAll(event);\r\n } else if ($(event.target).is('td') || $(event.target).is('input[data-action=\"addSelection\"]')) {\r\n new ActionsClass().toggleHighlight(event);\r\n }\r\n\r\n new ActionsClass().toggleMassActions();\r\n }\r\n });\r\n }\r\n\r\n decodeHash() {\r\n return decodeURIComponent(window.location.hash.substring(1));\r\n }\r\n\r\n}\r\n\r\nwindow.Files = new FileManager;\r\n"]} \ No newline at end of file diff --git a/public/themes/pterodactyl/js/frontend/files/src/actions.js b/public/themes/pterodactyl/js/frontend/files/src/actions.js deleted file mode 100644 index 244bcaab6..000000000 --- a/public/themes/pterodactyl/js/frontend/files/src/actions.js +++ /dev/null @@ -1,549 +0,0 @@ -"use strict"; - -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -class ActionsClass { - constructor(element, menu) { - this.element = element; - this.menu = menu; - } - - destroy() { - this.element = undefined; - } - - sanitizedString(value) { - return $('
    ').text(value).html(); - } - - folder(path) { - let inputValue - if (path) { - inputValue = path - } else { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.data('name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - if ($(this.element).data('type') === 'file') { - inputValue = currentPath; - } else { - inputValue = `${currentPath}${currentName}/`; - } - } - - swal({ - type: 'input', - title: 'Create Folder', - text: 'Please enter the path and folder name below.', - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true, - inputValue: inputValue - }, (val) => { - if (val === false) { - return false; - } - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`, - timeout: 10000, - data: JSON.stringify({ - path: val, - }), - }).done(data => { - swal.close(); - Files.list(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: '', - text: error, - }); - }); - }); - } - - move() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - swal({ - type: 'input', - title: 'Move File', - text: 'Please enter the new path for the file below.', - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true, - inputValue: `${currentPath}${currentName}`, - }, (val) => { - if (val === false) { - return false; - } - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`, - timeout: 10000, - data: JSON.stringify({ - from: `${currentPath}${currentName}`, - to: `${val}`, - }), - }).done(data => { - nameBlock.parent().addClass('warning').delay(200).fadeOut(); - swal.close(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: '', - text: error, - }); - }); - }); - - } - - rename() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentLink = nameBlock.find('a'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const attachEditor = ` - - - `; - - nameBlock.html(attachEditor); - const inputField = nameBlock.find('input'); - const inputLoader = nameBlock.find('.input-loader'); - - inputField.focus(); - inputField.on('blur keydown', e => { - // Save Field - if ( - (e.type === 'keydown' && e.which === 27) - || e.type === 'blur' - || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val()) - ) { - if (!_.isEmpty(currentLink)) { - nameBlock.html(currentLink); - } else { - nameBlock.html(currentName); - } - inputField.remove(); - ContextMenu.unbind().run(); - return; - } - - if (e.type === 'keydown' && e.which !== 13) return; - - inputLoader.show(); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`, - timeout: 10000, - data: JSON.stringify({ - from: `${currentPath}${currentName}`, - to: `${currentPath}${inputField.val()}`, - }), - }).done(data => { - nameBlock.attr('data-name', inputField.val()); - if (!_.isEmpty(currentLink)) { - let newLink = currentLink.attr('href'); - if (nameBlock.parent().data('type') !== 'folder') { - newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val(); - } - currentLink.attr('href', newLink); - nameBlock.html( - currentLink.html(inputField.val()) - ); - } else { - nameBlock.html(inputField.val()); - } - inputField.remove(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - nameBlock.addClass('has-error').delay(2000).queue(() => { - nameBlock.removeClass('has-error').dequeue(); - }); - inputField.popover({ - animation: true, - placement: 'top', - content: error, - title: 'Save Error' - }).popover('show'); - }).always(() => { - inputLoader.remove(); - ContextMenu.unbind().run(); - }); - }); - } - - copy() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - swal({ - type: 'input', - title: 'Copy File', - text: 'Please enter the new path for the copied file below.', - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true, - inputValue: `${currentPath}${currentName}`, - }, (val) => { - if (val === false) { - return false; - } - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`, - timeout: 10000, - data: JSON.stringify({ - from: `${currentPath}${currentName}`, - to: `${val}`, - }), - }).done(data => { - swal({ - type: 'success', - title: '', - text: 'File successfully copied.' - }); - Files.list(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: '', - text: error, - }); - }); - }); - } - - download() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const fileName = decodeURIComponent(nameBlock.attr('data-name')); - const filePath = decodeURIComponent(nameBlock.data('path')); - - window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`; - } - - delete() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const delPath = decodeURIComponent(nameBlock.data('path')); - const delName = decodeURIComponent(nameBlock.data('name')); - - swal({ - type: 'warning', - title: '', - text: 'Are you sure you want to delete ' + this.sanitizedString(delName) + '?', - html: true, - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true - }, () => { - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`, - timeout: 10000, - data: JSON.stringify({ - items: [`${delPath}${delName}`] - }), - }).done(data => { - nameBlock.parent().addClass('warning').delay(200).fadeOut(); - swal({ - type: 'success', - title: 'File Deleted' - }); - }).fail(jqXHR => { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: 'An error occurred while attempting to delete this file. Please try again.', - }); - }); - }); - } - - toggleMassActions() { - if ($('#file_listing input[type="checkbox"]:checked').length) { - $('#mass_actions').removeClass('disabled'); - } else { - $('#mass_actions').addClass('disabled'); - } - } - - toggleHighlight(event) { - const parent = $(event.currentTarget); - const item = $(event.currentTarget).find('input'); - - if($(item).is(':checked')) { - $(item).prop('checked', false); - parent.removeClass('warning').delay(200); - } else { - $(item).prop('checked', true); - parent.addClass('warning').delay(200); - } - } - - highlightAll(event) { - let parent; - const item = $(event.currentTarget).find('input'); - - if($(item).is(':checked')) { - $('#file_listing input[type=checkbox]').prop('checked', false); - $('#file_listing input[data-action="addSelection"]').each(function() { - parent = $(this).closest('tr'); - parent.removeClass('warning').delay(200); - }); - } else { - $('#file_listing input[type=checkbox]').prop('checked', true); - $('#file_listing input[data-action="addSelection"]').each(function() { - parent = $(this).closest('tr'); - parent.addClass('warning').delay(200); - }); - } - } - - deleteSelected() { - let selectedItems = []; - let selectedItemsElements = []; - let parent; - let nameBlock; - let delLocation; - - $('#file_listing input[data-action="addSelection"]:checked').each(function() { - parent = $(this).closest('tr'); - nameBlock = $(parent).find('td[data-identifier="name"]'); - delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name')); - - selectedItems.push(delLocation); - selectedItemsElements.push(parent); - }); - - if (selectedItems.length != 0) - { - let formattedItems = ""; - let i = 0; - let self = this; - - $.each(selectedItems, function(key, value) { - formattedItems += ("" + self.sanitizedString(value) + ", "); - i++; - return i < 5; - }); - - formattedItems = formattedItems.slice(0, -2); - if (selectedItems.length > 5) { - formattedItems += ', and ' + (selectedItems.length - 5) + ' other(s)'; - } - - swal({ - type: 'warning', - title: '', - text: 'Are you sure you want to delete the following files: ' + formattedItems + '?', - html: true, - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true - }, () => { - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`, - timeout: 10000, - data: JSON.stringify({ - items: selectedItems - }), - }).done(data => { - $('#file_listing input:checked').each(function() { - $(this).prop('checked', false); - }); - - $.each(selectedItemsElements, function() { - $(this).addClass('warning').delay(200).fadeOut(); - }) - - swal({ - type: 'success', - title: 'Files Deleted' - }); - }).fail(jqXHR => { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: 'An error occurred while attempting to delete these files. Please try again.', - }); - }); - }); - } else { - swal({ - type: 'warning', - title: '', - text: 'Please select files/folders to delete.', - }); - } - } - - decompress() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const compPath = decodeURIComponent(nameBlock.data('path')); - const compName = decodeURIComponent(nameBlock.data('name')); - - swal({ - title: ' Decompressing...', - text: 'This might take a few seconds to complete.', - html: true, - allowOutsideClick: false, - allowEscapeKey: false, - showConfirmButton: false, - }); - - $.ajax({ - type: 'POST', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`, - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - data: JSON.stringify({ - files: `${compPath}${compName}` - }) - }).done(data => { - swal.close(); - Files.list(compPath); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: error - }); - }); - } - - compress() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const compPath = decodeURIComponent(nameBlock.data('path')); - const compName = decodeURIComponent(nameBlock.data('name')); - - $.ajax({ - type: 'POST', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`, - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - data: JSON.stringify({ - files: `${compPath}${compName}`, - to: compPath.toString() - }) - }).done(data => { - Files.list(compPath, err => { - if (err) return; - const fileListing = $('#file_listing').find(`[data-name="${data.saved_as}"]`).parent(); - fileListing.addClass('success pulsate').delay(3000).queue(() => { - fileListing.removeClass('success pulsate').dequeue(); - }); - }); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: this.sanitizedString(error) - }); - }); - } -} diff --git a/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js b/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js deleted file mode 100644 index 6796f8b09..000000000 --- a/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js +++ /dev/null @@ -1,203 +0,0 @@ -"use strict"; - -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -class ContextMenuClass { - constructor() { - this.activeLine = null; - } - - run() { - this.directoryClick(); - this.rightClick(); - } - - makeMenu(parent) { - $(document).find('#fileOptionMenu').remove(); - if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active'); - - let newFilePath = $('#file_listing').data('current-dir'); - if (parent.data('type') === 'folder') { - const nameBlock = parent.find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - newFilePath = `${currentPath}${currentName}`; - } - - let buildMenu = ''; - return buildMenu; - } - - rightClick() { - $('[data-action="toggleMenu"]').on('mousedown', event => { - event.preventDefault(); - if ($(document).find('#fileOptionMenu').is(':visible')) { - $('body').trigger('click'); - return; - } - this.showMenu(event); - }); - $('#file_listing > tbody td').on('contextmenu', event => { - this.showMenu(event); - }); - } - - showMenu(event) { - const parent = $(event.target).closest('tr'); - const menu = $(this.makeMenu(parent)); - - if (parent.data('type') === 'disabled') return; - event.preventDefault(); - - $(menu).appendTo('body'); - $(menu).data('invokedOn', $(event.target)).show().css({ - position: 'absolute', - left: event.pageX - 150, - top: event.pageY, - }); - - this.activeLine = parent; - this.activeLine.addClass('active'); - - // Handle Events - const Actions = new ActionsClass(parent, menu); - if (Pterodactyl.permissions.moveFiles) { - $(menu).find('li[data-action="move"]').unbind().on('click', e => { - e.preventDefault(); - Actions.move(); - }); - $(menu).find('li[data-action="rename"]').unbind().on('click', e => { - e.preventDefault(); - Actions.rename(); - }); - } - - if (Pterodactyl.permissions.copyFiles) { - $(menu).find('li[data-action="copy"]').unbind().on('click', e => { - e.preventDefault(); - Actions.copy(); - }); - } - - if (Pterodactyl.permissions.compressFiles) { - if (parent.data('type') === 'folder') { - $(menu).find('li[data-action="compress"]').removeClass('hidden'); - } - $(menu).find('li[data-action="compress"]').unbind().on('click', e => { - e.preventDefault(); - Actions.compress(); - }); - } - - if (Pterodactyl.permissions.decompressFiles) { - if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) { - $(menu).find('li[data-action="decompress"]').removeClass('hidden'); - } - $(menu).find('li[data-action="decompress"]').unbind().on('click', e => { - e.preventDefault(); - Actions.decompress(); - }); - } - - if (Pterodactyl.permissions.createFiles) { - $(menu).find('li[data-action="folder"]').unbind().on('click', e => { - e.preventDefault(); - Actions.folder(); - }); - } - - if (Pterodactyl.permissions.downloadFiles) { - if (parent.data('type') === 'file') { - $(menu).find('li[data-action="download"]').removeClass('hidden'); - } - $(menu).find('li[data-action="download"]').unbind().on('click', e => { - e.preventDefault(); - Actions.download(); - }); - } - - if (Pterodactyl.permissions.deleteFiles) { - $(menu).find('li[data-action="delete"]').unbind().on('click', e => { - e.preventDefault(); - Actions.delete(); - }); - } - - $(window).unbind().on('click', event => { - if($(event.target).is('.disable-menu-hide')) { - event.preventDefault(); - return; - } - $(menu).unbind().remove(); - if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active'); - }); - } - - directoryClick() { - $('a[data-action="directory-view"]').on('click', function (event) { - event.preventDefault(); - - const path = $(this).parent().data('path') || ''; - const name = $(this).parent().data('name') || ''; - - window.location.hash = encodeURIComponent(path + name); - Files.list(); - }); - } -} - -window.ContextMenu = new ContextMenuClass; diff --git a/public/themes/pterodactyl/js/frontend/files/src/index.js b/public/themes/pterodactyl/js/frontend/files/src/index.js deleted file mode 100644 index 83c1460bb..000000000 --- a/public/themes/pterodactyl/js/frontend/files/src/index.js +++ /dev/null @@ -1,139 +0,0 @@ -"use strict"; - -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -class FileManager { - constructor() { - this.list(this.decodeHash()); - } - - list(path, next) { - if (_.isUndefined(path)) { - path = this.decodeHash(); - } - - this.loader(true); - $.ajax({ - type: 'POST', - url: Pterodactyl.meta.directoryList, - headers: { - 'X-CSRF-Token': Pterodactyl.meta.csrftoken, - }, - data: { - directory: path, - }, - }).done(data => { - this.loader(false); - $('#load_files').slideUp(10).html(data).slideDown(10, () => { - ContextMenu.run(); - this.reloadFilesButton(); - this.addFolderButton(); - this.selectItem(); - this.selectAll(); - this.selectiveDeletion(); - this.selectRow(); - if (_.isFunction(next)) { - return next(); - } - }); - $('#internal_alert').slideUp(); - - if (typeof Siofu === 'object') { - Siofu.listenOnInput(document.getElementById("files_touch_target")); - } - }).fail(jqXHR => { - this.loader(false); - if (_.isFunction(next)) { - return next(new Error('Failed to load file listing.')); - } - - if ((path !== '' && path !== '/') && jqXHR.status === 404) { - return this.list('', next); - } - - swal({ - type: 'error', - title: 'File Error', - text: jqXHR.responseJSON.errors[0].detail || 'An error occurred while attempting to process this request. Please try again.', - }); - console.error(jqXHR); - }); - } - - loader(show) { - if (show){ - $('.file-overlay').fadeIn(100); - } else { - $('.file-overlay').fadeOut(100); - } - } - - reloadFilesButton() { - $('i[data-action="reload-files"]').unbind().on('click', () => { - $('i[data-action="reload-files"]').addClass('fa-spin'); - this.list(); - }); - } - - selectItem() { - $('[data-action="addSelection"]').on('click', event => { - event.preventDefault(); - }); - } - - selectAll() { - $('[data-action="selectAll"]').on('click', event => { - event.preventDefault(); - }); - } - - selectiveDeletion() { - $('[data-action="selective-deletion"]').on('mousedown', event => { - new ActionsClass().deleteSelected(); - }); - } - - addFolderButton() { - $('[data-action="add-folder"]').unbind().on('click', () => { - new ActionsClass().folder($('#file_listing').data('current-dir') || '/'); - }); - } - - selectRow() { - $('#file_listing tr').on('mousedown', event => { - if (event.which === 1) { - if ($(event.target).is('th') || $(event.target).is('input[data-action="selectAll"]')) { - new ActionsClass().highlightAll(event); - } else if ($(event.target).is('td') || $(event.target).is('input[data-action="addSelection"]')) { - new ActionsClass().toggleHighlight(event); - } - - new ActionsClass().toggleMassActions(); - } - }); - } - - decodeHash() { - return decodeURIComponent(window.location.hash.substring(1)); - } - -} - -window.Files = new FileManager; diff --git a/public/themes/pterodactyl/js/frontend/files/upload.js b/public/themes/pterodactyl/js/frontend/files/upload.js deleted file mode 100644 index 873fbf636..000000000 --- a/public/themes/pterodactyl/js/frontend/files/upload.js +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -(function initUploader() { - var notifyUploadSocketError = false; - uploadSocket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/upload/' + Pterodactyl.server.uuid, { - 'query': 'token=' + Pterodactyl.server.daemonSecret, - }); - - uploadSocket.io.on('connect_error', function (err) { - if(typeof notifyUploadSocketError !== 'object') { - notifyUploadSocketError = $.notify({ - message: 'There was an error attempting to establish a connection to the uploader endpoint.

    ' + err, - }, { - type: 'danger', - delay: 0 - }); - } - }); - - uploadSocket.on('error', err => { - Siofu.destroy(); - console.error(err); - }); - - uploadSocket.on('connect', function () { - if (notifyUploadSocketError !== false) { - notifyUploadSocketError.close(); - notifyUploadSocketError = false; - } - }); - - window.Siofu = new SocketIOFileUpload(uploadSocket); - Siofu.listenOnDrop(document.getElementById("load_files")); - - if (document.getElementById("files_touch_target")) { - Siofu.listenOnInput(document.getElementById("files_touch_target")); - } - - window.addEventListener('dragover', function (event) { - event.preventDefault(); - }, false); - - window.addEventListener('drop', function (event) { - event.preventDefault(); - }, false); - - window.foldersDetectedInDrag = function (event) { - var folderDetected = false; - var files = event.dataTransfer.files; - for (var i = 0, f; f = files[i]; i++) { - if (!f.type && f.size === 0) { - return true; - } - } - - return folderDetected; - }; - - var dropCounter = 0; - $('#load_files').bind({ - dragenter: function (event) { - event.preventDefault(); - dropCounter++; - $(this).addClass('hasFileHover'); - }, - dragleave: function (event) { - dropCounter--; - if (dropCounter === 0) { - $(this).removeClass('hasFileHover'); - } - }, - drop: function (event) { - if (window.foldersDetectedInDrag(event.originalEvent)) { - $.notify({ - message: 'Folder uploads are not supported. Please use SFTP to upload whole directories.', - }, { - type: 'warning', - delay: 0 - }); - } - - dropCounter = 0; - $(this).removeClass('hasFileHover'); - } - }); - - Siofu.addEventListener('start', function (event) { - window.onbeforeunload = function () { - return 'A file upload in in progress, are you sure you want to continue?'; - }; - event.file.meta.path = $('#file_listing').data('current-dir'); - event.file.meta.identifier = Math.random().toString(36).slice(2); - - $('#append_files_to').append(' \ - \ - ' + event.file.name + ' \ -   \ - \ - \ -
    \ -
    \ -
    \ - \ - \ - '); - }); - - Siofu.addEventListener('progress', function(event) { - window.onbeforeunload = function () { - return 'A file upload in in progress, are you sure you want to continue?'; - }; - var percent = event.bytesLoaded / event.file.size * 100; - if (percent >= 100) { - $('.prog-bar-' + event.file.meta.identifier).css('width', '100%').removeClass('progress-bar-info').addClass('progress-bar-success').parent().removeClass('active'); - } else { - $('.prog-bar-' + event.file.meta.identifier).css('width', percent + '%'); - } - }); - - // Do something when a file is uploaded: - Siofu.addEventListener('complete', function(event) { - window.onbeforeunload = function () {}; - if (!event.success) { - $('.prog-bar-' + event.file.meta.identifier).css('width', '100%').removeClass('progress-bar-info').addClass('progress-bar-danger'); - $.notify({ - message: 'An error was encountered while attempting to upload this file.' - }, { - type: 'danger', - delay: 5000 - }); - } - }); - - Siofu.addEventListener('error', function(event) { - window.onbeforeunload = function () {}; - console.error(event); - $('.prog-bar-' + event.file.meta.identifier).css('width', '100%').removeClass('progress-bar-info').addClass('progress-bar-danger'); - $.notify({ - message: 'An error was encountered while attempting to upload this file: ' + event.message + '.', - }, { - type: 'danger', - delay: 8000 - }); - }); -})(); diff --git a/public/themes/pterodactyl/js/frontend/server.socket.js b/public/themes/pterodactyl/js/frontend/server.socket.js deleted file mode 100644 index 09063351d..000000000 --- a/public/themes/pterodactyl/js/frontend/server.socket.js +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -$('#console-popout').on('click', function (event) { - event.preventDefault(); - window.open($(this).attr('href'), 'Pterodactyl Console', 'width=800,height=400'); -}); -var Server = (function () { - - function initSocket() { - if (typeof $.notifyDefaults !== 'function') { - console.error('Notify does not appear to be loaded.'); - return; - } - - if (typeof io !== 'function') { - console.error('Socket.io is required to use this panel.'); - return; - } - - $.notifyDefaults({ - placement: { - from: 'bottom', - align: 'right' - }, - newest_on_top: true, - delay: 2000, - offset: { - x: 20, - y: 60, - }, - animate: { - enter: 'animated bounceInUp', - exit: 'animated bounceOutDown' - } - }); - - var notifySocketError = false; - - window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/ws/' + Pterodactyl.server.uuid, { - 'query': 'token=' + Pterodactyl.server.daemonSecret, - }); - - Socket.on('error', function (err) { - if(typeof notifySocketError !== 'object') { - notifySocketError = $.notify({ - message: 'There was an error attempting to establish a WebSocket connection to the Daemon. This panel will not work as expected.

    ' + err, - }, { - type: 'danger', - delay: 0, - }); - } - setStatusIcon(999); - }); - - Socket.io.on('connect_error', function (err) { - if(typeof notifySocketError !== 'object') { - notifySocketError = $.notify({ - message: 'There was an error attempting to establish a WebSocket connection to the Daemon. This panel will not work as expected.

    ' + err, - }, { - type: 'danger', - delay: 0, - }); - } - setStatusIcon(999); - }); - - // Connected to Socket Successfully - Socket.on('connect', function () { - if (notifySocketError !== false) { - notifySocketError.close(); - notifySocketError = false; - } - }); - - Socket.on('initial status', function (data) { - setStatusIcon(data.status); - }); - - Socket.on('status', function (data) { - setStatusIcon(data.status); - }); - } - - function setStatusIcon(status) { - switch (status) { - case 0: - $('#server_status_icon').html(' Offline'); - break; - case 1: - $('#server_status_icon').html(' Online'); - break; - case 2: - $('#server_status_icon').html(' Starting'); - break; - case 3: - $('#server_status_icon').html(' Stopping'); - break; - default: - $('#server_status_icon').html(' Connection Error'); - break; - } - } - - return { - init: function () { - initSocket(); - }, - - setStatusIcon: setStatusIcon, - } - -})(); - -Server.init(); diff --git a/public/themes/pterodactyl/js/frontend/serverlist.js b/public/themes/pterodactyl/js/frontend/serverlist.js deleted file mode 100644 index 6e4d5c4e4..000000000 --- a/public/themes/pterodactyl/js/frontend/serverlist.js +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -(function updateServerStatus() { - var Status = { - 0: 'Offline', - 1: 'Online', - 2: 'Starting', - 3: 'Stopping' - }; - $('.dynamic-update').each(function (index, data) { - var element = $(this); - var serverShortUUID = $(this).data('server'); - - $.ajax({ - type: 'GET', - url: Router.route('index.status', { server: serverShortUUID }), - timeout: 5000, - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - if (typeof data.status === 'undefined') { - element.find('[data-action="status"]').html('Error'); - return; - } - switch (data.status) { - case 0: - element.find('[data-action="status"]').html('Offline'); - break; - case 1: - element.find('[data-action="status"]').html('Online'); - break; - case 2: - element.find('[data-action="status"]').html('Starting'); - break; - case 3: - element.find('[data-action="status"]').html('Stopping'); - break; - case 20: - element.find('[data-action="status"]').html('Installing'); - break; - case 30: - element.find('[data-action="status"]').html('Suspended'); - break; - } - if (data.status > 0 && data.status < 4) { - var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); - var currentCpu = data.proc.cpu.total; - if (cpuMax !== 0) { - currentCpu = parseFloat(((data.proc.cpu.total / cpuMax) * 100).toFixed(2).toString()); - } - if (data.status !== 0) { - var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); - var currentCpu = data.proc.cpu.total; - if (cpuMax !== 0) { - currentCpu = parseFloat(((data.proc.cpu.total / cpuMax) * 100).toFixed(2).toString()); - } - element.find('[data-action="memory"]').html(parseInt(data.proc.memory.total / (1024 * 1024))); - element.find('[data-action="cpu"]').html(currentCpu); - element.find('[data-action="disk"]').html(parseInt(data.proc.disk.used)); - } else { - element.find('[data-action="memory"]').html('--'); - element.find('[data-action="cpu"]').html('--'); - element.find('[data-action="disk"]').html('--'); - } - } - }).fail(function (jqXHR) { - if (jqXHR.status === 504) { - element.find('[data-action="status"]').html('Gateway Timeout'); - } else { - element.find('[data-action="status"]').html('Error'); - } - }); - }).promise().done(function () { - setTimeout(updateServerStatus, 10000); - }); -})(); diff --git a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js deleted file mode 100644 index 857c32cfa..000000000 --- a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -$(document).ready(function () { - $('[data-toggle="tooltip"]').tooltip(); - $('[data-action="delete-schedule"]').click(function () { - var self = $(this); - swal({ - type: 'error', - title: 'Delete Schedule?', - text: 'Are you sure you want to delete this schedule? There is no undo.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Delete Schedule', - confirmButtonColor: '#d9534f', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'DELETE', - url: Router.route('server.schedules.view', { - server: Pterodactyl.server.uuidShort, - schedule: self.data('schedule-id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Schedule has been deleted.' - }); - self.parent().parent().slideUp(); - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occurred while attempting to delete this schedule.' - }); - }); - }); - }); - - $('[data-action="trigger-schedule"]').click(function (event) { - event.preventDefault(); - var self = $(this); - swal({ - type: 'info', - title: 'Trigger Schedule', - text: 'This will run the selected schedule now.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Continue', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'POST', - url: Router.route('server.schedules.trigger', { - server: Pterodactyl.server.uuidShort, - schedule: self.data('schedule-id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Schedule has been added to the next-run queue.' - }); - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occurred while attempting to trigger this schedule.' - }); - }); - }); - }); - - $('[data-action="toggle-schedule"]').click(function (event) { - var self = $(this); - swal({ - type: 'info', - title: 'Toggle Schedule', - text: 'This will toggle the selected schedule.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Continue', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'POST', - url: Router.route('server.schedules.toggle', { - server: Pterodactyl.server.uuidShort, - schedule: self.data('schedule-id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Schedule has been toggled.' - }); - if (data.status !== 1) { - self.parent().parent().addClass('muted muted-hover'); - } else { - self.parent().parent().removeClass('muted muted-hover'); - } - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occurred while attempting to toggle this schedule.' - }); - }); - }); - }); -}); diff --git a/public/themes/pterodactyl/js/frontend/tasks/view-actions.js b/public/themes/pterodactyl/js/frontend/tasks/view-actions.js deleted file mode 100644 index 86b7f8561..000000000 --- a/public/themes/pterodactyl/js/frontend/tasks/view-actions.js +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -$(document).ready(function () { - function setupSelect2() { - $('select[name="tasks[time_value][]"]').select2(); - $('select[name="tasks[time_interval][]"]').select2(); - $('select[name="tasks[action][]"]').select2(); - } - - setupSelect2(); - - $('[data-action="update-field"]').on('change', function (event) { - event.preventDefault(); - var updateField = $(this).data('field'); - var selected = $(this).map(function (i, opt) { - return $(opt).val(); - }).toArray(); - if (selected.length === $(this).find('option').length) { - $('input[name=' + updateField + ']').val('*'); - } else { - $('input[name=' + updateField + ']').val(selected.join(',')); - } - }); - - $('button[data-action="add-new-task"]').on('click', function () { - if ($('#containsTaskList').find('.task-list-item').length >= 5) { - swal('Task Limit Reached', 'You may only assign a maximum of 5 tasks to one schedule.'); - return; - } - - var clone = $('div[data-target="task-clone"]').clone(); - clone.insertBefore('#taskAppendBefore').removeAttr('data-target'); - clone.find('select:first').attr('selected'); - clone.find('input').val(''); - clone.find('span.select2-container').remove(); - clone.find('div[data-attribute="remove-task-element"]').addClass('input-group').find('div.input-group-btn').removeClass('hidden'); - clone.find('button[data-action="remove-task"]').on('click', function () { - clone.remove(); - }); - setupSelect2(); - $(this).data('element', clone); - }); -}); diff --git a/resources/views/admin/api/index.blade.php b/resources/views/admin/api/index.blade.php index 7d82b3292..d863c5779 100644 --- a/resources/views/admin/api/index.blade.php +++ b/resources/views/admin/api/index.blade.php @@ -77,7 +77,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.api.delete', { identifier: self.data('attr') }), + url: '/admin/api/revoke/' + self.data('attr'), headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' } diff --git a/resources/views/admin/nodes/view/allocation.blade.php b/resources/views/admin/nodes/view/allocation.blade.php index bab9fa5d1..c6e30db5d 100644 --- a/resources/views/admin/nodes/view/allocation.blade.php +++ b/resources/views/admin/nodes/view/allocation.blade.php @@ -218,7 +218,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.nodes.view.allocation.removeSingle', { node: Pterodactyl.node.id, allocation: allocation }), + url: '/admin/nodes/view/' + Pterodactyl.node.id + '/allocation/remove/' + allocation, headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, }).done(function (data) { element.parent().parent().addClass('warning').delay(100).fadeOut(); @@ -247,7 +247,7 @@ clearTimeout(fadeTimers[element.data('id')]); $.ajax({ method: 'POST', - url: Router.route('admin.nodes.view.allocation.setAlias', { id: Pterodactyl.node.id }), + url: '/admin/nodes/view/' + Pterodactyl.node.id + '/allocation/alias', headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, data: { alias: element.val(), @@ -321,9 +321,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.nodes.view.allocation.removeMultiple', { - node: Pterodactyl.node.id - }), + url: '/admin/nodes/view/' + Pterodactyl.node.id + '/allocations', headers: {'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')}, data: JSON.stringify({ allocations: selectedIds diff --git a/resources/views/admin/packs/new.blade.php b/resources/views/admin/packs/new.blade.php index 1595083e9..5778555aa 100644 --- a/resources/views/admin/packs/new.blade.php +++ b/resources/views/admin/packs/new.blade.php @@ -130,7 +130,7 @@ $.ajax({ method: 'GET', - url: Router.route('admin.packs.new.template'), + url: '/admin/packs/new/template', headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, }).fail(function (jqXhr) { console.error(jqXhr); diff --git a/resources/views/admin/servers/view/database.blade.php b/resources/views/admin/servers/view/database.blade.php index 5bea4be77..962eafd0c 100644 --- a/resources/views/admin/servers/view/database.blade.php +++ b/resources/views/admin/servers/view/database.blade.php @@ -131,7 +131,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.servers.view.database.delete', { server: '{{ $server->id }}', database: self.data('id') }), + url: '/admin/servers/view/{{ $server->id }}/database/' + self.data('id') + '/delete', headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, }).done(function () { self.parent().parent().slideUp(); @@ -152,7 +152,7 @@ $(this).addClass('disabled').find('i').addClass('fa-spin'); $.ajax({ type: 'PATCH', - url: Router.route('admin.servers.view.database', { server: '{{ $server->id }}' }), + url: '/admin/servers/view/{{ $server->id }}/database', headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, data: { database: $(this).data('id') }, }).done(function (data) { diff --git a/resources/views/admin/servers/view/details.blade.php b/resources/views/admin/servers/view/details.blade.php index a84a9144c..21075377b 100644 --- a/resources/views/admin/servers/view/details.blade.php +++ b/resources/views/admin/servers/view/details.blade.php @@ -85,7 +85,7 @@ - {!! Theme::js('js/laroute.js?t={cache-version}') !!} {!! 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}') !!} diff --git a/resources/views/layouts/error.blade.php b/resources/views/layouts/error.blade.php index 0ec1b00fa..89b85fd2d 100644 --- a/resources/views/layouts/error.blade.php +++ b/resources/views/layouts/error.blade.php @@ -60,7 +60,6 @@
    @section('footer-scripts') - {!! Theme::js('js/laroute.js?t={cache-version}') !!} {!! Theme::js('vendor/jquery/jquery.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}') !!} diff --git a/resources/views/layouts/master.blade.php b/resources/views/layouts/master.blade.php index f83a6f151..6e8944b8d 100644 --- a/resources/views/layouts/master.blade.php +++ b/resources/views/layouts/master.blade.php @@ -277,7 +277,6 @@ {!! Theme::js('js/keyboard.polyfill.js?t={cache-version}') !!} - {!! Theme::js('js/laroute.js?t={cache-version}') !!} {!! 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}') !!} From 2cabfeec15331ff82aff819989cacfa708d6175c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 15 Dec 2019 18:31:15 -0800 Subject: [PATCH 57/98] Daemon secret is not a thing anymore --- .../Servers/ServerCreationService.php | 2 -- .../Controllers/JavascriptInjection.php | 30 ------------------- 2 files changed, 32 deletions(-) diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index ca663262a..a76787933 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Services\Servers; use Ramsey\Uuid\Uuid; use Illuminate\Support\Arr; -use Illuminate\Support\Str; use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; @@ -228,7 +227,6 @@ class ServerCreationService 'egg_id' => Arr::get($data, 'egg_id'), 'pack_id' => empty($data['pack_id']) ? null : $data['pack_id'], 'startup' => Arr::get($data, 'startup'), - 'daemonSecret' => Str::random(Node::DAEMON_SECRET_LENGTH), 'image' => Arr::get($data, 'image'), 'database_limit' => Arr::get($data, 'database_limit'), 'allocation_limit' => Arr::get($data, 'allocation_limit'), diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php index 2f0e472e5..556c99550 100644 --- a/app/Traits/Controllers/JavascriptInjection.php +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -32,36 +32,6 @@ trait JavascriptInjection return $this; } - /** - * Injects server javascript into the page to be used by other services. - * - * @param array $args - * @param bool $overwrite - * @return array - */ - public function injectJavascript($args = [], $overwrite = false) - { - $request = $this->request ?? app()->make(Request::class); - $server = $request->attributes->get('server'); - $token = $request->attributes->get('server_token'); - - $response = array_merge_recursive([ - 'server' => [ - 'uuid' => $server->uuid, - 'uuidShort' => $server->uuidShort, - 'daemonSecret' => $token, - ], - 'server_token' => $token, - 'node' => [ - 'fqdn' => $server->node->fqdn, - 'scheme' => $server->node->scheme, - 'daemonListen' => $server->node->daemonListen, - ], - ], $args); - - return Javascript::put($overwrite ? $args : $response); - } - /** * Injects the exact array passed in, nothing more. * From ba0757f05cbc99ba0de17e9f40394ac52e893f43 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 15 Dec 2019 18:50:35 -0800 Subject: [PATCH 58/98] Dont run checker in prod builds --- webpack.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 7266ef700..9d1fc731d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,7 +20,6 @@ let plugins = [ integrity: true, integrityHashes: ['sha384'], }), - new ForkTsCheckerWebpackPlugin(), ]; if (isProduction) { @@ -43,6 +42,8 @@ if (isProduction) { ], }), ]); +} else { + plugins.concat([new ForkTsCheckerWebpackPlugin()]); } module.exports = { From d07ee9a36be97c1043e2fddc2fbaef89b680f3ac Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 15 Dec 2019 19:10:01 -0800 Subject: [PATCH 59/98] Add create directory button --- .../api/server/files/createDirectory.ts | 9 ++ .../server/files/FileManagerContainer.tsx | 69 ++++++++------ .../server/files/NewDirectoryButton.tsx | 89 +++++++++++++++++++ 3 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 resources/scripts/api/server/files/createDirectory.ts create mode 100644 resources/scripts/components/server/files/NewDirectoryButton.tsx diff --git a/resources/scripts/api/server/files/createDirectory.ts b/resources/scripts/api/server/files/createDirectory.ts new file mode 100644 index 000000000..588868655 --- /dev/null +++ b/resources/scripts/api/server/files/createDirectory.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, root: string, name: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/files/create-folder`, { root, name }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 86652a4b1..6e5b13320 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -8,6 +8,13 @@ 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'; +import { FileObject } from '@/api/server/files/loadDirectory'; +import NewDirectoryButton from '@/components/server/files/NewDirectoryButton'; + +const sortFiles = (files: FileObject[]): FileObject[] => { + return files.sort((a, b) => a.name.localeCompare(b.name)) + .sort((a, b) => a.isFile === b.isFile ? 0 : (a.isFile ? 1 : -1)); +}; export default () => { const [ loading, setLoading ] = useState(true); @@ -37,34 +44,46 @@ export default () => { loading ? : - !files.length ? -

    - This directory seems to be empty. -

    - : - -
    - {files.length > 250 ? - -
    -

    - This directory is too large to display in the browser, limiting - the output to the first 250 files. -

    -
    - { - files.slice(0, 250).map(file => ( + + {!files.length ? +

    + This directory seems to be empty. +

    + : + + +
    + {files.length > 250 ? + +
    +

    + This directory is too large to display in the browser, + limiting + the output to the first 250 files. +

    +
    + { + sortFiles(files.slice(0, 250)).map(file => ( + + )) + } +
    + : + sortFiles(files).map(file => ( )) } - - : - files.map(file => ( - - )) - } -
    -
    +
    + +
    + } +
    + + +
    + }
    diff --git a/resources/scripts/components/server/files/NewDirectoryButton.tsx b/resources/scripts/components/server/files/NewDirectoryButton.tsx new file mode 100644 index 000000000..dfd2bde1f --- /dev/null +++ b/resources/scripts/components/server/files/NewDirectoryButton.tsx @@ -0,0 +1,89 @@ +import React, { useState } from 'react'; +import Modal from '@/components/elements/Modal'; +import { ServerContext } from '@/state/server'; +import { Form, Formik, FormikActions } from 'formik'; +import Field from '@/components/elements/Field'; +import { join } from 'path'; +import { object, string } from 'yup'; +import createDirectory from '@/api/server/files/createDirectory'; +import v4 from 'uuid/v4'; + +interface Values { + directoryName: string; +} + +const schema = object().shape({ + directoryName: string().required('A valid directory name must be provided.'), +}); + +export default () => { + const [ visible, setVisible ] = useState(false); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const directory = ServerContext.useStoreState(state => state.files.directory); + const pushFile = ServerContext.useStoreActions(actions => actions.files.pushFile); + + const submit = (values: Values, { setSubmitting }: FormikActions) => { + createDirectory(uuid, directory, values.directoryName) + .then(() => { + pushFile({ + uuid: v4(), + name: values.directoryName, + mode: '0644', + size: 0, + isFile: false, + isEditable: false, + isSymlink: false, + mimetype: '', + createdAt: new Date(), + modifiedAt: new Date(), + }); + setVisible(false); + }) + .catch(error => { + console.error(error); + setSubmitting(false); + }); + }; + + return ( + + + {({ resetForm, isSubmitting, values }) => ( + { + setVisible(false); + resetForm(); + }} + > +
    + +

    + This directory will be created as +  /home/container/{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')} +

    +
    + +
    + +
    + )} +
    + +
    + ); +}; From 85b47ceb79922dd6c96049c84dea13a5d5bd1652 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 16 Dec 2019 19:55:02 -0800 Subject: [PATCH 60/98] Slightly more logical handling when moving things around --- .../components/server/files/RenameFileModal.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/resources/scripts/components/server/files/RenameFileModal.tsx b/resources/scripts/components/server/files/RenameFileModal.tsx index 7f50c8413..a94b91034 100644 --- a/resources/scripts/components/server/files/RenameFileModal.tsx +++ b/resources/scripts/components/server/files/RenameFileModal.tsx @@ -17,7 +17,7 @@ type Props = RequiredModalProps & { file: FileObject; useMoveTerminology?: boole export default ({ file, useMoveTerminology, ...props }: Props) => { const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const directory = ServerContext.useStoreState(state => state.files.directory); - const pushFile = ServerContext.useStoreActions(actions => actions.files.pushFile); + const { pushFile, removeFile } = ServerContext.useStoreActions(actions => actions.files); const submit = (values: FormikValues, { setSubmitting }: FormikActions) => { const renameFrom = join(directory, file.name); @@ -25,7 +25,14 @@ export default ({ file, useMoveTerminology, ...props }: Props) => { renameFile(uuid, { renameFrom, renameTo }) .then(() => { - pushFile({ ...file, name: values.name }); + if (!useMoveTerminology && values.name.split('/').length === 1) { + pushFile({ ...file, name: values.name }); + } + + if ((useMoveTerminology || values.name.split('/').length > 1) && file.uuid.length > 0) { + removeFile(file.uuid); + } + props.onDismissed(); }) .catch(error => { From 2a92304023fc6d863d8e0d0b6c37062ad58cf614 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 16 Dec 2019 21:02:30 -0800 Subject: [PATCH 61/98] Fix server creation logic --- .../Admin/Servers/CreateServerController.php | 4 +-- .../Eloquent/ServerRepository.php | 16 +++++++----- .../Servers/ServerCreationService.php | 25 +++++++++++++++++-- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Admin/Servers/CreateServerController.php b/app/Http/Controllers/Admin/Servers/CreateServerController.php index eb8f74da6..f63cf814b 100644 --- a/app/Http/Controllers/Admin/Servers/CreateServerController.php +++ b/app/Http/Controllers/Admin/Servers/CreateServerController.php @@ -111,16 +111,16 @@ class CreateServerController extends Controller * * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + * @throws \Throwable */ public function store(ServerFormRequest $request) { $server = $this->creationService->handle( - $request->validated() + $request->except(['_token']) ); $this->alert->success( diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index 88556234a..874b1cc95 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -3,9 +3,9 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\User; -use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Repositories\Concerns\Searchable; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -273,12 +273,16 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt */ public function getByUuid(string $uuid): Server { - Assert::notEmpty($uuid, 'Expected non-empty string as first argument passed to ' . __METHOD__); - try { - return $this->getBuilder()->with('nest', 'node')->where(function ($query) use ($uuid) { - $query->where('uuidShort', $uuid)->orWhere('uuid', $uuid); - })->firstOrFail($this->getColumns()); + /** @var \Pterodactyl\Models\Server $model */ + $model = $this->getBuilder() + ->with('nest', 'node') + ->where(function (Builder $query) use ($uuid) { + $query->where('uuidShort', $uuid)->orWhere('uuid', $uuid); + }) + ->firstOrFail($this->getColumns()); + + return $model; } catch (ModelNotFoundException $exception) { throw new RecordNotFoundException; } diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index a76787933..9cc1535a4 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -18,6 +18,7 @@ use Pterodactyl\Repositories\Eloquent\AllocationRepository; use Pterodactyl\Services\Deployment\FindViableNodesService; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; use Pterodactyl\Services\Deployment\AllocationSelectionService; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class ServerCreationService { @@ -71,6 +72,11 @@ class ServerCreationService */ private $daemonServerRepository; + /** + * @var \Pterodactyl\Services\Servers\ServerDeletionService + */ + private $serverDeletionService; + /** * CreationService constructor. * @@ -81,6 +87,7 @@ class ServerCreationService * @param \Pterodactyl\Repositories\Eloquent\EggRepository $eggRepository * @param \Pterodactyl\Services\Deployment\FindViableNodesService $findViableNodesService * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService + * @param \Pterodactyl\Services\Servers\ServerDeletionService $serverDeletionService * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository * @param \Pterodactyl\Repositories\Eloquent\ServerVariableRepository $serverVariableRepository * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService @@ -93,6 +100,7 @@ class ServerCreationService EggRepository $eggRepository, FindViableNodesService $findViableNodesService, ServerConfigurationStructureService $configurationStructureService, + ServerDeletionService $serverDeletionService, ServerRepository $repository, ServerVariableRepository $serverVariableRepository, VariableValidatorService $validatorService @@ -107,6 +115,7 @@ class ServerCreationService $this->repository = $repository; $this->serverVariableRepository = $serverVariableRepository; $this->daemonServerRepository = $daemonServerRepository; + $this->serverDeletionService = $serverDeletionService; } /** @@ -157,14 +166,26 @@ class ServerCreationService // Create the server and assign any additional allocations to it. $server = $this->createModel($data); + $this->storeAssignedAllocations($server, $data); $this->storeEggVariables($server, $eggVariableData); + // Due to the design of the Daemon, we need to persist this server to the disk + // before we can actually create it on the Daemon. + // + // If that connection fails out we will attempt to perform a cleanup by just + // deleting the server itself from the system. + $this->connection->commit(); + $structure = $this->configurationStructureService->handle($server); - $this->connection->transaction(function () use ($server, $structure) { + try { $this->daemonServerRepository->setServer($server)->create($structure); - }); + } catch (DaemonConnectionException $exception) { + $this->serverDeletionService->withForce(true)->handle($server); + + throw $exception; + } return $server; } From a2473103721a65f066e877da2454f57edd4636b5 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 16 Dec 2019 21:14:27 -0800 Subject: [PATCH 62/98] Fix nav for servers --- .../servers/partials/navigation.blade.php | 37 +++++++++++++++++++ .../views/admin/servers/view/build.blade.php | 19 +--------- .../admin/servers/view/database.blade.php | 19 +--------- .../views/admin/servers/view/delete.blade.php | 19 +--------- .../admin/servers/view/details.blade.php | 19 +--------- .../views/admin/servers/view/index.blade.php | 19 +--------- .../views/admin/servers/view/manage.blade.php | 19 +--------- .../admin/servers/view/startup.blade.php | 19 +--------- 8 files changed, 44 insertions(+), 126 deletions(-) create mode 100644 resources/views/admin/servers/partials/navigation.blade.php diff --git a/resources/views/admin/servers/partials/navigation.blade.php b/resources/views/admin/servers/partials/navigation.blade.php new file mode 100644 index 000000000..32316e611 --- /dev/null +++ b/resources/views/admin/servers/partials/navigation.blade.php @@ -0,0 +1,37 @@ +@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 index dde0617d2..b28f324ba 100644 --- a/resources/views/admin/servers/view/build.blade.php +++ b/resources/views/admin/servers/view/build.blade.php @@ -20,24 +20,7 @@ @endsection @section('content') -
    -
    - -
    -
    +@include('admin.servers.partials.navigation')
    diff --git a/resources/views/admin/servers/view/database.blade.php b/resources/views/admin/servers/view/database.blade.php index 962eafd0c..7798487c2 100644 --- a/resources/views/admin/servers/view/database.blade.php +++ b/resources/views/admin/servers/view/database.blade.php @@ -20,24 +20,7 @@ @endsection @section('content') -
    -
    - -
    -
    +@include('admin.servers.partials.navigation')
    diff --git a/resources/views/admin/servers/view/delete.blade.php b/resources/views/admin/servers/view/delete.blade.php index ec33ef037..bec32b954 100644 --- a/resources/views/admin/servers/view/delete.blade.php +++ b/resources/views/admin/servers/view/delete.blade.php @@ -20,24 +20,7 @@ @endsection @section('content') -
    -
    - -
    -
    +@include('admin.servers.partials.navigation')
    diff --git a/resources/views/admin/servers/view/details.blade.php b/resources/views/admin/servers/view/details.blade.php index 21075377b..9d5cc3125 100644 --- a/resources/views/admin/servers/view/details.blade.php +++ b/resources/views/admin/servers/view/details.blade.php @@ -20,24 +20,7 @@ @endsection @section('content') -
    -
    - -
    -
    +@include('admin.servers.partials.navigation')
    diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php index 417a9e0b7..3d75d4f4d 100644 --- a/resources/views/admin/servers/view/index.blade.php +++ b/resources/views/admin/servers/view/index.blade.php @@ -19,24 +19,7 @@ @endsection @section('content') -
    -
    - -
    -
    +@include('admin.servers.partials.navigation')
    diff --git a/resources/views/admin/servers/view/manage.blade.php b/resources/views/admin/servers/view/manage.blade.php index 57319b4e2..6980ad4c5 100644 --- a/resources/views/admin/servers/view/manage.blade.php +++ b/resources/views/admin/servers/view/manage.blade.php @@ -20,24 +20,7 @@ @endsection @section('content') -
    -
    - -
    -
    +@include('admin.servers.partials.navigation')
    diff --git a/resources/views/admin/servers/view/startup.blade.php b/resources/views/admin/servers/view/startup.blade.php index afbce8b95..0e339fa2a 100644 --- a/resources/views/admin/servers/view/startup.blade.php +++ b/resources/views/admin/servers/view/startup.blade.php @@ -20,24 +20,7 @@ @endsection @section('content') -
    -
    - -
    -
    +@include('admin.servers.partials.navigation')
    From 03f2c614315323dc9269b8d92e04b6a989ba2641 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 16 Dec 2019 21:25:48 -0800 Subject: [PATCH 63/98] Fix error when creating user --- app/Http/Controllers/Admin/UserController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 7889b4757..14b3b594f 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -147,7 +147,7 @@ class UserController extends Controller public function store(UserFormRequest $request) { $user = $this->creationService->handle($request->normalize()); - $this->alert->success($this->translator->trans('admin/user.notices.account_created'))->flash(); + $this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash(); return redirect()->route('admin.users.view', $user->id); } From 7405d7c8ee2f59cbcecddd874d050f767a6a1807 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 16 Dec 2019 21:27:01 -0800 Subject: [PATCH 64/98] Fix routing to server pages --- resources/views/admin/servers/index.blade.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php index 117a4de70..f8cd07991 100644 --- a/resources/views/admin/servers/index.blade.php +++ b/resources/views/admin/servers/index.blade.php @@ -66,8 +66,7 @@ @endif - - + @endforeach From d75073116f11db890bfaf6fd85ab2ecebbfaf8a6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 16 Dec 2019 21:42:58 -0800 Subject: [PATCH 65/98] Health check --- resources/views/admin/nodes/index.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php index b4ea579a1..6805e9ef4 100644 --- a/resources/views/admin/nodes/index.blade.php +++ b/resources/views/admin/nodes/index.blade.php @@ -55,7 +55,7 @@ @foreach ($nodes as $node) - + {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }} {{ $node->location->short }} {{ $node->memory }} MB @@ -87,7 +87,7 @@ type: 'GET', url: $(element).data('location'), headers: { - 'X-Access-Token': $(element).data('secret'), + 'Authorization': 'Bearer ' + $(element).data('secret'), }, timeout: 5000 }).done(function (data) { From e78421864575885f141a3915af5890fd5f81ed78 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 16:38:40 -0800 Subject: [PATCH 66/98] Add support for creating a new file --- .../scripts/components/elements/AceEditor.tsx | 2 +- .../server/files/FileEditContainer.tsx | 58 ++++++++++++++----- .../server/files/FileManagerBreadcrumbs.tsx | 17 ++++-- .../server/files/FileManagerContainer.tsx | 6 +- .../components/server/files/FileNameModal.tsx | 54 +++++++++++++++++ resources/scripts/routers/ServerRouter.tsx | 2 +- 6 files changed, 116 insertions(+), 23 deletions(-) create mode 100644 resources/scripts/components/server/files/FileNameModal.tsx diff --git a/resources/scripts/components/elements/AceEditor.tsx b/resources/scripts/components/elements/AceEditor.tsx index 238174d6f..3b01307fa 100644 --- a/resources/scripts/components/elements/AceEditor.tsx +++ b/resources/scripts/components/elements/AceEditor.tsx @@ -59,7 +59,7 @@ export interface Props { } export default ({ style, initialContent, initialModePath, fetchContent, onContentSaved }: Props) => { - const [ mode, setMode ] = useState('plain_text'); + const [ mode, setMode ] = useState('ace/mode/plain_text'); const [ editor, setEditor ] = useState(); const ref = useCallback(node => { diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index e8ff165f6..8c9b192b5 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -8,27 +8,33 @@ import { httpErrorToHuman } from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import saveFileContents from '@/api/server/files/saveFileContents'; import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; +import { useParams } from 'react-router'; +import FileNameModal from '@/components/server/files/FileNameModal'; const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor')); export default () => { - const { location: { hash } } = useRouter(); - const [ loading, setLoading ] = useState(true); + const { action } = useParams(); + const { history, location: { hash } } = useRouter(); + const [ loading, setLoading ] = useState(action === 'edit'); const [ content, setContent ] = useState(''); + const [ modalVisible, setModalVisible ] = useState(false); - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const { id, uuid } = ServerContext.useStoreState(state => state.server.data!); const addError = useStoreState((state: Actions) => state.flashes.addError); let fetchFileContent: null | (() => Promise) = null; - useEffect(() => { - getFileContents(uuid, hash.replace(/^#/, '')) - .then(setContent) - .catch(error => console.error(error)) - .then(() => setLoading(false)); - }, [ uuid, hash ]); + if (action !== 'new') { + useEffect(() => { + getFileContents(uuid, hash.replace(/^#/, '')) + .then(setContent) + .catch(error => console.error(error)) + .then(() => setLoading(false)); + }, [ uuid, hash ]); + } - const save = (e: React.MouseEvent) => { + const save = (name?: string) => { if (!fetchFileContent) { return; } @@ -36,7 +42,15 @@ export default () => { setLoading(true); fetchFileContent() .then(content => { - return saveFileContents(uuid, hash.replace(/^#/, ''), content); + return saveFileContents(uuid, name || hash.replace(/^#/, ''), content); + }) + .then(() => { + if (name) { + history.push(`/server/${id}/files/edit#${hash.replace(/^#/, '')}/${name}`); + return; + } + + return Promise.resolve(); }) .catch(error => { console.error(error); @@ -47,7 +61,15 @@ export default () => { return (
    - + + setModalVisible(false)} + onFileNamed={(name) => { + setModalVisible(false); + save(name); + }} + />
    { />
    - + {action === 'edit' ? + + : + + }
    ); diff --git a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx index 7f670e3d0..d0bee1a86 100644 --- a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx +++ b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx @@ -1,8 +1,14 @@ import React, { useEffect, useState } from 'react'; import { ServerContext } from '@/state/server'; -import { NavLink } from 'react-router-dom'; +import { NavLink, useParams } from 'react-router-dom'; -export default ({ withinFileEditor }: { withinFileEditor?: boolean }) => { +interface Props { + withinFileEditor?: boolean; + isNewFile?: boolean; +} + +export default ({ withinFileEditor, isNewFile }: Props) => { + const { action } = useParams(); const [ file, setFile ] = useState(null); const id = ServerContext.useStoreState(state => state.server.data!.id); const directory = ServerContext.useStoreState(state => state.files.directory); @@ -11,12 +17,12 @@ export default ({ withinFileEditor }: { withinFileEditor?: boolean }) => { useEffect(() => { const parts = window.location.hash.replace(/^#(\/)*/, '/').split('/'); - if (withinFileEditor) { + if (withinFileEditor && !isNewFile) { setFile(parts.pop() || null); } setDirectory(parts.join('/')); - }, [ withinFileEditor, setDirectory ]); + }, [ withinFileEditor, isNewFile, setDirectory ]); const breadcrumbs = (): { name: string; path?: string }[] => directory.split('/') .filter(directory => !!directory) @@ -28,6 +34,9 @@ export default ({ withinFileEditor }: { withinFileEditor?: boolean }) => { return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` }; }); + if (withinFileEditor) + console.log(breadcrumbs()); + return (
    /home/ diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 6e5b13320..2ed28f467 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -10,6 +10,7 @@ import FileObjectRow from '@/components/server/files/FileObjectRow'; import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs'; import { FileObject } from '@/api/server/files/loadDirectory'; import NewDirectoryButton from '@/components/server/files/NewDirectoryButton'; +import { Link } from 'react-router-dom'; const sortFiles = (files: FileObject[]): FileObject[] => { return files.sort((a, b) => a.name.localeCompare(b.name)) @@ -19,6 +20,7 @@ const sortFiles = (files: FileObject[]): FileObject[] => { export default () => { const [ loading, setLoading ] = useState(true); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const { id } = ServerContext.useStoreState(state => state.server.data!); const { contents: files, directory } = ServerContext.useStoreState(state => state.files); const { getDirectoryContents } = ServerContext.useStoreActions(actions => actions.files); @@ -79,9 +81,9 @@ export default () => { }
    - +
    } diff --git a/resources/scripts/components/server/files/FileNameModal.tsx b/resources/scripts/components/server/files/FileNameModal.tsx new file mode 100644 index 000000000..16aafc0d0 --- /dev/null +++ b/resources/scripts/components/server/files/FileNameModal.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import { Form, Formik, FormikActions } from 'formik'; +import { object, string } from 'yup'; +import Field from '@/components/elements/Field'; + +type Props = RequiredModalProps & { + onFileNamed: (name: string) => void; +}; + +interface Values { + fileName: string; +} + +export default ({ onFileNamed, onDismissed, ...props }: Props) => { + const submit = (values: Values, { setSubmitting }: FormikActions) => { + onFileNamed(values.fileName); + setSubmitting(false); + }; + + return ( + + {({ resetForm }) => ( + { + resetForm(); + onDismissed(); + }} + {...props} + > + + +
    + +
    + +
    + )} +
    + ); +}; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 5867134ee..ac380a9bd 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -58,7 +58,7 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) ( From 00bf5644f83076bfed37c22f590fb9dccf140906 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 16:39:25 -0800 Subject: [PATCH 67/98] Remove debugging code --- .../scripts/components/server/files/FileManagerBreadcrumbs.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx index d0bee1a86..bbe08149e 100644 --- a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx +++ b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx @@ -34,9 +34,6 @@ export default ({ withinFileEditor, isNewFile }: Props) => { return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` }; }); - if (withinFileEditor) - console.log(breadcrumbs()); - return (
    /home/ From 02c0d934c3015bd3ec45aa104a08630649ab0bf0 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 16:40:28 -0800 Subject: [PATCH 68/98] Make text more readable --- .../components/server/databases/DatabasesContainer.tsx | 2 +- .../scripts/components/server/files/FileManagerContainer.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 877800d68..d01ae1fef 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -52,7 +52,7 @@ export default () => { /> )) : -

    +

    It looks like you have no databases. Click the button below to create one now.

    } diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 2ed28f467..bec0c2a31 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -48,7 +48,7 @@ export default () => { : {!files.length ? -

    +

    This directory seems to be empty.

    : @@ -60,8 +60,7 @@ export default () => {

    This directory is too large to display in the browser, - limiting - the output to the first 250 files. + limiting the output to the first 250 files.

    { From 11c17245c2a3c0c49bb18885a62ebf48017c1eba Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 17:31:04 -0800 Subject: [PATCH 69/98] Handle websocket authentication slightly differently to make errors easier to work with --- .../components/server/WebsocketHandler.tsx | 7 +++-- resources/scripts/plugins/Websocket.ts | 28 ++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index d84c39413..9e93a1838 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -11,7 +11,7 @@ export default () => { const updateToken = (uuid: string, socket: Websocket) => { getWebsocketToken(uuid) - .then(data => socket.setToken(data.token)) + .then(data => socket.setToken(data.token, true)) .catch(error => console.error(error)); }; @@ -24,7 +24,7 @@ export default () => { const socket = new Websocket(); - socket.on('SOCKET_OPEN', () => setConnectionState(true)); + socket.on('auth success', () => setConnectionState(true)); socket.on('SOCKET_CLOSE', () => setConnectionState(false)); socket.on('SOCKET_ERROR', () => setConnectionState(false)); socket.on('status', (status) => setServerStatus(status)); @@ -38,7 +38,10 @@ export default () => { getWebsocketToken(server.uuid) .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)); diff --git a/resources/scripts/plugins/Websocket.ts b/resources/scripts/plugins/Websocket.ts index 0bf393e87..ee5a6f65b 100644 --- a/resources/scripts/plugins/Websocket.ts +++ b/resources/scripts/plugins/Websocket.ts @@ -23,9 +23,9 @@ export class Websocket extends EventEmitter { private token: string = ''; // Connects to the websocket instance and sets the token for the initial request. - connect (url: string) { + connect (url: string): this { this.url = url; - this.socket = new Sockette(`${this.url}?token=${this.token}`, { + this.socket = new Sockette(`${this.url}`, { onmessage: e => { try { let { event, args } = JSON.parse(e.data); @@ -34,11 +34,19 @@ export class Websocket extends EventEmitter { console.warn('Failed to parse incoming websocket message.', ex); } }, - onopen: () => this.emit('SOCKET_OPEN'), - onreconnect: () => this.emit('SOCKET_RECONNECT'), + onopen: () => { + this.emit('SOCKET_OPEN'); + this.authenticate(); + }, + onreconnect: () => { + this.emit('SOCKET_RECONNECT'); + this.authenticate(); + }, onclose: () => this.emit('SOCKET_CLOSE'), onerror: () => this.emit('SOCKET_ERROR'), }); + + return this; } // Returns the URL connected to for the socket. @@ -48,11 +56,11 @@ export class Websocket extends EventEmitter { // Sets the authentication token to use when sending commands back and forth // between the websocket instance. - setToken (token: string): this { + setToken (token: string, isUpdate = false): this { this.token = token; - if (this.url) { - this.send('auth', token); + if (isUpdate) { + this.authenticate(); } return this; @@ -63,6 +71,12 @@ export class Websocket extends EventEmitter { return this.token; } + authenticate () { + if (this.url && this.token) { + this.send('auth', this.token); + } + } + close (code?: number, reason?: string) { this.url = null; this.token = ''; From a1a344bc48d630c4dbb16eb0b33fc6372c0c4a72 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 17:40:03 -0800 Subject: [PATCH 70/98] Big red warning box to prevent the inevitable users trying to run this as a prod build --- resources/views/templates/wrapper.blade.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resources/views/templates/wrapper.blade.php b/resources/views/templates/wrapper.blade.php index 81fbb5c08..8c3adbd94 100644 --- a/resources/views/templates/wrapper.blade.php +++ b/resources/views/templates/wrapper.blade.php @@ -38,6 +38,14 @@ @include('layouts.scripts') + @if(\Illuminate\Support\Str::contains(config('app.version'), ['-alpha', '-beta'])) +
    +

    + You are running a pre-release version of Pterodactyl. Please report any issues + via GitHub. +

    +
    + @endif @section('content') @yield('above-container') @yield('container') From 3e915e526b464885c7a799fd7dd4adedff650b69 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 17:43:50 -0800 Subject: [PATCH 71/98] Fix file creation in nested directory --- .../scripts/components/server/files/FileEditContainer.tsx | 2 +- .../scripts/components/server/files/FileNameModal.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 8c9b192b5..2d0de6576 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -46,7 +46,7 @@ export default () => { }) .then(() => { if (name) { - history.push(`/server/${id}/files/edit#${hash.replace(/^#/, '')}/${name}`); + history.push(`/server/${id}/files/edit#/${name}`); return; } diff --git a/resources/scripts/components/server/files/FileNameModal.tsx b/resources/scripts/components/server/files/FileNameModal.tsx index 16aafc0d0..98cdd49fa 100644 --- a/resources/scripts/components/server/files/FileNameModal.tsx +++ b/resources/scripts/components/server/files/FileNameModal.tsx @@ -3,6 +3,8 @@ import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikActions } from 'formik'; import { object, string } from 'yup'; import Field from '@/components/elements/Field'; +import { ServerContext } from '@/state/server'; +import { join } from 'path'; type Props = RequiredModalProps & { onFileNamed: (name: string) => void; @@ -13,8 +15,10 @@ interface Values { } export default ({ onFileNamed, onDismissed, ...props }: Props) => { + const directory = ServerContext.useStoreState(state => state.files.directory); + const submit = (values: Values, { setSubmitting }: FormikActions) => { - onFileNamed(values.fileName); + onFileNamed(join(directory, values.fileName)); setSubmitting(false); }; @@ -40,6 +44,7 @@ export default ({ onFileNamed, onDismissed, ...props }: Props) => { name={'fileName'} label={'File Name'} description={'Enter the name that this file should be saved as.'} + autoFocus={true} />
    -
    -
    -

    - Your SFTP password is the same as the password you use to access this panel. -

    +
    +
    +
    +

    + Your SFTP password is the same as the password you use to access this panel. +

    +
    +
    +
    From f6b414741d987b3d6cc510131a400dff2b736614 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 21:51:42 -0800 Subject: [PATCH 74/98] Wait for connection before opening terminal --- resources/scripts/components/server/Console.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index df5661c0a..c233e60ea 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -1,4 +1,4 @@ -import React, { createRef, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ITerminalOptions, Terminal } from 'xterm'; import * as TerminalFit from 'xterm/lib/addons/fit/fit'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; @@ -36,7 +36,8 @@ const terminalProps: ITerminalOptions = { }; export default () => { - const ref = createRef(); + const [ terminalElement, setTerminalElement ] = useState(null); + const useRef = useCallback(node => setTerminalElement(node), []); const terminal = useMemo(() => new Terminal({ ...terminalProps }), []); const { connected, instance } = ServerContext.useStoreState(state => state.socket); @@ -62,14 +63,14 @@ export default () => { }; useEffect(() => { - if (ref.current && !terminal.element) { - terminal.open(ref.current); + if (connected && terminalElement && !terminal.element) { + terminal.open(terminalElement); // @see https://github.com/xtermjs/xterm.js/issues/2265 // @see https://github.com/xtermjs/xterm.js/issues/2230 TerminalFit.fit(terminal); } - }, [terminal, ref]); + }, [ terminal, connected, terminalElement ]); useEffect(() => { if (connected && instance) { @@ -99,7 +100,7 @@ export default () => { maxHeight: '32rem', }} > -
    +
    $
    From 04d67eaa1053bbaa8ce98d57b4cd4616225dacf0 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 21:56:45 -0800 Subject: [PATCH 75/98] Minor fixes for blocking indexing and notg interrupting user expierence --- resources/scripts/TransitionRouter.tsx | 2 ++ resources/views/templates/wrapper.blade.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/scripts/TransitionRouter.tsx b/resources/scripts/TransitionRouter.tsx index f7898e597..eec5b4a98 100644 --- a/resources/scripts/TransitionRouter.tsx +++ b/resources/scripts/TransitionRouter.tsx @@ -17,7 +17,9 @@ export default ({ children }: Props) => (

    © 2015 - 2019  Pterodactyl Software diff --git a/resources/views/templates/wrapper.blade.php b/resources/views/templates/wrapper.blade.php index 8c3adbd94..d49a2b9e8 100644 --- a/resources/views/templates/wrapper.blade.php +++ b/resources/views/templates/wrapper.blade.php @@ -7,7 +7,7 @@ - + From ef5d0fb4a2a2d11899e070018d04518c573e74a5 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 21 Dec 2019 23:26:15 -0800 Subject: [PATCH 76/98] Add deletion code to the panel --- app/Repositories/Wings/DaemonServerRepository.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Repositories/Wings/DaemonServerRepository.php b/app/Repositories/Wings/DaemonServerRepository.php index 676eed6d7..ae4d1333a 100644 --- a/app/Repositories/Wings/DaemonServerRepository.php +++ b/app/Repositories/Wings/DaemonServerRepository.php @@ -72,11 +72,19 @@ class DaemonServerRepository extends DaemonRepository } /** - * Delete a server from the daemon. + * Delete a server from the daemon, forcibly if passed. + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ public function delete(): void { - throw new BadMethodCallException('Method is not implemented.'); + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->delete('/api/servers/' . $this->server->uuid); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } } /** From 696653016515ff51f5c24d6df9878a3d23101a7d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 00:16:13 -0800 Subject: [PATCH 77/98] Better redux devtooling support --- package.json | 1 + resources/scripts/state/server/index.ts | 8 +++++++- webpack.config.js | 2 +- yarn.lock | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e53ac2a13..c3e89969c 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "postcss-preset-env": "^6.7.0", "precss": "^4.0.0", "purgecss-webpack-plugin": "^1.6.0", + "redux-devtools-extension": "^2.13.8", "resolve-url-loader": "^3.0.0", "source-map-loader": "^0.2.4", "style-loader": "^0.23.1", diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index 334b7b493..8fc23aee5 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -4,6 +4,7 @@ import socket, { SocketStore } from './socket'; import { ServerDatabase } from '@/api/server/getServerDatabases'; import files, { ServerFileStore } from '@/state/server/files'; import subusers, { ServerSubuserStore } from '@/state/server/subusers'; +import { composeWithDevTools } from 'redux-devtools-extension'; export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; @@ -88,4 +89,9 @@ export const ServerContext = createContextStore({ state.socket.instance = null; state.socket.connected = false; }), -}, { name: 'ServerStore' }); +}, { + compose: composeWithDevTools({ + name: 'ServerStore', + trace: true, + }), +}); diff --git a/webpack.config.js b/webpack.config.js index 9d1fc731d..f5c050c68 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -50,7 +50,7 @@ module.exports = { cache: true, target: 'web', mode: process.env.NODE_ENV, - devtool: isProduction ? false : 'eval-source-map', + devtool: isProduction ? false : process.env.DEVTOOL || 'source-map', performance: { hints: false, }, diff --git a/yarn.lock b/yarn.lock index 9ba396b2a..f46c2f81f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6136,6 +6136,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +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.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" From 058f72c923d36664eb14769d9e1ef073c99bbdee Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 00:16:25 -0800 Subject: [PATCH 78/98] Don't disconnect from the websocket when updating the server object state --- resources/scripts/components/server/WebsocketHandler.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 9e93a1838..930fb8894 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -45,11 +45,6 @@ export default () => { setInstance(socket); }) .catch(error => console.error(error)); - - return () => { - socket && socket.close(); - instance && instance!.removeAllListeners(); - }; }, [ server ]); return null; From 59bfc212c9b16e44669082a6b3979d50ec6456d5 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 13:28:51 -0800 Subject: [PATCH 79/98] Include all server information in details endpoints for daemon to use --- ...roller.php => ServerDetailsController.php} | 29 ++++++++++++++----- routes/api-remote.php | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) rename app/Http/Controllers/Api/Remote/Servers/{ServerConfigurationController.php => ServerDetailsController.php} (52%) diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerConfigurationController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php similarity index 52% rename from app/Http/Controllers/Api/Remote/Servers/ServerConfigurationController.php rename to app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php index eeb157542..c9d9f4bc6 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerConfigurationController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -7,8 +7,9 @@ use Illuminate\Http\JsonResponse; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Eggs\EggConfigurationService; use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Services\Servers\ServerConfigurationStructureService; -class ServerConfigurationController extends Controller +class ServerDetailsController extends Controller { /** * @var \Pterodactyl\Services\Eggs\EggConfigurationService @@ -20,21 +21,34 @@ class ServerConfigurationController extends Controller */ private $repository; + /** + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + private $configurationStructureService; + /** * ServerConfigurationController constructor. * * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService * @param \Pterodactyl\Services\Eggs\EggConfigurationService $eggConfigurationService */ - public function __construct(ServerRepository $repository, EggConfigurationService $eggConfigurationService) - { + public function __construct( + ServerRepository $repository, + ServerConfigurationStructureService $configurationStructureService, + EggConfigurationService $eggConfigurationService + ) { $this->eggConfigurationService = $eggConfigurationService; $this->repository = $repository; + $this->configurationStructureService = $configurationStructureService; } /** + * Returns details about the server that allows Wings to self-recover and ensure + * that the state of the server matches the Panel at all times. + * * @param \Illuminate\Http\Request $request - * @param $uuid + * @param string $uuid * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException @@ -43,8 +57,9 @@ class ServerConfigurationController extends Controller { $server = $this->repository->getByUuid($uuid); - return JsonResponse::create( - $this->eggConfigurationService->handle($server) - ); + return JsonResponse::create([ + 'settings' => $this->configurationStructureService->handle($server), + 'process_configuration' => $this->eggConfigurationService->handle($server), + ]); } } diff --git a/routes/api-remote.php b/routes/api-remote.php index f35810ac1..476e90512 100644 --- a/routes/api-remote.php +++ b/routes/api-remote.php @@ -12,5 +12,5 @@ Route::group(['prefix' => '/scripts'], function () { // Routes for the Wings daemon. Route::post('/sftp/auth', 'SftpAuthenticationController'); Route::group(['prefix' => '/servers/{uuid}'], function () { - Route::get('/configuration', 'Servers\ServerConfigurationController'); + Route::get('/', 'Servers\ServerDetailsController'); }); From 34ffcdae7a4cdbe2749431458052bc8bfca8c722 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 13:45:40 -0800 Subject: [PATCH 80/98] Remove unnecessary API calls to daemon --- .../Commands/Server/RebuildServerCommand.php | 109 ------------------ .../Controllers/Admin/ServersController.php | 27 +---- .../Servers/ServerDetailsController.php | 5 +- .../Servers/ServerManagementController.php | 33 +----- .../Servers/BuildModificationService.php | 7 +- .../Servers/ContainerRebuildService.php | 44 ------- .../Servers/StartupModificationService.php | 24 ---- routes/admin.php | 1 - routes/api-application.php | 1 - .../Servers/ContainerRebuildServiceTest.php | 71 ------------ 10 files changed, 10 insertions(+), 312 deletions(-) delete mode 100644 app/Console/Commands/Server/RebuildServerCommand.php delete mode 100644 app/Services/Servers/ContainerRebuildService.php delete mode 100644 tests/Unit/Services/Servers/ContainerRebuildServiceTest.php diff --git a/app/Console/Commands/Server/RebuildServerCommand.php b/app/Console/Commands/Server/RebuildServerCommand.php deleted file mode 100644 index cf20f9df3..000000000 --- a/app/Console/Commands/Server/RebuildServerCommand.php +++ /dev/null @@ -1,109 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Console\Commands\Server; - -use Webmozart\Assert\Assert; -use Illuminate\Console\Command; -use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Services\Servers\ServerConfigurationStructureService; - -class RebuildServerCommand extends Command -{ - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - protected $configurationStructureService; - - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - protected $daemonRepository; - - /** - * @var string - */ - protected $description = 'Rebuild a single server, all servers on a node, or all servers on the panel.'; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; - - /** - * @var string - */ - protected $signature = 'p:server:rebuild - {server? : The ID of the server to rebuild.} - {--node= : ID of the node to rebuild all servers on. Ignored if server is passed.}'; - - /** - * RebuildServerCommand constructor. - * - * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonRepository - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - */ - public function __construct( - DaemonServerRepository $daemonRepository, - ServerConfigurationStructureService $configurationStructureService, - ServerRepositoryInterface $repository - ) { - parent::__construct(); - - $this->configurationStructureService = $configurationStructureService; - $this->daemonRepository = $daemonRepository; - $this->repository = $repository; - } - - /** - * Handle command execution. - */ - public function handle() - { - $servers = $this->getServersToProcess(); - $bar = $this->output->createProgressBar(count($servers)); - - $servers->each(function ($server) use ($bar) { - $bar->clear(); - $json = array_merge($this->configurationStructureService->handle($server), ['rebuild' => true]); - - try { - $this->daemonRepository->setServer($server)->update($json); - } catch (RequestException $exception) { - $this->output->error(trans('command/messages.server.rebuild_failed', [ - 'name' => $server->name, - 'id' => $server->id, - 'node' => $server->node->name, - 'message' => $exception->getMessage(), - ])); - } - - $bar->advance(); - $bar->display(); - }); - - $this->line(''); - } - - /** - * Return the servers to be rebuilt. - * - * @return \Illuminate\Database\Eloquent\Collection - */ - private function getServersToProcess() - { - Assert::nullOrIntegerish($this->argument('server'), 'Value passed in server argument must be null or an integer, received %s.'); - Assert::nullOrIntegerish($this->option('node'), 'Value passed in node option must be null or integer, received %s.'); - - return $this->repository->getDataForRebuild($this->argument('server'), $this->option('node')); - } -} diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 70c3af092..8bcb10390 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -18,7 +18,6 @@ use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Servers\SuspensionService; use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Services\Servers\ReinstallServerService; -use Pterodactyl\Services\Servers\ContainerRebuildService; use Pterodactyl\Services\Servers\BuildModificationService; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Services\Servers\DetailsModificationService; @@ -54,11 +53,6 @@ class ServersController extends Controller */ protected $config; - /** - * @var \Pterodactyl\Services\Servers\ContainerRebuildService - */ - protected $containerRebuildService; - /** * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface */ @@ -121,7 +115,6 @@ class ServersController extends Controller * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository * @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Services\Servers\ContainerRebuildService $containerRebuildService * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService * @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository @@ -139,7 +132,6 @@ class ServersController extends Controller AllocationRepositoryInterface $allocationRepository, BuildModificationService $buildModificationService, ConfigRepository $config, - ContainerRebuildService $containerRebuildService, DatabaseManagementService $databaseManagementService, DatabasePasswordService $databasePasswordService, DatabaseRepositoryInterface $databaseRepository, @@ -156,7 +148,6 @@ class ServersController extends Controller $this->allocationRepository = $allocationRepository; $this->buildModificationService = $buildModificationService; $this->config = $config; - $this->containerRebuildService = $containerRebuildService; $this->databaseHostRepository = $databaseHostRepository; $this->databaseManagementService = $databaseManagementService; $this->databasePasswordService = $databasePasswordService; @@ -235,21 +226,6 @@ class ServersController extends Controller return redirect()->route('admin.servers.view.manage', $server->id); } - /** - * Setup a server to have a container rebuild. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\RedirectResponse - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function rebuildContainer(Server $server) - { - $this->containerRebuildService->handle($server); - $this->alert->success(trans('admin/server.alerts.rebuild_on_boot'))->flash(); - - return redirect()->route('admin.servers.view.manage', $server->id); - } - /** * Manage the suspension status for a server. * @@ -302,7 +278,7 @@ class ServersController extends Controller * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function delete(Request $request, Server $server) { @@ -320,7 +296,6 @@ class ServersController extends Controller * @return \Illuminate\Http\RedirectResponse * * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index d4d0cc14d..c2c3fa1cf 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -63,15 +63,16 @@ class ServerDetailsController extends ApplicationApiController * Update the build details for a specific server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest $request + * @param \Pterodactyl\Models\Server $server * @return array * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function build(UpdateServerBuildConfigurationRequest $request): array + public function build(UpdateServerBuildConfigurationRequest $request, Server $server): array { - $server = $this->buildModificationService->handle($request->getModel(Server::class), $request->validated()); + $server = $this->buildModificationService->handle($server, $request->validated()); return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php index 7a48f4bfd..90372f220 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php @@ -6,17 +6,11 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Services\Servers\SuspensionService; use Pterodactyl\Services\Servers\ReinstallServerService; -use Pterodactyl\Services\Servers\ContainerRebuildService; use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class ServerManagementController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\ContainerRebuildService - */ - private $rebuildService; - /** * @var \Pterodactyl\Services\Servers\ReinstallServerService */ @@ -30,18 +24,15 @@ class ServerManagementController extends ApplicationApiController /** * SuspensionController constructor. * - * @param \Pterodactyl\Services\Servers\ContainerRebuildService $rebuildService * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallServerService * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService */ public function __construct( - ContainerRebuildService $rebuildService, ReinstallServerService $reinstallServerService, SuspensionService $suspensionService ) { parent::__construct(); - $this->rebuildService = $rebuildService; $this->reinstallServerService = $reinstallServerService; $this->suspensionService = $suspensionService; } @@ -53,9 +44,7 @@ class ServerManagementController extends ApplicationApiController * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\Response * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function suspend(ServerWriteRequest $request, Server $server): Response { @@ -71,9 +60,7 @@ class ServerManagementController extends ApplicationApiController * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\Response * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function unsuspend(ServerWriteRequest $request, Server $server): Response { @@ -99,20 +86,4 @@ class ServerManagementController extends ApplicationApiController return $this->returnNoContent(); } - - /** - * Mark a server as needing its container rebuilt the next time it is started. - * - * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\Response - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function rebuild(ServerWriteRequest $request, Server $server): Response - { - $this->rebuildService->handle($server); - - return $this->returnNoContent(); - } } diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 13400ee9a..0e3085dc5 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -107,9 +107,10 @@ class BuildModificationService $updateData = $this->structureService->handle($server); try { - $this->daemonServerRepository->setServer($server)->update( - Arr::only($updateData, ['allocations', 'build', 'container']) - ); + $this->daemonServerRepository + ->setServer($server) + ->update(Arr::only($updateData, ['build'])); + $this->connection->commit(); } catch (RequestException $exception) { throw new DaemonConnectionException($exception); diff --git a/app/Services/Servers/ContainerRebuildService.php b/app/Services/Servers/ContainerRebuildService.php deleted file mode 100644 index a5e21c1dc..000000000 --- a/app/Services/Servers/ContainerRebuildService.php +++ /dev/null @@ -1,44 +0,0 @@ -repository = $repository; - } - - /** - * Mark a server for rebuild on next boot cycle. This just makes an empty patch - * request to Wings which will automatically mark the container as requiring a rebuild - * on the next boot as a result. - * - * @param \Pterodactyl\Models\Server $server - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function handle(Server $server) - { - try { - $this->repository->setServer($server)->update([]); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } - } -} diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index ebcd58923..405fee47b 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -2,16 +2,12 @@ namespace Pterodactyl\Services\Servers; -use Illuminate\Support\Arr; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Traits\Services\HasUserLevels; -use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; class StartupModificationService @@ -48,11 +44,6 @@ class StartupModificationService */ private $validatorService; - /** - * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository - */ - private $daemonServerRepository; - /** * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService */ @@ -62,7 +53,6 @@ class StartupModificationService * StartupModificationService constructor. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $eggRepository * @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository @@ -72,7 +62,6 @@ class StartupModificationService */ public function __construct( ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository, EggRepositoryInterface $eggRepository, EnvironmentService $environmentService, ServerRepositoryInterface $repository, @@ -86,7 +75,6 @@ class StartupModificationService $this->repository = $repository; $this->serverVariableRepository = $serverVariableRepository; $this->validatorService = $validatorService; - $this->daemonServerRepository = $daemonServerRepository; $this->structureService = $structureService; } @@ -98,7 +86,6 @@ class StartupModificationService * @return \Pterodactyl\Models\Server * * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -123,17 +110,6 @@ class StartupModificationService $this->updateAdministrativeSettings($data, $server); } - $updateData = $this->structureService->handle($server); - - try { - $this->daemonServerRepository->setServer($server)->update( - Arr::only($updateData, ['environment', 'invocation', 'service']) - ); - } catch (RequestException $exception) { - $this->connection->rollBack(); - throw new DaemonConnectionException($exception); - } - $this->connection->commit(); return $server; diff --git a/routes/admin.php b/routes/admin.php index 598f46bbb..63aa580d9 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -123,7 +123,6 @@ Route::group(['prefix' => 'servers'], function () { Route::post('/view/{server}/startup', 'ServersController@saveStartup'); Route::post('/view/{server}/database', 'ServersController@newDatabase'); Route::post('/view/{server}/manage/toggle', 'ServersController@toggleInstall')->name('admin.servers.view.manage.toggle'); - Route::post('/view/{server}/manage/rebuild', 'ServersController@rebuildContainer')->name('admin.servers.view.manage.rebuild'); Route::post('/view/{server}/manage/suspension', 'ServersController@manageSuspension')->name('admin.servers.view.manage.suspension'); Route::post('/view/{server}/manage/reinstall', 'ServersController@reinstallServer')->name('admin.servers.view.manage.reinstall'); Route::post('/view/{server}/delete', 'ServersController@delete'); diff --git a/routes/api-application.php b/routes/api-application.php index 9b3b4f073..fe7181667 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -84,7 +84,6 @@ Route::group(['prefix' => '/servers'], function () { Route::post('/{server}/suspend', 'Servers\ServerManagementController@suspend')->name('api.application.servers.suspend'); Route::post('/{server}/unsuspend', 'Servers\ServerManagementController@unsuspend')->name('api.application.servers.unsuspend'); Route::post('/{server}/reinstall', 'Servers\ServerManagementController@reinstall')->name('api.application.servers.reinstall'); - Route::post('/{server}/rebuild', 'Servers\ServerManagementController@rebuild')->name('api.application.servers.rebuild'); Route::delete('/{server}', 'Servers\ServerController@delete'); Route::delete('/{server}/{force?}', 'Servers\ServerController@delete'); diff --git a/tests/Unit/Services/Servers/ContainerRebuildServiceTest.php b/tests/Unit/Services/Servers/ContainerRebuildServiceTest.php deleted file mode 100644 index 5f97fcbde..000000000 --- a/tests/Unit/Services/Servers/ContainerRebuildServiceTest.php +++ /dev/null @@ -1,71 +0,0 @@ -exception = m::mock(RequestException::class)->makePartial(); - $this->repository = m::mock(ServerRepositoryInterface::class); - - $this->server = factory(Server::class)->make(['node_id' => 1]); - $this->service = new ContainerRebuildService($this->repository); - } - - /** - * Test that a server is marked for rebuild. - */ - public function testServerIsMarkedForRebuild() - { - $this->repository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() - ->shouldReceive('rebuild')->withNoArgs()->once()->andReturn(new Response); - - $this->service->handle($this->server); - } - - /** - * Test that an exception thrown by guzzle is rendered as a displayable exception. - * - * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function testExceptionThrownByGuzzle() - { - $this->repository->shouldReceive('setServer')->with($this->server)->once()->andThrow($this->exception); - - $this->service->handle($this->server); - } -} From bc3286c7e4299197698a6862d0efbf78b297ee17 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 13:50:35 -0800 Subject: [PATCH 81/98] Don't need this box anymore --- .../views/admin/servers/view/manage.blade.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/resources/views/admin/servers/view/manage.blade.php b/resources/views/admin/servers/view/manage.blade.php index 6980ad4c5..d84555acb 100644 --- a/resources/views/admin/servers/view/manage.blade.php +++ b/resources/views/admin/servers/view/manage.blade.php @@ -58,22 +58,6 @@

    -
    -
    -
    -

    Rebuild Container

    -
    -
    -

    This will trigger a rebuild of the server container when it next starts up. This is useful if you modified the server configuration file manually, or something just didn't work out correctly.

    -
    - -
    -
    @if(! $server->suspended)
    From 52ea0f2d0ad8e4c15d4117492611df08c4810ea8 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 13:55:58 -0800 Subject: [PATCH 82/98] Fix allocation management --- resources/views/admin/nodes/view/allocation.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/admin/nodes/view/allocation.blade.php b/resources/views/admin/nodes/view/allocation.blade.php index c6e30db5d..4777b82f3 100644 --- a/resources/views/admin/nodes/view/allocation.blade.php +++ b/resources/views/admin/nodes/view/allocation.blade.php @@ -218,7 +218,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: '/admin/nodes/view/' + Pterodactyl.node.id + '/allocation/remove/' + allocation, + url: '/admin/nodes/view/' + {{ $node->id }} + '/allocation/remove/' + allocation, headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, }).done(function (data) { element.parent().parent().addClass('warning').delay(100).fadeOut(); @@ -247,7 +247,7 @@ clearTimeout(fadeTimers[element.data('id')]); $.ajax({ method: 'POST', - url: '/admin/nodes/view/' + Pterodactyl.node.id + '/allocation/alias', + url: '/admin/nodes/view/' + {{ $node->id }} + '/allocation/alias', headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, data: { alias: element.val(), @@ -321,7 +321,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: '/admin/nodes/view/' + Pterodactyl.node.id + '/allocations', + url: '/admin/nodes/view/' + {{ $node->id }} + '/allocations', headers: {'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')}, data: JSON.stringify({ allocations: selectedIds From 6276a03a4ebc208f0e80da15ddebb174eb277806 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 14:03:49 -0800 Subject: [PATCH 83/98] Fix links in admin area --- .../Helpers/SoftwareVersionService.php | 59 ++++++++++--------- resources/views/admin/index.blade.php | 6 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index b984271c9..9d54161e9 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -1,25 +1,22 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Helpers; -use stdClass; use Exception; use GuzzleHttp\Client; use Cake\Chronos\Chronos; +use Illuminate\Support\Arr; use Illuminate\Contracts\Cache\Repository as CacheRepository; -use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException; class SoftwareVersionService { - const VERSION_CACHE_KEY = 'pterodactyl:versions'; + const VERSION_CACHE_KEY = 'pterodactyl:versioning_data'; + + /** + * @var array + */ + private static $result; /** * @var \Illuminate\Contracts\Cache\Repository @@ -31,28 +28,20 @@ class SoftwareVersionService */ protected $client; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - /** * SoftwareVersionService constructor. * * @param \Illuminate\Contracts\Cache\Repository $cache * @param \GuzzleHttp\Client $client - * @param \Illuminate\Contracts\Config\Repository $config */ public function __construct( CacheRepository $cache, - Client $client, - ConfigRepository $config + Client $client ) { $this->cache = $cache; $this->client = $client; - $this->config = $config; - $this->cacheVersionData(); + self::$result = $this->cacheVersionData(); } /** @@ -62,7 +51,7 @@ class SoftwareVersionService */ public function getPanel() { - return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'panel', 'error'); + return Arr::get(self::$result, 'panel') ?? 'error'; } /** @@ -72,7 +61,7 @@ class SoftwareVersionService */ public function getDaemon() { - return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'daemon', 'error'); + return Arr::get(self::$result, 'daemon') ?? 'error'; } /** @@ -82,7 +71,17 @@ class SoftwareVersionService */ public function getDiscord() { - return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'discord', 'https://pterodactyl.io/discord'); + return Arr::get(self::$result, 'discord') ?? 'https://pterodactyl.io/discord'; + } + + /** + * Get the URL for donations. + * + * @return string + */ + public function getDonations() + { + return Arr::get(self::$result, 'donations') ?? 'https://paypal.me/PterodactylSoftware'; } /** @@ -92,11 +91,11 @@ class SoftwareVersionService */ public function isLatestPanel() { - if ($this->config->get('app.version') === 'canary') { + if (config()->get('app.version') === 'canary') { return true; } - return version_compare($this->config->get('app.version'), $this->getPanel()) >= 0; + return version_compare(config()->get('app.version'), $this->getPanel()) >= 0; } /** @@ -116,20 +115,22 @@ class SoftwareVersionService /** * Keeps the versioning cache up-to-date with the latest results from the CDN. + * + * @return array */ protected function cacheVersionData() { - $this->cache->remember(self::VERSION_CACHE_KEY, Chronos::now()->addMinutes(config('pterodactyl.cdn.cache_time')), function () { + return $this->cache->remember(self::VERSION_CACHE_KEY, Chronos::now()->addMinutes(config()->get('pterodactyl.cdn.cache_time', 60)), function () { try { - $response = $this->client->request('GET', $this->config->get('pterodactyl.cdn.url')); + $response = $this->client->request('GET', config()->get('pterodactyl.cdn.url')); if ($response->getStatusCode() === 200) { - return json_decode($response->getBody()); + return json_decode($response->getBody(), true); } throw new CdnVersionFetchingException; } catch (Exception $exception) { - return new stdClass(); + return []; } }); } diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php index 9e9690f8e..6c1364a71 100644 --- a/resources/views/admin/index.blade.php +++ b/resources/views/admin/index.blade.php @@ -45,14 +45,14 @@
     
    @endsection From 5a31771b4becb00fcd0aa7ca89a02f5d03e3306e Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 14:33:08 -0800 Subject: [PATCH 84/98] Fixes for purge CSS --- resources/scripts/components/server/Console.tsx | 15 +++++++++++++-- resources/styles/components/navigation.css | 4 ++++ webpack.config.js | 7 +++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index c233e60ea..abcf60558 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -3,6 +3,7 @@ import { ITerminalOptions, Terminal } from 'xterm'; import * as TerminalFit from 'xterm/lib/addons/fit/fit'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { ServerContext } from '@/state/server'; +import styled from 'styled-components'; const theme = { background: 'transparent', @@ -35,6 +36,16 @@ const terminalProps: ITerminalOptions = { theme: theme, }; +const TerminalDiv = styled.div` + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-thumb { + ${tw`bg-neutral-900`}; + } +`; + export default () => { const [ terminalElement, setTerminalElement ] = useState(null); const useRef = useCallback(node => setTerminalElement(node), []); @@ -94,13 +105,13 @@ export default () => {
    -
    +
    $
    diff --git a/resources/styles/components/navigation.css b/resources/styles/components/navigation.css index 5db937ed8..c59259244 100644 --- a/resources/styles/components/navigation.css +++ b/resources/styles/components/navigation.css @@ -25,6 +25,7 @@ @apply .flex .items-center .h-full .no-underline .text-neutral-300 .px-6; transition: background-color 150ms linear, color 150ms linear, box-shadow 150ms ease-in; + /*! purgecss start ignore */ &.active, &:hover { @apply .text-neutral-100 .bg-black; box-shadow: inset 0 -2px config('colors.cyan-700'); @@ -33,6 +34,7 @@ &.active { box-shadow: inset 0 -2px config('colors.cyan-500'); } + /*! purgecss end ignore */ } } } @@ -43,6 +45,7 @@ .items { @apply .flex .items-center .text-sm .mx-2; + /*! purgecss start ignore */ & > a, & > div { @apply .inline-block .py-3 .px-4 .text-neutral-300 .no-underline; transition: color 150ms linear, box-shadow 150ms ease-in; @@ -60,5 +63,6 @@ box-shadow: inset 0 -2px config('colors.cyan-500'); } } + /*! purgecss end ignore */ } } diff --git a/webpack.config.js b/webpack.config.js index f5c050c68..243275917 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -const _ = require('lodash'); const path = require('path'); const tailwind = require('tailwindcss'); const glob = require('glob-all'); @@ -34,7 +33,7 @@ if (isProduction) { { extractor: class { static extract (content) { - return content.match(/[A-z0-9-:\/]+/g) || []; + return content.match(/[A-Za-z0-9-_:\\/]+/g) || []; } }, extensions: ['html', 'ts', 'tsx', 'js', 'php'], @@ -63,7 +62,7 @@ module.exports = { path: path.resolve(__dirname, 'public/assets'), filename: isProduction ? 'bundle.[chunkhash:8].js' : 'bundle.[hash:8].js', chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].[hash:8].js', - publicPath: _.get(process.env, 'PUBLIC_PATH', '') + '/assets/', + publicPath: (process.env.PUBLIC_PATH || '') + '/assets/', crossOriginLoading: 'anonymous', }, module: { @@ -168,7 +167,7 @@ module.exports = { }, devServer: { contentBase: path.join(__dirname, 'public'), - publicPath: _.get(process.env, 'PUBLIC_PATH', '') + '/assets/', + publicPath: (process.env.PUBLIC_PATH || '') + '/assets/', allowedHosts: [ '.pterodactyl.test', ], From 446d5be62b1d61a0351cfd0d0af1a6c6075a3269 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 14:53:27 -0800 Subject: [PATCH 85/98] Show proper spinners --- .../auth/LoginCheckpointContainer.tsx | 3 +- .../components/auth/LoginContainer.tsx | 3 +- .../auth/ResetPasswordContainer.tsx | 3 +- .../scripts/components/elements/Spinner.tsx | 30 ++++++---- resources/styles/components/forms.css | 6 +- resources/styles/components/spinners.css | 59 ------------------- 6 files changed, 31 insertions(+), 73 deletions(-) diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index 54ac3ee48..7c474a3fe 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -7,6 +7,7 @@ import { Actions, useStoreActions } from 'easy-peasy'; import { StaticContext } from 'react-router'; import FlashMessageRender from '@/components/FlashMessageRender'; import { ApplicationStore } from '@/state'; +import Spinner from '@/components/elements/Spinner'; export default ({ history, location: { state } }: RouteComponentProps<{}, StaticContext, { token?: string }>) => { const [ code, setCode ] = useState(''); @@ -71,7 +72,7 @@ export default ({ history, location: { state } }: RouteComponentProps<{}, Static disabled={isLoading || code.length !== 6} > {isLoading ? -   + : 'Continue' } diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index ed543d6d5..1af30c2ba 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -11,6 +11,7 @@ import Field from '@/components/elements/Field'; import { httpErrorToHuman } from '@/api/http'; import { FlashMessage } from '@/state/flashes'; import ReCAPTCHA from 'react-google-recaptcha'; +import Spinner from '@/components/elements/Spinner'; type OwnProps = RouteComponentProps & { clearFlashes: ActionCreator; @@ -63,7 +64,7 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl className={'btn btn-primary btn-jumbo'} > {isSubmitting ? -   + : 'Login' } diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index 6954ebd2a..ec3b3ff6f 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -8,6 +8,7 @@ import LoginFormContainer from '@/components/auth/LoginFormContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; +import Spinner from '@/components/elements/Spinner'; type Props = Readonly & {}>; @@ -89,7 +90,7 @@ export default (props: Props) => { disabled={isLoading || !canSubmit()} > {isLoading ? -   + : 'Reset Password' } diff --git a/resources/scripts/components/elements/Spinner.tsx b/resources/scripts/components/elements/Spinner.tsx index 83ee202be..0376516e5 100644 --- a/resources/scripts/components/elements/Spinner.tsx +++ b/resources/scripts/components/elements/Spinner.tsx @@ -3,17 +3,27 @@ import classNames from 'classnames'; export type SpinnerSize = 'large' | 'normal' | 'tiny'; -export default ({ size, centered }: { size?: SpinnerSize; centered?: boolean }) => ( +interface Props { + size?: SpinnerSize; + centered?: boolean; + className?: string; +} + +export default ({ size, centered, className }: Props) => ( centered ? -
    -
    +
    +
    : -
    +
    ); diff --git a/resources/styles/components/forms.css b/resources/styles/components/forms.css index b83d0132b..3504f5e43 100644 --- a/resources/styles/components/forms.css +++ b/resources/styles/components/forms.css @@ -1,5 +1,9 @@ textarea, select, input, button { - outline: none; + @apply .outline-none; +} + +button:focus, button:focus-visible { + @apply .outline-none; } input[type=number]::-webkit-outer-spin-button, diff --git a/resources/styles/components/spinners.css b/resources/styles/components/spinners.css index 771db060a..864445b31 100644 --- a/resources/styles/components/spinners.css +++ b/resources/styles/components/spinners.css @@ -1,62 +1,3 @@ -.spinner { - @apply .h-4 .relative .bg-transparent; - pointer-events: none; - - &.spinner-xl { - @apply .h-16; - } - - &:after { - @apply .border-2 .border-neutral-400 .absolute .block .h-4 .w-4 .rounded-full; - animation: spinners--spin 500ms infinite linear; - border-top-color: transparent !important; - border-right-color: transparent !important; - content: ''; - left: calc(50% - (1em / 2)); - } - - &.spinner-relative:after { - @apply .relative; - } - - &.spinner-xl:after { - @apply .h-16 .w-16; - left: calc(50% - (4rem / 2)); - } - - /** - * Speeds - */ - &.spin-slow:after { - animation: spinners--spin 1200ms infinite linear; - } - - /** - * Spinner Colors - */ - &.blue:after, &.text-blue:after { - @apply .border-primary-500; - } - - &.white:after, &.text-white:after { - @apply .border-white; - } - - &.spinner-thick:after { - @apply .border-4; - } -} - -@keyframes spinners--spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} - .spinner-circle { @apply .w-8 .h-8; border: 3px solid hsla(211, 12%, 43%, 0.2); From c96bad4080fcedb34add24e9e64a51c6115f5d6b Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 15:08:11 -0800 Subject: [PATCH 86/98] FIx force deletion --- app/Services/Servers/ServerDeletionService.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index 83a936054..d4ce9a896 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Services\Servers; use Psr\Log\LoggerInterface; use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\DatabaseRepository; @@ -100,17 +99,11 @@ class ServerDeletionService { try { $this->daemonServerRepository->setServer($server)->delete(); - } catch (RequestException $exception) { - $response = $exception->getResponse(); - - if (is_null($response) || (! is_null($response) && $response->getStatusCode() !== 404)) { - // If not forcing the deletion, throw an exception, otherwise just log it and - // continue with server deletion process in the panel. - if (! $this->force) { - throw new DaemonConnectionException($exception); - } else { - $this->writer->warning($exception); - } + } catch (DaemonConnectionException $exception) { + if ($this->force) { + $this->writer->warning($exception); + } else { + throw $exception; } } From 293ebc9344caff104542bd2e5b04939bbe2db85c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 15:31:12 -0800 Subject: [PATCH 87/98] Show note when no servers. --- .../scripts/components/dashboard/DashboardContainer.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 3440aed4c..46ef66ebc 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -19,10 +19,14 @@ export default () => { return (
    - { + {servers.length === 0 ? servers.map(server => ( )) + : +

    + It looks like you have no servers. +

    }
    ); From edf27a55421514f3166d354d03041b2910e661f6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 15:41:38 -0800 Subject: [PATCH 88/98] Don't cache the manifest like this, pointless --- app/Services/Helpers/AssetHashService.php | 37 +++++------------------ 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php index c02506a9e..c3d4c98ff 100644 --- a/app/Services/Helpers/AssetHashService.php +++ b/app/Services/Helpers/AssetHashService.php @@ -2,10 +2,9 @@ namespace Pterodactyl\Services\Helpers; -use Cake\Chronos\Chronos; +use Illuminate\Support\Arr; use Illuminate\Filesystem\FilesystemManager; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Cache\Repository as CacheRepository; class AssetHashService { @@ -14,11 +13,6 @@ class AssetHashService */ public const MANIFEST_PATH = './assets/manifest.json'; - /** - * @var \Illuminate\Contracts\Cache\Repository - */ - private $cache; - /** * @var \Illuminate\Contracts\Filesystem\Filesystem */ @@ -38,13 +32,11 @@ class AssetHashService * AssetHashService constructor. * * @param \Illuminate\Contracts\Foundation\Application $application - * @param \Illuminate\Contracts\Cache\Repository $cache * @param \Illuminate\Filesystem\FilesystemManager $filesystem */ - public function __construct(Application $application, CacheRepository $cache, FilesystemManager $filesystem) + public function __construct(Application $application, FilesystemManager $filesystem) { $this->application = $application; - $this->cache = $cache; $this->filesystem = $filesystem->createLocalDriver(['root' => public_path()]); } @@ -59,9 +51,9 @@ class AssetHashService public function url(string $resource): string { $file = last(explode('/', $resource)); - $data = array_get($this->manifest(), $file, $file); + $data = Arr::get($this->manifest(), $file) ?? $file; - return str_replace($file, array_get($data, 'src', $file), $resource); + return str_replace($file, Arr::get($data, 'src') ?? $file, $resource); } /** @@ -77,7 +69,7 @@ class AssetHashService $file = last(explode('/', $resource)); $data = array_get($this->manifest(), $file, $file); - return array_get($data, 'integrity', ''); + return Arr::get($data, 'integrity') ?? ''; } /** @@ -122,21 +114,8 @@ class AssetHashService */ protected function manifest(): array { - if (! is_null(self::$manifest)) { - return self::$manifest; - } - - // Skip checking the cache if we are not in production. - if ($this->application->environment() === 'production') { - $stored = $this->cache->get('Core:AssetManifest'); - if (! is_null($stored)) { - return self::$manifest = $stored; - } - } - - $contents = json_decode($this->filesystem->get(self::MANIFEST_PATH), true); - $this->cache->put('Core:AssetManifest', $contents, Chronos::now()->addMinutes(1440)); - - return self::$manifest = $contents; + return self::$manifest ?: self::$manifest = json_decode( + $this->filesystem->get(self::MANIFEST_PATH), true + ); } } From eb39826f46b1140bf0344b47aea0bf2a4024ec55 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 17:03:44 -0800 Subject: [PATCH 89/98] Add base logic to configure two factor on account --- .../TwoFactorAuthenticationTokenInvalid.php | 11 +- .../Api/Client/TwoFactorController.php | 106 ++++++++++++++++ app/Services/Users/ToggleTwoFactorService.php | 4 +- package.json | 1 + .../api/account/enableAccountTwoFactor.ts | 9 ++ .../api/account/getTwoFactorTokenUrl.ts | 9 ++ resources/scripts/components/App.tsx | 43 ++++--- .../dashboard/AccountOverviewContainer.tsx | 30 ++++- .../forms/ConfigureTwoFactorForm.tsx | 38 ++++++ .../dashboard/forms/SetupTwoFactorModal.tsx | 113 ++++++++++++++++++ resources/scripts/gloabl.d.ts | 2 +- resources/scripts/style.d.ts | 17 +++ resources/styles/components/forms.css | 53 ++++---- routes/api-client.php | 3 + yarn.lock | 4 + 15 files changed, 389 insertions(+), 54 deletions(-) create mode 100644 app/Http/Controllers/Api/Client/TwoFactorController.php create mode 100644 resources/scripts/api/account/enableAccountTwoFactor.ts create mode 100644 resources/scripts/api/account/getTwoFactorTokenUrl.ts create mode 100644 resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx create mode 100644 resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx create mode 100644 resources/scripts/style.d.ts diff --git a/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php b/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php index e14a33aed..a4ea4dd09 100644 --- a/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php +++ b/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php @@ -1,16 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\User; -use Exception; +use Pterodactyl\Exceptions\DisplayException; -class TwoFactorAuthenticationTokenInvalid extends Exception +class TwoFactorAuthenticationTokenInvalid extends DisplayException { } diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php new file mode 100644 index 000000000..17bae9baf --- /dev/null +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -0,0 +1,106 @@ +setupService = $setupService; + $this->validation = $validation; + $this->toggleTwoFactorService = $toggleTwoFactorService; + } + + /** + * Returns two-factor token credentials that allow a user to configure + * it on their account. If two-factor is already enabled this endpoint + * will return a 400 error. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(Request $request) + { + if ($request->user()->totp_enabled) { + throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.'); + } + + return JsonResponse::create([ + 'data' => [ + 'image_url_data' => $this->setupService->handle($request->user()), + ], + ]); + } + + /** + * Updates a user's account to have two-factor enabled. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Validation\ValidationException + * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException + * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException + * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid + */ + public function store(Request $request) + { + $validator = $this->validation->make($request->all(), [ + 'code' => 'required|string', + ]); + + if ($validator->fails()) { + throw new ValidationException($validator); + } + + $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + public function delete() + { + } +} diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php index b68dc911d..2c393db01 100644 --- a/app/Services/Users/ToggleTwoFactorService.php +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -65,7 +65,9 @@ class ToggleTwoFactorService $isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('pterodactyl.auth.2fa.window')); if (! $isValidToken) { - throw new TwoFactorAuthenticationTokenInvalid; + throw new TwoFactorAuthenticationTokenInvalid( + 'The token provided is not valid.' + ); } $this->repository->withoutFreshModel()->update($user->id, [ diff --git a/package.json b/package.json index c3e89969c..126bcf2a2 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-transition-group": "^4.3.0", "sockette": "^2.0.6", "styled-components": "^4.4.1", + "styled-components-breakpoint": "^3.0.0-preview.20", "use-react-router": "^1.0.7", "uuid": "^3.3.2", "xterm": "^3.14.4", diff --git a/resources/scripts/api/account/enableAccountTwoFactor.ts b/resources/scripts/api/account/enableAccountTwoFactor.ts new file mode 100644 index 000000000..d44d09acb --- /dev/null +++ b/resources/scripts/api/account/enableAccountTwoFactor.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (code: string): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/client/account/two-factor', { code }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/getTwoFactorTokenUrl.ts b/resources/scripts/api/account/getTwoFactorTokenUrl.ts new file mode 100644 index 000000000..6d9a2aa94 --- /dev/null +++ b/resources/scripts/api/account/getTwoFactorTokenUrl.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/api/client/account/two-factor') + .then(({ data }) => resolve(data.data.image_url_data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx index bb16b7bc8..510835678 100644 --- a/resources/scripts/components/App.tsx +++ b/resources/scripts/components/App.tsx @@ -8,6 +8,7 @@ import ServerRouter from '@/routers/ServerRouter'; import AuthenticationRouter from '@/routers/AuthenticationRouter'; import { Provider } from 'react-redux'; import { SiteSettings } from '@/state/settings'; +import { DefaultTheme, ThemeProvider } from 'styled-components'; interface ExtendedWindow extends Window { SiteConfiguration?: SiteSettings; @@ -23,6 +24,16 @@ interface ExtendedWindow extends Window { }; } +const theme: DefaultTheme = { + breakpoints: { + xs: 0, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + }, +}; + const App = () => { const { PterodactylUser, SiteConfiguration } = (window as ExtendedWindow); if (PterodactylUser && !store.getState().user.data) { @@ -43,21 +54,23 @@ const App = () => { } return ( - - - -
    - - - - - - - -
    -
    -
    -
    + + + + +
    + + + + + + + +
    +
    +
    +
    +
    ); }; diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx index e35fb7d61..777a4e3a5 100644 --- a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -2,16 +2,38 @@ 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'; +import ConfigureTwoFactorForm from '@/components/dashboard/forms/ConfigureTwoFactorForm'; +import styled from 'styled-components'; +import { breakpoint } from 'styled-components-breakpoint'; + +const Container = styled.div` + ${tw`flex flex-wrap my-10`}; + + & > div { + ${tw`w-full`}; + + ${breakpoint('md')` + width: calc(50% - 1rem); + `} + + ${breakpoint('xl')` + ${tw`w-auto flex-1`}; + `} + } +`; export default () => { return ( -
    - + + - + -
    + + + + ); }; diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx new file mode 100644 index 000000000..5aef3bce9 --- /dev/null +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react'; +import { useStoreState } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import SetupTwoFactorModal from '@/components/dashboard/forms/SetupTwoFactorModal'; + +export default () => { + const user = useStoreState((state: ApplicationStore) => state.user.data!); + const [visible, setVisible] = useState(false); + + return user.useTotp ? +
    +

    + Two-factor authentication is currently enabled on your account. +

    +
    + +
    +
    + : +
    + setVisible(false)}/> +

    + You do not currently have two-factor authentication enabled on your account. Click + the button below to begin configuring it. +

    +
    + +
    +
    + ; +}; diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx new file mode 100644 index 000000000..6151094bb --- /dev/null +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -0,0 +1,113 @@ +import React, { useEffect, useState } from 'react'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import { Form, Formik, FormikActions } from 'formik'; +import { object, string } from 'yup'; +import Field from '@/components/elements/Field'; +import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl'; +import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; + +interface Values { + code: string; +} + +export default ({ visible, onDismissed }: RequiredModalProps) => { + const [ token, setToken ] = useState(''); + const [ loading, setLoading ] = useState(true); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + useEffect(() => { + if (!visible) { + clearFlashes('account:two-factor'); + getTwoFactorTokenUrl() + .then(setToken) + .catch(error => { + console.error(error); + }); + } + }, [ visible ]); + + const submit = ({ code }: Values, { resetForm, setSubmitting }: FormikActions) => { + clearFlashes('account:two-factor'); + enableAccountTwoFactor(code) + .then(() => { + resetForm(); + setToken(''); + setLoading(true); + }) + .catch(error => { + console.error(error); + + addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); + setSubmitting(false); + }); + }; + + return ( + + {({ isSubmitting, isValid, resetForm }) => ( + { + resetForm(); + setToken(''); + setLoading(true); + onDismissed(); + }} + dismissable={!isSubmitting} + showSpinnerOverlay={loading || isSubmitting} + > +
    + +
    +
    +
    + {!token || !token.length ? + + : + setLoading(false)} + className={'w-full h-full shadow-none rounded-0'} + /> + } +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    + )} +
    + ); +}; diff --git a/resources/scripts/gloabl.d.ts b/resources/scripts/gloabl.d.ts index 2a3eab57c..b0dfa478c 100644 --- a/resources/scripts/gloabl.d.ts +++ b/resources/scripts/gloabl.d.ts @@ -1 +1 @@ -declare function tw(a: TemplateStringsArray | string): any; +declare function tw (a: TemplateStringsArray | string): any; diff --git a/resources/scripts/style.d.ts b/resources/scripts/style.d.ts new file mode 100644 index 000000000..b60e8039b --- /dev/null +++ b/resources/scripts/style.d.ts @@ -0,0 +1,17 @@ +import { Breakpoints, css, DefaultTheme, StyledProps } from 'styled-components'; + +declare module 'styled-components' { + type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + + export interface DefaultTheme { + breakpoints: { + [name in 'xs' | 'sm' | 'md' | 'lg' | 'xl']: number; + }; + } +} + +declare module 'styled-components-breakpoint' { + type CSSFunction = (...params: Parameters) =>

    ({ theme }: StyledProps

    ) => ReturnType; + + export const breakpoint: (breakpointA: Breakpoints, breakpointB?: Breakpoints) => CSSFunction; +} diff --git a/resources/styles/components/forms.css b/resources/styles/components/forms.css index 3504f5e43..56323d76a 100644 --- a/resources/styles/components/forms.css +++ b/resources/styles/components/forms.css @@ -145,32 +145,29 @@ a.btn { @apply .rounded .p-2 .uppercase .tracking-wide .text-sm; transition: all 150ms linear; - /** - * Button Colors - */ - &.btn-primary { - @apply .bg-primary-500 .border-primary-600 .border .text-primary-50; + &.btn-secondary { + @apply .border .border-neutral-600 .bg-transparent .text-neutral-200; &:hover:not(:disabled) { - @apply .bg-primary-600 .border-primary-700; + @apply .border-neutral-500 .text-neutral-100; } - } - &.btn-green { - @apply .bg-green-500 .border-green-600 .border .text-green-50; - - &:hover:not(:disabled) { - @apply .bg-green-600 .border-green-700; - } - } - - &.btn-red { - &:not(.btn-secondary) { + &.btn-red:hover:not(:disabled) { @apply .bg-red-500 .border-red-600 .text-red-50; } + &.btn-green:hover:not(:disabled) { + @apply .bg-green-500 .border-green-600 .text-green-50; + } + } + + &.btn-primary { + &:not(.btn-secondary) { + @apply .bg-primary-500 .border-primary-600 .border .text-primary-50; + } + &:hover:not(:disabled) { - @apply .bg-red-600 .border-red-700; + @apply .bg-primary-600 .border-primary-700; } } @@ -182,16 +179,24 @@ a.btn { } } - &.btn-secondary { - @apply .border .border-neutral-600 .bg-transparent .text-neutral-200; - - &:hover:not(:disabled) { - @apply .border-neutral-500 .text-neutral-100; + &.btn-green { + &:not(.btn-secondary) { + @apply .bg-green-500 .border-green-600 .border .text-green-50; } - &.btn-red:hover:not(:disabled) { + &:hover:not(:disabled), &.btn-secondary:active:not(:disabled) { + @apply .bg-green-600 .border-green-700; + } + } + + &.btn-red { + &:not(.btn-secondary) { @apply .bg-red-500 .border-red-600 .text-red-50; } + + &:hover:not(:disabled), &.btn-secondary:active:not(:disabled) { + @apply .bg-red-600 .border-red-700; + } } /** diff --git a/routes/api-client.php b/routes/api-client.php index 37f699ff1..53cd113e0 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -16,6 +16,9 @@ Route::get('/permissions', 'ClientController@permissions'); Route::group(['prefix' => '/account'], function () { Route::get('/', 'AccountController@index')->name('api.client.account'); + Route::get('/two-factor', 'TwoFactorController@index'); + Route::post('/two-factor', 'TwoFactorController@store'); + Route::delete('/two-factor', 'TwoFactorController@delete'); Route::put('/email', 'AccountController@updateEmail')->name('api.client.account.update-email'); Route::put('/password', 'AccountController@updatePassword')->name('api.client.account.update-password'); diff --git a/yarn.lock b/yarn.lock index f46c2f81f..a327ede5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6857,6 +6857,10 @@ style-loader@^0.23.1: loader-utils "^1.1.0" schema-utils "^1.0.0" +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" + styled-components@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.4.1.tgz#e0631e889f01db67df4de576fedaca463f05c2f2" From 2a653cdd8d5fa3e548e4b7f281160df58c6a8cd7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 20:23:43 -0800 Subject: [PATCH 90/98] Automatically update the user data when 2FA is enabled --- .../forms/ConfigureTwoFactorForm.tsx | 2 +- .../dashboard/forms/SetupTwoFactorModal.tsx | 37 ++++++++----------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx index 5aef3bce9..7f8e502ab 100644 --- a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -20,7 +20,7 @@ export default () => {

    :
    - setVisible(false)}/> + {visible && setVisible(false)}/>}

    You do not currently have two-factor authentication enabled on your account. Click the button below to begin configuring it. diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx index 6151094bb..5a9439ad5 100644 --- a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -14,29 +14,28 @@ interface Values { code: string; } -export default ({ visible, onDismissed }: RequiredModalProps) => { +export default ({ ...props }: RequiredModalProps) => { const [ token, setToken ] = useState(''); const [ loading, setLoading ] = useState(true); + + const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); useEffect(() => { - if (!visible) { - clearFlashes('account:two-factor'); - getTwoFactorTokenUrl() - .then(setToken) - .catch(error => { - console.error(error); - }); - } - }, [ visible ]); + clearFlashes('account:two-factor'); + getTwoFactorTokenUrl() + .then(setToken) + .catch(error => { + console.error(error); + }); + }, []); - const submit = ({ code }: Values, { resetForm, setSubmitting }: FormikActions) => { + const submit = ({ code }: Values, { setSubmitting }: FormikActions) => { clearFlashes('account:two-factor'); enableAccountTwoFactor(code) .then(() => { - resetForm(); - setToken(''); - setLoading(true); + updateUserData({ useTotp: true }); + props.onDismissed(); }) .catch(error => { console.error(error); @@ -56,15 +55,9 @@ export default ({ visible, onDismissed }: RequiredModalProps) => { .matches(/^(\d){6}$/, 'Authenticator code must be 6 digits.'), })} > - {({ isSubmitting, isValid, resetForm }) => ( + {({ isSubmitting, isValid }) => ( { - resetForm(); - setToken(''); - setLoading(true); - onDismissed(); - }} + {...props} dismissable={!isSubmitting} showSpinnerOverlay={loading || isSubmitting} > From 9a0ed6b29115adeac9cf5014006db423f9c7c263 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 20:41:25 -0800 Subject: [PATCH 91/98] Add ability to disable two factor authentication --- .../Api/Client/TwoFactorController.php | 25 ++++++- .../api/account/disableAccountTwoFactor.ts | 9 +++ .../forms/ConfigureTwoFactorForm.tsx | 9 ++- .../dashboard/forms/DisableTwoFactorModal.tsx | 67 +++++++++++++++++++ 4 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 resources/scripts/api/account/disableAccountTwoFactor.ts create mode 100644 resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 17bae9baf..0dae225e5 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client; +use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; @@ -100,7 +101,29 @@ class TwoFactorController extends ClientApiController return JsonResponse::create([], Response::HTTP_NO_CONTENT); } - public function delete() + /** + * Disables two-factor authentication on an account if the password provided + * is valid. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function delete(Request $request) { + if (! password_verify($request->input('password') ?? '', $request->user()->password)) { + throw new BadRequestHttpException( + 'The password provided was not valid.' + ); + } + + /** @var \Pterodactyl\Models\User $user */ + $user = $request->user(); + + $user->update([ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => false, + ]); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); } } diff --git a/resources/scripts/api/account/disableAccountTwoFactor.ts b/resources/scripts/api/account/disableAccountTwoFactor.ts new file mode 100644 index 000000000..2b41fe20f --- /dev/null +++ b/resources/scripts/api/account/disableAccountTwoFactor.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (password: string): Promise => { + return new Promise((resolve, reject) => { + http.delete('/api/client/account/two-factor', { params: { password } }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx index 7f8e502ab..aadd388ee 100644 --- a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -2,18 +2,23 @@ import React, { useState } from 'react'; import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import SetupTwoFactorModal from '@/components/dashboard/forms/SetupTwoFactorModal'; +import DisableTwoFactorModal from '@/components/dashboard/forms/DisableTwoFactorModal'; export default () => { const user = useStoreState((state: ApplicationStore) => state.user.data!); - const [visible, setVisible] = useState(false); + const [ visible, setVisible ] = useState(false); return user.useTotp ?

    + {visible && setVisible(false)}/>}

    Two-factor authentication is currently enabled on your account.

    -
    diff --git a/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx new file mode 100644 index 000000000..f3c3e1cbe --- /dev/null +++ b/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Form, Formik, FormikActions } from 'formik'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Field from '@/components/elements/Field'; +import { object, string } from 'yup'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor'; +import { httpErrorToHuman } from '@/api/http'; + +interface Values { + password: string; +} + +export default ({ ...props }: RequiredModalProps) => { + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); + + const submit = ({ password }: Values, { setSubmitting }: FormikActions) => { + clearFlashes('account:two-factor'); + disableAccountTwoFactor(password) + .then(() => { + updateUserData({ useTotp: false }); + props.onDismissed(); + }) + .catch(error => { + console.error(error); + + addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); + setSubmitting(false); + }); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + +
    + + +
    + +
    + +
    + )} +
    + ); +}; From 6ccac6e040cdf46cef045cd4dcadd54822a36186 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 20:51:50 -0800 Subject: [PATCH 92/98] Mobile improvements for two-factor form --- .../dashboard/forms/ConfigureTwoFactorForm.tsx | 16 ++++++++++++++-- .../dashboard/forms/SetupTwoFactorModal.tsx | 10 +++++----- resources/scripts/components/elements/Modal.tsx | 2 ++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx index aadd388ee..dc6b467a6 100644 --- a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -10,7 +10,13 @@ export default () => { return user.useTotp ?
    - {visible && setVisible(false)}/>} + {visible && + setVisible(false)} + /> + }

    Two-factor authentication is currently enabled on your account.

    @@ -25,7 +31,13 @@ export default () => {
    :
    - {visible && setVisible(false)}/>} + {visible && + setVisible(false)} + /> + }

    You do not currently have two-factor authentication enabled on your account. Click the button below to begin configuring it. diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx index 5a9439ad5..01e6f0c88 100644 --- a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -63,9 +63,9 @@ export default ({ ...props }: RequiredModalProps) => { >

    -
    -
    -
    +
    +
    +
    {!token || !token.length ? { }
    -
    +
    { autoFocus={!loading} />
    -
    +
    diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index 195495dfe..075b5f01a 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -7,6 +7,7 @@ import Spinner from '@/components/elements/Spinner'; export interface RequiredModalProps { visible: boolean; onDismissed: () => void; + appear?: boolean; } type Props = RequiredModalProps & { @@ -38,6 +39,7 @@ export default (props: Props) => { props.onDismissed()} From 513692fef5599ccc4edd24c70efd7d1b58e47e2a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 20:56:34 -0800 Subject: [PATCH 93/98] Whoops, actually show servers --- resources/scripts/components/dashboard/DashboardContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 46ef66ebc..b017efd61 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -19,7 +19,7 @@ export default () => { return (
    - {servers.length === 0 ? + {servers.length > 0 ? servers.map(server => ( )) From 1f6f7c4bb4ef6005bf8550ebf730661a65870197 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 21:18:29 -0800 Subject: [PATCH 94/98] Fix authentication page on mobile devices --- resources/scripts/TransitionRouter.tsx | 19 ++++- .../components/auth/LoginContainer.tsx | 2 - .../components/auth/LoginFormContainer.tsx | 49 +++++++++---- .../scripts/routers/AuthenticationRouter.tsx | 2 +- resources/scripts/routers/DashboardRouter.tsx | 12 ++-- resources/scripts/routers/ServerRouter.tsx | 72 +++++++++---------- .../styles/components/authentication.css | 9 --- resources/styles/components/forms.css | 4 ++ resources/styles/main.css | 1 - resources/views/templates/auth/core.blade.php | 4 +- 10 files changed, 94 insertions(+), 80 deletions(-) delete mode 100644 resources/styles/components/authentication.css diff --git a/resources/scripts/TransitionRouter.tsx b/resources/scripts/TransitionRouter.tsx index eec5b4a98..10bbedab8 100644 --- a/resources/scripts/TransitionRouter.tsx +++ b/resources/scripts/TransitionRouter.tsx @@ -1,19 +1,32 @@ import React from 'react'; import { Route } from 'react-router'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import styled from 'styled-components'; +import { breakpoint } from 'styled-components-breakpoint'; type Props = Readonly<{ children: React.ReactNode; }>; +const ContentContainer = styled.div` + max-width: 1200px; + ${tw`mx-4`}; + + ${breakpoint('xl')` + ${tw`mx-auto`}; + `}; +`; + export default ({ children }: Props) => ( (
    - {children} -
    + + {children} + +

    © 2015 - 2019  ( Pterodactyl Software

    -
    +
    diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 1af30c2ba..fae4fa40d 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -32,8 +32,6 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl handleSubmit(e); }; - console.log(values.recaptchaData); - return ( {ref.current && ref.current.render()} diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index 2144cb56a..68ff4a3a9 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -1,21 +1,40 @@ import React, { forwardRef } from 'react'; +import styled from 'styled-components'; +import { Form } from 'formik'; +import { breakpoint } from 'styled-components-breakpoint'; type Props = React.DetailedHTMLProps, HTMLFormElement>; +const LoginContainer = styled.div` + ${tw`bg-white shadow-lg rounded-lg p-6 mx-1`}; + + ${breakpoint('sm')` + ${tw`w-4/5 mx-auto`} + `}; + + ${breakpoint('md')` + ${tw`flex p-10`} + `}; + + ${breakpoint('lg')` + ${tw`w-3/5`} + `}; + + ${breakpoint('xl')` + ${tw`w-full`} + max-width: 660px; + `}; +`; + export default forwardRef(({ className, ...props }, ref) => ( - -
    - -
    -
    - {props.children} -
    - +
    + +
    + +
    +
    + {props.children} +
    +
    +
    )); diff --git a/resources/scripts/routers/AuthenticationRouter.tsx b/resources/scripts/routers/AuthenticationRouter.tsx index 4054f55d5..15f745e8d 100644 --- a/resources/scripts/routers/AuthenticationRouter.tsx +++ b/resources/scripts/routers/AuthenticationRouter.tsx @@ -6,7 +6,7 @@ import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer'; import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer'; export default ({ match }: RouteComponentProps) => ( -
    +
    diff --git a/resources/scripts/routers/DashboardRouter.tsx b/resources/scripts/routers/DashboardRouter.tsx index d9312fd8c..0b218486f 100644 --- a/resources/scripts/routers/DashboardRouter.tsx +++ b/resources/scripts/routers/DashboardRouter.tsx @@ -10,13 +10,11 @@ export default ({ location }: RouteComponentProps) => ( -
    - - - - - -
    + + + + +
    ); diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index ac380a9bd..6ff19fa1c 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -1,4 +1,4 @@ -import React, { lazy, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom'; import NavigationBar from '@/components/NavigationBar'; import ServerConsole from '@/components/server/ServerConsole'; @@ -12,8 +12,6 @@ import FileManagerContainer from '@/components/server/files/FileManagerContainer import { CSSTransition } from 'react-transition-group'; import SuspenseSpinner from '@/components/elements/SuspenseSpinner'; import FileEditContainer from '@/components/server/files/FileEditContainer'; -import UsersContainer from '@/components/server/users/UsersContainer'; -import ScheduleContainer from '@/components/server/schedules/ScheduleContainer'; import SettingsContainer from '@/components/server/settings/SettingsContainer'; const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { @@ -32,48 +30,44 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
    -
    -
    - Console - File Manager - Databases - {/* User Management */} - {/* Schedules */} - Settings -
    +
    + Console + File Manager + Databases + {/* User Management */} + {/* Schedules */} + Settings
    -
    - {!server ? -
    - -
    - : - - - - - ( - - - - )} - exact - /> - - {/* */} - {/* */} - - - - } -
    + {!server ? +
    + +
    + : + + + + + ( + + + + )} + exact + /> + + {/* */} + {/* */} + + + + }
    diff --git a/resources/styles/components/authentication.css b/resources/styles/components/authentication.css deleted file mode 100644 index 2d841fe2e..000000000 --- a/resources/styles/components/authentication.css +++ /dev/null @@ -1,9 +0,0 @@ -.login-box { - @apply .bg-white .shadow-lg .rounded-lg .p-6; - - @screen xsx { - @apply .rounded-none; - margin-top: 25%; - box-shadow: 0 15px 30px 0 rgba(0, 0, 0, .2), 0 -15px 30px 0 rgba(0, 0, 0, .2); - } -} diff --git a/resources/styles/components/forms.css b/resources/styles/components/forms.css index 56323d76a..b3ce4e78f 100644 --- a/resources/styles/components/forms.css +++ b/resources/styles/components/forms.css @@ -1,3 +1,7 @@ +form { + @apply .m-0; +} + textarea, select, input, button { @apply .outline-none; } diff --git a/resources/styles/main.css b/resources/styles/main.css index d77937e72..bac7986f1 100644 --- a/resources/styles/main.css +++ b/resources/styles/main.css @@ -11,7 +11,6 @@ */ @import "components/typography.css"; @import "components/animations.css"; -@import "components/authentication.css"; @import "components/forms.css"; @import "components/miscellaneous.css"; @import "components/modal.css"; diff --git a/resources/views/templates/auth/core.blade.php b/resources/views/templates/auth/core.blade.php index 0889bb3a6..775f3e43a 100644 --- a/resources/views/templates/auth/core.blade.php +++ b/resources/views/templates/auth/core.blade.php @@ -3,7 +3,5 @@ ]) @section('container') -
    -
    -
    +
    @endsection From c4fdcb59377a3098438b377b9a84b03c5daf36ac Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 21:22:08 -0800 Subject: [PATCH 95/98] Fix navigation bar issues --- resources/scripts/components/server/ServerConsole.tsx | 2 +- resources/styles/components/navigation.css | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index a746e4556..55668ffbd 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -77,7 +77,7 @@ export default () => { return (
    -
    +

    a, & > div { From 338ffd0a8f7c4881c07bb0c9c8f2949d9ce03d60 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 21:27:26 -0800 Subject: [PATCH 96/98] Fix up build script --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 126bcf2a2..5912eff3e 100644 --- a/package.json +++ b/package.json @@ -108,10 +108,10 @@ "yarn-deduplicate": "^1.1.1" }, "scripts": { - "clean": "rm -rf public/assets/*.js && rm -rf public/assets/*.css", + "clean": "rm -rf public/assets/*.{js,css,map}", "watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", "build": "NODE_ENV=development ./node_modules/.bin/webpack --progress", - "build:production": "NODE_ENV=production ./node_modules/.bin/webpack --mode production", + "build:production": "yarn run clean && NODE_ENV=production ./node_modules/.bin/webpack --mode production", "serve": "yarn run clean && PUBLIC_PATH=https://pterodactyl.test:8080 NODE_ENV=development webpack-dev-server --host 0.0.0.0 --hot --https --key /etc/ssl/private/pterodactyl.test-key.pem --cert /etc/ssl/private/pterodactyl.test.pem" }, "browserslist": [ From 4173001f107e63ea51a4a23eec44b9b0ba244fbb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 21:44:50 -0800 Subject: [PATCH 97/98] Put this up one level so it doesn't disappear on releases --- public/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 public/.gitignore diff --git a/public/.gitignore b/public/.gitignore new file mode 100644 index 000000000..30e2ee69b --- /dev/null +++ b/public/.gitignore @@ -0,0 +1,2 @@ +assets/* +!assets/*.svg From 63918cb5491b40977ccb864961d3e5887ed5b4bb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 24 Dec 2019 16:48:50 -0800 Subject: [PATCH 98/98] Use the correct certificate key in the config --- app/Models/Node.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Node.php b/app/Models/Node.php index 7df3660fc..63d937218 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -163,7 +163,7 @@ class Node extends Validable 'port' => $this->daemonListen, 'ssl' => [ 'enabled' => (! $this->behind_proxy && $this->scheme === 'https'), - 'certificate' => '/etc/letsencrypt/live/' . $this->fqdn . '/fullchain.pem', + 'cert' => '/etc/letsencrypt/live/' . $this->fqdn . '/fullchain.pem', 'key' => '/etc/letsencrypt/live/' . $this->fqdn . '/privkey.pem', ], 'upload_limit' => $this->upload_size,