Add geolocation and variable pruning

This commit is contained in:
Corey 2024-04-22 23:02:51 +00:00
parent 34b498cbfc
commit b1387b79e2
13 changed files with 176 additions and 72 deletions

17
.gitattributes vendored
View File

@ -8,13 +8,11 @@
# #
# Note that binary is a macro for -text -diff. # Note that binary is a macro for -text -diff.
###################################################################### ######################################################################
# Auto detect # Auto detect
## Handle line endings automatically for files detected as ## Handle line endings automatically for files detected as
## text and leave all files detected as binary untouched. ## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below. ## This will handle all files NOT defined below.
* text=auto * text=auto
# Source code # Source code
*.bash text eol=lf *.bash text eol=lf
*.bat text eol=crlf *.bat text eol=crlf
@ -52,10 +50,8 @@
*.tsx text *.tsx text
*.xml text *.xml text
*.xhtml text diff=html *.xhtml text diff=html
# Docker # Docker
Dockerfile text Dockerfile text
# Documentation # Documentation
*.ipynb text eol=lf *.ipynb text eol=lf
*.markdown text diff=markdown *.markdown text diff=markdown
@ -81,7 +77,6 @@ NEWS text
readme text readme text
*README* text *README* text
TODO text TODO text
# Templates # Templates
*.dot text *.dot text
*.ejs text *.ejs text
@ -100,7 +95,6 @@ TODO text
*.tpl text *.tpl text
*.twig text *.twig text
*.vue text *.vue text
# Configs # Configs
*.cnf text *.cnf text
*.conf text *.conf text
@ -124,10 +118,8 @@ Makefile text
makefile text makefile text
# Fixes syntax highlighting on GitHub to allow comments # Fixes syntax highlighting on GitHub to allow comments
tsconfig.json linguist-language=JSON-with-Comments tsconfig.json linguist-language=JSON-with-Comments
# Heroku # Heroku
Procfile text Procfile text
# Graphics # Graphics
*.ai binary *.ai binary
*.bmp binary *.bmp binary
@ -155,7 +147,6 @@ Procfile text
*.tiff binary *.tiff binary
*.wbmp binary *.wbmp binary
*.webp binary *.webp binary
# Audio # Audio
*.kar binary *.kar binary
*.m4a binary *.m4a binary
@ -164,7 +155,6 @@ Procfile text
*.mp3 binary *.mp3 binary
*.ogg binary *.ogg binary
*.ra binary *.ra binary
# Video # Video
*.3gpp binary *.3gpp binary
*.3gp binary *.3gp binary
@ -184,7 +174,6 @@ Procfile text
*.swc binary *.swc binary
*.swf binary *.swf binary
*.webm binary *.webm binary
# Archives # Archives
*.7z binary *.7z binary
*.gz binary *.gz binary
@ -192,26 +181,22 @@ Procfile text
*.rar binary *.rar binary
*.tar binary *.tar binary
*.zip binary *.zip binary
# Fonts # Fonts
*.ttf binary *.ttf binary
*.eot binary *.eot binary
*.otf binary *.otf binary
*.woff binary *.woff binary
*.woff2 binary *.woff2 binary
# Executables # Executables
*.exe binary *.exe binary
*.pyc binary *.pyc binary
# Prevents massive diffs caused by vendored, minified files # Prevents massive diffs caused by vendored, minified files
**/.yarn/releases/** binary **/.yarn/releases/** binary
**/.yarn/plugins/** binary **/.yarn/plugins/** binary
# RC files (like .babelrc or .eslintrc) # RC files (like .babelrc or .eslintrc)
*.*rc text *.*rc text
# Ignore files (like .npmignore or .gitignore) # Ignore files (like .npmignore or .gitignore)
*.*ignore text *.*ignore text
# Prevents massive diffs from built files # Prevents massive diffs from built files
dist/* binary dist/* binary
*.mmdb filter=lfs diff=lfs merge=lfs -text

View File

@ -6,6 +6,8 @@ Ixion is designed to catalog public [LandSandBoat](https://github.com/LandSandBo
The React client fetches server data from the API and displays it in a relevant way to users. Filtering is built into the client for now. When the API response fails or is empty, a default LSB demo server is displayed, pulling some info from GitHub. The React client fetches server data from the API and displays it in a relevant way to users. Filtering is built into the client for now. When the API response fails or is empty, a default LSB demo server is displayed, pulling some info from GitHub.
[IP Geolocation by DB-IP](https://db-ip.com)
## Features ## Features
- **Automatic Verification**: Verifies LSB server URL by sending a request to the public API. [Requires the LSB server to enable the HTTP server.](https://github.com/LandSandBoat/server/blob/df311283c4abb779d212e2b8af6734b0d8d11ad7/settings/default/network.lua#L33) - **Automatic Verification**: Verifies LSB server URL by sending a request to the public API. [Requires the LSB server to enable the HTTP server.](https://github.com/LandSandBoat/server/blob/df311283c4abb779d212e2b8af6734b0d8d11ad7/settings/default/network.lua#L33)

View File

@ -5,4 +5,4 @@ CSRF_TRUSTED_ORIGINS=https://api.ixion.dev
CORS_ALLOWED_ORIGINS=https://ixion.dev CORS_ALLOWED_ORIGINS=https://ixion.dev
# API configuration # API configuration
SERVER_INACTIVITY_TIMEOUT=24 SERVER_UPDATE_INTERVAL=10

View File

@ -26,7 +26,9 @@ app.conf.result_backend = f"redis://redis:6379"
app.conf.beat_schedule = { app.conf.beat_schedule = {
"verify-urls-every-hour": { "verify-urls-every-hour": {
"task": "api.tasks.verify_urls_task", "task": "api.tasks.verify_urls_task",
"schedule": timedelta(hours=1), "schedule": timedelta(
minutes=int(os.getenv("SERVER_UPDATE_INTERVAL", default="10"))
),
# "schedule": timedelta(minutes=1), # "schedule": timedelta(minutes=1),
}, },
} }

View File

@ -42,6 +42,7 @@ INSTALLED_APPS = [
"django.contrib.staticfiles", "django.contrib.staticfiles",
"corsheaders", "corsheaders",
"rest_framework", "rest_framework",
"geoip2",
"v1", "v1",
] ]
@ -139,3 +140,5 @@ CORS_ALLOWED_ORIGINS = os.getenv(
CORS_ALLOW_ALL_ORIGINS = ( CORS_ALLOW_ALL_ORIGINS = (
os.getenv("CORS_ALLOW_ALL_ORIGINS", default="False").lower() == "true" os.getenv("CORS_ALLOW_ALL_ORIGINS", default="False").lower() == "true"
) )
GEOIP_PATH = os.path.join(BASE_DIR, "geoip")

View File

@ -6,3 +6,4 @@ celery
redis redis
python-dotenv python-dotenv
gunicorn gunicorn
geoip2

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-04-22 19:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('v1', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='server',
name='location',
field=models.CharField(editable=False, max_length=255, null=True),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 5.0.4 on 2024-04-22 20:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('v1', '0002_server_location'),
]
operations = [
migrations.RemoveField(
model_name='server',
name='inactivity_counter',
),
migrations.AddField(
model_name='server',
name='up',
field=models.BooleanField(editable=False, null=True),
),
migrations.AlterField(
model_name='server',
name='updated',
field=models.DateTimeField(editable=False, null=True),
),
]

View File

@ -1,3 +1,4 @@
import datetime
import json import json
import os import os
import socket import socket
@ -7,6 +8,7 @@ from django.core.validators import URLValidator
from django.core.exceptions import ValidationError 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
from django.contrib.gis.geoip2 import GeoIP2
# Required and recommended settings used to build the initial client server card # Required and recommended settings used to build the initial client server card
required_settings = [ required_settings = [
@ -41,14 +43,20 @@ class Server(models.Model):
null=False, null=False,
validators=[OptionalSchemeURLValidator()], validators=[OptionalSchemeURLValidator()],
) )
location = models.CharField(max_length=255, null=True, editable=False)
max_level = models.IntegerField(null=True, editable=False) 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) customizations = models.JSONField(null=True, editable=False)
login_limit = models.IntegerField(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(null=True, editable=False)
inactivity_counter = models.IntegerField(null=True, editable=False) up = models.BooleanField(null=True, editable=False)
@property
def expires(self):
time_since_creation = self.updated - self.created
return self.updated + min(time_since_creation, datetime.timedelta(hours=24))
def _str_(self): def _str_(self):
return self.url return self.url
@ -69,17 +77,14 @@ class Server(models.Model):
# Validate the server API # Validate the server API
if not self.parse_server_api(): if not self.parse_server_api():
if ( if self.expires <= datetime.datetime.now(datetime.timezone.utc):
self.inactivity_counter is None
or self.inactivity_counter
>= int(os.getenv("SERVER_INACTIVITY_TIMEOUT", default="24")) - 1
):
return False return False
self.inactivity_counter += 1 self.up = False
else: else:
if self.settings["API.DO_NOT_TRACK"]: if self.settings["API.DO_NOT_TRACK"]:
return False return False
self.inactivity_counter = 0 self.up = True
self.updated = datetime.datetime.now(datetime.timezone.utc)
# Proceed with saving the object # Proceed with saving the object
logger.info(f"Saving Server object with URL: {self.url}") logger.info(f"Saving Server object with URL: {self.url}")
@ -142,6 +147,10 @@ class Server(models.Model):
# except socket.error: # except socket.error:
# return False # return False
g = GeoIP2()
city = g.city(f"{self.url}")
self.location = city["continent_code"]
return True return True
except requests.RequestException: except requests.RequestException:

View File

@ -10,12 +10,13 @@ class ServerSerializer(serializers.ModelSerializer):
"id", "id",
"name", "name",
"url", "url",
"location",
"max_level", "max_level",
"customizations", "customizations",
"login_limit", "login_limit",
"active_sessions", "active_sessions",
"updated", "updated",
"inactivity_counter", "up",
] ]
def create(self, validated_data): def create(self, validated_data):

View File

@ -142,32 +142,57 @@ export default function ServerCard({ server }: { server: ServerData }) {
<Card className="mb-2"> <Card className="mb-2">
<Accordion className="my-0"> <Accordion className="my-0">
<AccordionSummary expandIcon={<ExpandMore />}> <AccordionSummary expandIcon={<ExpandMore />}>
<Box className="flex items-center justify-center"> <Box className="flex flex-col items-center justify-center">
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? ( <Box className="flex content-center">
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? (
<Tooltip
arrow
disableInteractive
title="Server is undergoing maintenance."
>
<Warning color="warning" />
</Tooltip>
) : (
<div>
{!server.up ? (
<Tooltip
arrow
disableInteractive
title="Server is offline."
>
<PublicOff color="error" />
</Tooltip>
) : (
<Tooltip arrow disableInteractive title="Server is online.">
<Public color="success" />
</Tooltip>
)}
</div>
)}
</Box>
<Box className="flex content-center">
<Tooltip <Tooltip
arrow arrow
disableInteractive disableInteractive
title="Server is undergoing maintenance." title="Estimated server geolocation provided by DB-IP."
> >
<Warning color="warning" /> <Typography
variant="caption"
component={Link}
to="https://db-ip.com"
target="_blank"
onClick={(event) => {
event.stopPropagation();
}}
sx={{
textDecoration: 'none',
}}
color={(theme) => alpha(theme.palette.text.primary, 0.5)}
>
{server.location}
</Typography>
</Tooltip> </Tooltip>
) : ( </Box>
<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> </Box>
<CardContent className="grow py-1"> <CardContent className="grow py-1">
<Box className="flex content-center"> <Box className="flex content-center">

View File

@ -15,19 +15,21 @@ export default interface ServerData {
id: number; id: number;
name: string; name: string;
url: string; url: string;
location: string;
max_level: number; max_level: number;
settings?: ServerSettings; settings?: ServerSettings;
customizations: ServerSettings; customizations: ServerSettings;
login_limit: number; login_limit: number;
active_sessions: number; active_sessions: number;
updated: string; updated: string;
inactivity_counter: number; up: boolean;
} }
export const DemoServerData: ServerData = { export const DemoServerData: ServerData = {
id: 0, id: 0,
name: 'LandSandBoat Demo', name: 'LandSandBoat Demo',
url: 'github.com/LandSandBoat/server', url: 'github.com/LandSandBoat/server',
location: 'NA',
max_level: 99, max_level: 99,
settings: LsbDefaults, settings: LsbDefaults,
customizations: { customizations: {
@ -43,7 +45,7 @@ export const DemoServerData: ServerData = {
login_limit: 0, login_limit: 0,
active_sessions: 0, active_sessions: 0,
updated: new Date().toISOString(), updated: new Date().toISOString(),
inactivity_counter: 0, up: true,
}; };
export const ServerSettingsInfo: Record<string, ServerSetting> = { export const ServerSettingsInfo: Record<string, ServerSetting> = {

View File

@ -108,32 +108,61 @@ export default function ServerDetails({
) : ( ) : (
<Card className="mb-2"> <Card className="mb-2">
<Box className="flex px-4"> <Box className="flex px-4">
<Box className="flex items-center justify-center"> <Box className="flex flex-col items-center justify-center">
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? ( <Box className="flex content-center">
{server.customizations['LOGIN.MAINT_MODE'] === 1 ? (
<Tooltip
arrow
disableInteractive
title="Server is undergoing maintenance."
>
<Warning color="warning" />
</Tooltip>
) : (
<div>
{!server.up ? (
<Tooltip
arrow
disableInteractive
title="Server is offline."
>
<PublicOff color="error" />
</Tooltip>
) : (
<Tooltip
arrow
disableInteractive
title="Server is online."
>
<Public color="success" />
</Tooltip>
)}
</div>
)}
</Box>
<Box className="flex content-center">
<Tooltip <Tooltip
arrow arrow
disableInteractive disableInteractive
title="Server is undergoing maintenance." title="Estimated server geolocation provided by DB-IP"
> >
<Warning color="warning" /> <Typography
variant="caption"
component={Link}
to="https://db-ip.com"
target="_blank"
onClick={(event) => {
event.stopPropagation();
}}
sx={{
textDecoration: 'none',
}}
color={(theme) => alpha(theme.palette.text.primary, 0.5)}
>
{server.location}
</Typography>
</Tooltip> </Tooltip>
) : ( </Box>
<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> </Box>
<CardContent className="grow py-1"> <CardContent className="grow py-1">
<Box className="flex content-center"> <Box className="flex content-center">