Show console when an admin is viewing an installing server
This commit is contained in:
parent
446dc8b33d
commit
6056b6f45d
|
@ -17,6 +17,16 @@ class AuthenticateServerAccess
|
||||||
*/
|
*/
|
||||||
private $repository;
|
private $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes that this middleware should not apply to if the user is an admin.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
'api:client:server.view',
|
||||||
|
'api:client:server.ws',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AuthenticateServerAccess constructor.
|
* AuthenticateServerAccess constructor.
|
||||||
*
|
*
|
||||||
|
@ -36,6 +46,8 @@ class AuthenticateServerAccess
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
|
/** @var \Pterodactyl\Models\User $user */
|
||||||
|
$user = $request->user();
|
||||||
$server = $request->route()->parameter('server');
|
$server = $request->route()->parameter('server');
|
||||||
|
|
||||||
if (! $server instanceof Server) {
|
if (! $server instanceof Server) {
|
||||||
|
@ -45,9 +57,9 @@ class AuthenticateServerAccess
|
||||||
// At the very least, ensure that the user trying to make this request is the
|
// At the very least, ensure that the user trying to make this request is the
|
||||||
// server owner, a subuser, or a root admin. We'll leave it up to the controllers
|
// server owner, a subuser, or a root admin. We'll leave it up to the controllers
|
||||||
// to authenticate more detailed permissions if needed.
|
// to authenticate more detailed permissions if needed.
|
||||||
if ($request->user()->id !== $server->owner_id && ! $request->user()->root_admin) {
|
if ($user->id !== $server->owner_id && ! $user->root_admin) {
|
||||||
// Check for subuser status.
|
// Check for subuser status.
|
||||||
if (! $server->subusers->contains('user_id', $request->user()->id)) {
|
if (! $server->subusers->contains('user_id', $user->id)) {
|
||||||
throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
|
throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +69,11 @@ class AuthenticateServerAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $server->isInstalled()) {
|
if (! $server->isInstalled()) {
|
||||||
throw new ConflictHttpException('Server has not completed the installation process.');
|
// Throw an exception for all server routes; however if the user is an admin and requesting the
|
||||||
|
// server details, don't throw the exception for them.
|
||||||
|
if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) {
|
||||||
|
throw new ConflictHttpException('Server has not completed the installation process.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->attributes->set('server', $server);
|
$request->attributes->set('server', $server);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import SuspenseSpinner from '@/components/elements/SuspenseSpinner';
|
||||||
import TitledGreyBox from '@/components/elements/TitledGreyBox';
|
import TitledGreyBox from '@/components/elements/TitledGreyBox';
|
||||||
import Can from '@/components/elements/Can';
|
import Can from '@/components/elements/Can';
|
||||||
import PageContentBlock from '@/components/elements/PageContentBlock';
|
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||||
|
import ContentContainer from '@/components/elements/ContentContainer';
|
||||||
|
|
||||||
type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
|
type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
|
||||||
|
|
||||||
|
@ -123,36 +124,47 @@ export default () => {
|
||||||
<span className={'text-neutral-500'}> / {server.limits.disk} MB</span>
|
<span className={'text-neutral-500'}> / {server.limits.disk} MB</span>
|
||||||
</p>
|
</p>
|
||||||
</TitledGreyBox>
|
</TitledGreyBox>
|
||||||
<Can action={[ 'control.start', 'control.stop', 'control.restart' ]} matchAny={true}>
|
{!server.isInstalling ?
|
||||||
<div className={'grey-box justify-center'}>
|
<Can action={[ 'control.start', 'control.stop', 'control.restart' ]} matchAny={true}>
|
||||||
<Can action={'control.start'}>
|
<div className={'grey-box justify-center'}>
|
||||||
<button
|
<Can action={'control.start'}>
|
||||||
className={'btn btn-secondary btn-green btn-xs mr-2'}
|
<button
|
||||||
disabled={status !== 'offline'}
|
className={'btn btn-secondary btn-green btn-xs mr-2'}
|
||||||
onClick={e => {
|
disabled={status !== 'offline'}
|
||||||
e.preventDefault();
|
onClick={e => {
|
||||||
sendPowerCommand('start');
|
e.preventDefault();
|
||||||
}}
|
sendPowerCommand('start');
|
||||||
>
|
}}
|
||||||
Start
|
>
|
||||||
</button>
|
Start
|
||||||
</Can>
|
</button>
|
||||||
<Can action={'control.restart'}>
|
</Can>
|
||||||
<button
|
<Can action={'control.restart'}>
|
||||||
className={'btn btn-secondary btn-primary btn-xs mr-2'}
|
<button
|
||||||
onClick={e => {
|
className={'btn btn-secondary btn-primary btn-xs mr-2'}
|
||||||
e.preventDefault();
|
onClick={e => {
|
||||||
sendPowerCommand('restart');
|
e.preventDefault();
|
||||||
}}
|
sendPowerCommand('restart');
|
||||||
>
|
}}
|
||||||
Restart
|
>
|
||||||
</button>
|
Restart
|
||||||
</Can>
|
</button>
|
||||||
<Can action={'control.stop'}>
|
</Can>
|
||||||
<StopOrKillButton onPress={action => sendPowerCommand(action)}/>
|
<Can action={'control.stop'}>
|
||||||
</Can>
|
<StopOrKillButton onPress={action => sendPowerCommand(action)}/>
|
||||||
|
</Can>
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
|
:
|
||||||
|
<div className={'mt-4 rounded bg-yellow-500 p-3'}>
|
||||||
|
<ContentContainer>
|
||||||
|
<p className={'text-sm text-yellow-900'}>
|
||||||
|
This server is currently running its installation process and most actions are
|
||||||
|
unavailable.
|
||||||
|
</p>
|
||||||
|
</ContentContainer>
|
||||||
</div>
|
</div>
|
||||||
</Can>
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1 ml-4'}>
|
<div className={'flex-1 ml-4'}>
|
||||||
<SuspenseSpinner>
|
<SuspenseSpinner>
|
||||||
|
|
|
@ -20,12 +20,16 @@ import Spinner from '@/components/elements/Spinner';
|
||||||
import ServerError from '@/components/screens/ServerError';
|
import ServerError from '@/components/screens/ServerError';
|
||||||
import { httpErrorToHuman } from '@/api/http';
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
import NotFound from '@/components/screens/NotFound';
|
import NotFound from '@/components/screens/NotFound';
|
||||||
|
import { useStoreState } from 'easy-peasy';
|
||||||
|
import ServerInstallingBar from '@/components/elements/ServerInstallingBar';
|
||||||
|
import useServer from '@/plugins/useServer';
|
||||||
import ScreenBlock from '@/components/screens/ScreenBlock';
|
import ScreenBlock from '@/components/screens/ScreenBlock';
|
||||||
|
|
||||||
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
|
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
|
||||||
|
const { rootAdmin } = useStoreState(state => state.user.data!);
|
||||||
const [ error, setError ] = useState('');
|
const [ error, setError ] = useState('');
|
||||||
const [ installing, setInstalling ] = useState(false);
|
const [ installing, setInstalling ] = useState(false);
|
||||||
const server = ServerContext.useStoreState(state => state.server.data);
|
const server = useServer();
|
||||||
const getServer = ServerContext.useStoreActions(actions => actions.server.getServer);
|
const getServer = ServerContext.useStoreActions(actions => actions.server.getServer);
|
||||||
const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState);
|
const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState);
|
||||||
|
|
||||||
|
@ -33,6 +37,10 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
||||||
clearServerState();
|
clearServerState();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInstalling(server?.isInstalling !== false);
|
||||||
|
}, [ server?.isInstalling ]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError('');
|
setError('');
|
||||||
setInstalling(false);
|
setInstalling(false);
|
||||||
|
@ -55,19 +63,12 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
||||||
<React.Fragment key={'server-router'}>
|
<React.Fragment key={'server-router'}>
|
||||||
<NavigationBar/>
|
<NavigationBar/>
|
||||||
{!server ?
|
{!server ?
|
||||||
!installing ?
|
error ?
|
||||||
error ?
|
<ServerError message={error}/>
|
||||||
<ServerError message={error}/>
|
|
||||||
:
|
|
||||||
<div className={'flex justify-center m-20'}>
|
|
||||||
<Spinner size={'large'}/>
|
|
||||||
</div>
|
|
||||||
:
|
:
|
||||||
<ScreenBlock
|
<div className={'flex justify-center m-20'}>
|
||||||
title={'Your server is installing.'}
|
<Spinner size={'large'}/>
|
||||||
image={'/assets/svgs/server_installing.svg'}
|
</div>
|
||||||
message={'Please check back in a few minutes.'}
|
|
||||||
/>
|
|
||||||
:
|
:
|
||||||
<>
|
<>
|
||||||
<CSSTransition timeout={250} classNames={'fade'} appear={true} in={true}>
|
<CSSTransition timeout={250} classNames={'fade'} appear={true} in={true}>
|
||||||
|
@ -95,29 +96,43 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
<WebsocketHandler/>
|
{(installing && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${server.id}`)))) ?
|
||||||
<TransitionRouter>
|
<ScreenBlock
|
||||||
<Switch location={location}>
|
title={'Your server is installing.'}
|
||||||
<Route path={`${match.path}`} component={ServerConsole} exact/>
|
image={'/assets/svgs/server_installing.svg'}
|
||||||
<Route path={`${match.path}/files`} component={FileManagerContainer} exact/>
|
message={'Please check back in a few minutes.'}
|
||||||
<Route
|
/>
|
||||||
path={`${match.path}/files/:action(edit|new)`}
|
:
|
||||||
render={props => (
|
<>
|
||||||
<SuspenseSpinner>
|
<WebsocketHandler/>
|
||||||
<FileEditContainer {...props as any}/>
|
<TransitionRouter>
|
||||||
</SuspenseSpinner>
|
<Switch location={location}>
|
||||||
)}
|
<Route path={`${match.path}`} component={ServerConsole} exact/>
|
||||||
exact
|
<Route path={`${match.path}/files`} component={FileManagerContainer} exact/>
|
||||||
/>
|
<Route
|
||||||
<Route path={`${match.path}/databases`} component={DatabasesContainer} exact/>
|
path={`${match.path}/files/:action(edit|new)`}
|
||||||
<Route path={`${match.path}/schedules`} component={ScheduleContainer} exact/>
|
render={props => (
|
||||||
<Route path={`${match.path}/schedules/:id`} component={ScheduleEditContainer} exact/>
|
<SuspenseSpinner>
|
||||||
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
<FileEditContainer {...props as any}/>
|
||||||
<Route path={`${match.path}/backups`} component={BackupContainer} exact/>
|
</SuspenseSpinner>
|
||||||
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
|
)}
|
||||||
<Route path={'*'} component={NotFound}/>
|
exact
|
||||||
</Switch>
|
/>
|
||||||
</TransitionRouter>
|
<Route path={`${match.path}/databases`} component={DatabasesContainer} exact/>
|
||||||
|
<Route path={`${match.path}/schedules`} component={ScheduleContainer} exact/>
|
||||||
|
<Route
|
||||||
|
path={`${match.path}/schedules/:id`}
|
||||||
|
component={ScheduleEditContainer}
|
||||||
|
exact
|
||||||
|
/>
|
||||||
|
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
||||||
|
<Route path={`${match.path}/backups`} component={BackupContainer} exact/>
|
||||||
|
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
|
||||||
|
<Route path={'*'} component={NotFound}/>
|
||||||
|
</Switch>
|
||||||
|
</TransitionRouter>
|
||||||
|
</>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -11,17 +11,17 @@ use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess;
|
||||||
| Endpoint: /api/client
|
| Endpoint: /api/client
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
Route::get('/', 'ClientController@index')->name('api.client.index');
|
Route::get('/', 'ClientController@index')->name('api:client.index');
|
||||||
Route::get('/permissions', 'ClientController@permissions');
|
Route::get('/permissions', 'ClientController@permissions');
|
||||||
|
|
||||||
Route::group(['prefix' => '/account'], function () {
|
Route::group(['prefix' => '/account'], function () {
|
||||||
Route::get('/', 'AccountController@index')->name('api.client.account');
|
Route::get('/', 'AccountController@index')->name('api:client.account');
|
||||||
Route::get('/two-factor', 'TwoFactorController@index');
|
Route::get('/two-factor', 'TwoFactorController@index');
|
||||||
Route::post('/two-factor', 'TwoFactorController@store');
|
Route::post('/two-factor', 'TwoFactorController@store');
|
||||||
Route::delete('/two-factor', 'TwoFactorController@delete');
|
Route::delete('/two-factor', 'TwoFactorController@delete');
|
||||||
|
|
||||||
Route::put('/email', 'AccountController@updateEmail')->name('api.client.account.update-email');
|
Route::put('/email', 'AccountController@updateEmail')->name('api:client.account.update-email');
|
||||||
Route::put('/password', 'AccountController@updatePassword')->name('api.client.account.update-password');
|
Route::put('/password', 'AccountController@updatePassword')->name('api:client.account.update-password');
|
||||||
|
|
||||||
Route::get('/api-keys', 'ApiKeyController@index');
|
Route::get('/api-keys', 'ApiKeyController@index');
|
||||||
Route::post('/api-keys', 'ApiKeyController@store');
|
Route::post('/api-keys', 'ApiKeyController@store');
|
||||||
|
@ -37,30 +37,29 @@ Route::group(['prefix' => '/account'], function () {
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class]], function () {
|
Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class]], function () {
|
||||||
Route::get('/', 'Servers\ServerController@index')->name('api.client.servers.view');
|
Route::get('/', 'Servers\ServerController@index')->name('api:client:server.view');
|
||||||
Route::get('/websocket', 'Servers\WebsocketController')->name('api.client.servers.websocket');
|
Route::get('/websocket', 'Servers\WebsocketController')->name('api:client:server.ws');
|
||||||
Route::get('/resources', 'Servers\ResourceUtilizationController')
|
Route::get('/resources', 'Servers\ResourceUtilizationController')->name('api:client:server.resources');
|
||||||
->name('api.client.servers.resources');
|
|
||||||
|
|
||||||
Route::post('/command', 'Servers\CommandController@index')->name('api.client.servers.command');
|
Route::post('/command', 'Servers\CommandController@index');
|
||||||
Route::post('/power', 'Servers\PowerController@index')->name('api.client.servers.power');
|
Route::post('/power', 'Servers\PowerController@index');
|
||||||
|
|
||||||
Route::group(['prefix' => '/databases'], function () {
|
Route::group(['prefix' => '/databases'], function () {
|
||||||
Route::get('/', 'Servers\DatabaseController@index')->name('api.client.servers.databases');
|
Route::get('/', 'Servers\DatabaseController@index');
|
||||||
Route::post('/', 'Servers\DatabaseController@store');
|
Route::post('/', 'Servers\DatabaseController@store');
|
||||||
Route::post('/{database}/rotate-password', 'Servers\DatabaseController@rotatePassword');
|
Route::post('/{database}/rotate-password', 'Servers\DatabaseController@rotatePassword');
|
||||||
Route::delete('/{database}', 'Servers\DatabaseController@delete')->name('api.client.servers.databases.delete');
|
Route::delete('/{database}', 'Servers\DatabaseController@delete');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => '/files'], function () {
|
Route::group(['prefix' => '/files'], function () {
|
||||||
Route::get('/list', 'Servers\FileController@listDirectory')->name('api.client.servers.files.list');
|
Route::get('/list', 'Servers\FileController@listDirectory');
|
||||||
Route::get('/contents', 'Servers\FileController@getFileContents')->name('api.client.servers.files.contents');
|
Route::get('/contents', 'Servers\FileController@getFileContents');
|
||||||
Route::get('/download', 'Servers\FileController@download');
|
Route::get('/download', 'Servers\FileController@download');
|
||||||
Route::put('/rename', 'Servers\FileController@renameFile')->name('api.client.servers.files.rename');
|
Route::put('/rename', 'Servers\FileController@renameFile');
|
||||||
Route::post('/copy', 'Servers\FileController@copyFile')->name('api.client.servers.files.copy');
|
Route::post('/copy', 'Servers\FileController@copyFile');
|
||||||
Route::post('/write', 'Servers\FileController@writeFileContents')->name('api.client.servers.files.write');
|
Route::post('/write', 'Servers\FileController@writeFileContents');
|
||||||
Route::post('/delete', 'Servers\FileController@delete')->name('api.client.servers.files.delete');
|
Route::post('/delete', 'Servers\FileController@delete');
|
||||||
Route::post('/create-folder', 'Servers\FileController@createFolder')->name('api.client.servers.files.create-folder');
|
Route::post('/create-folder', 'Servers\FileController@createFolder');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => '/schedules'], function () {
|
Route::group(['prefix' => '/schedules'], function () {
|
||||||
|
@ -76,7 +75,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => '/network'], function () {
|
Route::group(['prefix' => '/network'], function () {
|
||||||
Route::get('/', 'Servers\NetworkController@index')->name('api.client.servers.network');
|
Route::get('/', 'Servers\NetworkController@index');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => '/users'], function () {
|
Route::group(['prefix' => '/users'], function () {
|
||||||
|
|
Loading…
Reference in New Issue