feat(api, web): enhance agent management and linting capabilities
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 2m17s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

- Added a new linting command for OpenAPI specifications in the package.json, improving code quality checks.
- Updated frontend documentation to clarify component usage and structure, including detailed descriptions for `SettingsShell` and `Auth callback`.
- Refactored agent-related API routes to streamline control-plane functionalities, consolidating multiple routes for better organization.
- Improved error handling in the API to provide more informative responses for validation errors, enhancing user feedback during interactions.

These changes enhance the overall development experience and improve the management of agents within the application.
This commit is contained in:
Denozordec
2026-07-30 14:13:05 +07:00
parent fb95ef22b3
commit f160992d94
68 changed files with 3155 additions and 6899 deletions
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest'
import { putSettingsBodySchema } from './contracts.js'
describe('putSettingsBodySchema', () => {
it('accepts whitelisted keys', () => {
const parsed = putSettingsBodySchema.parse({
enroll_seed: 'x',
show_quick_actions: 'true',
})
expect(parsed.show_quick_actions).toBe('true')
})
it('rejects unknown keys', () => {
expect(() =>
putSettingsBodySchema.parse({ evil_key: 'nope' }),
).toThrow()
})
})
+24
View File
@@ -320,6 +320,29 @@ export const evobgpCommunitySchema = z.object({
title: z.string().nullable().optional(),
})
/** Whitelist keys for PUT /api/v1/settings. */
export const SETTINGS_KEYS = [
'enroll_seed',
'evobgp_api_url',
'evobgp_api_token',
'agent_sync_interval_sec',
'show_quick_actions',
] as const
export const putSettingsBodySchema = z
.record(z.string(), z.string())
.superRefine((obj, ctx) => {
for (const key of Object.keys(obj)) {
if (!(SETTINGS_KEYS as readonly string[]).includes(key)) {
ctx.addIssue({
code: 'custom',
message: `Unknown settings key: ${key}`,
path: [key],
})
}
}
})
export type Agent = z.infer<typeof agentSchema>
export type IpList = z.infer<typeof ipListSchema>
export type PolicyRule = z.infer<typeof policyRuleSchema>
@@ -331,3 +354,4 @@ export type DashboardStats = z.infer<typeof dashboardStatsSchema>
export type InstallLink = z.infer<typeof installLinkSchema>
export type EvobgpCommunity = z.infer<typeof evobgpCommunitySchema>
export type DefaultAction = z.infer<typeof defaultActionSchema>
export type PutSettingsBody = z.infer<typeof putSettingsBodySchema>
+22
View File
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest'
import { permissionForRequest } from '@evofw/shared'
describe('permissionForRequest', () => {
it('maps install-links to agents permissions', () => {
expect(permissionForRequest('GET', '/api/v1/install-links')).toBe(
'fw:agents:read',
)
expect(permissionForRequest('POST', '/api/v1/install-links')).toBe(
'fw:agents:write',
)
})
it('maps integrations to lists read / settings admin', () => {
expect(
permissionForRequest('GET', '/api/v1/integrations/evobgp/communities'),
).toBe('fw:lists:read')
expect(
permissionForRequest('POST', '/api/v1/integrations/evobgp/refresh'),
).toBe('fw:settings:admin')
})
})
+4 -1
View File
@@ -29,7 +29,7 @@ export function permissionForRequest(
const m = method.toUpperCase()
const write = m !== 'GET' && m !== 'HEAD' && m !== 'OPTIONS'
if (path.startsWith('/api/v1/agents')) {
if (path.startsWith('/api/v1/agents') || path.startsWith('/api/v1/install-links')) {
return write ? 'fw:agents:write' : 'fw:agents:read'
}
if (path.startsWith('/api/v1/lists')) {
@@ -45,6 +45,9 @@ export function permissionForRequest(
if (path.startsWith('/api/v1/stats') || path.startsWith('/api/v1/dashboard')) {
return 'fw:stats:read'
}
if (path.startsWith('/api/v1/integrations')) {
return write ? 'fw:settings:admin' : 'fw:lists:read'
}
if (path.startsWith('/api/v1/settings') || path.startsWith('/api/v1/install-context')) {
return write ? 'fw:settings:admin' : 'fw:settings:read'
}