feat: allow 'all' for policy source and destination zones
This commit is contained in:
23
backend/alembic/versions/0007_policy_zones_nullable.py
Normal file
23
backend/alembic/versions/0007_policy_zones_nullable.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"""make policy zone ids nullable (support 'all')
|
||||||
|
|
||||||
|
Revision ID: 0007
|
||||||
|
Revises: 0006
|
||||||
|
Create Date: 2026-03-01
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
revision = "0007"
|
||||||
|
down_revision = "0006"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.alter_column("policies", "src_zone_id", existing_type=sa.Integer(), nullable=True)
|
||||||
|
op.alter_column("policies", "dst_zone_id", existing_type=sa.Integer(), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.alter_column("policies", "src_zone_id", existing_type=sa.Integer(), nullable=False)
|
||||||
|
op.alter_column("policies", "dst_zone_id", existing_type=sa.Integer(), nullable=False)
|
||||||
@@ -72,16 +72,16 @@ class Policy(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)
|
||||||
src_zone_id: Mapped[int] = mapped_column(Integer, ForeignKey("zones.id"), nullable=False)
|
src_zone_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True)
|
||||||
dst_zone_id: Mapped[int] = mapped_column(Integer, ForeignKey("zones.id"), nullable=False)
|
dst_zone_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True)
|
||||||
policy: Mapped[str] = mapped_column(String(16), nullable=False)
|
policy: Mapped[str] = mapped_column(String(16), nullable=False)
|
||||||
log_level: Mapped[str] = mapped_column(String(16), default="")
|
log_level: Mapped[str] = mapped_column(String(16), default="")
|
||||||
comment: Mapped[str] = mapped_column(Text, default="")
|
comment: Mapped[str] = mapped_column(Text, default="")
|
||||||
position: Mapped[int] = mapped_column(Integer, default=0)
|
position: Mapped[int] = mapped_column(Integer, default=0)
|
||||||
|
|
||||||
config: Mapped["Config"] = relationship("Config", back_populates="policies")
|
config: Mapped["Config"] = relationship("Config", back_populates="policies")
|
||||||
src_zone: Mapped["Zone"] = relationship("Zone", foreign_keys=[src_zone_id])
|
src_zone: Mapped["Zone | None"] = relationship("Zone", foreign_keys=[src_zone_id])
|
||||||
dst_zone: Mapped["Zone"] = relationship("Zone", foreign_keys=[dst_zone_id])
|
dst_zone: Mapped["Zone | None"] = relationship("Zone", foreign_keys=[dst_zone_id])
|
||||||
|
|
||||||
|
|
||||||
class Rule(Base):
|
class Rule(Base):
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ class InterfaceOut(BaseModel):
|
|||||||
|
|
||||||
# --- Policy ---
|
# --- Policy ---
|
||||||
class PolicyCreate(BaseModel):
|
class PolicyCreate(BaseModel):
|
||||||
src_zone_id: int
|
src_zone_id: Optional[int] = None
|
||||||
dst_zone_id: int
|
dst_zone_id: Optional[int] = None
|
||||||
policy: str
|
policy: str
|
||||||
log_level: str = ""
|
log_level: str = ""
|
||||||
comment: str = ""
|
comment: str = ""
|
||||||
@@ -109,8 +109,8 @@ class PolicyUpdate(BaseModel):
|
|||||||
class PolicyOut(BaseModel):
|
class PolicyOut(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
config_id: int
|
config_id: int
|
||||||
src_zone_id: int
|
src_zone_id: Optional[int]
|
||||||
dst_zone_id: int
|
dst_zone_id: Optional[int]
|
||||||
policy: str
|
policy: str
|
||||||
log_level: str
|
log_level: str
|
||||||
comment: str
|
comment: str
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ class ShorewallGenerator:
|
|||||||
def policy(self) -> str:
|
def policy(self) -> str:
|
||||||
lines = [self._header("policy"), "#SOURCE".ljust(16) + "DEST".ljust(16) + "POLICY".ljust(16) + "LOG LEVEL\n"]
|
lines = [self._header("policy"), "#SOURCE".ljust(16) + "DEST".ljust(16) + "POLICY".ljust(16) + "LOG LEVEL\n"]
|
||||||
for p in sorted(self._config.policies, key=lambda x: x.position):
|
for p in sorted(self._config.policies, key=lambda x: x.position):
|
||||||
lines.append(self._col(p.src_zone.name, p.dst_zone.name, p.policy, p.log_level or "-"))
|
src = p.src_zone.name if p.src_zone else "all"
|
||||||
|
dst = p.dst_zone.name if p.dst_zone else "all"
|
||||||
|
lines.append(self._col(src, dst, p.policy, p.log_level or "-"))
|
||||||
return "".join(lines)
|
return "".join(lines)
|
||||||
|
|
||||||
def rules(self) -> str:
|
def rules(self) -> str:
|
||||||
|
|||||||
@@ -104,20 +104,20 @@ export default function ConfigDetail() {
|
|||||||
{
|
{
|
||||||
key: 'src_zone_id' as const,
|
key: 'src_zone_id' as const,
|
||||||
label: 'Source',
|
label: 'Source',
|
||||||
render: (r: AnyEntity) => zones.find((z) => z.id === r['src_zone_id'])?.name ?? String(r['src_zone_id']),
|
render: (r: AnyEntity) => r['src_zone_id'] == null ? 'all' : (zones.find((z) => z.id === r['src_zone_id'])?.name ?? String(r['src_zone_id'])),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'dst_zone_id' as const,
|
key: 'dst_zone_id' as const,
|
||||||
label: 'Destination',
|
label: 'Destination',
|
||||||
render: (r: AnyEntity) => zones.find((z) => z.id === r['dst_zone_id'])?.name ?? String(r['dst_zone_id']),
|
render: (r: AnyEntity) => r['dst_zone_id'] == null ? 'all' : (zones.find((z) => z.id === r['dst_zone_id'])?.name ?? String(r['dst_zone_id'])),
|
||||||
},
|
},
|
||||||
{ key: 'policy' as const, label: 'Policy' },
|
{ key: 'policy' as const, label: 'Policy' },
|
||||||
{ key: 'log_level' as const, label: 'Log Level' },
|
{ key: 'log_level' as const, label: 'Log Level' },
|
||||||
{ key: 'position' as const, label: 'Position' },
|
{ key: 'position' as const, label: 'Position' },
|
||||||
] as Column<AnyEntity>[],
|
] as Column<AnyEntity>[],
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'src_zone_id', label: 'Source Zone', required: true, type: 'select' as const, options: zoneOptions },
|
{ name: 'src_zone_id', label: 'Source Zone', type: 'select' as const, options: [{ value: '', label: 'all' }, ...zoneOptions] },
|
||||||
{ name: 'dst_zone_id', label: 'Destination Zone', required: true, type: 'select' as const, options: zoneOptions },
|
{ name: 'dst_zone_id', label: 'Destination Zone', type: 'select' as const, options: [{ value: '', label: 'all' }, ...zoneOptions] },
|
||||||
{ name: 'policy', label: 'Policy', required: true, type: 'select' as const, options: [{ value: 'ACCEPT', label: 'ACCEPT' }, { value: 'DROP', label: 'DROP' }, { value: 'REJECT', label: 'REJECT' }, { value: 'CONTINUE', label: 'CONTINUE' }] },
|
{ name: 'policy', label: 'Policy', required: true, type: 'select' as const, options: [{ value: 'ACCEPT', label: 'ACCEPT' }, { value: 'DROP', label: 'DROP' }, { value: 'REJECT', label: 'REJECT' }, { value: 'CONTINUE', label: 'CONTINUE' }] },
|
||||||
{ name: 'log_level', label: 'Log Level' },
|
{ name: 'log_level', label: 'Log Level' },
|
||||||
{ name: 'comment', label: 'Comment' },
|
{ name: 'comment', label: 'Comment' },
|
||||||
|
|||||||
Reference in New Issue
Block a user