Improve settings display
|
|
@ -17,6 +17,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ixion-dev-node_modules:/workspace/client/node_modules
|
- ixion-dev-node_modules:/workspace/client/node_modules
|
||||||
- ../client:/workspace/client:cached
|
- ../client:/workspace/client:cached
|
||||||
|
- ../api:/workspace/api:cached
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
@ -32,6 +33,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ixion-dev-static:/workspace/api/static
|
- ixion-dev-static:/workspace/api/static
|
||||||
- ../api:/workspace/api:cached
|
- ../api:/workspace/api:cached
|
||||||
|
- ../client:/workspace/client:cached
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,13 @@ Including another URLconf
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
from v1.views import server
|
from v1.views.server import ServerViewSet, ServerDetailsViewSet
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
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 = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("v1/", include(router.urls)),
|
path("", include(router.urls)),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
ports:
|
- static:/app/static
|
||||||
- 8000:8000
|
expose:
|
||||||
|
- 8000
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
celery:
|
celery:
|
||||||
|
|
@ -28,3 +29,14 @@ services:
|
||||||
redis:
|
redis:
|
||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
restart: always
|
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 os
|
||||||
|
import socket
|
||||||
import requests
|
import requests
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.validators import URLValidator
|
from django.core.validators import URLValidator
|
||||||
|
|
@ -6,13 +8,23 @@ from django.core.exceptions import ValidationError
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from v1.common.logging import logger
|
from v1.common.logging import logger
|
||||||
|
|
||||||
|
# Required and recommended settings used to build the initial client server card
|
||||||
required_settings = [
|
required_settings = [
|
||||||
"MAIN.SERVER_NAME",
|
"MAIN.SERVER_NAME",
|
||||||
"MAIN.MAX_LEVEL",
|
"MAIN.MAX_LEVEL",
|
||||||
"LOGIN.LOGIN_LIMIT",
|
"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):
|
class OptionalSchemeURLValidator(URLValidator):
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
|
|
@ -23,12 +35,16 @@ class OptionalSchemeURLValidator(URLValidator):
|
||||||
|
|
||||||
|
|
||||||
class Server(models.Model):
|
class Server(models.Model):
|
||||||
|
name = models.CharField(max_length=255, null=True, editable=False)
|
||||||
url = models.CharField(
|
url = models.CharField(
|
||||||
max_length=400,
|
max_length=255,
|
||||||
null=False,
|
null=False,
|
||||||
validators=[OptionalSchemeURLValidator()],
|
validators=[OptionalSchemeURLValidator()],
|
||||||
)
|
)
|
||||||
|
max_level = models.IntegerField(null=True, editable=False)
|
||||||
settings = models.JSONField(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)
|
active_sessions = models.IntegerField(null=True, editable=False)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
updated = models.DateTimeField(auto_now=True)
|
updated = models.DateTimeField(auto_now=True)
|
||||||
|
|
@ -73,26 +89,59 @@ class Server(models.Model):
|
||||||
def parse_server_api(self):
|
def parse_server_api(self):
|
||||||
try:
|
try:
|
||||||
# Request server settings from API
|
# Request server settings from API
|
||||||
response = requests.get(f"http://{self.url}/api/settings")
|
response = requests.get(f"http://{self.url}/api/settings", timeout=5)
|
||||||
response.raise_for_status() # Raise an exception for HTTP errors (4xx and 5xx)
|
response.raise_for_status()
|
||||||
json_data = response.json()
|
server_settings = response.json()
|
||||||
|
if not isinstance(server_settings, dict):
|
||||||
# Validate the JSON structure
|
|
||||||
if not isinstance(json_data, dict):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Check for required settings
|
||||||
for setting in required_settings:
|
for setting in required_settings:
|
||||||
if setting not in json_data:
|
if setting not in server_settings:
|
||||||
return False
|
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
|
# Request the active session count from API
|
||||||
response = requests.get(f"http://{self.url}/api/sessions")
|
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
|
session_count = response.text
|
||||||
if session_count.isdigit():
|
if session_count.isdigit():
|
||||||
self.active_sessions = int(session_count)
|
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
|
return True
|
||||||
|
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,26 @@ from django.core.exceptions import ValidationError
|
||||||
class ServerSerializer(serializers.ModelSerializer):
|
class ServerSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Server
|
model = Server
|
||||||
fields = "__all__"
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"url",
|
||||||
|
"max_level",
|
||||||
|
"customizations",
|
||||||
|
"login_limit",
|
||||||
|
"active_sessions",
|
||||||
|
"updated",
|
||||||
|
"inactivity_counter",
|
||||||
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
try:
|
try:
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise serializers.ValidationError({"url": e.messages})
|
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 rest_framework import viewsets, mixins
|
||||||
from django.db.models.fields.json import KT
|
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
|
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(
|
class ServerViewSet(
|
||||||
mixins.CreateModelMixin,
|
mixins.CreateModelMixin,
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
|
|
@ -28,14 +18,6 @@ class ServerViewSet(
|
||||||
"annotate_field": "settings__MAIN.SERVER_NAME",
|
"annotate_field": "settings__MAIN.SERVER_NAME",
|
||||||
"filter_type": "startswith",
|
"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):
|
def get_queryset(self):
|
||||||
|
|
@ -58,3 +40,11 @@ class ServerViewSet(
|
||||||
queryset._result_cache = None
|
queryset._result_cache = None
|
||||||
|
|
||||||
return queryset
|
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"
|
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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<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 = {}) => {
|
export const fetchData = async (queryParams = {}) => {
|
||||||
const queryString = new URLSearchParams(queryParams).toString();
|
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 {
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
signal: AbortSignal.timeout(5000),
|
signal: AbortSignal.timeout(5000),
|
||||||
|
|
@ -47,7 +47,7 @@ export const fetchData = async (queryParams = {}) => {
|
||||||
export const postData = async (inputText: string) => {
|
export const postData = async (inputText: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export const AccordionSummary = styled((props: AccordionSummaryProps) => (
|
||||||
'& .MuiAccordionSummary-content': {
|
'& .MuiAccordionSummary-content': {
|
||||||
margin: theme.spacing(0),
|
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',
|
minHeight: '32px',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,6 @@ export default function Header({
|
||||||
sx={{
|
sx={{
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
userSelect: 'none',
|
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import {
|
import {
|
||||||
|
Check,
|
||||||
|
Close,
|
||||||
ContentCopy,
|
ContentCopy,
|
||||||
ExpandMore,
|
ExpandMore,
|
||||||
Launch,
|
Launch,
|
||||||
|
|
@ -10,13 +12,9 @@ import {
|
||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
Chip,
|
||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableRow,
|
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
|
@ -25,7 +23,11 @@ import {
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
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';
|
import { Accordion, AccordionDetails, AccordionSummary } from './Accordion';
|
||||||
|
|
||||||
export default function ServerCard({ server }: { server: ServerData }) {
|
export default function ServerCard({ server }: { server: ServerData }) {
|
||||||
|
|
@ -63,15 +65,75 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatExternalUrl(url: string): string {
|
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 (!/^https?:\/\//i.test(url)) {
|
||||||
// If not, prepend 'https://' to the URL
|
|
||||||
return `https://${url}`;
|
return `https://${url}`;
|
||||||
}
|
}
|
||||||
// Otherwise, return the original URL
|
|
||||||
return 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 = [
|
const expansions = [
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
key="rotz"
|
key="rotz"
|
||||||
|
|
@ -125,7 +187,7 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
||||||
<Accordion className="my-0">
|
<Accordion className="my-0">
|
||||||
<AccordionSummary expandIcon={<ExpandMore />}>
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
<Box className="flex items-center justify-center">
|
<Box className="flex items-center justify-center">
|
||||||
{server.settings['LOGIN.MAINT_MODE'] === 1 ? (
|
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
arrow
|
arrow
|
||||||
disableInteractive
|
disableInteractive
|
||||||
|
|
@ -158,14 +220,16 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
||||||
color={(theme) => alpha(theme.palette.text.primary, 0.87)}
|
color={(theme) => alpha(theme.palette.text.primary, 0.87)}
|
||||||
sx={{ lineHeight: 1.0 }}
|
sx={{ lineHeight: 1.0 }}
|
||||||
>
|
>
|
||||||
{server.settings['MAIN.SERVER_NAME']}
|
{server.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
{typeof server.settings['API.WEBSITE'] === 'string' &&
|
{typeof server.customizations['API.WEBSITE'] === 'string' &&
|
||||||
server.settings['API.WEBSITE'] !== '' && (
|
server.customizations['API.WEBSITE'] !== '' && (
|
||||||
<Tooltip arrow disableInteractive title="Visit website.">
|
<Tooltip arrow disableInteractive title="Visit website.">
|
||||||
<IconButton
|
<IconButton
|
||||||
component={Link}
|
component={Link}
|
||||||
to={formatExternalUrl(server.settings['API.WEBSITE'])}
|
to={formatExternalUrl(
|
||||||
|
server.customizations['API.WEBSITE']
|
||||||
|
)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -225,11 +289,12 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
||||||
<ToggleButtonGroup
|
<ToggleButtonGroup
|
||||||
size="small"
|
size="small"
|
||||||
value={[
|
value={[
|
||||||
server.settings['LOGIN.RISE_OF_ZILART'] && 'rotz',
|
server.customizations['LOGIN.RISE_OF_ZILART'] && 'rotz',
|
||||||
server.settings['LOGIN.CHAINS_OF_PROMATHIA'] && 'cop',
|
server.customizations['LOGIN.CHAINS_OF_PROMATHIA'] && 'cop',
|
||||||
server.settings['LOGIN.TREASURES_OF_AHT_URGHAN'] && 'toau',
|
server.customizations['LOGIN.TREASURES_OF_AHT_URGHAN'] &&
|
||||||
server.settings['LOGIN.WINGS_OF_THE_GODDESS'] && 'wotg',
|
'toau',
|
||||||
server.settings['LOGIN.SEEKERS_OF_ADOULIN'] && 'soa',
|
server.customizations['LOGIN.WINGS_OF_THE_GODDESS'] && 'wotg',
|
||||||
|
server.customizations['LOGIN.SEEKERS_OF_ADOULIN'] && 'soa',
|
||||||
]}
|
]}
|
||||||
sx={{ '& button': { lineHeight: 1.0 } }}
|
sx={{ '& button': { lineHeight: 1.0 } }}
|
||||||
>
|
>
|
||||||
|
|
@ -241,95 +306,57 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
||||||
variant="h5"
|
variant="h5"
|
||||||
color={(theme) => theme.palette.text.secondary}
|
color={(theme) => theme.palette.text.secondary}
|
||||||
>
|
>
|
||||||
{`Lv.${server.settings['MAIN.MAX_LEVEL']}`}
|
{`Lv.${server.max_level}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails className="p-2">
|
<AccordionDetails className="p-2">
|
||||||
<TableContainer>
|
{server.customizations['MAIN.SERVER_MESSAGE'] && (
|
||||||
<Table size="small">
|
<>
|
||||||
<TableBody>
|
<Box className="flex justify-center">
|
||||||
<TableRow>
|
<Typography variant="body2">
|
||||||
<TableCell>
|
{server.customizations['MAIN.SERVER_MESSAGE']}
|
||||||
<Tooltip
|
</Typography>
|
||||||
title={
|
</Box>
|
||||||
ServerSettingsInfo['LOGIN.LOGIN_LIMIT'].description
|
<Divider sx={{ marginY: 1 }} />
|
||||||
}
|
</>
|
||||||
arrow
|
)}
|
||||||
disableInteractive
|
<Box
|
||||||
>
|
sx={{
|
||||||
<Typography variant="caption" sx={{ userSelect: 'none' }}>
|
display: 'flex',
|
||||||
{`${ServerSettingsInfo['LOGIN.LOGIN_LIMIT'].name}: `}
|
flexWrap: 'wrap',
|
||||||
{server.settings['LOGIN.LOGIN_LIMIT'] === 0
|
justifyContent: 'center',
|
||||||
? 'unlimited'
|
maxWidth: '100%',
|
||||||
: server.settings['LOGIN.LOGIN_LIMIT']}
|
}}
|
||||||
</Typography>
|
>
|
||||||
</Tooltip>
|
{Object.entries(server.customizations).map(renderSettingsChip)}
|
||||||
</TableCell>
|
</Box>
|
||||||
<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>
|
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Divider />
|
<Divider />
|
||||||
<CardContent className="flex justify-between py-1">
|
<CardContent className="flex justify-between py-1">
|
||||||
<Typography variant="caption" sx={{ userSelect: 'none' }}>
|
<Typography variant="caption">
|
||||||
{server.active_sessions} active sessions
|
{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>
|
||||||
<Typography variant="caption" sx={{ userSelect: 'none' }}>
|
<Typography variant="caption">
|
||||||
Updated: {new Date(server.updated).toLocaleString()}
|
Updated: {new Date(server.updated).toLocaleString()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -5,4 +5,5 @@
|
||||||
* {
|
* {
|
||||||
-webkit-user-drag: none; /* Safari */
|
-webkit-user-drag: none; /* Safari */
|
||||||
user-drag: none;
|
user-drag: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,30 +45,26 @@ export default function Home({
|
||||||
}, [setAlertInfo, setServers]);
|
}, [setAlertInfo, setServers]);
|
||||||
|
|
||||||
const filterServers = (server: ServerData): boolean => {
|
const filterServers = (server: ServerData): boolean => {
|
||||||
if (
|
if (searchState.name.value.length > 0) {
|
||||||
searchState.name.value.length > 0 &&
|
const serverName = server.name.toLowerCase();
|
||||||
typeof server.settings['MAIN.SERVER_NAME'] === 'string'
|
|
||||||
) {
|
|
||||||
const serverName = server.settings['MAIN.SERVER_NAME'].toLowerCase();
|
|
||||||
if (!serverName.includes(searchState.name.value.toLowerCase())) {
|
if (!serverName.includes(searchState.name.value.toLowerCase())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof server.settings['MAIN.MAX_LEVEL'] === 'number') {
|
if (server.max_level < searchState.maxLevel.value[0]) {
|
||||||
if (server.settings['MAIN.MAX_LEVEL'] < searchState.maxLevel.value[0]) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
if (server.max_level > searchState.maxLevel.value[1]) {
|
||||||
if (server.settings['MAIN.MAX_LEVEL'] > searchState.maxLevel.value[1]) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
searchState.trusts.value &&
|
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 searchEnabled = searchState.trusts.value.includes('enabled');
|
||||||
const searchDisabled = searchState.trusts.value.includes('disabled');
|
const searchDisabled = searchState.trusts.value.includes('disabled');
|
||||||
if (serverTrusts && searchDisabled && !searchEnabled) {
|
if (serverTrusts && searchDisabled && !searchEnabled) {
|
||||||
|
|
@ -81,9 +77,9 @@ export default function Home({
|
||||||
|
|
||||||
if (
|
if (
|
||||||
searchState.levelSync.value &&
|
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 searchEnabled = searchState.levelSync.value.includes('enabled');
|
||||||
const searchDisabled = searchState.levelSync.value.includes('disabled');
|
const searchDisabled = searchState.levelSync.value.includes('disabled');
|
||||||
if (serverLevelSync && searchDisabled && !searchEnabled) {
|
if (serverLevelSync && searchDisabled && !searchEnabled) {
|
||||||
|
|
@ -98,19 +94,19 @@ export default function Home({
|
||||||
const searchNoneEnabled = searchState.expansions.value.includes('none');
|
const searchNoneEnabled = searchState.expansions.value.includes('none');
|
||||||
const searchRotzEnabled = searchState.expansions.value.includes('rotz');
|
const searchRotzEnabled = searchState.expansions.value.includes('rotz');
|
||||||
const serverRotzEnabled =
|
const serverRotzEnabled =
|
||||||
server.settings['LOGIN.RISE_OF_ZILART'] === true;
|
server.customizations['LOGIN.RISE_OF_ZILART'] === true;
|
||||||
const searchCopEnabled = searchState.expansions.value.includes('cop');
|
const searchCopEnabled = searchState.expansions.value.includes('cop');
|
||||||
const serverCopEnabled =
|
const serverCopEnabled =
|
||||||
server.settings['LOGIN.CHAINS_OF_PROMATHIA'] === true;
|
server.customizations['LOGIN.CHAINS_OF_PROMATHIA'] === true;
|
||||||
const searchToauEnabled = searchState.expansions.value.includes('toau');
|
const searchToauEnabled = searchState.expansions.value.includes('toau');
|
||||||
const serverToauEnabled =
|
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 searchWotgEnabled = searchState.expansions.value.includes('wotg');
|
||||||
const serverWotgEnabled =
|
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 searchSoaEnabled = searchState.expansions.value.includes('soa');
|
||||||
const serverSoaEnabled =
|
const serverSoaEnabled =
|
||||||
server.settings['LOGIN.SEEKERS_OF_ADOULIN'] === true;
|
server.customizations['LOGIN.SEEKERS_OF_ADOULIN'] === true;
|
||||||
if (
|
if (
|
||||||
searchNoneEnabled &&
|
searchNoneEnabled &&
|
||||||
(serverRotzEnabled ||
|
(serverRotzEnabled ||
|
||||||
|
|
@ -139,27 +135,22 @@ export default function Home({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchState.multibox.value) {
|
if (searchState.multibox.value) {
|
||||||
const serverMultibox = server.settings['LOGIN.LOGIN_LIMIT'];
|
const serverMultibox = server.login_limit;
|
||||||
if (typeof serverMultibox === 'number') {
|
if (searchState.multibox.value.includes('none') && serverMultibox !== 1) {
|
||||||
if (
|
return false;
|
||||||
searchState.multibox.value.includes('none') &&
|
}
|
||||||
serverMultibox !== 1
|
if (
|
||||||
) {
|
searchState.multibox.value.includes('unlimited') &&
|
||||||
return false;
|
serverMultibox !== 0
|
||||||
}
|
) {
|
||||||
if (
|
return false;
|
||||||
searchState.multibox.value.includes('unlimited') &&
|
}
|
||||||
serverMultibox !== 0
|
if (
|
||||||
) {
|
searchState.multibox.value.includes('limited') &&
|
||||||
return false;
|
!searchState.multibox.value.includes('unlimited') &&
|
||||||
}
|
serverMultibox < 2
|
||||||
if (
|
) {
|
||||||
searchState.multibox.value.includes('limited') &&
|
return false;
|
||||||
!searchState.multibox.value.includes('unlimited') &&
|
|
||||||
serverMultibox < 2
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||