Skip to content

Commit

Permalink
feat: add calculator with htmx
Browse files Browse the repository at this point in the history
  • Loading branch information
JacobCoffee committed Jul 20, 2023
1 parent 53a9849 commit 7fa0075
Show file tree
Hide file tree
Showing 7 changed files with 419 additions and 117 deletions.
3 changes: 2 additions & 1 deletion app/domain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from litestar.pagination import OffsetPagination
from pydantic import EmailStr

from . import system, urls, web
from . import calculator, system, urls, web

if TYPE_CHECKING:
from collections.abc import Mapping
Expand All @@ -26,6 +26,7 @@
routes: list[ControllerRouterHandler] = [
# system.controllers.system.SystemController,
web.controllers.web.WebController,
calculator.controllers.calculator.CalculatorController,
]
"""Routes for the application."""

Expand Down
69 changes: 68 additions & 1 deletion app/domain/calculator/controllers/calculator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,79 @@
"""Calculator Controller."""
from __future__ import annotations

from litestar import Controller
import ipaddress
from typing import TYPE_CHECKING, Annotated

from litestar import Controller, get

__all__ = ["CalculatorController"]

from litestar.params import Parameter # noqa
from litestar.status_codes import HTTP_200_OK
from litestar.contrib.htmx.response import HTMXTemplate

from app.domain import urls
from app.domain.calculator.schema import NetworkInfo

if TYPE_CHECKING:
from litestar.contrib.htmx.request import HTMXRequest


class CalculatorController(Controller):
"""Calculator Controller."""

opt = {"exclude_from_auth": True}

@get(
path=urls.IP,
operation_id="CalculatorIP",
name="calculator:ip",
status_code=HTTP_200_OK,
)
async def ip(
self,
request: HTMXRequest,
ip: Annotated[
str,
Parameter(
...,
description="The IP address in standard IPv4 format.",
pattern=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
),
],
prefix: Annotated[
str, Parameter(..., description="The CIDR notation.", pattern=r"^(?:[0-9]|[1-2][0-9]|3[0-2])$")
],
) -> HTMXTemplate:
"""Calculate IP."""

net = ipaddress.IPv4Network(f"{ip}/{prefix}", strict=False)

subnet_mask = str(net.netmask)
wildcard_subnet_mask = str(net.hostmask)
total_ips = net.num_addresses
usable_ips = total_ips - 2 # minus network and broadcast addresses
network_ip = str(net.network_address)
broadcast_ip = str(net.broadcast_address)

# First and last usable IP addresses
hosts = list(net.hosts())
first_ip = str(hosts[0]) if hosts else None
last_ip = str(hosts[-1]) if hosts else None

network_info = NetworkInfo(
subnet_mask=subnet_mask,
wildcard_subnet_mask=wildcard_subnet_mask,
total_ips=total_ips,
usable_ips=usable_ips,
network_ip=network_ip,
broadcast_ip=broadcast_ip,
first_ip=first_ip,
last_ip=last_ip,
)

return HTMXTemplate(
template_name="partial.html",
context=network_info.dict(), # Convert the pydantic model to a dict
push_url="/calculate",
)
48 changes: 42 additions & 6 deletions app/domain/calculator/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,62 @@

from pydantic import BaseModel, Field

__all__ = ("NetworkData",)
__all__ = ("NetworkData", "NetworkInfo")


class NetworkData(BaseModel):
"""Network Data Schema."""

ip: Annotated[
int,
str,
Field(
...,
description="The IP address in standard IPv4 format.",
regex=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
),
]

cidr: Annotated[
int,
prefix: Annotated[
str,
Field(
...,
description="The CIDR notation.",
regex=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$",
description="The Subnet Prefix.",
regex=r"^(3[012]|[12]?\d)$",
),
]


class NetworkInfo(BaseModel):
"""Network Info Schema."""

subnet_mask: Annotated[
str,
Field(
...,
description="The subnet mask in standard IPv4 format.",
regex=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
),
]
wildcard_subnet_mask: Annotated[
str,
Field(
...,
description="The wildcard subnet mask in standard IPv4 format.",
regex=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
),
]
total_ips: Annotated[int, Field(..., description="The total number of IPs in the network.")]
usable_ips: Annotated[int, Field(..., description="The total number of usable IPs in the network.")]
network_ip: Annotated[
str, Field(..., description="The network IP in standard IPv4 format.", regex=r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
]
broadcast_ip: Annotated[
str,
Field(..., description="The broadcast IP in standard IPv4 format.", regex=r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$"),
]
first_ip: Annotated[
str, Field(..., description="The first IP in standard IPv4 format.", regex=r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
]
last_ip: Annotated[
str, Field(..., description="The last IP in standard IPv4 format.", regex=r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
]
8 changes: 8 additions & 0 deletions app/domain/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from typing import Final

# --- System

INDEX: Final = "/"
"""Index URL."""
SITE_ROOT: Final = "/{path:str}"
Expand All @@ -11,3 +13,9 @@
"""OpenAPI schema URL."""
SYSTEM_HEALTH: Final = "/health"
"""System health URL."""

# --- API
CALCULATOR: Final = "/calculator"
"""Root Calculator Domain URL."""
IP: Final = f"{CALCULATOR}/ip"
"""IP Calculator URL."""
Loading

0 comments on commit 7fa0075

Please sign in to comment.