Skip to content

Commit ccb5d59

Browse files
committed
Refine bouncer status logic and cleanup UI
Update bouncer health logic to require a valid key and consider pulls within 60 minutes as "connected", mark valid-but-old as "stale", otherwise "disconnected" (applied to bouncers handler and health diagnostics). Clean up frontend: remove search context, shortcut and search input from Header, memoize breadcrumbs, remove location-driven scope handling and footer from Layout, move footer/copyright into Sidebar. Replace hardcoded sidebar version with import.meta.env.VITE_APP_VERSION and add Vite config to inject package.json version. Misc: adjust sidebar theme button sizing/layout and minor logging whitespace fix in DeleteBouncer.
1 parent e79414c commit ccb5d59

6 files changed

Lines changed: 32 additions & 90 deletions

File tree

internal/api/handlers/bouncers.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ func GetBouncers(dockerClient *docker.Client, cfg *config.Config) gin.HandlerFun
5050

5151
// Compute status for each bouncer
5252
for i := range bouncers {
53-
// Primary indicator: if last pull was recent (within 5 minutes), bouncer is connected
54-
if time.Since(bouncers[i].LastPull) <= 5*time.Minute {
53+
// Primary indicator: valid key + pulled within 60 minutes = connected
54+
if bouncers[i].Valid && time.Since(bouncers[i].LastPull) <= 60*time.Minute {
5555
bouncers[i].Status = "connected"
5656
} else if bouncers[i].Valid {
57-
// Last pull is old but key is valid - bouncer exists but inactive
57+
// Valid key but hasn't pulled recently - stale but registered
5858
bouncers[i].Status = "stale"
5959
} else {
60-
// Key is invalid - bouncer is disconnected
60+
// Key is invalid/revoked - bouncer is disconnected
6161
bouncers[i].Status = "disconnected"
6262
}
6363
}
@@ -161,7 +161,7 @@ func DeleteBouncer(dockerClient *docker.Client, cfg *config.Config) gin.HandlerF
161161
// Execute delete command
162162
cmd := []string{"cscli", "bouncers", "delete", name}
163163
output, err := dockerClient.ExecCommand(cfg.CrowdsecContainerName, cmd)
164-
164+
165165
// Log the output for debugging
166166
logger.Info("Delete command executed", "cmd", cmd, "output", output, "error", err)
167167

internal/api/handlers/health_diagnostics.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func parseBouncersJSON(bouncerOutput string, computeStatus bool) ([]models.Bounc
4545
}
4646

4747
if computeStatus {
48-
if time.Since(bouncer.LastPull) <= 5*time.Minute {
48+
if bouncer.Valid && time.Since(bouncer.LastPull) <= 60*time.Minute {
4949
bouncer.Status = "connected"
5050
} else if bouncer.Valid {
5151
bouncer.Status = "stale"
@@ -92,7 +92,7 @@ func checkBouncersHealth(dockerClient *docker.Client, containerName string) mode
9292

9393
activeBouncers := 0
9494
for _, b := range bouncers {
95-
if time.Since(b.LastPull) <= 5*time.Minute {
95+
if b.Valid && time.Since(b.LastPull) <= 60*time.Minute {
9696
activeBouncers++
9797
}
9898
}

web/src/layouts/Header.tsx

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef } from "react"
1+
import { useMemo } from "react"
22
import {
33
Breadcrumb,
44
BreadcrumbItem,
@@ -17,10 +17,9 @@ import {
1717
DropdownMenuSeparator,
1818
DropdownMenuTrigger,
1919
} from "@/components/ui/dropdown-menu"
20-
import { User, Settings, Github, Book, Server, Search, Menu, PanelLeftClose, PanelLeftOpen } from "lucide-react"
20+
import { User, Settings, Github, Book, Server, Menu, PanelLeftClose, PanelLeftOpen } from "lucide-react"
2121
import { Button } from "@/components/ui/button"
2222
import { Badge } from "@/components/ui/badge"
23-
import { Input } from "@/components/ui/input"
2423
import {
2524
Select,
2625
SelectContent,
@@ -32,7 +31,6 @@ import EnrollDialog from "@/components/dialogs/EnrollDialog"
3231
import { CrowdSecLogo } from "@/components/icons/CrowdSecLogo"
3332
import { useQuery } from "@tanstack/react-query"
3433
import { hostsAPI, setSelectedHost, type HostInfo } from "@/lib/api"
35-
import { useSearch } from "@/contexts/SearchContext"
3634

3735
interface HeaderProps {
3836
onMenuClick?: () => void
@@ -42,8 +40,6 @@ interface HeaderProps {
4240

4341
export default function Header({ onMenuClick, isCollapsed, onToggleCollapse }: HeaderProps) {
4442
const location = useLocation()
45-
const { query, scope, setQuery } = useSearch()
46-
const searchRef = useRef<HTMLInputElement>(null)
4743

4844
const { data: hostsData } = useQuery({
4945
queryKey: ['docker-hosts'],
@@ -56,7 +52,7 @@ export default function Header({ onMenuClick, isCollapsed, onToggleCollapse }: H
5652

5753
// Generate breadcrumbs from path
5854
const pathSegments = location.pathname.split('/').filter(Boolean)
59-
const breadcrumbs = [
55+
const breadcrumbs = useMemo(() => [
6056
{ name: 'Home', href: '/' },
6157
...pathSegments.map((segment, index) => {
6258
const href = `/${pathSegments.slice(0, index + 1).join('/')}`
@@ -65,31 +61,7 @@ export default function Header({ onMenuClick, isCollapsed, onToggleCollapse }: H
6561
href
6662
}
6763
})
68-
]
69-
70-
const scopeLabel = useMemo(() => {
71-
if (scope === 'hub') return 'Hub'
72-
if (scope === 'logs') return 'Logs'
73-
if (scope === 'scenarios') return 'Scenarios'
74-
if (scope === 'bouncers') return 'Bouncers'
75-
if (scope === 'alerts') return 'Alerts'
76-
if (scope === 'decisions') return 'Decisions'
77-
return 'Global'
78-
}, [scope])
79-
80-
useEffect(() => {
81-
const handler = (event: KeyboardEvent) => {
82-
if (event.key !== '/' || event.defaultPrevented) return
83-
const target = event.target as HTMLElement | null
84-
if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) {
85-
return
86-
}
87-
event.preventDefault()
88-
searchRef.current?.focus()
89-
}
90-
window.addEventListener('keydown', handler)
91-
return () => window.removeEventListener('keydown', handler)
92-
}, [])
64+
], [pathSegments])
9365

9466
return (
9567
<header className="h-16 border-b border-sidebar-border bg-card px-6 flex items-center justify-between shadow-sm">
@@ -145,21 +117,6 @@ export default function Header({ onMenuClick, isCollapsed, onToggleCollapse }: H
145117

146118
{/* Right Section: Links & User Profile */}
147119
<div className="flex items-center gap-4">
148-
<div className="hidden lg:flex items-center gap-2">
149-
<Badge variant="outline" className="text-[10px] uppercase tracking-wide">
150-
Searching: {scopeLabel}
151-
</Badge>
152-
<div className="relative">
153-
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
154-
<Input
155-
ref={searchRef}
156-
value={query}
157-
onChange={(event) => setQuery(event.target.value)}
158-
placeholder={`Search ${scopeLabel}...`}
159-
className="h-9 w-[240px] pl-9"
160-
/>
161-
</div>
162-
</div>
163120
{/* Host Selector */}
164121
{showHostSelector && (
165122
<div className="flex items-center gap-2">

web/src/layouts/Layout.tsx

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { ReactNode, useEffect, useState, useCallback } from 'react'
2-
import { useLocation } from 'react-router-dom'
1+
import { ReactNode, useState, useCallback } from 'react'
32
import Sidebar from './Sidebar'
43
import Header from './Header'
54
import { useConfigEvents } from '@/hooks/useConfigEvents'
6-
import { useSearch } from '@/contexts/SearchContext'
75

86
interface LayoutProps {
97
children: ReactNode
@@ -12,8 +10,6 @@ interface LayoutProps {
1210
export default function Layout({ children }: LayoutProps) {
1311
// Listen for config drift/missing events and show toast notifications
1412
useConfigEvents()
15-
const location = useLocation()
16-
const { setScope, clear } = useSearch()
1713
const [isCollapsed, setIsCollapsed] = useState(
1814
() => localStorage.getItem('sidebar-collapsed') === 'true'
1915
)
@@ -23,26 +19,6 @@ export default function Layout({ children }: LayoutProps) {
2319
setMobileOpen(false)
2420
}, [])
2521

26-
useEffect(() => {
27-
const path = location.pathname
28-
if (path.startsWith('/hub')) {
29-
setScope('hub')
30-
} else if (path.startsWith('/logs')) {
31-
setScope('logs')
32-
} else if (path.startsWith('/scenarios')) {
33-
setScope('scenarios')
34-
} else if (path.startsWith('/bouncers')) {
35-
setScope('bouncers')
36-
} else if (path.startsWith('/alerts')) {
37-
setScope('alerts')
38-
} else if (path.startsWith('/decisions')) {
39-
setScope('decisions')
40-
} else {
41-
setScope('global')
42-
}
43-
clear()
44-
}, [location.pathname, setScope, clear])
45-
4622
return (
4723
<div className="h-full bg-background overflow-hidden">
4824
{/* Mobile overlay */}
@@ -84,9 +60,6 @@ export default function Layout({ children }: LayoutProps) {
8460
{children}
8561
</div>
8662
</main>
87-
<footer className="border-t border-sidebar-border py-3 text-center text-xs text-muted-foreground bg-background shrink-0">
88-
<p>&copy; {new Date().getFullYear()} HHF Technology &middot; Powered by CrowdSec</p>
89-
</footer>
9063
</div>
9164
</div>
9265
</div>

web/src/layouts/Sidebar.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export default function Sidebar({ isCollapsed, setIsCollapsed: _setIsCollapsed,
135135
<div className="flex flex-col">
136136
<span>CrowdSec Manager</span>
137137
<Badge variant="secondary" className="text-[10px] px-1 py-0 h-5 mt-1 w-fit whitespace-nowrap">
138-
Beta-v0.0.6
138+
v{import.meta.env.VITE_APP_VERSION}
139139
</Badge>
140140
</div>
141141
</div>
@@ -209,26 +209,32 @@ export default function Sidebar({ isCollapsed, setIsCollapsed: _setIsCollapsed,
209209
</div>
210210
</ScrollArea>
211211

212-
{/* Footer with Theme Toggle and Collapse */}
213-
<div className="px-3 py-2">
212+
{/* Footer with Theme Toggle and Copyright */}
213+
<div className="px-3 py-2 shrink-0">
214214
<Separator className="mb-3 bg-sidebar-border" />
215-
<div className="space-y-1">
215+
<div className={cn("flex items-center", isCollapsed ? "justify-center" : "justify-between gap-2")}>
216216
<Button
217217
variant="ghost"
218-
size={isCollapsed ? "icon" : "default"}
218+
size={isCollapsed ? "icon" : "sm"}
219219
onClick={toggleTheme}
220220
className={cn(
221-
"w-full justify-start text-muted-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-foreground overflow-hidden",
222-
isCollapsed && "justify-center"
221+
"text-muted-foreground hover:bg-sidebar-accent/50 hover:text-sidebar-foreground shrink-0",
222+
isCollapsed ? "h-8 w-8" : "gap-2 px-2"
223223
)}
224224
>
225225
{theme === "dark" ? (
226226
<Sun className="h-4 w-4 shrink-0" />
227227
) : (
228228
<Moon className="h-4 w-4 shrink-0" />
229229
)}
230-
{!isCollapsed && <span className="ml-2 truncate">Toggle Theme</span>}
230+
{!isCollapsed && <span className="truncate">Toggle Theme</span>}
231231
</Button>
232+
{!isCollapsed && (
233+
<p className="text-[10px] text-muted-foreground text-right leading-tight shrink-0">
234+
&copy; {new Date().getFullYear()} HHF Technology<br />
235+
Powered by CrowdSec
236+
</p>
237+
)}
232238
</div>
233239
</div>
234240
</div>

web/vite.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { defineConfig } from 'vite'
22
import react from '@vitejs/plugin-react-swc'
33
import path from 'path'
4+
import { readFileSync } from 'fs'
5+
6+
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8'))
47

58
// https://vitejs.dev/config/
69
export default defineConfig({
710
plugins: [react()],
11+
define: {
12+
'import.meta.env.VITE_APP_VERSION': JSON.stringify(pkg.version),
13+
},
814
resolve: {
915
alias: {
1016
'@': path.resolve(__dirname, './src'),

0 commit comments

Comments
 (0)