mirror of https://github.com/cocosolos/ixion.git
Change server details viewset
Change primary key to url instead of arbitrary id. Queries for server details will now attempt to add the server if it doesn't exist.
This commit is contained in:
parent
9b0a1701d4
commit
739cfdd07e
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.0.4 on 2024-05-02 22:25
|
||||
|
||||
import v1.models.server
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('v1', '0003_remove_server_inactivity_counter_server_up_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='server',
|
||||
name='id',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='server',
|
||||
name='url',
|
||||
field=models.CharField(max_length=255, primary_key=True, serialize=False, validators=[v1.models.server.OptionalSchemeURLValidator()]),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 5.0.4 on 2024-05-02 23:51
|
||||
|
||||
import v1.models.server
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('v1', '0004_remove_server_id_alter_server_url'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='server',
|
||||
name='url',
|
||||
field=models.CharField(error_messages={'unique': 'A server with this URL already exists.'}, max_length=255, primary_key=True, serialize=False, validators=[v1.models.server.OptionalSchemeURLValidator()]),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import datetime
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import requests
|
||||
from django.db import models
|
||||
from django.core.validators import URLValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
from urllib.parse import urlparse
|
||||
from v1.common.logging import logger
|
||||
from django.contrib.gis.geoip2 import GeoIP2
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
# Required and recommended settings used to build the initial client server card
|
||||
required_settings = [
|
||||
|
|
@ -39,9 +38,11 @@ class OptionalSchemeURLValidator(URLValidator):
|
|||
class Server(models.Model):
|
||||
name = models.CharField(max_length=255, null=True, editable=False)
|
||||
url = models.CharField(
|
||||
primary_key=True,
|
||||
max_length=255,
|
||||
null=False,
|
||||
validators=[OptionalSchemeURLValidator()],
|
||||
error_messages={"unique": "A server with this URL already exists."},
|
||||
)
|
||||
location = models.CharField(max_length=255, null=True, editable=False)
|
||||
max_level = models.IntegerField(null=True, editable=False)
|
||||
|
|
@ -55,8 +56,11 @@ class Server(models.Model):
|
|||
|
||||
@property
|
||||
def expires(self):
|
||||
try:
|
||||
time_since_creation = self.updated - self.created
|
||||
return self.updated + min(time_since_creation, datetime.timedelta(hours=24))
|
||||
except:
|
||||
return datetime.datetime(0, 1, 1, 0, 0, 0)
|
||||
|
||||
def _str_(self):
|
||||
return self.url
|
||||
|
|
@ -70,13 +74,10 @@ class Server(models.Model):
|
|||
parsed_url = urlparse(self.url)
|
||||
self.url = parsed_url.netloc.lower()
|
||||
|
||||
# Check if server URL is unique
|
||||
existing_server = Server.objects.filter(url=self.url).first()
|
||||
if existing_server and existing_server.pk != self.pk:
|
||||
raise ValidationError("A server with this URL already exists.")
|
||||
|
||||
# Validate the server API
|
||||
if not self.parse_server_api():
|
||||
if not self.created:
|
||||
raise ValidationError("Server verification failed.")
|
||||
if self.expires <= datetime.datetime.now(datetime.timezone.utc):
|
||||
return False
|
||||
self.up = False
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ class ServerSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Server
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"url",
|
||||
"location",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
from django.http import Http404
|
||||
from rest_framework import viewsets, mixins
|
||||
from rest_framework.response import Response
|
||||
from django.db.models.fields.json import KT
|
||||
from v1.serializers.server import ServerDetailsSerializer, ServerSerializer
|
||||
from v1.models.server import Server
|
||||
from rest_framework import status
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class ServerViewSet(
|
||||
|
|
@ -43,8 +47,53 @@ class ServerViewSet(
|
|||
|
||||
|
||||
class ServerDetailsViewSet(
|
||||
mixins.CreateModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
queryset = Server.objects.all()
|
||||
serializer_class = ServerDetailsSerializer
|
||||
|
||||
def get_object(self):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
url_param = self.request.query_params.get("url")
|
||||
if url_param is not None:
|
||||
try:
|
||||
return queryset.get(url=url_param)
|
||||
except Server.DoesNotExist:
|
||||
raise Http404("Server not found with the provided URL.")
|
||||
return super().get_object()
|
||||
|
||||
def create_server(self, url_param):
|
||||
try:
|
||||
serializer = self.get_serializer(data={"url": url_param})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
return serializer.instance
|
||||
except ValidationError as e:
|
||||
raise Http404(e.message)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
url_param = request.data.get("url")
|
||||
if url_param:
|
||||
try:
|
||||
server = Server.objects.get(url=url_param)
|
||||
serializer = self.get_serializer(server)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
except Server.DoesNotExist:
|
||||
instance = self.create_server(url_param)
|
||||
serializer = self.get_serializer(instance)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
url_param = self.request.query_params.get("url")
|
||||
if url_param is not None:
|
||||
queryset = queryset.filter(url=url_param)
|
||||
if not queryset.exists():
|
||||
instance = self.create_server(url_param)
|
||||
serializer = self.get_serializer(instance)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return self.retrieve(request, *args, **kwargs)
|
||||
raise Http404("No URL provided.")
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function App() {
|
|||
element={<Home servers={servers} searchState={searchState} />}
|
||||
/>
|
||||
<Route
|
||||
path="/server/:id"
|
||||
path="/server/:url"
|
||||
element={<ServerDetails setAlertInfo={setAlertInfo} />}
|
||||
/>
|
||||
<Route path="/about" element={<About />} />
|
||||
|
|
|
|||
|
|
@ -26,23 +26,34 @@ export const fetchDataFromBackend = async (
|
|||
queryParams = {}
|
||||
) => {
|
||||
const queryString = new URLSearchParams(queryParams).toString();
|
||||
const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/${dir}/${queryString ? `?${queryString}` : ''}`;
|
||||
const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/${dir}${queryString ? `?${queryString}` : ''}`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`(${response.status.toString()} - ${response.statusText || 'unknown'})`
|
||||
);
|
||||
let errorMessage =
|
||||
'An unknown error occurred while fetching data from the backend.';
|
||||
|
||||
// Check if the response has JSON content
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.url || errorData.detail;
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(`${err.message} - ${url}`);
|
||||
throw new Error(err.message);
|
||||
} else {
|
||||
throw new Error('Something went wrong.');
|
||||
throw new Error(
|
||||
'An unhandled error occurred while fetching data from the backend.'
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -59,7 +70,7 @@ export const postData = async (inputText: string) => {
|
|||
body: JSON.stringify({ url: inputText }),
|
||||
}
|
||||
);
|
||||
let message = `Server verification failed!`;
|
||||
let message = `An unknown error occurred while sending data to the backend.`;
|
||||
let success = false;
|
||||
if (!response.ok) {
|
||||
// bad url or duplicate
|
||||
|
|
@ -73,13 +84,13 @@ export const postData = async (inputText: string) => {
|
|||
};
|
||||
}
|
||||
const responseData = await response.json();
|
||||
if (responseData.id !== null) {
|
||||
if (response.status === 201) {
|
||||
message = 'Added new server!';
|
||||
success = true;
|
||||
}
|
||||
return { message, success, data: responseData };
|
||||
} catch (err) {
|
||||
let message = 'An unknown error occurred.';
|
||||
let message = 'An unhandled error while sending data to the backend.';
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default function AddServer({
|
|||
severity: 'success',
|
||||
});
|
||||
if (response.data && typeof response.data === 'object') {
|
||||
navigate(`/server/${response.data.id}`);
|
||||
navigate(`/server/${encodeURIComponent(response.data.url)}`);
|
||||
setServers([response.data, ...servers]);
|
||||
}
|
||||
if (inputRef.current) {
|
||||
|
|
@ -119,7 +119,11 @@ export default function AddServer({
|
|||
<StyledInputBase
|
||||
inputRef={inputRef}
|
||||
placeholder="URL"
|
||||
inputProps={{ 'aria-label': 'add-server' }}
|
||||
autoComplete="off"
|
||||
inputProps={{
|
||||
name: 'server-url',
|
||||
'aria-label': 'add-server',
|
||||
}}
|
||||
onKeyUp={handleKeyUp}
|
||||
disabled={isLoading}
|
||||
endAdornment={
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ export default function SearchServers({
|
|||
flexDirection="column"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography id="max-level-slider" variant="caption">
|
||||
<Typography id="max-level" variant="caption">
|
||||
Max Level
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
|
@ -194,15 +194,16 @@ export default function SearchServers({
|
|||
onChange={handleSearchMaxLevelMin}
|
||||
// onBlur={handleBlur}
|
||||
inputProps={{
|
||||
name: 'max-level-min',
|
||||
step: 1,
|
||||
min: 1,
|
||||
max: 99,
|
||||
type: 'number',
|
||||
'aria-labelledby': 'max-level-min',
|
||||
'aria-labelledby': 'max-level',
|
||||
}}
|
||||
/>
|
||||
<Slider
|
||||
aria-labelledby="max-level-slider"
|
||||
aria-labelledby="max-level"
|
||||
value={searchState.maxLevel}
|
||||
onChange={handleSearchMaxLevel}
|
||||
valueLabelDisplay="auto"
|
||||
|
|
@ -218,11 +219,12 @@ export default function SearchServers({
|
|||
onChange={handleSearchMaxLevelMax}
|
||||
// onBlur={handleBlur}
|
||||
inputProps={{
|
||||
name: 'max-level-max',
|
||||
step: 1,
|
||||
min: 1,
|
||||
max: 99,
|
||||
type: 'number',
|
||||
'aria-labelledby': 'max-level-max',
|
||||
'aria-labelledby': 'max-level',
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ export default function ServerCard({ server }: { server: ServerData }) {
|
|||
<Tooltip title="View full settings." arrow disableInteractive>
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={`/server/${server.id}`}
|
||||
to={`/server/${encodeURIComponent(server.url)}`}
|
||||
onClick={scrollToTop}
|
||||
className="p-0"
|
||||
disableRipple
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export interface ServerSettings {
|
|||
}
|
||||
|
||||
export default interface ServerData {
|
||||
id: number;
|
||||
name: string;
|
||||
url: string;
|
||||
location: string;
|
||||
|
|
@ -26,7 +25,6 @@ export default interface ServerData {
|
|||
}
|
||||
|
||||
export const DemoServerData: ServerData = {
|
||||
id: 0,
|
||||
name: 'LandSandBoat Demo',
|
||||
url: 'github.com/LandSandBoat/server',
|
||||
location: 'NA',
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export default function Home({
|
|||
<ErrorCard error="" />
|
||||
) : (
|
||||
filteredServers.map((server: ServerData) => (
|
||||
<ServerCard key={server.id} server={server} />
|
||||
<ServerCard key={server.url} server={server} />
|
||||
))
|
||||
)}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default function ServerDetails({
|
|||
}: {
|
||||
setAlertInfo: React.Dispatch<React.SetStateAction<AlertResponse>>;
|
||||
}) {
|
||||
const { id } = useParams();
|
||||
const { url } = useParams();
|
||||
const [server, setServer] = useState<ServerData>();
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
|
|
@ -38,10 +38,10 @@ export default function ServerDetails({
|
|||
let data: ServerData;
|
||||
const fetchServerData = async () => {
|
||||
try {
|
||||
if (id === 'demo') {
|
||||
if (url === 'demo') {
|
||||
data = await fetchDemo();
|
||||
} else {
|
||||
data = await fetchDataFromBackend(`server/${id}`);
|
||||
data = await fetchDataFromBackend(`server/?url=${url}`);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
|
|
@ -58,7 +58,7 @@ export default function ServerDetails({
|
|||
};
|
||||
|
||||
fetchServerData();
|
||||
}, [id, setServer, setAlertInfo, setError]);
|
||||
}, [url, setServer, setAlertInfo, setError]);
|
||||
|
||||
const [clipboardTooltip, setClipboardTooltip] = useState('Copy server URL.');
|
||||
const [clipboardTooltipOpen, setClipboardTooltipOpen] = useState(false);
|
||||
|
|
@ -93,12 +93,12 @@ export default function ServerDetails({
|
|||
}
|
||||
};
|
||||
|
||||
function formatExternalUrl(url: string): string {
|
||||
function formatExternalUrl(serverUrl: string): string {
|
||||
// prepend 'https://' to the URL if it's not already there
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
return `https://${url}`;
|
||||
if (!/^https?:\/\//i.test(serverUrl)) {
|
||||
return `https://${serverUrl}`;
|
||||
}
|
||||
return url;
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in New Issue