Skip to content

Commit 4960bc7

Browse files
committed
Merge branch 'develop' of github.com:pterodactyl/panel into develop
2 parents cfb5b8c + bda1ff5 commit 4960bc7

File tree

9 files changed

+178
-52
lines changed

9 files changed

+178
-52
lines changed

app/Http/Controllers/Api/Client/TwoFactorController.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,7 @@ public function index(Request $request)
6161
}
6262

6363
return new JsonResponse([
64-
'data' => [
65-
'image_url_data' => $this->setupService->handle($request->user()),
66-
],
64+
'data' => $this->setupService->handle($request->user()),
6765
]);
6866
}
6967

app/Services/Users/TwoFactorSetupService.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function __construct(
4949
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
5050
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
5151
*/
52-
public function handle(User $user): string
52+
public function handle(User $user): array
5353
{
5454
$secret = '';
5555
try {
@@ -66,11 +66,14 @@ public function handle(User $user): string
6666

6767
$company = urlencode(preg_replace('/\s/', '', $this->config->get('app.name')));
6868

69-
return sprintf(
70-
'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s',
71-
rawurlencode($company),
72-
rawurlencode($user->email),
73-
rawurlencode($secret)
74-
);
69+
return [
70+
'image_url_data' => sprintf(
71+
'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s',
72+
rawurlencode($company),
73+
rawurlencode($user->email),
74+
rawurlencode($secret),
75+
),
76+
'secret' => $secret,
77+
];
7578
}
7679
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import http from '@/api/http';
2+
3+
export interface TwoFactorTokenData {
4+
// eslint-disable-next-line camelcase
5+
image_url_data: string;
6+
secret: string;
7+
}
8+
9+
export default (): Promise<TwoFactorTokenData> => {
10+
return new Promise((resolve, reject) => {
11+
http.get('/api/client/account/two-factor')
12+
.then(({ data }) => resolve(data.data))
13+
.catch(reject);
14+
});
15+
};

resources/scripts/api/account/getTwoFactorTokenUrl.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useContext, useEffect, useState } from 'react';
22
import { Form, Formik, FormikHelpers } from 'formik';
33
import { object, string } from 'yup';
4-
import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl';
4+
import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
55
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
66
import { Actions, useStoreActions } from 'easy-peasy';
77
import { ApplicationStore } from '@/state';
@@ -12,21 +12,22 @@ import Button from '@/components/elements/Button';
1212
import asModal from '@/hoc/asModal';
1313
import ModalContext from '@/context/ModalContext';
1414
import QRCode from 'qrcode.react';
15+
import CopyOnClick from '@/components/elements/CopyOnClick';
1516

1617
interface Values {
1718
code: string;
1819
}
1920

2021
const SetupTwoFactorModal = () => {
21-
const [ token, setToken ] = useState('');
22+
const [ token, setToken ] = useState<TwoFactorTokenData | null>(null);
2223
const [ recoveryTokens, setRecoveryTokens ] = useState<string[]>([]);
2324

2425
const { dismiss, setPropOverrides } = useContext(ModalContext);
2526
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
2627
const { clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
2728

2829
useEffect(() => {
29-
getTwoFactorTokenUrl()
30+
getTwoFactorTokenData()
3031
.then(setToken)
3132
.catch(error => {
3233
console.error(error);
@@ -102,13 +103,17 @@ const SetupTwoFactorModal = () => {
102103
<div css={tw`flex flex-wrap`}>
103104
<div css={tw`w-full md:flex-1`}>
104105
<div css={tw`w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto`}>
105-
{!token || !token.length ?
106+
{!token ?
106107
<img
107108
src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
108109
css={tw`w-64 h-64 rounded`}
109110
/>
110111
:
111-
<QRCode renderAs={'svg'} value={token} css={tw`w-full h-full shadow-none rounded-none`}/>
112+
<QRCode
113+
renderAs={'svg'}
114+
value={token.image_url_data}
115+
css={tw`w-full h-full shadow-none rounded-none`}
116+
/>
112117
}
113118
</div>
114119
</div>
@@ -121,11 +126,21 @@ const SetupTwoFactorModal = () => {
121126
title={'Code From Authenticator'}
122127
description={'Enter the code from your authenticator device after scanning the QR image.'}
123128
/>
129+
{token &&
130+
<div css={tw`mt-4 pt-4 border-t border-neutral-500 text-neutral-200`}>
131+
Alternatively, enter the following token into your authenticator application:
132+
<CopyOnClick text={token.secret}>
133+
<div css={tw`text-sm bg-neutral-900 rounded mt-2 py-2 px-4 font-mono`}>
134+
<code css={tw`font-mono`}>
135+
{token.secret}
136+
</code>
137+
</div>
138+
</CopyOnClick>
139+
</div>
140+
}
124141
</div>
125142
<div css={tw`mt-6 md:mt-0 text-right`}>
126-
<Button>
127-
Setup
128-
</Button>
143+
<Button>Setup</Button>
129144
</div>
130145
</div>
131146
</div>

resources/scripts/components/server/ServerConsole.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import ServerContentBlock from '@/components/elements/ServerContentBlock';
77
import ServerDetailsBlock from '@/components/server/ServerDetailsBlock';
88
import isEqual from 'react-fast-compare';
99
import PowerControls from '@/components/server/PowerControls';
10-
import { EulaModalFeature } from '@feature/index';
10+
import { EulaModalFeature, JavaVersionModalFeature } from '@feature/index';
1111
import ErrorBoundary from '@/components/elements/ErrorBoundary';
1212
import Spinner from '@/components/elements/Spinner';
1313

@@ -60,6 +60,7 @@ const ServerConsole = () => {
6060
{eggFeatures.includes('eula') &&
6161
<React.Suspense fallback={null}>
6262
<EulaModalFeature/>
63+
<JavaVersionModalFeature/>
6364
</React.Suspense>
6465
}
6566
</div>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { ServerContext } from '@/state/server';
3+
import Modal from '@/components/elements/Modal';
4+
import tw from 'twin.macro';
5+
import Button from '@/components/elements/Button';
6+
import setSelectedDockerImage from '@/api/server/setSelectedDockerImage';
7+
import FlashMessageRender from '@/components/FlashMessageRender';
8+
import useFlash from '@/plugins/useFlash';
9+
import { SocketEvent, SocketRequest } from '@/components/server/events';
10+
import Select from '@/components/elements/Select';
11+
12+
const dockerImageList = [
13+
{ name: 'Java 8', image: 'ghcr.io/pterodactyl/yolks:java_8' },
14+
{ name: 'Java 11', image: 'ghcr.io/pterodactyl/yolks:java_11' },
15+
{ name: 'Java 16', image: 'ghcr.io/pterodactyl/yolks:java_16' },
16+
];
17+
18+
const JavaVersionModalFeature = () => {
19+
const [ visible, setVisible ] = useState(false);
20+
const [ loading, setLoading ] = useState(false);
21+
const [ selectedVersion, setSelectedVersion ] = useState('ghcr.io/pterodactyl/yolks:java_16');
22+
23+
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
24+
const status = ServerContext.useStoreState(state => state.status.value);
25+
const { clearFlashes, clearAndAddHttpError } = useFlash();
26+
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
27+
28+
useEffect(() => {
29+
if (!connected || !instance || status === 'running') return;
30+
31+
const errors = [
32+
'minecraft 1.17 requires running the server with java 16 or above',
33+
'java.lang.unsupportedclassversionerror',
34+
'unsupported major.minor version',
35+
'has been compiled by a more recent version of the java runtime',
36+
];
37+
38+
const listener = (line: string) => {
39+
if (errors.some(p => line.toLowerCase().includes(p))) {
40+
setVisible(true);
41+
}
42+
};
43+
44+
instance.addListener(SocketEvent.CONSOLE_OUTPUT, listener);
45+
46+
return () => {
47+
instance.removeListener(SocketEvent.CONSOLE_OUTPUT, listener);
48+
};
49+
}, [ connected, instance, status ]);
50+
51+
const updateJava = () => {
52+
setLoading(true);
53+
clearFlashes('feature:javaVersion');
54+
55+
setSelectedDockerImage(uuid, selectedVersion)
56+
.then(() => {
57+
if (status === 'offline' && instance) {
58+
instance.send(SocketRequest.SET_STATE, 'restart');
59+
}
60+
61+
setLoading(false);
62+
setVisible(false);
63+
})
64+
.catch(error => {
65+
console.error(error);
66+
clearAndAddHttpError({ key: 'feature:javaVersion', error });
67+
})
68+
.then(() => setLoading(false));
69+
};
70+
71+
useEffect(() => {
72+
clearFlashes('feature:javaVersion');
73+
}, []);
74+
75+
return (
76+
<Modal visible={visible} onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
77+
<FlashMessageRender key={'feature:javaVersion'} css={tw`mb-4`}/>
78+
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Invalid Java Version, Update Docker Image?</h2>
79+
<p css={tw`mt-4`}>This server is unable to start due to the required java version not being met.</p>
80+
<p css={tw`mt-4`}>By pressing {'"Update Docker Image"'} below you are acknowledging that the docker image this server uses will be changed to a image below that has the Java version you are requesting.</p>
81+
<div css={tw`sm:flex items-center mt-4`}>
82+
<p>Please select a Java version from the list below.</p>
83+
<Select
84+
onChange={e => setSelectedVersion(e.target.value)}
85+
>
86+
{dockerImageList.map((key, index) => {
87+
return (
88+
<option key={index} value={key.image}>{key.name}</option>
89+
);
90+
})}
91+
</Select>
92+
</div>
93+
<div css={tw`mt-8 sm:flex items-center justify-end`}>
94+
<Button isSecondary onClick={() => setVisible(false)} css={tw`w-full sm:w-auto border-transparent`}>
95+
Cancel
96+
</Button>
97+
<Button onClick={updateJava} css={tw`mt-4 sm:mt-0 sm:ml-4 w-full sm:w-auto`}>
98+
Update Docker Image
99+
</Button>
100+
</div>
101+
</Modal>
102+
);
103+
};
104+
105+
export default JavaVersionModalFeature;

resources/scripts/components/server/features/eula/EulaModalFeature.tsx

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,37 +53,34 @@ const EulaModalFeature = () => {
5353
.then(() => setLoading(false));
5454
};
5555

56-
useEffect(() => () => {
56+
useEffect(() => {
5757
clearFlashes('feature:eula');
5858
}, []);
5959

6060
return (
61-
!visible ?
62-
null
63-
:
64-
<Modal visible onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
65-
<FlashMessageRender key={'feature:eula'} css={tw`mb-4`}/>
66-
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Accept Minecraft&reg; EULA</h2>
67-
<p css={tw`text-neutral-200`}>
61+
<Modal visible={visible} onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
62+
<FlashMessageRender key={'feature:eula'} css={tw`mb-4`}/>
63+
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Accept Minecraft&reg; EULA</h2>
64+
<p css={tw`text-neutral-200`}>
6865
By pressing {'"I Accept"'} below you are indicating your agreement to the&nbsp;
69-
<a
70-
target={'_blank'}
71-
css={tw`text-primary-300 underline transition-colors duration-150 hover:text-primary-400`}
72-
rel={'noreferrer noopener'}
73-
href="https://account.mojang.com/documents/minecraft_eula"
74-
>
66+
<a
67+
target={'_blank'}
68+
css={tw`text-primary-300 underline transition-colors duration-150 hover:text-primary-400`}
69+
rel={'noreferrer noopener'}
70+
href="https://account.mojang.com/documents/minecraft_eula"
71+
>
7572
Minecraft&reg; EULA
76-
</a>.
77-
</p>
78-
<div css={tw`mt-8 sm:flex items-center justify-end`}>
79-
<Button isSecondary onClick={() => setVisible(false)} css={tw`w-full sm:w-auto border-transparent`}>
73+
</a>.
74+
</p>
75+
<div css={tw`mt-8 sm:flex items-center justify-end`}>
76+
<Button isSecondary onClick={() => setVisible(false)} css={tw`w-full sm:w-auto border-transparent`}>
8077
Cancel
81-
</Button>
82-
<Button onClick={onAcceptEULA} css={tw`mt-4 sm:mt-0 sm:ml-4 w-full sm:w-auto`}>
78+
</Button>
79+
<Button onClick={onAcceptEULA} css={tw`mt-4 sm:mt-0 sm:ml-4 w-full sm:w-auto`}>
8380
I Accept
84-
</Button>
85-
</div>
86-
</Modal>
81+
</Button>
82+
</div>
83+
</Modal>
8784
);
8885
};
8986

resources/scripts/components/server/features/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ import { lazy } from 'react';
77
* on the feature and the egg).
88
*/
99
const EulaModalFeature = lazy(() => import(/* webpackChunkName: "feature.eula" */'@feature/eula/EulaModalFeature'));
10+
const JavaVersionModalFeature = lazy(() => import(/* webpackChunkName: "feature.javaVersion" */'@feature/JavaVersionModalFeature'));
1011

11-
export { EulaModalFeature };
12+
export { EulaModalFeature, JavaVersionModalFeature };

0 commit comments

Comments
 (0)