Compare commits

...

7 Commits

Author SHA1 Message Date
02c8f71957 feat: complete snat with all shorewall columns (proto, port, ipsec, mark, user, switch, origdest, probability)
All checks were successful
Build containers when image tags change / build-if-image-changed (frontend, shorefront-frontend, shorefront frontend, frontend/Dockerfile, git.baumann.gr/adebaumann/shorefront-frontend, .frontend.image) (push) Successful in 1m14s
Build containers when image tags change / build-if-image-changed (backend, shorefront-backend, shorefront backend, backend/Dockerfile, git.baumann.gr/adebaumann/shorefront-backend, .backend.image) (push) Successful in 2m2s
2026-03-01 11:28:25 +01:00
36224cebcd feat: complete rules with all shorewall columns (origdest, rate, user, mark, connlimit, time, headers, switch, helper) 2026-03-01 11:25:09 +01:00
3c259a1862 feat: add placeholder support to EntityForm FieldDef 2026-03-01 11:18:53 +01:00
e05e9d5975 feat: add limit:burst and connlimit:mask fields to policies 2026-03-01 11:18:26 +01:00
3dc97df6cd feat: allow 'all' for policy source and destination zones 2026-03-01 11:14:42 +01:00
8b787a99c2 feat: add broadcast field to interfaces 2026-03-01 11:13:13 +01:00
58ef0dec63 feat: allow interfaces to have no zone (shorewall '-' zone) 2026-03-01 11:11:52 +01:00
12 changed files with 349 additions and 36 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

@@ -0,0 +1,21 @@
"""add broadcast column to interfaces
Revision ID: 0006
Revises: 0005
Create Date: 2026-03-01
"""
from alembic import op
import sqlalchemy as sa
revision = "0006"
down_revision = "0005"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column("interfaces", sa.Column("broadcast", sa.String(64), server_default="''", nullable=False))
def downgrade() -> None:
op.drop_column("interfaces", "broadcast")

View 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)

View File

@@ -0,0 +1,23 @@
"""add limit_burst and connlimit_mask to policies
Revision ID: 0008
Revises: 0007
Create Date: 2026-03-01
"""
from alembic import op
import sqlalchemy as sa
revision = "0008"
down_revision = "0007"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column("policies", sa.Column("limit_burst", sa.String(64), server_default="''", nullable=False))
op.add_column("policies", sa.Column("connlimit_mask", sa.String(32), server_default="''", nullable=False))
def downgrade() -> None:
op.drop_column("policies", "connlimit_mask")
op.drop_column("policies", "limit_burst")

View File

@@ -0,0 +1,35 @@
"""add missing shorewall rule columns
Revision ID: 0009
Revises: 0008
Create Date: 2026-03-01
"""
from alembic import op
import sqlalchemy as sa
revision = "0009"
down_revision = "0008"
branch_labels = None
depends_on = None
_NEW_COLS = [
("origdest", sa.String(128)),
("rate_limit", sa.String(64)),
("user_group", sa.String(64)),
("mark", sa.String(32)),
("connlimit", sa.String(32)),
("time", sa.String(128)),
("headers", sa.String(128)),
("switch_name", sa.String(32)),
("helper", sa.String(32)),
]
def upgrade() -> None:
for col_name, col_type in _NEW_COLS:
op.add_column("rules", sa.Column(col_name, col_type, server_default="''", nullable=False))
def downgrade() -> None:
for col_name, _ in reversed(_NEW_COLS):
op.drop_column("rules", col_name)

View File

@@ -0,0 +1,34 @@
"""add missing shorewall snat columns
Revision ID: 0010
Revises: 0009
Create Date: 2026-03-01
"""
from alembic import op
import sqlalchemy as sa
revision = "0010"
down_revision = "0009"
branch_labels = None
depends_on = None
_NEW_COLS = [
("proto", sa.String(16)),
("port", sa.String(64)),
("ipsec", sa.String(128)),
("mark", sa.String(32)),
("user_group", sa.String(64)),
("switch_name", sa.String(32)),
("origdest", sa.String(128)),
("probability", sa.String(16)),
]
def upgrade() -> None:
for col_name, col_type in _NEW_COLS:
op.add_column("snat", sa.Column(col_name, col_type, server_default="''", nullable=False))
def downgrade() -> None:
for col_name, _ in reversed(_NEW_COLS):
op.drop_column("snat", col_name)

View File

@@ -59,11 +59,12 @@ class Interface(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
config_id: Mapped[int] = mapped_column(Integer, ForeignKey("configs.id"), 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)
broadcast: Mapped[str] = mapped_column(String(64), default="")
options: Mapped[str] = mapped_column(Text, default="")
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):
@@ -71,16 +72,18 @@ class Policy(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
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)
dst_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 | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True)
policy: Mapped[str] = mapped_column(String(16), nullable=False)
log_level: Mapped[str] = mapped_column(String(16), default="")
limit_burst: Mapped[str] = mapped_column(String(64), default="")
connlimit_mask: Mapped[str] = mapped_column(String(32), default="")
comment: Mapped[str] = mapped_column(Text, default="")
position: Mapped[int] = mapped_column(Integer, default=0)
config: Mapped["Config"] = relationship("Config", back_populates="policies")
src_zone: Mapped["Zone"] = relationship("Zone", foreign_keys=[src_zone_id])
dst_zone: Mapped["Zone"] = relationship("Zone", foreign_keys=[dst_zone_id])
src_zone: Mapped["Zone | None"] = relationship("Zone", foreign_keys=[src_zone_id])
dst_zone: Mapped["Zone | None"] = relationship("Zone", foreign_keys=[dst_zone_id])
class Rule(Base):
@@ -96,6 +99,15 @@ class Rule(Base):
proto: Mapped[str] = mapped_column(String(16), default="")
dport: Mapped[str] = mapped_column(String(64), default="")
sport: Mapped[str] = mapped_column(String(64), default="")
origdest: Mapped[str] = mapped_column(String(128), default="")
rate_limit: Mapped[str] = mapped_column(String(64), default="")
user_group: Mapped[str] = mapped_column(String(64), default="")
mark: Mapped[str] = mapped_column(String(32), default="")
connlimit: Mapped[str] = mapped_column(String(32), default="")
time: Mapped[str] = mapped_column(String(128), default="")
headers: Mapped[str] = mapped_column(String(128), default="")
switch_name: Mapped[str] = mapped_column(String(32), default="")
helper: Mapped[str] = mapped_column(String(32), default="")
comment: Mapped[str] = mapped_column(Text, default="")
position: Mapped[int] = mapped_column(Integer, default=0)
@@ -112,6 +124,14 @@ class Snat(Base):
source_network: Mapped[str] = mapped_column(String(64), nullable=False)
out_interface: Mapped[str] = mapped_column(String(32), nullable=False)
to_address: Mapped[str] = mapped_column(String(64), default="")
proto: Mapped[str] = mapped_column(String(16), default="")
port: Mapped[str] = mapped_column(String(64), default="")
ipsec: Mapped[str] = mapped_column(String(128), default="")
mark: Mapped[str] = mapped_column(String(32), default="")
user_group: Mapped[str] = mapped_column(String(64), default="")
switch_name: Mapped[str] = mapped_column(String(32), default="")
origdest: Mapped[str] = mapped_column(String(128), default="")
probability: Mapped[str] = mapped_column(String(16), default="")
comment: Mapped[str] = mapped_column(Text, default="")
config: Mapped["Config"] = relationship("Config", back_populates="snat_entries")

View File

@@ -64,13 +64,15 @@ class ZoneOut(BaseModel):
# --- Interface ---
class InterfaceCreate(BaseModel):
name: str
zone_id: int
zone_id: Optional[int] = None
broadcast: str = ""
options: str = ""
class InterfaceUpdate(BaseModel):
name: Optional[str] = None
zone_id: Optional[int] = None
broadcast: Optional[str] = None
options: Optional[str] = None
@@ -78,7 +80,8 @@ class InterfaceOut(BaseModel):
id: int
config_id: int
name: str
zone_id: int
zone_id: Optional[int]
broadcast: str
options: str
model_config = {"from_attributes": True}
@@ -86,10 +89,12 @@ class InterfaceOut(BaseModel):
# --- Policy ---
class PolicyCreate(BaseModel):
src_zone_id: int
dst_zone_id: int
src_zone_id: Optional[int] = None
dst_zone_id: Optional[int] = None
policy: str
log_level: str = ""
limit_burst: str = ""
connlimit_mask: str = ""
comment: str = ""
position: int = 0
@@ -99,6 +104,8 @@ class PolicyUpdate(BaseModel):
dst_zone_id: Optional[int] = None
policy: Optional[str] = None
log_level: Optional[str] = None
limit_burst: Optional[str] = None
connlimit_mask: Optional[str] = None
comment: Optional[str] = None
position: Optional[int] = None
@@ -106,10 +113,12 @@ class PolicyUpdate(BaseModel):
class PolicyOut(BaseModel):
id: int
config_id: int
src_zone_id: int
dst_zone_id: int
src_zone_id: Optional[int]
dst_zone_id: Optional[int]
policy: str
log_level: str
limit_burst: str
connlimit_mask: str
comment: str
position: int
@@ -126,6 +135,15 @@ class RuleCreate(BaseModel):
proto: str = ""
dport: str = ""
sport: str = ""
origdest: str = ""
rate_limit: str = ""
user_group: str = ""
mark: str = ""
connlimit: str = ""
time: str = ""
headers: str = ""
switch_name: str = ""
helper: str = ""
comment: str = ""
position: int = 0
@@ -139,6 +157,15 @@ class RuleUpdate(BaseModel):
proto: Optional[str] = None
dport: Optional[str] = None
sport: Optional[str] = None
origdest: Optional[str] = None
rate_limit: Optional[str] = None
user_group: Optional[str] = None
mark: Optional[str] = None
connlimit: Optional[str] = None
time: Optional[str] = None
headers: Optional[str] = None
switch_name: Optional[str] = None
helper: Optional[str] = None
comment: Optional[str] = None
position: Optional[int] = None
@@ -154,6 +181,15 @@ class RuleOut(BaseModel):
proto: str
dport: str
sport: str
origdest: str
rate_limit: str
user_group: str
mark: str
connlimit: str
time: str
headers: str
switch_name: str
helper: str
comment: str
position: int
@@ -165,6 +201,14 @@ class SnatCreate(BaseModel):
source_network: str
out_interface: str
to_address: str = ""
proto: str = ""
port: str = ""
ipsec: str = ""
mark: str = ""
user_group: str = ""
switch_name: str = ""
origdest: str = ""
probability: str = ""
comment: str = ""
@@ -172,6 +216,14 @@ class SnatUpdate(BaseModel):
source_network: Optional[str] = None
out_interface: Optional[str] = None
to_address: Optional[str] = None
proto: Optional[str] = None
port: Optional[str] = None
ipsec: Optional[str] = None
mark: Optional[str] = None
user_group: Optional[str] = None
switch_name: Optional[str] = None
origdest: Optional[str] = None
probability: Optional[str] = None
comment: Optional[str] = None
@@ -181,6 +233,14 @@ class SnatOut(BaseModel):
source_network: str
out_interface: str
to_address: str
proto: str
port: str
ipsec: str
mark: str
user_group: str
switch_name: str
origdest: str
probability: str
comment: str
model_config = {"from_attributes": True}

View File

@@ -26,27 +26,51 @@ class ShorewallGenerator:
return "".join(lines)
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) + "BROADCAST".ljust(16) + "OPTIONS\n"]
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.broadcast or "-", iface.options or "-"))
return "".join(lines)
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".ljust(16) + "LIMIT:BURST".ljust(20) + "CONNLIMIT:MASK\n",
]
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 "-",
p.limit_burst or "-",
p.connlimit_mask or "-",
width=16,
))
return "".join(lines)
def rules(self) -> str:
lines = [
self._header("rules"),
"#ACTION".ljust(16) + "SOURCE".ljust(24) + "DEST".ljust(24) + "PROTO".ljust(10) + "DPORT".ljust(10) + "SPORT\n",
"#ACTION".ljust(16) + "SOURCE".ljust(24) + "DEST".ljust(24)
+ "PROTO".ljust(10) + "DPORT".ljust(16) + "SPORT".ljust(16)
+ "ORIGDEST".ljust(20) + "RATE".ljust(16) + "USER".ljust(16)
+ "MARK".ljust(12) + "CONNLIMIT".ljust(14) + "TIME".ljust(20)
+ "HEADERS".ljust(16) + "SWITCH".ljust(16) + "HELPER\n",
"SECTION NEW\n",
]
for r in sorted(self._config.rules, key=lambda x: x.position):
src = (r.src_zone.name if r.src_zone else "all") + (f":{r.src_ip}" if r.src_ip else "")
dst = (r.dst_zone.name if r.dst_zone else "all") + (f":{r.dst_ip}" if r.dst_ip else "")
lines.append(self._col(r.action, src, dst, r.proto or "-", r.dport or "-", r.sport or "-", width=16))
lines.append(self._col(
r.action, src, dst,
r.proto or "-", r.dport or "-", r.sport or "-",
r.origdest or "-", r.rate_limit or "-", r.user_group or "-",
r.mark or "-", r.connlimit or "-", r.time or "-",
r.headers or "-", r.switch_name or "-", r.helper or "-",
width=16,
))
return "".join(lines)
def hosts(self) -> str:
@@ -63,10 +87,22 @@ class ShorewallGenerator:
return "".join(lines)
def snat(self) -> str:
lines = [self._header("snat"), "#ACTION".ljust(24) + "SOURCE".ljust(24) + "DEST\n"]
lines = [
self._header("snat"),
"#ACTION".ljust(24) + "SOURCE".ljust(24) + "DEST".ljust(20)
+ "PROTO".ljust(10) + "PORT".ljust(16) + "IPSEC".ljust(16)
+ "MARK".ljust(12) + "USER/GROUP".ljust(16) + "SWITCH".ljust(16)
+ "ORIGDEST".ljust(20) + "PROBABILITY\n",
]
for m in self._config.snat_entries:
action = f"SNAT:{m.to_address}" if m.to_address else "MASQUERADE"
lines.append(self._col(action, m.source_network, m.out_interface, width=24))
lines.append(self._col(
action, m.source_network, m.out_interface,
m.proto or "-", m.port or "-", m.ipsec or "-",
m.mark or "-", m.user_group or "-", m.switch_name or "-",
m.origdest or "-", m.probability or "-",
width=16,
))
return "".join(lines)
def as_json(self) -> dict:

View File

@@ -14,6 +14,7 @@ export interface FieldDef {
required?: boolean
type?: 'text' | 'select' | 'number'
options?: { value: string | number; label: string }[]
placeholder?: string
}
interface Props {
@@ -67,6 +68,7 @@ export default function EntityForm({ open, title, fields, initialValues, onClose
value={values[f.name] ?? ''}
onChange={(e) => handleChange(f.name, e.target.value)}
size="small"
placeholder={f.placeholder}
/>
)
)}

View File

@@ -18,8 +18,8 @@ import { zonesApi, interfacesApi, policiesApi, rulesApi, snatApi, hostsApi, para
interface Zone { id: number; name: string; type: string; options: string }
interface Iface { id: number; name: string; zone_id: number; options: string }
interface Policy { id: number; src_zone_id: number; dst_zone_id: number; policy: string; log_level: string; comment: string; position: number }
interface Rule { id: number; action: string; src_zone_id: number | null; dst_zone_id: number | null; src_ip: string; dst_ip: string; proto: string; dport: string; sport: string; comment: string; position: number }
interface Snat { id: number; source_network: string; out_interface: string; to_address: string; comment: string }
interface Rule { id: number; action: string; src_zone_id: number | null; dst_zone_id: number | null; src_ip: string; dst_ip: string; proto: string; dport: string; sport: string; origdest: string; rate_limit: string; user_group: string; mark: string; connlimit: string; time: string; headers: string; switch_name: string; helper: string; comment: string; position: number }
interface Snat { id: number; source_network: string; out_interface: string; to_address: string; proto: string; port: string; ipsec: string; mark: string; user_group: string; switch_name: string; origdest: string; probability: string; comment: string }
interface Host { id: number; zone_id: number; interface: string; subnet: string; options: string }
interface Param { id: number; name: string; value: string }
@@ -83,13 +83,15 @@ export default function ConfigDetail() {
{
key: 'zone_id' as const,
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: 'broadcast' as const, label: 'Broadcast' },
{ key: 'options' as const, label: 'Options' },
] as Column<AnyEntity>[],
fields: [
{ 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: 'broadcast', label: 'Broadcast' },
{ name: 'options', label: 'Options' },
] as FieldDef[],
},
@@ -102,22 +104,26 @@ export default function ConfigDetail() {
{
key: 'src_zone_id' as const,
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,
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: 'log_level' as const, label: 'Log Level' },
{ key: 'limit_burst' as const, label: 'Limit:Burst' },
{ key: 'connlimit_mask' as const, label: 'ConnLimit:Mask' },
{ key: 'position' as const, label: 'Position' },
] as Column<AnyEntity>[],
fields: [
{ name: 'src_zone_id', label: 'Source Zone', required: true, type: 'select' as const, options: zoneOptions },
{ name: 'dst_zone_id', label: 'Destination 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', 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: 'log_level', label: 'Log Level' },
{ name: 'limit_burst', label: 'Limit:Burst', placeholder: 'e.g. 10/sec:20' },
{ name: 'connlimit_mask', label: 'ConnLimit:Mask', placeholder: 'e.g. 10:24' },
{ name: 'comment', label: 'Comment' },
{ name: 'position', label: 'Position', type: 'number' as const },
] as FieldDef[],
@@ -141,17 +147,40 @@ export default function ConfigDetail() {
},
{ key: 'proto' as const, label: 'Proto' },
{ key: 'dport' as const, label: 'DPort' },
{ key: 'origdest' as const, label: 'OrigDest' },
{ key: 'position' as const, label: 'Position' },
] as Column<AnyEntity>[],
fields: [
{ name: 'action', label: 'Action', required: true },
{ name: 'src_zone_id', label: 'Source Zone', type: 'select' as const, options: zoneOptions },
{ name: 'dst_zone_id', label: 'Dest Zone', 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: 'Dest Zone', type: 'select' as const, options: [{ value: '', label: 'all' }, ...zoneOptions] },
{ name: 'src_ip', label: 'Source IP/CIDR' },
{ name: 'dst_ip', label: 'Dest IP/CIDR' },
{ name: 'proto', label: 'Protocol' },
{ name: 'dport', label: 'Dest Port' },
{ name: 'sport', label: 'Source Port' },
{ name: 'proto', label: 'Protocol', placeholder: 'e.g. tcp, udp, icmp' },
{ name: 'dport', label: 'Dest Port(s)' },
{ name: 'sport', label: 'Source Port(s)' },
{ name: 'origdest', label: 'Original Dest', placeholder: 'e.g. 192.168.1.1' },
{ name: 'rate_limit', label: 'Rate Limit', placeholder: 'e.g. 10/sec:20' },
{ name: 'user_group', label: 'User/Group', placeholder: 'e.g. joe:wheel' },
{ name: 'mark', label: 'Mark', placeholder: 'e.g. 0x100/0xff0' },
{ name: 'connlimit', label: 'ConnLimit', placeholder: 'e.g. 10:24' },
{ name: 'time', label: 'Time', placeholder: 'e.g. timestart=09:00&timestop=17:00' },
{ name: 'headers', label: 'Headers (IPv6)', placeholder: 'e.g. auth,esp' },
{ name: 'switch_name', label: 'Switch', placeholder: 'e.g. vpn_enabled' },
{ name: 'helper', label: 'Helper', type: 'select' as const, options: [
{ value: '', label: '(none)' },
{ value: 'amanda', label: 'amanda' },
{ value: 'ftp', label: 'ftp' },
{ value: 'irc', label: 'irc' },
{ value: 'netbios-ns', label: 'netbios-ns' },
{ value: 'pptp', label: 'pptp' },
{ value: 'Q.931', label: 'Q.931' },
{ value: 'RAS', label: 'RAS' },
{ value: 'sane', label: 'sane' },
{ value: 'sip', label: 'sip' },
{ value: 'snmp', label: 'snmp' },
{ value: 'tftp', label: 'tftp' },
]},
{ name: 'comment', label: 'Comment' },
{ name: 'position', label: 'Position', type: 'number' as const },
] as FieldDef[],
@@ -165,12 +194,21 @@ export default function ConfigDetail() {
{ key: 'out_interface' as const, label: 'Out Interface' },
{ key: 'source_network' as const, label: 'Source Network' },
{ key: 'to_address' as const, label: 'To Address' },
{ key: 'comment' as const, label: 'Comment' },
{ key: 'proto' as const, label: 'Proto' },
{ key: 'probability' as const, label: 'Probability' },
] as Column<AnyEntity>[],
fields: [
{ name: 'out_interface', label: 'Out Interface', required: true },
{ name: 'source_network', label: 'Source Network', required: true },
{ name: 'to_address', label: 'To Address' },
{ name: 'to_address', label: 'To Address (blank = MASQUERADE)', placeholder: 'e.g. 1.2.3.4' },
{ name: 'proto', label: 'Protocol', placeholder: 'e.g. tcp, udp' },
{ name: 'port', label: 'Port', placeholder: 'e.g. 80, 1024:65535' },
{ name: 'ipsec', label: 'IPsec', placeholder: 'e.g. mode=tunnel' },
{ name: 'mark', label: 'Mark', placeholder: 'e.g. 0x100/0xff0' },
{ name: 'user_group', label: 'User/Group', placeholder: 'e.g. joe:wheel' },
{ name: 'switch_name', label: 'Switch', placeholder: 'e.g. vpn_enabled' },
{ name: 'origdest', label: 'Orig Dest', placeholder: 'e.g. 1.2.3.4' },
{ name: 'probability', label: 'Probability', placeholder: 'e.g. 0.25' },
{ name: 'comment', label: 'Comment' },
] as FieldDef[],
},

View File

@@ -42,4 +42,4 @@ keycloak:
redirectUri: https://shorefront.baumann.gr/api/auth/oidc/callback
containers:
version: "0.008"
version: "0.009"