Improve settings display
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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/;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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__"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 430 B |
|
After Width: | Height: | Size: 807 B |
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -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"}
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ export default function Header({
|
|||
sx={{
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
userSelect: 'none',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -5,4 +5,5 @@
|
|||
* {
|
||||
-webkit-user-drag: none; /* Safari */
|
||||
user-drag: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||