Crackhead Programming be like

I am ashamed of what i have done but I got hl2dm to render properly
with the gamestats and everything. It's for the best as it made me
realize some of the things im not accounting for

Signed-off-by: Pratyush Desai <pratyush.desai@liberta.casa>
This commit is contained in:
Pratyush Desai 2025-04-23 19:33:02 +05:30
parent c836caa6af
commit e835462471
Signed by: pratyush
GPG Key ID: DBA5BB7505946FAD
7 changed files with 353 additions and 37 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-04-23 09:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webpanel", "0013_migrate_port_to_dict"),
]
operations = [
migrations.AddField(
model_name="server",
name="query_protocol",
field=models.CharField(max_length=20, null=True),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.1.5 on 2025-04-23 10:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webpanel", "0014_server_query_protocol"),
]
operations = [
migrations.RemoveField(
model_name="server",
name="query_protocol",
),
migrations.AddField(
model_name="game",
name="query_protocol",
field=models.CharField(max_length=20, null=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.1.5 on 2025-04-23 10:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("webpanel", "0015_remove_server_query_protocol_game_query_protocol"),
]
operations = [
migrations.AddField(
model_name="game",
name="default_query_port_key",
field=models.CharField(
blank=True,
help_text="Default key (e.g., '27015/udp' or '27910') used for status queries within the Server's port mappings.",
max_length=20,
null=True,
),
),
]

View File

@ -8,6 +8,13 @@ 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='game_thumbnails/', null=True, blank=True)
query_protocol=models.CharField(max_length=20, null=True)
default_query_port_key = models.CharField(
max_length=20,
blank=True,
null=True,
help_text="Default key (e.g., '27015/udp' or '27910') used for status queries within the Server's port mappings."
)
def __str__(self) -> str:
return self.name

View File

@ -3,34 +3,195 @@
{% block title %}{{ game.name }} - Game Details{% endblock %}
{% block content %}
<h2>{{ game.name }}</h2>
<h2>{{ game.name }}</h2>
{# --- Add the CSS from previous example for .server-box etc. --- #}
<style>
.server-box {
border: 1px solid #555;
padding: 15px;
margin-bottom: 15px;
background-color: #2a2a2a;
border-radius: 4px;
}
<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>
.server-box legend {
font-weight: bold;
color: #0f0;
/* Lime green */
padding: 0 5px;
}
.server-details p {
margin: 6px 0;
font-size: 0.95em;
}
.live-stats {
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed #444;
color: #eee;
}
.live-stats strong {
color: #bbb;
width: 120px;
display: inline-block;
}
/* Align labels */
.player-list {
list-style: none;
padding-left: 15px;
font-size: 0.9em;
color: #ccc;
max-height: 150px;
overflow-y: auto;
margin-top: 5px;
border-left: 2px solid #444;
padding-left: 10px;
}
.player-list li {
margin-bottom: 3px;
}
.error-msg {
color: #ff8888;
font-style: italic;
font-size: 0.9em;
}
.game-thumbnail {
float: left;
margin-right: 20px;
margin-bottom: 20px;
width: 150px;
text-align: center;
}
.server-lists {
overflow: hidden;
}
.clear-float {
clear: both;
}
h3 {
margin-top: 25px;
color: #ccc;
border-bottom: 1px solid #555;
padding-bottom: 5px;
}
hr.section-divider {
margin: 30px 0;
border: 0;
border-top: 1px solid #444;
}
</style>
<div class="game-detail">
<div class="game-thumbnail">
{% if game.thumbnail %}
<img src="{{ game.thumbnail.url }}" alt="{{ game.name }}"
style="width: 100%; height: auto; border: 1px solid #444;">
{% else %}
<div
style="width: 150px; height: 200px; background-color: #333; display: flex; align-items: center; justify-content: center; color: #888; border: 1px solid #444;">
No Thumbnail</div>
{% endif %}
<p style="margin-top: 5px;">{{ game.name }}</p>
</div>
{% endblock %}
<div class="server-lists">
<h3>Active Servers</h3>
{% if active_servers %}
{% for server in active_servers %}
{# --- CORRECTED FIELDSET FOR ACTIVE SERVERS --- #}
<fieldset class="server-box">
<legend>{{ server.name }}</legend>
<div class="server-details">
<p><strong>Podman Status:</strong> <span style="color: limegreen;">Online</span></p>
<p><strong>IP Address:</strong> {{ server.ip_address|default:"N/A" }}</p>
<p><strong>Port Mappings:</strong> {{ server.get_ports_display }}</p>
{# --- Live Stats Section (Checks Game Model Config & Query Result) --- #}
<div class="live-stats">
{# Check if query succeeded and returned data #}
{% if server.live_stats %}
<p><strong>Live Status:</strong> <span style="color: lightgreen;">Reachable</span></p>
{# Display common stats using consistent keys from the dictionary #}
<p><strong>Server Name:</strong> {{ server.live_stats.server_name|default:"N/A" }}</p>
<p><strong>Map:</strong> {{ server.live_stats.map_name|default:"N/A" }}</p>
<p>
<strong>Players:</strong>
{{ server.live_stats.current_players|default:"0" }} /
{{ server.live_stats.max_players|default:"?" }}
{% if server.live_stats.bots is not None %} ({{ server.live_stats.bots }} bots){% endif %}
</p>
<p>
<strong>Password:</strong>
{% if server.live_stats.password_protected %}<span style="color: orange;">Yes</span>{% else
%}No{% endif %}
</p>
{% if server.live_stats.vac_enabled is not None %}
<p><strong>VAC Secured:</strong> {% if server.live_stats.vac_enabled %}Yes{% else %}No{% endif %}
</p>
{% endif %}
{% if server.live_stats.players_list %} {# Raw list, e.g. from Q2 #}
<p><strong>Player List (Raw):</strong></p>
<ul class="player-list">
{% for player_line in server.live_stats.players_list %}<li>{{ player_line }}</li>{% empty %}<li>
</li>{% endfor %}
</ul>
{% endif %}
{# Check if query was configured for this game (in Game model) but failed #}
{% elif server.game.query_protocol != 'none' and server.game.default_query_port_key %}
<p class="error-msg"><strong>Live Status:</strong> Unreachable (Timeout or Error)</p>
{# Query was not configured for this game in the Game model #}
{% else %}
<p><em>(Live query not configured for {{ server.game.name }})</em></p>
{% endif %}
</div>
{# --- End Live Stats Section --- #}
</div>
</fieldset>
{# --- END CORRECTED FIELDSET --- #}
{% endfor %}
{% else %}
<p>No active servers found for this game.</p>
{% endif %}
<hr class="section-divider"> {# Use HR for clearer separation #}
<h3>Dormant Servers</h3>
{% if dormant_servers %}
{% for server in dormant_servers %}
<fieldset class="server-box" style="opacity: 0.6;">
<legend>{{ server.name }}</legend>
<div class="server-details">
<p><strong>Podman Status:</strong> <span style="color: orange;">{{ server.status|title }}</span></p>
<p><strong>Image:</strong> {{ server.image }}</p>
<p><strong>Connect Info:</strong> {{ server.ip_address|default:"N/A" }} : {{ server.get_ports_display }}
</p>
</div>
</fieldset>
{% endfor %}
{% else %}
<p>No dormant servers found for this game.</p>
{% endif %}
</div> {# End server-lists #}
<div class="clear-float"></div> {# Clear float #}
<div class="game-info" style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #444;">
<h4>About {{ game.name }}</h4>
<p>Genre: {{ game.genre|default:"N/A" }}</p>
{# TODO: Pull more data #}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,12 @@
import a2s
# games tested by library author
# Half-Life 2, Half-Life, Team Fortress 2, Counter-Strike: Global Offensive,
# Counter-Strike 1.6, ARK: Survival Evolved, Rust
def a2s_query(ip, port):
address = (ip, port)
info = a2s.info(address)
return info

View File

@ -1,5 +1,15 @@
from django.shortcuts import render, get_object_or_404
from .models import Game, Server
from .utils import a2s_query
import logging
logger = logging.getLogger(__name__)
PROTOCOL_FUNCTION_MAP = {
'a2s': a2s_query,
# Add other protocols here -> 'quake2': query_quake2, etc.
}
def home(request):
"""Display the home page with links to other views."""
@ -11,12 +21,75 @@ def games(request):
return render(request, 'webpanel/games.html', {'games': games})
def game_detail(request, game_name):
print(f"Looking for game: {game_name}")
game = get_object_or_404(Game, name=game_name)
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})
logger.info(f"Accessing game detail for: {game_name}") # Use logger.info or logger.debug
game = get_object_or_404(Game, name__iexact=game_name) # Use iexact for case-insensitivity
query_protocol = getattr(game, 'query_protocol', 'none') # Safely get protocol
query_function = PROTOCOL_FUNCTION_MAP.get(query_protocol)
default_port_key = getattr(game, 'default_query_port_key', None) # Safely get port key
if not query_function:
logger.debug(f"Live query disabled for game '{game.name}' (protocol '{query_protocol}' not mapped or 'none').")
elif not default_port_key:
logger.warning(f"Live query configured for '{game.name}' (protocol '{query_protocol}') but 'Default query port key' is not set in Game model.")
else:
logger.debug(f"Live query configured for '{game.name}': Protocol='{query_protocol}', Port Key='{default_port_key}'")
servers_queryset = Server.objects.filter(game=game)
active_server_list = []
dormant_server_list = []
for server in servers_queryset:
server.sync_status()
server.live_stats = None
if server.status == 'online' and a2s_query and default_port_key:
logger.debug(f"Server '{server.name}' is online, attempting query using protocol '{query_protocol}'.")
query_port_host = None
port_keys_to_try = [default_port_key]
if '/' in default_port_key:
port_keys_to_try.append(default_port_key.split('/')[0]) # Try without /udp suffix
for key in port_keys_to_try:
if isinstance(server.port, dict) and key in server.port: # Check if server.port is a dict
try:
query_port_host = int(server.port[key])
logger.debug(f"Found host query port {query_port_host} using key '{key}'.")
break
except (ValueError, TypeError):
logger.warning(f"Invalid port value '{server.port[key]}' for key '{key}' in server '{server.name}'.")
query_port_host = None
if query_port_host is None:
logger.warning(f"Could not find host port for key '{default_port_key}' (or fallback) in server '{server.name}'. Port data: {server.port}")
ip_to_query = server.ip_address
if not ip_to_query or ip_to_query in ["0.0.0.0", "::"]:
ip_to_query = "127.0.0.1" # Default to localhost
logger.debug(f"Using IP {ip_to_query} for query.")
if ip_to_query and query_port_host is not None:
try:
logger.info(f"Querying {game.name} server '{server.name}' at {ip_to_query}:{query_port_host}")
server.live_stats = a2s_query(ip=ip_to_query, port=query_port_host) # Call the mapped function
if server.live_stats:
logger.info(f"Query successful for '{server.name}'.") # Add more detail if needed
else:
logger.warning(f"Query returned no data for '{server.name}'.")
except Exception as e:
logger.error(f"Error querying server '{server.name}': {e}", exc_info=True)
server.live_stats = None # Ensure it's None on error
else:
logger.warning(f"Skipping query for '{server.name}': Missing IP or Host Port.")
if server.status == 'online':
active_server_list.append(server)
else:
dormant_server_list.append(server)
return render(request, 'webpanel/game_detail.html', {
'game': game,
'active_servers': active_server_list, # Use the list we built
'dormant_servers': dormant_server_list # Use the list we built
})