Improve settings display

This commit is contained in:
Corey 2024-04-15 12:05:27 -04:00
parent ba172f599c
commit fb5e67924d
Signed by: coco
GPG Key ID: 051138DA6AEE3E60
25 changed files with 1532 additions and 851 deletions

View File

@ -17,6 +17,7 @@ services:
volumes:
- ixion-dev-node_modules:/workspace/client/node_modules
- ../client:/workspace/client:cached
- ../api:/workspace/api:cached
ports:
- 3000:3000
depends_on:
@ -32,6 +33,7 @@ services:
volumes:
- ixion-dev-static:/workspace/api/static
- ../api:/workspace/api:cached
- ../client:/workspace/client:cached
ports:
- 8000:8000
depends_on:

View File

@ -18,12 +18,13 @@ Including another URLconf
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from v1.views import server
from v1.views.server import ServerViewSet, ServerDetailsViewSet
router = routers.DefaultRouter()
router.register(r"servers", server.ServerViewSet, "v1")
router.register(r"servers", ServerViewSet, basename="servers")
router.register(r"server", ServerDetailsViewSet, basename="server")
urlpatterns = [
path("admin/", admin.site.urls),
path("v1/", include(router.urls)),
path("", include(router.urls)),
]

View File

@ -5,8 +5,9 @@ services:
restart: always
volumes:
- .:/app
ports:
- 8000:8000
- static:/app/static
expose:
- 8000
depends_on:
- redis
celery:
@ -28,3 +29,14 @@ services:
redis:
image: redis:alpine
restart: always
nginx:
image: nginx
ports:
- 9969:80
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./static:/app/static
depends_on:
- api
volumes:
static:

20
api/nginx.conf Normal file
View File

@ -0,0 +1,20 @@
upstream ixion_api {
server api:8000;
}
server {
listen 80;
location / {
proxy_pass http://ixion_api;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
alias /app/static/;
}
}

View File

@ -0,0 +1,44 @@
# Generated by Django 5.0.4 on 2024-04-15 16:13
import v1.models.server
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Server",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(editable=False, max_length=255, null=True)),
(
"url",
models.CharField(
max_length=255,
validators=[v1.models.server.OptionalSchemeURLValidator()],
),
),
("max_level", models.IntegerField(editable=False, null=True)),
("settings", models.JSONField(editable=False, null=True)),
("customizations", models.JSONField(editable=False, null=True)),
("login_limit", models.IntegerField(editable=False, null=True)),
("active_sessions", models.IntegerField(editable=False, null=True)),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
("inactivity_counter", models.IntegerField(editable=False, null=True)),
],
),
]

View File

@ -1,4 +1,6 @@
import json
import os
import socket
import requests
from django.db import models
from django.core.validators import URLValidator
@ -6,13 +8,23 @@ from django.core.exceptions import ValidationError
from urllib.parse import urlparse
from v1.common.logging import logger
# Required and recommended settings used to build the initial client server card
required_settings = [
"MAIN.SERVER_NAME",
"MAIN.MAX_LEVEL",
"LOGIN.LOGIN_LIMIT",
]
recommended_settings = [
"MAIN.ENABLE_TRUST_CASTING",
"MAP.LEVEL_SYNC_ENABLE",
"LOGIN.RISE_OF_ZILART",
"LOGIN.CHAINS_OF_PROMATHIA",
"LOGIN.TREASURES_OF_AHT_URGHAN",
"LOGIN.WINGS_OF_THE_GODDESS",
"LOGIN.SEEKERS_OF_ADOULIN",
]
class OptionalSchemeURLValidator(URLValidator):
def __call__(self, value):
@ -23,12 +35,16 @@ class OptionalSchemeURLValidator(URLValidator):
class Server(models.Model):
name = models.CharField(max_length=255, null=True, editable=False)
url = models.CharField(
max_length=400,
max_length=255,
null=False,
validators=[OptionalSchemeURLValidator()],
)
max_level = models.IntegerField(null=True, editable=False)
settings = models.JSONField(null=True, editable=False)
customizations = models.JSONField(null=True, editable=False)
login_limit = models.IntegerField(null=True, editable=False)
active_sessions = models.IntegerField(null=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
@ -73,26 +89,59 @@ class Server(models.Model):
def parse_server_api(self):
try:
# Request server settings from API
response = requests.get(f"http://{self.url}/api/settings")
response.raise_for_status() # Raise an exception for HTTP errors (4xx and 5xx)
json_data = response.json()
# Validate the JSON structure
if not isinstance(json_data, dict):
response = requests.get(f"http://{self.url}/api/settings", timeout=5)
response.raise_for_status()
server_settings = response.json()
if not isinstance(server_settings, dict):
return False
# Check for required settings
for setting in required_settings:
if setting not in json_data:
if setting not in server_settings:
return False
self.name = server_settings["MAIN.SERVER_NAME"]
self.max_level = server_settings["MAIN.MAX_LEVEL"]
self.login_limit = server_settings["LOGIN.LOGIN_LIMIT"]
self.settings = json_data
# Check customizations
with open("defaultLsbSettings.json", "r") as default_settings_file:
default_settings = json.load(default_settings_file)
customizations = {}
customizations["LOGIN.CLIENT_VER"] = server_settings["LOGIN.CLIENT_VER"]
for key, value in server_settings.items():
if key not in required_settings and (
key in recommended_settings
or key not in default_settings
or default_settings[key] != value
):
customizations[key] = value
self.customizations = customizations
self.settings = server_settings
# Request the active session count from API
response = requests.get(f"http://{self.url}/api/sessions")
response.raise_for_status() # Raise an exception for HTTP errors (4xx and 5xx)
response.raise_for_status()
session_count = response.text
if session_count.isdigit():
self.active_sessions = int(session_count)
# Test other server ports (you can actually change all of these?)
# ports_to_check = [
# 54230, # NETWORK.LOGIN_DATA_PORT, NETWORK.MAP_PORT
# 54231, # NETWORK.LOGIN_AUTH_PORT
# 54001, # NETWORK.LOGIN_VIEW_PORT
# ]
# for port in ports_to_check:
# try:
# with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# s.settimeout(5)
# s.connect((self.url, port))
# except socket.error:
# return False
return True
except requests.RequestException:

View File

@ -6,10 +6,26 @@ from django.core.exceptions import ValidationError
class ServerSerializer(serializers.ModelSerializer):
class Meta:
model = Server
fields = "__all__"
fields = [
"id",
"name",
"url",
"max_level",
"customizations",
"login_limit",
"active_sessions",
"updated",
"inactivity_counter",
]
def create(self, validated_data):
try:
return super().create(validated_data)
except ValidationError as e:
raise serializers.ValidationError({"url": e.messages})
class ServerDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = Server
fields = "__all__"

View File

@ -1,19 +1,9 @@
from rest_framework import viewsets, mixins
from django.db.models.fields.json import KT
from v1.serializers.server import ServerSerializer
from v1.serializers.server import ServerDetailsSerializer, ServerSerializer
from v1.models.server import Server
class ServerView(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
serializer_class = ServerSerializer
queryset = Server.objects.all()
class ServerViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
@ -28,14 +18,6 @@ class ServerViewSet(
"annotate_field": "settings__MAIN.SERVER_NAME",
"filter_type": "startswith",
},
"level_cap_min": {
"annotate_field": "settings__MAIN.MAX_LEVEL",
"filter_type": "gte",
},
"level_cap_max": {
"annotate_field": "settings__MAIN.MAX_LEVEL",
"filter_type": "lte",
},
}
def get_queryset(self):
@ -58,3 +40,11 @@ class ServerViewSet(
queryset._result_cache = None
return queryset
class ServerDetailsViewSet(
mixins.RetrieveModelMixin,
viewsets.GenericViewSet,
):
queryset = Server.objects.all()
serializer_class = ServerDetailsSerializer

View File

@ -16,7 +16,12 @@
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
<title>IXION - FFXI Private Server Directory</title>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<title>IXION - Private Servers</title>
</head>
<body>
<div id="root"></div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

BIN
client/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@ -23,7 +23,7 @@ export const fetchDemo = async () => {
export const fetchData = async (queryParams = {}) => {
const queryString = new URLSearchParams(queryParams).toString();
const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/v1/servers/${queryString ? `?${queryString}` : ''}`;
const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/servers/${queryString ? `?${queryString}` : ''}`;
try {
const response = await fetch(url, {
signal: AbortSignal.timeout(5000),
@ -47,7 +47,7 @@ export const fetchData = async (queryParams = {}) => {
export const postData = async (inputText: string) => {
try {
const response = await fetch(
`${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/v1/servers/`,
`${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/servers/`,
{
method: 'POST',
headers: {

View File

@ -30,7 +30,7 @@ export const AccordionSummary = styled((props: AccordionSummaryProps) => (
'& .MuiAccordionSummary-content': {
margin: theme.spacing(0),
},
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)', // Adjust the size and opacity as needed
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
minHeight: '32px',
}));

View File

@ -75,7 +75,6 @@ export default function Header({
sx={{
color: 'inherit',
textDecoration: 'none',
userSelect: 'none',
flexGrow: 1,
}}
>

View File

@ -1,4 +1,6 @@
import {
Check,
Close,
ContentCopy,
ExpandMore,
Launch,
@ -10,13 +12,9 @@ import {
Box,
Card,
CardContent,
Chip,
Divider,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
ToggleButton,
ToggleButtonGroup,
Tooltip,
@ -25,7 +23,11 @@ import {
} from '@mui/material';
import { useState } from 'react';
import { Link } from 'react-router-dom';
import ServerData, { ServerSettingsInfo } from '../data/ServerData';
import ServerData, {
ServerSetting,
ServerSettingsInfo,
} from '../data/ServerData';
import CopyImageIcon from '../images/copy-image.png';
import { Accordion, AccordionDetails, AccordionSummary } from './Accordion';
export default function ServerCard({ server }: { server: ServerData }) {
@ -63,15 +65,75 @@ export default function ServerCard({ server }: { server: ServerData }) {
};
function formatExternalUrl(url: string): string {
// Check if the URL starts with a valid protocol
// prepend 'https://' to the URL if it's not already there
if (!/^https?:\/\//i.test(url)) {
// If not, prepend 'https://' to the URL
return `https://${url}`;
}
// Otherwise, return the original URL
return url;
}
const renderSettingsChip = ([key, value]: [
string,
boolean | string | number,
]) => {
if (!ServerSettingsInfo[key]) return null;
const transformValue = (
v: boolean | string | number,
setting: ServerSetting
): string | number | boolean => {
return setting.transform?.(v) ?? v;
};
let chipValue: string | number | boolean | JSX.Element = transformValue(
value,
ServerSettingsInfo[key]
);
if (typeof chipValue === 'boolean') {
chipValue = chipValue ? (
<Check color="success" />
) : (
<Close color="error" />
);
}
return (
<Tooltip
arrow
disableInteractive
title={ServerSettingsInfo[key].description}
>
<Chip
key={key}
label={`${ServerSettingsInfo[key].name === '' ? key : ServerSettingsInfo[key].name}`}
avatar={
typeof chipValue === 'object' ? undefined : (
<Chip label={chipValue} size="small" />
)
}
icon={typeof chipValue === 'object' ? chipValue : undefined}
size="small"
className="m-1 pr-1"
sx={{
'& .MuiChip-avatar': {
width: 'auto',
marginX: 0,
order: 2,
},
'& .MuiChip-icon': {
marginX: 0,
order: 2,
},
'&> .MuiChip-label': {
paddingRight: 0.5,
},
boxShadow: '0px 3px 3px rgba(0, 0, 0, .25)',
}}
/>
</Tooltip>
);
};
const expansions = [
<ToggleButton
key="rotz"
@ -125,7 +187,7 @@ export default function ServerCard({ server }: { server: ServerData }) {
<Accordion className="my-0">
<AccordionSummary expandIcon={<ExpandMore />}>
<Box className="flex items-center justify-center">
{server.settings['LOGIN.MAINT_MODE'] === 1 ? (
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? (
<Tooltip
arrow
disableInteractive
@ -158,14 +220,16 @@ export default function ServerCard({ server }: { server: ServerData }) {
color={(theme) => alpha(theme.palette.text.primary, 0.87)}
sx={{ lineHeight: 1.0 }}
>
{server.settings['MAIN.SERVER_NAME']}
{server.name}
</Typography>
{typeof server.settings['API.WEBSITE'] === 'string' &&
server.settings['API.WEBSITE'] !== '' && (
{typeof server.customizations['API.WEBSITE'] === 'string' &&
server.customizations['API.WEBSITE'] !== '' && (
<Tooltip arrow disableInteractive title="Visit website.">
<IconButton
component={Link}
to={formatExternalUrl(server.settings['API.WEBSITE'])}
to={formatExternalUrl(
server.customizations['API.WEBSITE']
)}
target="_blank"
rel="noopener"
size="small"
@ -225,11 +289,12 @@ export default function ServerCard({ server }: { server: ServerData }) {
<ToggleButtonGroup
size="small"
value={[
server.settings['LOGIN.RISE_OF_ZILART'] && 'rotz',
server.settings['LOGIN.CHAINS_OF_PROMATHIA'] && 'cop',
server.settings['LOGIN.TREASURES_OF_AHT_URGHAN'] && 'toau',
server.settings['LOGIN.WINGS_OF_THE_GODDESS'] && 'wotg',
server.settings['LOGIN.SEEKERS_OF_ADOULIN'] && 'soa',
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 } }}
>
@ -241,95 +306,57 @@ export default function ServerCard({ server }: { server: ServerData }) {
variant="h5"
color={(theme) => theme.palette.text.secondary}
>
{`Lv.${server.settings['MAIN.MAX_LEVEL']}`}
{`Lv.${server.max_level}`}
</Typography>
</Box>
</AccordionSummary>
<AccordionDetails className="p-2">
<TableContainer>
<Table size="small">
<TableBody>
<TableRow>
<TableCell>
<Tooltip
title={
ServerSettingsInfo['LOGIN.LOGIN_LIMIT'].description
}
arrow
disableInteractive
>
<Typography variant="caption" sx={{ userSelect: 'none' }}>
{`${ServerSettingsInfo['LOGIN.LOGIN_LIMIT'].name}: `}
{server.settings['LOGIN.LOGIN_LIMIT'] === 0
? 'unlimited'
: server.settings['LOGIN.LOGIN_LIMIT']}
</Typography>
</Tooltip>
</TableCell>
<TableCell>
<Tooltip
title={
ServerSettingsInfo['MAIN.ENABLE_TRUST_CASTING']
.description
}
arrow
disableInteractive
>
<Typography variant="caption" sx={{ userSelect: 'none' }}>
{`${ServerSettingsInfo['MAIN.ENABLE_TRUST_CASTING'].name}: `}
{server.settings['MAIN.ENABLE_TRUST_CASTING'] === 1
? 'Enabled'
: 'Disabled'}
</Typography>
</Tooltip>
</TableCell>
<TableCell>
<Tooltip
title={
ServerSettingsInfo['MAP.LEVEL_SYNC_ENABLE'].description
}
arrow
disableInteractive
>
<Typography variant="caption" sx={{ userSelect: 'none' }}>
{`${ServerSettingsInfo['MAP.LEVEL_SYNC_ENABLE'].name}: `}
{server.settings['MAP.LEVEL_SYNC_ENABLE']
? 'Enabled'
: 'Disabled'}
</Typography>
</Tooltip>
</TableCell>
</TableRow>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell>
<Tooltip
title={ServerSettingsInfo['MAP.SPEED_MOD'].description}
arrow
disableInteractive
>
<Typography variant="caption" sx={{ userSelect: 'none' }}>
{`${ServerSettingsInfo['MAP.SPEED_MOD'].name}: `}
{typeof server.settings['MAP.SPEED_MOD'] === 'number'
? (server.settings['MAP.SPEED_MOD'] < 0 ? '' : '+') +
((50 + server.settings['MAP.SPEED_MOD']) / 50 - 1) *
100
: '???'}
%
</Typography>
</Tooltip>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
{server.customizations['MAIN.SERVER_MESSAGE'] && (
<>
<Box className="flex justify-center">
<Typography variant="body2">
{server.customizations['MAIN.SERVER_MESSAGE']}
</Typography>
</Box>
<Divider sx={{ marginY: 1 }} />
</>
)}
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
maxWidth: '100%',
}}
>
{Object.entries(server.customizations).map(renderSettingsChip)}
</Box>
</AccordionDetails>
</Accordion>
<Divider />
<CardContent className="flex justify-between py-1">
<Typography variant="caption" sx={{ userSelect: 'none' }}>
<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" sx={{ userSelect: 'none' }}>
<Typography variant="caption">
Updated: {new Date(server.updated).toLocaleString()}
</Typography>
</CardContent>

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -5,4 +5,5 @@
* {
-webkit-user-drag: none; /* Safari */
user-drag: none;
user-select: none;
}

View File

@ -45,30 +45,26 @@ export default function Home({
}, [setAlertInfo, setServers]);
const filterServers = (server: ServerData): boolean => {
if (
searchState.name.value.length > 0 &&
typeof server.settings['MAIN.SERVER_NAME'] === 'string'
) {
const serverName = server.settings['MAIN.SERVER_NAME'].toLowerCase();
if (searchState.name.value.length > 0) {
const serverName = server.name.toLowerCase();
if (!serverName.includes(searchState.name.value.toLowerCase())) {
return false;
}
}
if (typeof server.settings['MAIN.MAX_LEVEL'] === 'number') {
if (server.settings['MAIN.MAX_LEVEL'] < searchState.maxLevel.value[0]) {
return false;
}
if (server.settings['MAIN.MAX_LEVEL'] > searchState.maxLevel.value[1]) {
return false;
}
if (server.max_level < searchState.maxLevel.value[0]) {
return false;
}
if (server.max_level > searchState.maxLevel.value[1]) {
return false;
}
if (
searchState.trusts.value &&
typeof server.settings['MAIN.ENABLE_TRUST_CASTING'] === 'number'
typeof server.customizations['MAIN.ENABLE_TRUST_CASTING'] === 'number'
) {
const serverTrusts = server.settings['MAIN.ENABLE_TRUST_CASTING'] === 1;
const serverTrusts =
server.customizations['MAIN.ENABLE_TRUST_CASTING'] === 1;
const searchEnabled = searchState.trusts.value.includes('enabled');
const searchDisabled = searchState.trusts.value.includes('disabled');
if (serverTrusts && searchDisabled && !searchEnabled) {
@ -81,9 +77,9 @@ export default function Home({
if (
searchState.levelSync.value &&
typeof server.settings['MAP.LEVEL_SYNC_ENABLE'] === 'boolean'
typeof server.customizations['MAP.LEVEL_SYNC_ENABLE'] === 'boolean'
) {
const serverLevelSync = server.settings['MAP.LEVEL_SYNC_ENABLE'];
const serverLevelSync = server.customizations['MAP.LEVEL_SYNC_ENABLE'];
const searchEnabled = searchState.levelSync.value.includes('enabled');
const searchDisabled = searchState.levelSync.value.includes('disabled');
if (serverLevelSync && searchDisabled && !searchEnabled) {
@ -98,19 +94,19 @@ export default function Home({
const searchNoneEnabled = searchState.expansions.value.includes('none');
const searchRotzEnabled = searchState.expansions.value.includes('rotz');
const serverRotzEnabled =
server.settings['LOGIN.RISE_OF_ZILART'] === true;
server.customizations['LOGIN.RISE_OF_ZILART'] === true;
const searchCopEnabled = searchState.expansions.value.includes('cop');
const serverCopEnabled =
server.settings['LOGIN.CHAINS_OF_PROMATHIA'] === true;
server.customizations['LOGIN.CHAINS_OF_PROMATHIA'] === true;
const searchToauEnabled = searchState.expansions.value.includes('toau');
const serverToauEnabled =
server.settings['LOGIN.TREASURES_OF_AHT_URGHAN'] === true;
server.customizations['LOGIN.TREASURES_OF_AHT_URGHAN'] === true;
const searchWotgEnabled = searchState.expansions.value.includes('wotg');
const serverWotgEnabled =
server.settings['LOGIN.WINGS_OF_THE_GODDESS'] === true;
server.customizations['LOGIN.WINGS_OF_THE_GODDESS'] === true;
const searchSoaEnabled = searchState.expansions.value.includes('soa');
const serverSoaEnabled =
server.settings['LOGIN.SEEKERS_OF_ADOULIN'] === true;
server.customizations['LOGIN.SEEKERS_OF_ADOULIN'] === true;
if (
searchNoneEnabled &&
(serverRotzEnabled ||
@ -139,27 +135,22 @@ export default function Home({
}
if (searchState.multibox.value) {
const serverMultibox = server.settings['LOGIN.LOGIN_LIMIT'];
if (typeof serverMultibox === 'number') {
if (
searchState.multibox.value.includes('none') &&
serverMultibox !== 1
) {
return false;
}
if (
searchState.multibox.value.includes('unlimited') &&
serverMultibox !== 0
) {
return false;
}
if (
searchState.multibox.value.includes('limited') &&
!searchState.multibox.value.includes('unlimited') &&
serverMultibox < 2
) {
return false;
}
const serverMultibox = server.login_limit;
if (searchState.multibox.value.includes('none') && serverMultibox !== 1) {
return false;
}
if (
searchState.multibox.value.includes('unlimited') &&
serverMultibox !== 0
) {
return false;
}
if (
searchState.multibox.value.includes('limited') &&
!searchState.multibox.value.includes('unlimited') &&
serverMultibox < 2
) {
return false;
}
}