Compare commits

...

3 Commits

Author SHA1 Message Date
d56075a74e feat: expose app version from ConfigMap in sidebar footer
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 59s
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 1m27s
2026-03-01 11:51:30 +01:00
9382106e8d fix: reset generated files cache when modal closes so reopening fetches fresh data 2026-03-01 11:45:47 +01:00
390774c79a feat: default interface broadcast to 'detect' 2026-03-01 11:43:58 +01:00
10 changed files with 60 additions and 7 deletions

View File

@@ -0,0 +1,33 @@
"""change interface broadcast default to detect
Revision ID: 0011
Revises: 0010
Create Date: 2026-03-01
"""
from alembic import op
import sqlalchemy as sa
revision = "0011"
down_revision = "0010"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.alter_column(
"interfaces", "broadcast",
existing_type=sa.String(64),
server_default="detect",
nullable=False,
)
op.execute("UPDATE interfaces SET broadcast = 'detect' WHERE broadcast = ''")
def downgrade() -> None:
op.execute("UPDATE interfaces SET broadcast = '' WHERE broadcast = 'detect'")
op.alter_column(
"interfaces", "broadcast",
existing_type=sa.String(64),
server_default="''",
nullable=False,
)

View File

@@ -13,6 +13,7 @@ class Settings(BaseSettings):
keycloak_client_id: str keycloak_client_id: str
keycloak_client_secret: str keycloak_client_secret: str
keycloak_redirect_uri: str keycloak_redirect_uri: str
app_version: str = "dev"
class Config: class Config:
env_file = ".env" env_file = ".env"

View File

@@ -28,4 +28,4 @@ app.include_router(params.router, prefix="/configs", tags=["params"])
@app.get("/health") @app.get("/health")
def health() -> dict: def health() -> dict:
return {"status": "ok"} return {"status": "ok", "version": settings.app_version}

View File

@@ -60,7 +60,7 @@ class Interface(Base):
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 | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True) zone_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True)
broadcast: Mapped[str] = mapped_column(String(64), default="") broadcast: Mapped[str] = mapped_column(String(64), default="detect")
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")

View File

@@ -65,7 +65,7 @@ class ZoneOut(BaseModel):
class InterfaceCreate(BaseModel): class InterfaceCreate(BaseModel):
name: str name: str
zone_id: Optional[int] = None zone_id: Optional[int] = None
broadcast: str = "" broadcast: str = "detect"
options: str = "" options: str = ""

View File

@@ -1,4 +1,4 @@
import { useState } from 'react' import { useState, useEffect } from 'react'
import Dialog from '@mui/material/Dialog' import Dialog from '@mui/material/Dialog'
import DialogTitle from '@mui/material/DialogTitle' import DialogTitle from '@mui/material/DialogTitle'
import DialogContent from '@mui/material/DialogContent' import DialogContent from '@mui/material/DialogContent'
@@ -37,6 +37,10 @@ export default function GenerateModal({ open, configId, configName, onClose }: P
const [files, setFiles] = useState<GeneratedFiles | null>(null) const [files, setFiles] = useState<GeneratedFiles | null>(null)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
useEffect(() => {
if (!open) setFiles(null)
}, [open])
const handleOpen = async () => { const handleOpen = async () => {
if (files) return if (files) return
setLoading(true) setLoading(true)

View File

@@ -1,4 +1,4 @@
import { ReactNode } from 'react' import { ReactNode, useEffect, useState } from 'react'
import { useNavigate, useLocation } from 'react-router-dom' import { useNavigate, useLocation } from 'react-router-dom'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import Drawer from '@mui/material/Drawer' import Drawer from '@mui/material/Drawer'
@@ -13,6 +13,7 @@ import Tooltip from '@mui/material/Tooltip'
import DnsIcon from '@mui/icons-material/Dns' import DnsIcon from '@mui/icons-material/Dns'
import LogoutIcon from '@mui/icons-material/Logout' import LogoutIcon from '@mui/icons-material/Logout'
import { useAuth } from '../store/auth' import { useAuth } from '../store/auth'
import api from '../api'
const DRAWER_WIDTH = 240 const DRAWER_WIDTH = 240
@@ -22,6 +23,11 @@ export default function Layout({ children, title }: Props) {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const { user, logout } = useAuth() const { user, logout } = useAuth()
const [version, setVersion] = useState<string | null>(null)
useEffect(() => {
api.get('/health').then((r) => setVersion(r.data.version)).catch(() => {})
}, [])
return ( return (
<Box sx={{ display: 'flex', minHeight: '100vh' }}> <Box sx={{ display: 'flex', minHeight: '100vh' }}>
@@ -47,7 +53,10 @@ export default function Layout({ children, title }: Props) {
</List> </List>
<Divider sx={{ borderColor: '#2d3748' }} /> <Divider sx={{ borderColor: '#2d3748' }} />
<Box sx={{ px: 2, py: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <Box sx={{ px: 2, py: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="caption" sx={{ color: '#94a3b8' }}>{user?.username}</Typography> <Box>
<Typography variant="caption" sx={{ color: '#94a3b8', display: 'block' }}>{user?.username}</Typography>
{version && <Typography variant="caption" sx={{ color: '#4a5568', fontSize: 10 }}>v{version}</Typography>}
</Box>
<Tooltip title="Logout"> <Tooltip title="Logout">
<IconButton onClick={logout} size="small" sx={{ color: '#94a3b8' }}><LogoutIcon fontSize="small" /></IconButton> <IconButton onClick={logout} size="small" sx={{ color: '#94a3b8' }}><LogoutIcon fontSize="small" /></IconButton>
</Tooltip> </Tooltip>

View File

@@ -110,6 +110,11 @@ spec:
secretKeyRef: secretKeyRef:
name: shorefront-secret name: shorefront-secret
key: KEYCLOAK_CLIENT_SECRET key: KEYCLOAK_CLIENT_SECRET
- name: APP_VERSION
valueFrom:
configMapKeyRef:
name: shorefront-config
key: APP_VERSION
ports: ports:
- containerPort: 8000 - containerPort: 8000
resources: resources:

View File

@@ -14,3 +14,4 @@ data:
KEYCLOAK_REALM: {{ .Values.keycloak.realm | quote }} KEYCLOAK_REALM: {{ .Values.keycloak.realm | quote }}
KEYCLOAK_CLIENT_ID: {{ .Values.keycloak.clientId | quote }} KEYCLOAK_CLIENT_ID: {{ .Values.keycloak.clientId | quote }}
KEYCLOAK_REDIRECT_URI: {{ .Values.keycloak.redirectUri | quote }} KEYCLOAK_REDIRECT_URI: {{ .Values.keycloak.redirectUri | quote }}
APP_VERSION: {{ .Values.containers.version | quote }}

View File

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