mirror of https://github.com/cocosolos/ixion.git
Add detailed settings view
This commit is contained in:
parent
fb5e67924d
commit
ed229129c9
|
|
@ -12,6 +12,7 @@
|
|||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.15.6",
|
||||
"@mui/material": "^5.15.6",
|
||||
"@mui/x-data-grid": "^7.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.21.3"
|
||||
|
|
@ -1051,14 +1052,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/base": {
|
||||
"version": "5.0.0-beta.39",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.39.tgz",
|
||||
"integrity": "sha512-puyUptF7VJ+9/dMIRLF+DLR21cWfvejsA6OnatfJfqFp8aMhya7xQtvYLEfCch6ahvFZvNC9FFEGGR+qkgFjUg==",
|
||||
"version": "5.0.0-beta.40",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||
"integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@floating-ui/react-dom": "^2.0.8",
|
||||
"@mui/types": "^7.2.13",
|
||||
"@mui/utils": "^5.15.13",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
|
|
@ -1082,9 +1083,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "5.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.13.tgz",
|
||||
"integrity": "sha512-ERsk9EWpiitSiKnmUdFJGshtFk647l4p7r+mjRWe/F1l5kT1NTTKkaeDLcK3/lsy0udXjMgcG0bNwzbYBdDdhQ==",
|
||||
"version": "5.15.15",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz",
|
||||
"integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
|
|
@ -1116,16 +1117,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "5.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.13.tgz",
|
||||
"integrity": "sha512-E+QisOJcIzTTyeJ0o3lgYMcyrmCydb2S4cn9vTtGpIB9uR6fQ6La3dIGsXgYEGyeOB9YkWzQbNzYzvyODGEWKA==",
|
||||
"version": "5.15.15",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz",
|
||||
"integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/base": "5.0.0-beta.39",
|
||||
"@mui/core-downloads-tracker": "^5.15.13",
|
||||
"@mui/system": "^5.15.13",
|
||||
"@mui/types": "^7.2.13",
|
||||
"@mui/utils": "^5.15.13",
|
||||
"@mui/base": "5.0.0-beta.40",
|
||||
"@mui/core-downloads-tracker": "^5.15.15",
|
||||
"@mui/system": "^5.15.15",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"clsx": "^2.1.0",
|
||||
"csstype": "^3.1.3",
|
||||
|
|
@ -1160,12 +1161,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "5.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.13.tgz",
|
||||
"integrity": "sha512-j5Z2pRi6talCunIRIzpQERSaHwLd5EPdHMwIKDVCszro1RAzRZl7WmH68IMCgQmJMeglr+FalqNuq048qptGAg==",
|
||||
"version": "5.15.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz",
|
||||
"integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/utils": "^5.15.13",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1186,9 +1187,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "5.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz",
|
||||
"integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==",
|
||||
"version": "5.15.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
|
||||
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
|
|
@ -1217,15 +1218,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "5.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.13.tgz",
|
||||
"integrity": "sha512-eHaX3sniZXNWkxX0lmcLxROhQ5La0HkOuF7zxbSdAoHUOk07gboQYmF6hSJ/VBFx/GLanIw67FMTn88vc8niLg==",
|
||||
"version": "5.15.15",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz",
|
||||
"integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/private-theming": "^5.15.13",
|
||||
"@mui/styled-engine": "^5.15.11",
|
||||
"@mui/types": "^7.2.13",
|
||||
"@mui/utils": "^5.15.13",
|
||||
"@mui/private-theming": "^5.15.14",
|
||||
"@mui/styled-engine": "^5.15.14",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"clsx": "^2.1.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
|
|
@ -1256,9 +1257,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz",
|
||||
"integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==",
|
||||
"version": "7.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
|
||||
"integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
|
|
@ -1269,9 +1270,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "5.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.13.tgz",
|
||||
"integrity": "sha512-qNlR9FLEhORC4zVZ3fzF48213EhP/92N71AcFbhHN73lPJjAbq9lUv+71P7uEdRHdrrOlm8+1zE8/OBy6MUqdg==",
|
||||
"version": "5.15.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz",
|
||||
"integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@types/prop-types": "^15.7.11",
|
||||
|
|
@ -1295,6 +1296,31 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.2.0.tgz",
|
||||
"integrity": "sha512-WKmFo0eKhj3W7Fv8u5n2XP4UcdzuJ+mEYALiMUDAYsah/hPBH9mA1miXn9DjXF3i3dxgzrTjdJemTgTJxAQZKg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.0",
|
||||
"@mui/system": "^5.15.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^5.15.14",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
|
@ -6699,6 +6725,11 @@
|
|||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.15.6",
|
||||
"@mui/material": "^5.15.6",
|
||||
"@mui/x-data-grid": "^7.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.21.3"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import ServerData from './data/ServerData';
|
|||
import About from './pages/About';
|
||||
import Home from './pages/Home';
|
||||
import NotFound from './pages/NotFound';
|
||||
import ServerDetails from './pages/ServerDetails';
|
||||
|
||||
export function App() {
|
||||
const [alertInfo, setAlertInfo] = useState<{
|
||||
|
|
@ -58,6 +59,10 @@ export function App() {
|
|||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/server/:id"
|
||||
element={<ServerDetails setAlertInfo={setAlertInfo} />}
|
||||
/>
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ export const fetchDemo = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
export const fetchData = async (queryParams = {}) => {
|
||||
export const fetchData = async (dir = 'servers', queryParams = {}) => {
|
||||
const queryString = new URLSearchParams(queryParams).toString();
|
||||
const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/servers/${queryString ? `?${queryString}` : ''}`;
|
||||
const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/${dir}/${queryString ? `?${queryString}` : ''}`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
signal: AbortSignal.timeout(5000),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
styled,
|
||||
} from '@mui/material';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { postData } from '../apiUtil';
|
||||
import ServerData from '../data/ServerData';
|
||||
import { AlertResponse } from './Alert';
|
||||
|
|
@ -51,6 +52,7 @@ export default function AddServer({
|
|||
servers: ServerData[];
|
||||
setServers: React.Dispatch<React.SetStateAction<ServerData[]>>;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const containerRef = useRef<HTMLElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
|
@ -71,6 +73,7 @@ export default function AddServer({
|
|||
severity: 'success',
|
||||
});
|
||||
if (response.data && typeof response.data === 'object') {
|
||||
navigate(`/server/${response.data.id}`);
|
||||
setServers([response.data, ...servers]);
|
||||
}
|
||||
if (inputRef.current) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||
import ServerData from '../data/ServerData';
|
||||
|
||||
const expansions = [
|
||||
<ToggleButton
|
||||
key="rotz"
|
||||
value="rotz"
|
||||
aria-label="rotz-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
RotZ
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="cop"
|
||||
value="cop"
|
||||
aria-label="cop-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
CoP
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="toau"
|
||||
value="toau"
|
||||
aria-label="toau-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
ToAU
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="wotg"
|
||||
value="wotg"
|
||||
aria-label="wotg-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
WotG
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="soa"
|
||||
value="soa"
|
||||
aria-label="soa-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
SoA
|
||||
</ToggleButton>,
|
||||
];
|
||||
|
||||
export default function ExpansionBar({ server }: { server: ServerData }) {
|
||||
return (
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
value={[
|
||||
server.customizations['LOGIN.RISE_OF_ZILART'] && 'rotz',
|
||||
server.customizations['LOGIN.CHAINS_OF_PROMATHIA'] && 'cop',
|
||||
server.customizations['LOGIN.TREASURES_OF_AHT_URGHAN'] && 'toau',
|
||||
server.customizations['LOGIN.WINGS_OF_THE_GODDESS'] && 'wotg',
|
||||
server.customizations['LOGIN.SEEKERS_OF_ADOULIN'] && 'soa',
|
||||
]}
|
||||
sx={{ '& button': { lineHeight: 1.0 } }}
|
||||
>
|
||||
{expansions}
|
||||
</ToggleButtonGroup>
|
||||
);
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import {
|
|||
import IconButton from '@mui/material/IconButton';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { fetchData, fetchDemo } from '../apiUtil';
|
||||
import { fetchData } from '../apiUtil';
|
||||
import SearchState from '../data/SearchState';
|
||||
import ServerData from '../data/ServerData';
|
||||
import AddServer from './AddServer';
|
||||
|
|
@ -52,11 +52,6 @@ export default function Header({
|
|||
});
|
||||
}
|
||||
}
|
||||
setFetchLoading(50);
|
||||
if (data.length === 0) {
|
||||
data.push(await fetchDemo());
|
||||
}
|
||||
setFetchLoading(75);
|
||||
setServers(data);
|
||||
setFetchLoading(100);
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
Close,
|
||||
ContentCopy,
|
||||
ExpandMore,
|
||||
Info,
|
||||
Launch,
|
||||
Public,
|
||||
PublicOff,
|
||||
|
|
@ -15,8 +16,6 @@ import {
|
|||
Chip,
|
||||
Divider,
|
||||
IconButton,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
alpha,
|
||||
|
|
@ -29,6 +28,11 @@ import ServerData, {
|
|||
} from '../data/ServerData';
|
||||
import CopyImageIcon from '../images/copy-image.png';
|
||||
import { Accordion, AccordionDetails, AccordionSummary } from './Accordion';
|
||||
import ExpansionBar from './ExpansionsBar';
|
||||
|
||||
function scrollToTop() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
export default function ServerCard({ server }: { server: ServerData }) {
|
||||
const [clipboardTooltip, setClipboardTooltip] = useState('Copy server URL.');
|
||||
|
|
@ -48,7 +52,7 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
|||
handleClipboardTooltipOpen();
|
||||
setTimeout(() => {
|
||||
handleClipboardTooltipClose();
|
||||
setClipboardTooltip('Copy Server URL.');
|
||||
setClipboardTooltip('Copy server URL.');
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
handleClipboardTooltipOpen();
|
||||
|
|
@ -59,7 +63,7 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
|||
}
|
||||
setTimeout(() => {
|
||||
handleClipboardTooltipClose();
|
||||
setClipboardTooltip('Copy Server URL.');
|
||||
setClipboardTooltip('Copy server URL.');
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
|
@ -134,54 +138,6 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
|||
);
|
||||
};
|
||||
|
||||
const expansions = [
|
||||
<ToggleButton
|
||||
key="rotz"
|
||||
value="rotz"
|
||||
aria-label="rotz-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
RotZ
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="cop"
|
||||
value="cop"
|
||||
aria-label="cop-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
CoP
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="toau"
|
||||
value="toau"
|
||||
aria-label="toau-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
ToAU
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="wotg"
|
||||
value="wotg"
|
||||
aria-label="wotg-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
WotG
|
||||
</ToggleButton>,
|
||||
<ToggleButton
|
||||
key="soa"
|
||||
value="soa"
|
||||
aria-label="soa-enabled"
|
||||
className="py-0"
|
||||
disabled
|
||||
>
|
||||
SoA
|
||||
</ToggleButton>,
|
||||
];
|
||||
|
||||
return (
|
||||
<Card className="mb-2">
|
||||
<Accordion className="my-0">
|
||||
|
|
@ -286,20 +242,7 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
|||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
value={[
|
||||
server.customizations['LOGIN.RISE_OF_ZILART'] && 'rotz',
|
||||
server.customizations['LOGIN.CHAINS_OF_PROMATHIA'] && 'cop',
|
||||
server.customizations['LOGIN.TREASURES_OF_AHT_URGHAN'] &&
|
||||
'toau',
|
||||
server.customizations['LOGIN.WINGS_OF_THE_GODDESS'] && 'wotg',
|
||||
server.customizations['LOGIN.SEEKERS_OF_ADOULIN'] && 'soa',
|
||||
]}
|
||||
sx={{ '& button': { lineHeight: 1.0 } }}
|
||||
>
|
||||
{expansions}
|
||||
</ToggleButtonGroup>
|
||||
<ExpansionBar server={server} />
|
||||
</CardContent>
|
||||
<Box className="flex items-center justify-center">
|
||||
<Typography
|
||||
|
|
@ -334,8 +277,8 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
|||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Divider />
|
||||
<CardContent className="flex justify-between py-1">
|
||||
<Typography variant="caption">
|
||||
<CardContent className="flex justify-between py-0">
|
||||
<Typography variant="caption" className="flex items-center">
|
||||
{server.active_sessions} active sessions
|
||||
{server.login_limit !== 1 && (
|
||||
<Tooltip
|
||||
|
|
@ -356,7 +299,18 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
|||
</Tooltip>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
<Typography variant="caption" className="flex items-center">
|
||||
<Tooltip title="View full settings." arrow disableInteractive>
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={`/server/${server.id}`}
|
||||
onClick={scrollToTop}
|
||||
className="p-0"
|
||||
disableRipple
|
||||
>
|
||||
<Info className="p-1" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
Updated: {new Date(server.updated).toLocaleString()}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export interface ServerSetting {
|
|||
transform?: (arg: boolean | string | number) => boolean | string | number;
|
||||
}
|
||||
|
||||
interface ServerSettings {
|
||||
export interface ServerSettings {
|
||||
[key: string]: boolean | string | number;
|
||||
}
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ export default interface ServerData {
|
|||
name: string;
|
||||
url: string;
|
||||
max_level: number;
|
||||
settings?: ServerSettings;
|
||||
customizations: ServerSettings;
|
||||
login_limit: number;
|
||||
active_sessions: number;
|
||||
|
|
@ -28,9 +29,16 @@ export const DemoServerData: ServerData = {
|
|||
name: 'LandSandBoat Demo',
|
||||
url: 'github.com/LandSandBoat/server',
|
||||
max_level: 99,
|
||||
settings: LsbDefaults,
|
||||
customizations: {
|
||||
...LsbDefaults,
|
||||
'API.WEBSITE': 'https://landsandboat.github.io/server/',
|
||||
'MAIN.ENABLE_TRUST_CASTING': 1,
|
||||
'MAP.LEVEL_SYNC_ENABLE': true,
|
||||
'LOGIN.RISE_OF_ZILART': true,
|
||||
'LOGIN.CHAINS_OF_PROMATHIA': true,
|
||||
'LOGIN.TREASURES_OF_AHT_URGHAN': true,
|
||||
'LOGIN.WINGS_OF_THE_GODDESS': true,
|
||||
'LOGIN.SEEKERS_OF_ADOULIN': true,
|
||||
},
|
||||
login_limit: 0,
|
||||
active_sessions: 0,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Box } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { fetchData, fetchDemo } from '../apiUtil';
|
||||
import { fetchData } from '../apiUtil';
|
||||
import { AlertResponse } from '../components/Alert';
|
||||
import ErrorCard from '../components/ErrorCard';
|
||||
import ServerCard from '../components/ServerCard';
|
||||
|
|
@ -35,9 +35,7 @@ export default function Home({
|
|||
setError('An unknown error occurred.');
|
||||
}
|
||||
}
|
||||
if (data.length === 0) {
|
||||
data.push(await fetchDemo());
|
||||
}
|
||||
|
||||
setServers(data);
|
||||
};
|
||||
|
||||
|
|
@ -161,7 +159,7 @@ export default function Home({
|
|||
|
||||
return (
|
||||
<Box>
|
||||
{error ? (
|
||||
{error || filteredServers.length === 0 ? (
|
||||
<ErrorCard error={error} />
|
||||
) : (
|
||||
filteredServers.map((server: ServerData) => (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,307 @@
|
|||
import {
|
||||
ContentCopy,
|
||||
Launch,
|
||||
Public,
|
||||
PublicOff,
|
||||
Warning,
|
||||
} from '@mui/icons-material';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
alpha,
|
||||
} from '@mui/material';
|
||||
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { fetchData, fetchDemo } from '../apiUtil';
|
||||
import { AlertResponse } from '../components/Alert';
|
||||
import ErrorCard from '../components/ErrorCard';
|
||||
import ExpansionBar from '../components/ExpansionsBar';
|
||||
import ServerData, {
|
||||
ServerSettings,
|
||||
ServerSettingsInfo,
|
||||
} from '../data/ServerData';
|
||||
import CopyImageIcon from '../images/copy-image.png';
|
||||
|
||||
interface KeyValueRow {
|
||||
id: number;
|
||||
key: string;
|
||||
name: string;
|
||||
value: string | number | boolean;
|
||||
}
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{ field: 'key', headerName: 'Key', flex: 2 },
|
||||
{ field: 'name', headerName: 'Name', flex: 1 },
|
||||
{ field: 'value', headerName: 'Value', flex: 1 },
|
||||
{ field: 'description', headerName: 'Description', flex: 3 },
|
||||
];
|
||||
|
||||
function ServerSettingsDataGrid({
|
||||
serverSettings,
|
||||
}: {
|
||||
serverSettings: ServerSettings;
|
||||
}) {
|
||||
const rows: KeyValueRow[] = Object.entries(serverSettings).map(
|
||||
([key, value], index) => ({
|
||||
id: index + 1,
|
||||
key,
|
||||
name: ServerSettingsInfo[key]?.name || 'none',
|
||||
value,
|
||||
description: ServerSettingsInfo[key]?.description || '',
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className="w-full">
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
checkboxSelection={false}
|
||||
autoHeight
|
||||
density="compact"
|
||||
hideFooterSelectedRowCount
|
||||
showCellVerticalBorder
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ServerDetails({
|
||||
setAlertInfo,
|
||||
}: {
|
||||
setAlertInfo: React.Dispatch<React.SetStateAction<AlertResponse>>;
|
||||
}) {
|
||||
const { id } = useParams();
|
||||
const [server, setServer] = useState<ServerData>();
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
let data: ServerData;
|
||||
const fetchServerData = async () => {
|
||||
try {
|
||||
if (id === 'demo') {
|
||||
data = await fetchDemo();
|
||||
} else {
|
||||
data = await fetchData(`server/${id}`);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setAlertInfo({
|
||||
message: err.message,
|
||||
severity: 'error',
|
||||
});
|
||||
} else {
|
||||
setError('An unknown error occurred.');
|
||||
}
|
||||
}
|
||||
|
||||
setServer(data);
|
||||
};
|
||||
|
||||
fetchServerData();
|
||||
}, [id, setServer, setAlertInfo, setError]);
|
||||
|
||||
const [clipboardTooltip, setClipboardTooltip] = useState('Copy server URL.');
|
||||
const [clipboardTooltipOpen, setClipboardTooltipOpen] = useState(false);
|
||||
const handleClipboardTooltipClose = () => {
|
||||
setClipboardTooltipOpen(false);
|
||||
};
|
||||
|
||||
const handleClipboardTooltipOpen = () => {
|
||||
setClipboardTooltipOpen(true);
|
||||
};
|
||||
|
||||
const copyServerUrlToClipboard = async (text: string) => {
|
||||
try {
|
||||
navigator.clipboard.writeText(text);
|
||||
setClipboardTooltip('Copied server URL!');
|
||||
handleClipboardTooltipOpen();
|
||||
setTimeout(() => {
|
||||
handleClipboardTooltipClose();
|
||||
setClipboardTooltip('Copy server URL.');
|
||||
}, 3000);
|
||||
} catch (err) {
|
||||
handleClipboardTooltipOpen();
|
||||
if (err instanceof Error) {
|
||||
setClipboardTooltip(err.message);
|
||||
} else {
|
||||
setClipboardTooltip('Something went wrong!');
|
||||
}
|
||||
setTimeout(() => {
|
||||
handleClipboardTooltipClose();
|
||||
setClipboardTooltip('Copy server URL.');
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
function formatExternalUrl(url: string): string {
|
||||
// prepend 'https://' to the URL if it's not already there
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
return `https://${url}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{error || !server ? (
|
||||
<ErrorCard error={error} />
|
||||
) : (
|
||||
<Card className="mb-2">
|
||||
<Box className="flex px-4">
|
||||
<Box className="flex items-center justify-center">
|
||||
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? (
|
||||
<Tooltip
|
||||
arrow
|
||||
disableInteractive
|
||||
title="Server is undergoing maintenance."
|
||||
>
|
||||
<Warning color="warning" />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div>
|
||||
{server.inactivity_counter > 0 ? (
|
||||
<Tooltip
|
||||
arrow
|
||||
disableInteractive
|
||||
title={`Offline for ${server.inactivity_counter} hours.`}
|
||||
>
|
||||
<PublicOff color="error" />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip arrow disableInteractive title="Server is online.">
|
||||
<Public color="success" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
<CardContent className="grow py-1">
|
||||
<Box className="flex content-center">
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={(theme) => alpha(theme.palette.text.primary, 0.87)}
|
||||
sx={{ lineHeight: 1.0 }}
|
||||
>
|
||||
{server.name}
|
||||
</Typography>
|
||||
{typeof server.customizations['API.WEBSITE'] === 'string' &&
|
||||
server.customizations['API.WEBSITE'] !== '' && (
|
||||
<Tooltip arrow disableInteractive title="Visit website.">
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={formatExternalUrl(
|
||||
server.customizations['API.WEBSITE']
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
size="small"
|
||||
className="py-0"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<Launch
|
||||
sx={{
|
||||
fontSize: (theme) =>
|
||||
theme.typography.caption.fontSize,
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
<Box className="flex content-center">
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
color={(theme) => alpha(theme.palette.text.primary, 0.6)}
|
||||
>
|
||||
{server.url}
|
||||
</Typography>
|
||||
{window.isSecureContext && (
|
||||
<Tooltip
|
||||
arrow
|
||||
disableInteractive
|
||||
title={clipboardTooltip}
|
||||
open={
|
||||
clipboardTooltip === 'Copied server URL!' ||
|
||||
clipboardTooltipOpen
|
||||
}
|
||||
onOpen={handleClipboardTooltipOpen}
|
||||
onClose={handleClipboardTooltipClose}
|
||||
>
|
||||
<IconButton
|
||||
size="small"
|
||||
className="py-0"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
copyServerUrlToClipboard(server.url);
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<ContentCopy
|
||||
sx={{
|
||||
fontSize: (theme) =>
|
||||
theme.typography.caption.fontSize,
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
<ExpansionBar server={server} />
|
||||
</CardContent>
|
||||
<Box className="flex items-center justify-center">
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={(theme) => theme.palette.text.secondary}
|
||||
>
|
||||
{`Lv.${server.max_level}`}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider />
|
||||
<CardContent className="flex justify-between py-1">
|
||||
{server.settings && (
|
||||
<ServerSettingsDataGrid serverSettings={server.settings} />
|
||||
)}
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent className="flex justify-between py-1">
|
||||
<Typography variant="caption">
|
||||
{server.active_sessions} active sessions
|
||||
{server.login_limit !== 1 && (
|
||||
<Tooltip
|
||||
title={`Server allows ${server.login_limit === 0 ? 'unlimited' : server.login_limit} simultaneous game sessions per IP.`}
|
||||
arrow
|
||||
disableInteractive
|
||||
>
|
||||
<img
|
||||
src={CopyImageIcon}
|
||||
alt=""
|
||||
style={{
|
||||
maxHeight: '1.5em',
|
||||
marginLeft: '0.5em',
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
onContextMenu={(event) => event.preventDefault()}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
Updated: {new Date(server.updated).toLocaleString()}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue