Skip to content

Commit fa6075c

Browse files
Merge pull request #70 from hhftechnology/pangolin
Pangolin
2 parents 1aad7ec + 0d43416 commit fa6075c

3 files changed

Lines changed: 59 additions & 14 deletions

File tree

web/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "crowdsec-manager-ui",
3-
"version": "2.1.0",
3+
"version": "2.3.0",
44
"type": "module",
55
"scripts": {
66
"dev": "vite",

web/src/pages/Bouncers.tsx

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,58 @@ import { toast } from 'sonner'
3838
import { EmptyState, PageHeader, QueryError, ResultsSummary } from '@/components/common'
3939
import { useUrlFilters } from '@/hooks'
4040

41+
/**
42+
* Type guard to check if an object is a valid Bouncer.
43+
* This provides runtime validation of the data received from the API.
44+
*/
45+
function isBouncer(obj: unknown): obj is Bouncer {
46+
return (
47+
obj !== null &&
48+
typeof obj === 'object' &&
49+
'name' in obj &&
50+
typeof (obj as { name: unknown }).name === 'string' &&
51+
'ip_address' in obj &&
52+
typeof (obj as { ip_address: unknown }).ip_address === 'string' &&
53+
'valid' in obj &&
54+
typeof (obj as { valid: unknown }).valid === 'boolean' &&
55+
'last_pull' in obj &&
56+
typeof (obj as { last_pull: unknown }).last_pull === 'string' &&
57+
'type' in obj &&
58+
typeof (obj as { type: unknown }).type === 'string' &&
59+
'version' in obj &&
60+
typeof (obj as { version: unknown }).version === 'string' &&
61+
(!('status' in obj) || typeof (obj as { status: unknown }).status === 'string')
62+
)
63+
}
64+
65+
/**
66+
* Extracts an array of valid `Bouncer` objects from API payloads.
67+
*
68+
* Accepts either a raw array of items or an object containing a `bouncers` array and returns
69+
* only entries that conform to the expected `Bouncer` shape.
70+
*
71+
* @param raw - The API response payload to normalize; may be an array or an object with a `bouncers` property
72+
* @returns An array of `Bouncer` objects extracted from `raw`, or an empty array if none were found
73+
*/
74+
function normalizeBouncers(raw: unknown): Bouncer[] {
75+
if (Array.isArray(raw)) {
76+
return raw.filter(isBouncer)
77+
}
78+
79+
if (raw && typeof raw === 'object' && 'bouncers' in raw && Array.isArray((raw as { bouncers: unknown }).bouncers)) {
80+
return (raw as { bouncers: unknown[] }).bouncers.filter(isBouncer)
81+
}
82+
83+
return []
84+
}
85+
86+
/**
87+
* Page component that renders the Bouncers management UI for listing, searching, adding, and deleting CrowdSec bouncers.
88+
*
89+
* Renders a searchable table of registered bouncers, provides a dialog to create new bouncers (showing the API key once), and exposes delete actions with confirmation. Fetches and normalizes bouncer data from the Local API and performs add/delete mutations with user-facing success/error toasts.
90+
*
91+
* @returns The page's React element rendering the bouncers management interface.
92+
*/
4193
export default function Bouncers() {
4294
const queryClient = useQueryClient()
4395
const [urlFilters, setUrlFilter] = useUrlFilters(['q'], { q: '' })
@@ -50,18 +102,11 @@ export default function Bouncers() {
50102
queryKey: ['bouncers'],
51103
queryFn: async () => {
52104
const response = await api.crowdsec.getBouncers()
53-
const raw = response.data.data
54-
// Adapt to whatever shape the backend returns
55-
if (Array.isArray(raw)) return raw as Bouncer[]
56-
if (raw && typeof raw === 'object') {
57-
// Backend wraps as { bouncers: [...], count: N }
58-
const obj = raw as Record<string, unknown>
59-
for (const val of Object.values(obj)) {
60-
if (Array.isArray(val)) return val as Bouncer[]
61-
}
62-
}
63-
return [] as Bouncer[]
105+
return response.data.data
64106
},
107+
// The dashboard populates this cache key with the raw { bouncers, count }
108+
// payload shape, so normalize it per observer on the Bouncers page.
109+
select: normalizeBouncers,
65110
})
66111

67112
const addBouncerMutation = useMutation({

0 commit comments

Comments
 (0)