This commit is contained in:
2025-11-11 15:51:54 +00:00
parent 99ae905cb0
commit 3e0c5d2ecc
114 changed files with 9278 additions and 929 deletions

View File

@@ -0,0 +1,3 @@
# empty

29
app/apps/servers/admin.py Normal file
View File

@@ -0,0 +1,29 @@
from django.contrib import admin
from django.utils.html import format_html
from .models import Server
@admin.register(Server)
class ServerAdmin(admin.ModelAdmin):
list_display = ("avatar", "display_name", "hostname", "ipv4", "ipv6", "created_at")
list_display_links = ("display_name",)
search_fields = ("display_name", "hostname", "ipv4", "ipv6")
list_filter = ("created_at",)
readonly_fields = ("created_at", "updated_at")
fields = ("display_name", "hostname", "ipv4", "ipv6", "image", "created_at", "updated_at")
def avatar(self, obj: Server):
if obj.image_url:
return format_html(
'<img src="{}" alt="{}" style="width:28px;height:28px;border-radius:6px;object-fit:cover;" />',
obj.image_url,
obj.display_name,
)
initial = obj.initial
return format_html(
'<div style="width:28px;height:28px;border-radius:6px;background:#7C3AED;color:white;display:flex;align-items:center;justify-content:center;font-weight:600;">{}</div>',
initial,
)
avatar.short_description = ""

9
app/apps/servers/apps.py Normal file
View File

@@ -0,0 +1,9 @@
from django.apps import AppConfig
class ServersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.servers"
verbose_name = "Servers"

View File

@@ -0,0 +1,45 @@
from django.db import migrations, models
import django.core.validators
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")),
("display_name", models.CharField(max_length=128)),
(
"hostname",
models.CharField(
blank=True,
max_length=253,
null=True,
unique=True,
validators=[
django.core.validators.RegexValidator(
message="Enter a valid hostname.",
regex="^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*\\.?$",
)
],
),
),
("ipv4", models.GenericIPAddressField(blank=True, null=True, protocol="IPv4", unique=True)),
("ipv6", models.GenericIPAddressField(blank=True, null=True, protocol="IPv6", unique=True)),
("image", models.ImageField(blank=True, null=True, upload_to="servers/")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"verbose_name": "Server",
"verbose_name_plural": "Servers",
"ordering": ["display_name", "hostname", "ipv4", "ipv6"],
},
),
]

View File

@@ -0,0 +1,3 @@
# empty

View File

@@ -0,0 +1,43 @@
from __future__ import annotations
from django.core.validators import RegexValidator
from django.db import models
from django.utils.text import slugify
hostname_validator = RegexValidator(
regex=r"^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*\.?$",
message="Enter a valid hostname.",
)
class Server(models.Model):
display_name = models.CharField(max_length=128)
hostname = models.CharField(max_length=253, null=True, blank=True, unique=True, validators=[hostname_validator])
ipv4 = models.GenericIPAddressField(null=True, blank=True, protocol="IPv4", unique=True)
ipv6 = models.GenericIPAddressField(null=True, blank=True, protocol="IPv6", unique=True)
image = models.ImageField(upload_to="servers/", null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["display_name", "hostname", "ipv4", "ipv6"]
verbose_name = "Server"
verbose_name_plural = "Servers"
def __str__(self) -> str:
primary = self.hostname or self.ipv4 or self.ipv6 or "unassigned"
return f"{self.display_name} ({primary})"
@property
def image_url(self) -> str | None:
try:
return self.image.url if self.image else None
except Exception:
return None
@property
def initial(self) -> str:
return (self.display_name or "?").strip()[:1].upper() or "?"