Skip to content

Commit 6056b6f

Browse files
committed
Show console when an admin is viewing an installing server
1 parent 446dc8b commit 6056b6f

File tree

4 files changed

+130
-88
lines changed

4 files changed

+130
-88
lines changed

app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ class AuthenticateServerAccess
1717
*/
1818
private $repository;
1919

20+
/**
21+
* Routes that this middleware should not apply to if the user is an admin.
22+
*
23+
* @var string[]
24+
*/
25+
protected $except = [
26+
'api:client:server.view',
27+
'api:client:server.ws',
28+
];
29+
2030
/**
2131
* AuthenticateServerAccess constructor.
2232
*
@@ -36,6 +46,8 @@ public function __construct(ServerRepositoryInterface $repository)
3646
*/
3747
public function handle(Request $request, Closure $next)
3848
{
49+
/** @var \Pterodactyl\Models\User $user */
50+
$user = $request->user();
3951
$server = $request->route()->parameter('server');
4052

4153
if (! $server instanceof Server) {
@@ -45,9 +57,9 @@ public function handle(Request $request, Closure $next)
4557
// At the very least, ensure that the user trying to make this request is the
4658
// server owner, a subuser, or a root admin. We'll leave it up to the controllers
4759
// to authenticate more detailed permissions if needed.
48-
if ($request->user()->id !== $server->owner_id && ! $request->user()->root_admin) {
60+
if ($user->id !== $server->owner_id && ! $user->root_admin) {
4961
// Check for subuser status.
50-
if (! $server->subusers->contains('user_id', $request->user()->id)) {
62+
if (! $server->subusers->contains('user_id', $user->id)) {
5163
throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
5264
}
5365
}
@@ -57,7 +69,11 @@ public function handle(Request $request, Closure $next)
5769
}
5870

5971
if (! $server->isInstalled()) {
60-
throw new ConflictHttpException('Server has not completed the installation process.');
72+
// Throw an exception for all server routes; however if the user is an admin and requesting the
73+
// server details, don't throw the exception for them.
74+
if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) {
75+
throw new ConflictHttpException('Server has not completed the installation process.');
76+
}
6177
}
6278

6379
$request->attributes->set('server', $server);

resources/scripts/components/server/ServerConsole.tsx

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import SuspenseSpinner from '@/components/elements/SuspenseSpinner';
1212
import TitledGreyBox from '@/components/elements/TitledGreyBox';
1313
import Can from '@/components/elements/Can';
1414
import PageContentBlock from '@/components/elements/PageContentBlock';
15+
import ContentContainer from '@/components/elements/ContentContainer';
1516

1617
type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
1718

@@ -123,36 +124,47 @@ export default () => {
123124
<span className={'text-neutral-500'}> / {server.limits.disk} MB</span>
124125
</p>
125126
</TitledGreyBox>
126-
<Can action={[ 'control.start', 'control.stop', 'control.restart' ]} matchAny={true}>
127-
<div className={'grey-box justify-center'}>
128-
<Can action={'control.start'}>
129-
<button
130-
className={'btn btn-secondary btn-green btn-xs mr-2'}
131-
disabled={status !== 'offline'}
132-
onClick={e => {
133-
e.preventDefault();
134-
sendPowerCommand('start');
135-
}}
136-
>
137-
Start
138-
</button>
139-
</Can>
140-
<Can action={'control.restart'}>
141-
<button
142-
className={'btn btn-secondary btn-primary btn-xs mr-2'}
143-
onClick={e => {
144-
e.preventDefault();
145-
sendPowerCommand('restart');
146-
}}
147-
>
148-
Restart
149-
</button>
150-
</Can>
151-
<Can action={'control.stop'}>
152-
<StopOrKillButton onPress={action => sendPowerCommand(action)}/>
153-
</Can>
127+
{!server.isInstalling ?
128+
<Can action={[ 'control.start', 'control.stop', 'control.restart' ]} matchAny={true}>
129+
<div className={'grey-box justify-center'}>
130+
<Can action={'control.start'}>
131+
<button
132+
className={'btn btn-secondary btn-green btn-xs mr-2'}
133+
disabled={status !== 'offline'}
134+
onClick={e => {
135+
e.preventDefault();
136+
sendPowerCommand('start');
137+
}}
138+
>
139+
Start
140+
</button>
141+
</Can>
142+
<Can action={'control.restart'}>
143+
<button
144+
className={'btn btn-secondary btn-primary btn-xs mr-2'}
145+
onClick={e => {
146+
e.preventDefault();
147+
sendPowerCommand('restart');
148+
}}
149+
>
150+
Restart
151+
</button>
152+
</Can>
153+
<Can action={'control.stop'}>
154+
<StopOrKillButton onPress={action => sendPowerCommand(action)}/>
155+
</Can>
156+
</div>
157+
</Can>
158+
:
159+
<div className={'mt-4 rounded bg-yellow-500 p-3'}>
160+
<ContentContainer>
161+
<p className={'text-sm text-yellow-900'}>
162+
This server is currently running its installation process and most actions are
163+
unavailable.
164+
</p>
165+
</ContentContainer>
154166
</div>
155-
</Can>
167+
}
156168
</div>
157169
<div className={'flex-1 ml-4'}>
158170
<SuspenseSpinner>

resources/scripts/routers/ServerRouter.tsx

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,27 @@ import Spinner from '@/components/elements/Spinner';
2020
import ServerError from '@/components/screens/ServerError';
2121
import { httpErrorToHuman } from '@/api/http';
2222
import NotFound from '@/components/screens/NotFound';
23+
import { useStoreState } from 'easy-peasy';
24+
import ServerInstallingBar from '@/components/elements/ServerInstallingBar';
25+
import useServer from '@/plugins/useServer';
2326
import ScreenBlock from '@/components/screens/ScreenBlock';
2427

2528
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
29+
const { rootAdmin } = useStoreState(state => state.user.data!);
2630
const [ error, setError ] = useState('');
2731
const [ installing, setInstalling ] = useState(false);
28-
const server = ServerContext.useStoreState(state => state.server.data);
32+
const server = useServer();
2933
const getServer = ServerContext.useStoreActions(actions => actions.server.getServer);
3034
const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState);
3135

3236
useEffect(() => () => {
3337
clearServerState();
3438
}, []);
3539

40+
useEffect(() => {
41+
setInstalling(server?.isInstalling !== false);
42+
}, [ server?.isInstalling ]);
43+
3644
useEffect(() => {
3745
setError('');
3846
setInstalling(false);
@@ -55,19 +63,12 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
5563
<React.Fragment key={'server-router'}>
5664
<NavigationBar/>
5765
{!server ?
58-
!installing ?
59-
error ?
60-
<ServerError message={error}/>
61-
:
62-
<div className={'flex justify-center m-20'}>
63-
<Spinner size={'large'}/>
64-
</div>
66+
error ?
67+
<ServerError message={error}/>
6568
:
66-
<ScreenBlock
67-
title={'Your server is installing.'}
68-
image={'/assets/svgs/server_installing.svg'}
69-
message={'Please check back in a few minutes.'}
70-
/>
69+
<div className={'flex justify-center m-20'}>
70+
<Spinner size={'large'}/>
71+
</div>
7172
:
7273
<>
7374
<CSSTransition timeout={250} classNames={'fade'} appear={true} in={true}>
@@ -95,29 +96,43 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
9596
</div>
9697
</div>
9798
</CSSTransition>
98-
<WebsocketHandler/>
99-
<TransitionRouter>
100-
<Switch location={location}>
101-
<Route path={`${match.path}`} component={ServerConsole} exact/>
102-
<Route path={`${match.path}/files`} component={FileManagerContainer} exact/>
103-
<Route
104-
path={`${match.path}/files/:action(edit|new)`}
105-
render={props => (
106-
<SuspenseSpinner>
107-
<FileEditContainer {...props as any}/>
108-
</SuspenseSpinner>
109-
)}
110-
exact
111-
/>
112-
<Route path={`${match.path}/databases`} component={DatabasesContainer} exact/>
113-
<Route path={`${match.path}/schedules`} component={ScheduleContainer} exact/>
114-
<Route path={`${match.path}/schedules/:id`} component={ScheduleEditContainer} exact/>
115-
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
116-
<Route path={`${match.path}/backups`} component={BackupContainer} exact/>
117-
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
118-
<Route path={'*'} component={NotFound}/>
119-
</Switch>
120-
</TransitionRouter>
99+
{(installing && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${server.id}`)))) ?
100+
<ScreenBlock
101+
title={'Your server is installing.'}
102+
image={'/assets/svgs/server_installing.svg'}
103+
message={'Please check back in a few minutes.'}
104+
/>
105+
:
106+
<>
107+
<WebsocketHandler/>
108+
<TransitionRouter>
109+
<Switch location={location}>
110+
<Route path={`${match.path}`} component={ServerConsole} exact/>
111+
<Route path={`${match.path}/files`} component={FileManagerContainer} exact/>
112+
<Route
113+
path={`${match.path}/files/:action(edit|new)`}
114+
render={props => (
115+
<SuspenseSpinner>
116+
<FileEditContainer {...props as any}/>
117+
</SuspenseSpinner>
118+
)}
119+
exact
120+
/>
121+
<Route path={`${match.path}/databases`} component={DatabasesContainer} exact/>
122+
<Route path={`${match.path}/schedules`} component={ScheduleContainer} exact/>
123+
<Route
124+
path={`${match.path}/schedules/:id`}
125+
component={ScheduleEditContainer}
126+
exact
127+
/>
128+
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
129+
<Route path={`${match.path}/backups`} component={BackupContainer} exact/>
130+
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
131+
<Route path={'*'} component={NotFound}/>
132+
</Switch>
133+
</TransitionRouter>
134+
</>
135+
}
121136
</>
122137
}
123138
</React.Fragment>

routes/api-client.php

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111
| Endpoint: /api/client
1212
|
1313
*/
14-
Route::get('/', 'ClientController@index')->name('api.client.index');
14+
Route::get('/', 'ClientController@index')->name('api:client.index');
1515
Route::get('/permissions', 'ClientController@permissions');
1616

1717
Route::group(['prefix' => '/account'], function () {
18-
Route::get('/', 'AccountController@index')->name('api.client.account');
18+
Route::get('/', 'AccountController@index')->name('api:client.account');
1919
Route::get('/two-factor', 'TwoFactorController@index');
2020
Route::post('/two-factor', 'TwoFactorController@store');
2121
Route::delete('/two-factor', 'TwoFactorController@delete');
2222

23-
Route::put('/email', 'AccountController@updateEmail')->name('api.client.account.update-email');
24-
Route::put('/password', 'AccountController@updatePassword')->name('api.client.account.update-password');
23+
Route::put('/email', 'AccountController@updateEmail')->name('api:client.account.update-email');
24+
Route::put('/password', 'AccountController@updatePassword')->name('api:client.account.update-password');
2525

2626
Route::get('/api-keys', 'ApiKeyController@index');
2727
Route::post('/api-keys', 'ApiKeyController@store');
@@ -37,30 +37,29 @@
3737
|
3838
*/
3939
Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class]], function () {
40-
Route::get('/', 'Servers\ServerController@index')->name('api.client.servers.view');
41-
Route::get('/websocket', 'Servers\WebsocketController')->name('api.client.servers.websocket');
42-
Route::get('/resources', 'Servers\ResourceUtilizationController')
43-
->name('api.client.servers.resources');
40+
Route::get('/', 'Servers\ServerController@index')->name('api:client:server.view');
41+
Route::get('/websocket', 'Servers\WebsocketController')->name('api:client:server.ws');
42+
Route::get('/resources', 'Servers\ResourceUtilizationController')->name('api:client:server.resources');
4443

45-
Route::post('/command', 'Servers\CommandController@index')->name('api.client.servers.command');
46-
Route::post('/power', 'Servers\PowerController@index')->name('api.client.servers.power');
44+
Route::post('/command', 'Servers\CommandController@index');
45+
Route::post('/power', 'Servers\PowerController@index');
4746

4847
Route::group(['prefix' => '/databases'], function () {
49-
Route::get('/', 'Servers\DatabaseController@index')->name('api.client.servers.databases');
48+
Route::get('/', 'Servers\DatabaseController@index');
5049
Route::post('/', 'Servers\DatabaseController@store');
5150
Route::post('/{database}/rotate-password', 'Servers\DatabaseController@rotatePassword');
52-
Route::delete('/{database}', 'Servers\DatabaseController@delete')->name('api.client.servers.databases.delete');
51+
Route::delete('/{database}', 'Servers\DatabaseController@delete');
5352
});
5453

5554
Route::group(['prefix' => '/files'], function () {
56-
Route::get('/list', 'Servers\FileController@listDirectory')->name('api.client.servers.files.list');
57-
Route::get('/contents', 'Servers\FileController@getFileContents')->name('api.client.servers.files.contents');
55+
Route::get('/list', 'Servers\FileController@listDirectory');
56+
Route::get('/contents', 'Servers\FileController@getFileContents');
5857
Route::get('/download', 'Servers\FileController@download');
59-
Route::put('/rename', 'Servers\FileController@renameFile')->name('api.client.servers.files.rename');
60-
Route::post('/copy', 'Servers\FileController@copyFile')->name('api.client.servers.files.copy');
61-
Route::post('/write', 'Servers\FileController@writeFileContents')->name('api.client.servers.files.write');
62-
Route::post('/delete', 'Servers\FileController@delete')->name('api.client.servers.files.delete');
63-
Route::post('/create-folder', 'Servers\FileController@createFolder')->name('api.client.servers.files.create-folder');
58+
Route::put('/rename', 'Servers\FileController@renameFile');
59+
Route::post('/copy', 'Servers\FileController@copyFile');
60+
Route::post('/write', 'Servers\FileController@writeFileContents');
61+
Route::post('/delete', 'Servers\FileController@delete');
62+
Route::post('/create-folder', 'Servers\FileController@createFolder');
6463
});
6564

6665
Route::group(['prefix' => '/schedules'], function () {
@@ -76,7 +75,7 @@
7675
});
7776

7877
Route::group(['prefix' => '/network'], function () {
79-
Route::get('/', 'Servers\NetworkController@index')->name('api.client.servers.network');
78+
Route::get('/', 'Servers\NetworkController@index');
8079
});
8180

8281
Route::group(['prefix' => '/users'], function () {

0 commit comments

Comments
 (0)