Compare commits

..

2 Commits

Author SHA1 Message Date
88f5614cd4
Fix launch logic
Have cleaned up redundant logic used for testing
it ruined my life today.
Port mapping works but it's simply picking it up from
a single integer field. We might have to expand on this
logic for multi port mappings per instance.

Signed-off-by: Pratyush Desai <pratyush.desai@liberta.casa>
2025-04-16 21:01:58 +05:30
77ab980fd4
Create Frontend
Create views and templates to render the views for a list of games
And for it to show if server is running for the game selected.

Signed-off-by: Pratyush Desai <pratyush.desai@liberta.casa>
2025-04-16 14:30:51 +05:30
19 changed files with 564 additions and 299 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,51 +1,34 @@
from django.contrib import admin
from .models import Game, Server
from .utils import launch_pod_container, stop_pod_container, remove_pod_container
from .utils import stop_pod_container, remove_pod_container
import podman
from django.contrib import messages
@admin.action(description="Launch selected servers")
def launch_servers(modeladmin, request, queryset):
for server in queryset:
result = server.launch_pod_container()
messages.info(request, f"{server.name}: {result}")
@admin.action(description="Stop selected servers")
def stop_servers(modeladmin, request, queryset):
for server in queryset:
result = server.stop_pod_container()
messages.info(request, f"{server.name}: {result}")
@admin.action(description="Remove selected servers")
def remove_servers(modeladmin, request, queryset):
for server in queryset:
result = server.remove_pod_container()
messages.info(request, f"{server.name}: {result}")
@admin.register(Game)
class GameAdmin(admin.ModelAdmin):
list_display = ('name', 'genre')
list_display = ('name', 'genre', 'thumbnail')
search_fields = ('name', 'genre')
ordering = ('name',)
@admin.action(description='Launch Container')
def launch_container(modeladmin, request, queryset):
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
for server in queryset:
container_name = f"{server.game.name}_{server.ip_address}_{server.port}"
try:
# Ensure the command is passed as a list of strings
command = server.get_podman_run_command().split() if server.run_command else []
container = client.containers.run(
server.image,
detach=True,
name=container_name,
command=command,
ports={f"{server.port}/tcp": server.port},
remove=True, # Automatically remove on stop
)
server.sync_status()
modeladmin.message_user(request, f"Container launched: {container.id}")
except Exception as e:
modeladmin.message_user(request, f"Failed to launch {server}: {e}", level="error")
@admin.action(description='Stop Container')
def stop_container(modeladmin, request, queryset):
for server in queryset:
container_name = f"{server.game.name}_{server.ip_address}_{server.port}"
result = stop_pod_container(container_name)
server.sync_status()
modeladmin.message_user(request, f"Stop container result for {server}: {result}")
@admin.action(description='Remove Container')
def remove_container(modeladmin, request, queryset):
for server in queryset:
container_name = f"{server.game.name}_{server.ip_address}_{server.port}"
result = remove_pod_container(container_name)
server.sync_status()
modeladmin.message_user(request, f"Remove container result for {server}: {result}")
@admin.register(Server)
class ServerAdmin(admin.ModelAdmin):
list_display = ('game', 'name', 'ip_address', 'port', 'status', 'image', 'run_command', 'command_args')
@ -64,4 +47,4 @@ class ServerAdmin(admin.ModelAdmin):
list_filter = ('status', 'game')
search_fields = ('ip_address', 'game__name', 'image')
ordering = ('game', 'ip_address')
actions = [launch_container, stop_container, remove_container]
actions = [ stop_servers, remove_servers, launch_servers]

View File

@ -0,0 +1,20 @@
# Generated by Django 5.1.5 on 2025-04-15 19:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webpanel", "0006_rename_docker_image_server_image_and_more"),
]
operations = [
migrations.AddField(
model_name="game",
name="thumbnail",
field=models.ImageField(
blank=True, null=True, upload_to="game_thumbnails/"
),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 5.1.5 on 2025-04-16 08:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webpanel", "0007_game_thumbnail"),
]
operations = [
migrations.AlterField(
model_name="game",
name="thumbnail",
field=models.ImageField(
blank=True, null=True, upload_to="media/game_thumbnails/"
),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.1.5 on 2025-04-16 14:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webpanel", "0008_alter_game_thumbnail"),
]
operations = [
migrations.AddField(
model_name="server",
name="container_id",
field=models.CharField(blank=True, max_length=64, null=True),
),
migrations.AddField(
model_name="server",
name="last_log",
field=models.TextField(blank=True, null=True),
),
]

View File

@ -1,9 +1,13 @@
from django.db import models
import podman
import shlex
import re
class Game(models.Model):
name = models.CharField(max_length=100)
genre = models.CharField(max_length=50, blank=True, null=True)
thumbnail = models.ImageField(upload_to='media/game_thumbnails/', null=True, blank=True)
def __str__(self):
return self.name
@ -13,7 +17,7 @@ class Server(models.Model):
('online', 'Online'),
('offline', 'Offline'),
]
last_log = models.TextField(blank=True, null=True)
game = models.ForeignKey(Game, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
ip_address = models.GenericIPAddressField(null=True)
@ -21,6 +25,7 @@ class Server(models.Model):
image = models.CharField(max_length=200, null=True)
run_command = models.CharField(max_length=500, blank=True, null=True)
command_args = models.TextField(blank=True, null=True)
container_id = models.CharField(max_length=64, blank=True, null=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='offline')
def __str__(self):
@ -28,10 +33,10 @@ class Server(models.Model):
def sync_status(self):
"""Check the real-time status of the container and update the field."""
safe_name = re.sub(r'[^a-zA-Z0-9_.-]', '_', self.name)
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
container_name = f"{self.game.name}_{self.ip_address}_{self.port}"
try:
container = client.containers.get(container_name)
container = client.containers.get(safe_name)
if container.status == "running":
self.status = "online"
else:
@ -39,17 +44,77 @@ class Server(models.Model):
except podman.errors.NotFound:
self.status = "offline"
except Exception as e:
self.status = "offline" # Fallback in case of unexpected errors
self.status = "offline"
self.save()
def get_podman_run_command(self):
"""Returns the Podman run command, falling back to default image if not set."""
if self.run_command:
# Return command as a string to be split later
return self.run_command
else:
# Default command with image and arguments
base_command = f"{self.image}"
if self.command_args:
base_command += " " + self.command_args
return base_command
def launch_pod_container(self):
safe_name = re.sub(r'[^a-zA-Z0-9_.-]', '_', self.name) # sanitize name
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
try:
container = client.containers.create(
name=safe_name,
image=self.image,
ports={
f'{self.port}/udp': ('0.0.0.0', self.port),
f'{self.port}/tcp': ('0.0.0.0', self.port),
},
command=shlex.split(self.run_command),
detach=True,
)
container.start()
self.container_id = container.id
self.last_log = f"Launched container {container.id}"
self.is_running = True
self.sync_status()
self.save()
return f"Container launched successfully: {container.id}"
except podman.errors.APIError as e:
self.last_log = f"API Error: {str(e)}"
self.save()
return f"API Error: {e}"
except Exception as e:
self.last_log = f"Error: {str(e)}"
self.save()
return f"Error: {e}"
def stop_pod_container(self):
safe_name = re.sub(r'[^a-zA-Z0-9_.-]', '_', self.name)
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
try:
container = client.containers.get(safe_name)
container.stop()
self.status = "offline"
self.last_log = f"Stopped container {container.id}"
self.sync_status()
self.save()
return f"Container stopped successfully: {container.id}"
except podman.errors.NotFound:
self.last_log = f"Container '{safe_name}' not found"
self.save()
return f"Error: Container '{safe_name}' not found"
except Exception as e:
self.last_log = f"Error stopping container: {str(e)}"
self.save()
return f"Error: {e}"
def remove_pod_container(self):
safe_name = re.sub(r'[^a-zA-Z0-9_.-]', '_', self.name)
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
try:
container = client.containers.get(safe_name)
container.remove(force=True)
self.status = "offline"
self.container_id = None
self.last_log = f"Removed container {safe_name}"
self.sync_status()
self.save()
return f"Container removed successfully: {safe_name}"
except podman.errors.NotFound:
self.last_log = f"Container '{safe_name}' not found"
self.save()
return f"Error: Container '{safe_name}' not found"
except Exception as e:
self.last_log = f"Error removing container: {str(e)}"
self.save()
return f"Error: {e}"

View File

@ -1,46 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Active Servers</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.server-box {
border: 2px solid #007BFF;
border-radius: 5px;
padding: 10px;
margin-bottom: 15px;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
}
legend {
font-weight: bold;
color: #007BFF;
}
.server-details {
margin-left: 15px;
}
</style>
</head>
<body>
<h1>Active Servers</h1>
{% if servers %}
{% for server in servers %}
<fieldset class="server-box">
<legend>Server: {{ server.game.name }}</legend>
<div class="server-details">
<p><strong>Genre:</strong> {{ server.game.genre }}</p>
<p><strong>IP Address:</strong> {{ server.ip_address }}</p>
<p><strong>Port:</strong> {{ server.port }}</p>
<p><strong>Status:</strong> Online</p>
</div>
</fieldset>
{% endfor %}
{% else %}
<p>No active servers found.</p>
{% endif %}
</body>
</html>

View File

@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}GibCasa{% endblock %}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: monospace;
}
body {
background-color: #111;
color: #ccc;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.navbar {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #222;
padding: 1em 2em;
border-bottom: 1px solid #444;
}
.navbar .left,
.navbar .right {
display: flex;
align-items: center;
}
.navbar a {
color: white;
text-decoration: none;
margin-right: 1.5em;
font-size: 1em;
}
.navbar a:hover {
color: limegreen;
}
/*
.search-box input {
padding: 5px;
background: #333;
border: 1px solid #555;
color: #fff;
} */
.profile {
width: 35px;
height: 35px;
background: #444;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
cursor: pointer;
}
.content {
width: 80%;
padding: 1.5em;
}
footer {
width: 100%;
display: flex;
justify-content: center;
border-top: 1px solid #444;
padding: 1em;
margin-top: auto;
}
footer a {
color: white;
text-decoration: none;
margin: 0 10px;
}
footer a:hover {
color: limegreen;
}
.sub-nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #444;
margin-bottom: 20px;
}
.sub-nav a {
color: white;
text-decoration: none;
margin-right: 15px;
}
.sub-nav a.active {
color: limegreen;
}
.right-links a {
color: #a36eff;
text-decoration: none;
margin-left: 10px;
}
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
width: 100%;
margin-top: 20px;
}
.box {
background: #222;
padding: 20px;
border-radius: 5px;
color: #ff5500;
}
.upcoming {
color: #ffcc00;
opacity: 0.8;
}
.stats {
color: white;
}
.game-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 20px;
width: 100%;
justify-items: center;
}
.game-box {
width: 200px;
height: 270px;
border: 1px solid #888;
display: flex;
align-items: center;
justify-content: center;
background-color: #111;
text-align: center;
color: white;
font-size: 14px;
}
/* .game-box img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 5px;
} */
</style>
</head>
<body>
<div class="navbar">
<div class="center">
<pre>
┬ ┌┬┐ ┬─┐ ┌─┐ ┬─┐ ┐─┐ ┬─┐
│ │ │─│ │ │─┤ └─┐ │─┤
┘─┘ └┴┘ │─┘ └─┘ ┘ │ ──┘ ┘ │
</pre>
</div>
<div class="navbar">
<div class="left">
<a href="{% url 'home' %}">Home</a>
<a href="{% url 'games' %}">Games</a>
</div>
</div>
</div class="navbar">
<!-- <div class="right"> -->
<!-- <div class="search-box">
<input type="text" placeholder="Search...">
</div> -->
<!-- <a href="#">Auth / Settings</a> -->
<!-- <div class="profile">Dp</div> -->
<!-- </div> -->
</div>
<div class="content">
{% block content %}{% endblock %}
</div>
<footer>
<a href="https://ozFrags.net">Other Communities</a>
<a href="https://liberta.casa/rules.html">Terms of Service WIP</a>
<a href="#">License: AGPLish</a>
<a href="#">Contribute (WIP)</a>
<a href="https://liberta.casa/gamja/#gibcasa">Support</a>
</footer>
</body>
</html>

View File

@ -1,15 +1,45 @@
<!DOCTYPE html>
<html>
<head><title>{{ game.name }}</title></head>
<body>
<h1>{{ game.name }}</h1>
<p>{{ game.description }}</p>
<h2>Servers</h2>
<ul>
{% for server in game.servers.all %}
<li>{{ server.ip_address }}:{{ server.port }} - {{ server.status }}</li>
{% endfor %}
</ul>
<a href="/">Back to Games</a>
</body>
</html>
{% extends 'webpanel/base.html' %}
{% block title %}{{ game.name }} - Game Details{% endblock %}
{% block content %}
<h2>{{ game.name }}</h2>
<!-- <nav class="sub-nav">
<a href="{% url 'home' %}">Home</a>
<a href="{% url 'games' %}">Games</a>
<a href="#">Mods</a>
<div class="right-links">
<a href="#">Show All Active Servers</a>
<a href="#">Request A Server</a>
</div>
</nav> -->
<div class="game-detail">
<div class="game-box" style="width: 150px; text-align: center;">
<a href="{% url 'game_detail' game.name %}">
<img src="{{ game.thumbnail.url }}" alt="{{ game.name }}" style="width: 100%; height: auto;">
<p>{{ game.name }}</p>
</a>
</div>
<div class="active-servers">
{% if active_servers %}
{% for server in active_servers %}
<fieldset class="server-box">
<legend>Server: {{ game.name }}</legend>
<div class="server-details">
<p><strong>IP Address:</strong> {{ server.ip_address }}</p>
<p><strong>Port:</strong> {{ server.port }}</p>
<p><strong>Status:</strong> Online</p>
</div>
</fieldset>
{% endfor %}
{% else %}
<p>No active servers found for this game.</p>
{% endif %}
</div>
<div class="game-info">
<p>Pull data from some open API to populate information about the game and render it.</p>
</div>
</div>
{% endblock %}

View File

@ -1,49 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Active Servers for {{ game.name }}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1 {
color: #007BFF;
}
.server-box {
border: 2px solid #007BFF;
border-radius: 5px;
padding: 10px;
margin-bottom: 15px;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
}
legend {
font-weight: bold;
color: #007BFF;
}
.server-details {
margin-left: 15px;
}
</style>
</head>
<body>
<h1>Active Servers for {{ game.name }}</h1>
<h3>Genre: {{ game.genre }}</h3>
{% if servers %}
{% for server in servers %}
<fieldset class="server-box">
<legend>Server: {{ game.name }}</legend>
<div class="server-details">
<p><strong>IP Address:</strong> {{ server.ip_address }}</p>
<p><strong>Port:</strong> {{ server.port }}</p>
<p><strong>Status:</strong> Online</p>
</div>
</fieldset>
{% endfor %}
{% else %}
<p>No active servers found for this game.</p>
{% endif %}
</body>
</html>

View File

@ -0,0 +1,20 @@
{% extends 'webpanel/base.html' %}
{% block title %}Games - Game Servers{% endblock %}
{% block content %}
<h2>Games</h2>
<div class="game-grid" style="display: flex; flex-wrap: wrap; gap: 10px;">
{% for game in games %}
<div class="game-box" style="width: 150px; text-align: center;">
<a href="{% url 'game_detail' game.name %}">
<img src="{{ game.thumbnail.url }}" alt="{{ game.name }}" style="width: 100%; height: auto;">
<p>{{ game.name }}</p>
</a>
</div>
{% empty %}
<p>No games available.</p>
{% endfor %}
</div>
{% endblock %}

View File

@ -1,53 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GibCasa GSM</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1 {
color: #007BFF;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin: 10px 0;
}
a {
text-decoration: none;
color: #007BFF;
font-weight: bold;
}
a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<h1>Game Hosting Panel</h1>
<h2>Quick Links</h2>
<ul>
<li><a href="{% url 'active-servers' %}">View All Active Servers</a></li>
</ul>
<h2>Games</h2>
<ul>
{% for game in games %}
<li>
<a href="{% url 'game-servers' game.name %}">
View Active Servers for {{ game.name }}
</a>
</li>
{% empty %}
<li>No games available.</li>
{% endfor %}
</ul>
</body>
</html>
{% extends 'webpanel/base.html' %}
{% block title %}Home - Game Servers{% endblock %}
<style>
.sub-nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #444;
margin-bottom: 20px;
}
.sub-nav a {
color: white;
text-decoration: none;
margin-right: 15px;
}
.sub-nav a.active {
color: limegreen;
}
.right-links a {
color: #a36eff;
text-decoration: none;
margin-left: 10px;
}
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
width: 100%;
margin-top: 20px;
}
.box {
background: #222;
padding: 20px;
border-radius: 5px;
color: #ff5500;
}
.upcoming {
color: #ffcc00;
opacity: 0.8;
}
.stats {
color: white;
}
</style>
{% block content %}
<h2>Public Game Servers</h2>
<div class="grid-container">
<div class="box highlight">
<p>Concise yet extreme 3D RGB features and services list</p>
</div>
<div class="box upcoming">
<p>Upcoming shit</p>
</div>
<div class="box stats">
<ol>
<li>Server Uptime & reliability</li>
<li>Craziest Player Stats</li>
<li>??</li>
</ol>
</div>
</div>
{% endblock %}

View File

@ -1,9 +1,12 @@
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path
from . import views
urlpatterns = [
path('', views.home_view, name='home'),
path('<int:pk>/', views.game_detail, name='game_detail'),
path('active-servers/', views.active_servers_view, name='active-servers'),
path('games/<str:game_name>/', views.game_servers_view, name='game-servers'),
path('', views.home, name='home'),
path('games/', views.games, name='games'),
path('games/<str:game_name>/', views.game_detail, name='game_detail'),
# path('active-servers/', views.active_servers_view, name='active-servers'),
# path('games/<str:game_name>/', views.game_servers_view, name='game-servers'),
]

View File

@ -1,51 +0,0 @@
import podman
def launch_pod_container(image, run_command, name, ports):
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
try:
container = client.containers.create(
name=name,
image=image,
# ports=ports,
command=run_command,
detach=True,
network_mode='host'
)
container.start()
return f"Container launched successfully: {container.id}"
except podman.errors.APIError as e:
return f"API Error: {e}"
except Exception as e:
return f"Error: {e}"
def stop_pod_container(name):
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
try:
container = client.containers.get(name)
container.stop()
return f"Container stopped: {name}"
except podman.errors.NotFound:
return f"Container not found: {name}"
except Exception as e:
return f"Error stopping container {name}: {e}"
def remove_pod_container(name):
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
try:
container = client.containers.get(name)
container.remove(force=True) # Force removal if the container is running
return f"Container removed: {name}"
except podman.errors.NotFound:
return f"Container not found: {name}"
except Exception as e:
return f"Error removing container {name}: {e}"
def is_container_running(name):
client = podman.PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock")
try:
container = client.containers.get(name)
return container.status == "running"
except podman.errors.NotFound:
return False
except Exception as e:
return f"Error checking container {name}: {e}"

View File

@ -1,41 +1,52 @@
from django.shortcuts import render, get_object_or_404
from .models import Game, Server
def home_view(request):
def home(request):
"""Display the home page with links to other views."""
games = Game.objects.all() # Fetch all games
return render(request, 'webpanel/home.html', {'games': games})
# def game_list(request):
# games = Game.objects.all()
# return render(request, 'webpanel/game_list.html', {'games': games})
def games(request):
games = Game.objects.all()
return render(request, 'webpanel/games.html', {'games': games})
def game_detail(request, pk):
game = get_object_or_404(Game, pk=pk)
return render(request, 'webpanel/game_detail.html', {'game': game})
def active_servers_view(request):
"""Fetch and display all active servers."""
# Sync server status before fetching
servers = Server.objects.all()
for server in servers:
server.sync_status()
# Fetch only servers with status 'online'
active_servers = Server.objects.filter(status='online')
return render(request, 'webpanel/active_servers.html', {'servers': active_servers})
def game_servers_view(request, game_name):
"""Fetch and display active servers for a specific game."""
def game_detail(request, game_name):
print(f"Looking for game: {game_name}")
game = get_object_or_404(Game, name=game_name)
# Sync server status before fetching
servers = Server.objects.filter(game=game)
for server in servers:
server.sync_status()
dormant_servers = servers.filter(status='offline')
active_servers = servers.filter(status='online')
return render(request, 'webpanel/game_detail.html', {'game': game,
'active_servers': active_servers, 'dormant_servers': dormant_servers})
# Fetch only active servers for the game
active_servers = servers.filter(status='online')
# def game_detail(request, pk):
# game = get_object_or_404(Game, pk=pk)
# return render(request, 'webpanel/game_detail.html', {'game': game})
return render(request, 'webpanel/game_servers.html', {'game': game, 'servers': active_servers})
# def active_servers_view(request):
# """Fetch and display all active servers."""
# # Sync server status before fetching
# servers = Server.objects.all()
# for server in servers:
# server.sync_status()
# # Fetch only servers with status 'online'
# active_servers = Server.objects.filter(status='online')
# return render(request, 'webpanel/active_servers.html', {'servers': active_servers})
# def game_servers_view(request, game_name):
# """Fetch and display active servers for a specific game."""
# game = get_object_or_404(Game, name=game_name)
# # Sync server status before fetching
# servers = Server.objects.filter(game=game)
# for server in servers:
# server.sync_status()
# # Fetch only active servers for the game
# active_servers = servers.filter(status='online')
# return render(request, 'webpanel/game_servers.html', {'game': game, 'servers': active_servers})