feat: allow interfaces to have no zone (shorewall '-' zone)

This commit is contained in:
2026-03-01 11:11:52 +01:00
parent 21d404229a
commit 58ef0dec63
5 changed files with 29 additions and 7 deletions

View File

@@ -0,0 +1,21 @@
"""make interface zone_id nullable
Revision ID: 0005
Revises: 0004
Create Date: 2026-03-01
"""
from alembic import op
import sqlalchemy as sa
revision = "0005"
down_revision = "0004"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.alter_column("interfaces", "zone_id", existing_type=sa.Integer(), nullable=True)
def downgrade() -> None:
op.alter_column("interfaces", "zone_id", existing_type=sa.Integer(), nullable=False)

View File

@@ -59,11 +59,11 @@ class Interface(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True) id: Mapped[int] = mapped_column(Integer, primary_key=True)
config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), nullable=False) config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), nullable=False)
name: Mapped[str] = mapped_column(String(32), nullable=False) name: Mapped[str] = mapped_column(String(32), nullable=False)
zone_id: Mapped[int] = mapped_column(Integer, ForeignKey("zones.id"), nullable=False) zone_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True)
options: Mapped[str] = mapped_column(Text, default="") options: Mapped[str] = mapped_column(Text, default="")
config: Mapped["Config"] = relationship("Config", back_populates="interfaces") config: Mapped["Config"] = relationship("Config", back_populates="interfaces")
zone: Mapped["Zone"] = relationship("Zone", back_populates="interfaces") zone: Mapped["Zone | None"] = relationship("Zone", back_populates="interfaces")
class Policy(Base): class Policy(Base):

View File

@@ -64,7 +64,7 @@ class ZoneOut(BaseModel):
# --- Interface --- # --- Interface ---
class InterfaceCreate(BaseModel): class InterfaceCreate(BaseModel):
name: str name: str
zone_id: int zone_id: Optional[int] = None
options: str = "" options: str = ""
@@ -78,7 +78,7 @@ class InterfaceOut(BaseModel):
id: int id: int
config_id: int config_id: int
name: str name: str
zone_id: int zone_id: Optional[int]
options: str options: str
model_config = {"from_attributes": True} model_config = {"from_attributes": True}

View File

@@ -28,7 +28,8 @@ class ShorewallGenerator:
def interfaces(self) -> str: def interfaces(self) -> str:
lines = [self._header("interfaces"), "#ZONE".ljust(16) + "INTERFACE".ljust(16) + "OPTIONS\n"] lines = [self._header("interfaces"), "#ZONE".ljust(16) + "INTERFACE".ljust(16) + "OPTIONS\n"]
for iface in self._config.interfaces: for iface in self._config.interfaces:
lines.append(self._col(iface.zone.name, iface.name, iface.options or "-")) zone = iface.zone.name if iface.zone else "-"
lines.append(self._col(zone, iface.name, iface.options or "-"))
return "".join(lines) return "".join(lines)
def policy(self) -> str: def policy(self) -> str:

View File

@@ -83,13 +83,13 @@ export default function ConfigDetail() {
{ {
key: 'zone_id' as const, key: 'zone_id' as const,
label: 'Zone', label: 'Zone',
render: (r: AnyEntity) => zones.find((z) => z.id === r['zone_id'])?.name ?? String(r['zone_id']), render: (r: AnyEntity) => r['zone_id'] == null ? '-' : (zones.find((z) => z.id === r['zone_id'])?.name ?? String(r['zone_id'])),
}, },
{ key: 'options' as const, label: 'Options' }, { key: 'options' as const, label: 'Options' },
] as Column<AnyEntity>[], ] as Column<AnyEntity>[],
fields: [ fields: [
{ name: 'name', label: 'Interface Name', required: true }, { name: 'name', label: 'Interface Name', required: true },
{ name: 'zone_id', label: 'Zone', required: true, type: 'select' as const, options: zoneOptions }, { name: 'zone_id', label: 'Zone', type: 'select' as const, options: [{ value: '', label: '- (no zone)' }, ...zoneOptions] },
{ name: 'options', label: 'Options' }, { name: 'options', label: 'Options' },
] as FieldDef[], ] as FieldDef[],
}, },