- Added support for policy modes ('blacklist' and 'whitelist') in agent and policy set management.
- Updated API endpoints to handle policy mode during agent assignment and rule operations.
- Enhanced the web UI to display and manage policy modes for agents and rules, ensuring all assigned sets share a consistent mode.
- Introduced new validation to enforce single policy mode across assigned sets for agents.
- Improved error handling for policy mode conflicts and updated documentation accordingly.
Co-authored-by: Cursor <cursoragent@cursor.com>
246 lines
7.5 KiB
TypeScript
246 lines
7.5 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
export const agentPlatformSchema = z.enum(['linux', 'mikrotik'])
|
|
export const agentStatusSchema = z.enum([
|
|
'invited',
|
|
'pending',
|
|
'approved',
|
|
'revoked',
|
|
])
|
|
export const policyModeSchema = z.enum(['blacklist', 'whitelist'])
|
|
export const policyActionSchema = z.enum(['allow', 'deny'])
|
|
export const ipListTypeSchema = z.enum([
|
|
'static',
|
|
'json_url',
|
|
'domains',
|
|
'evobgp_community',
|
|
])
|
|
|
|
export const agentSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
hostname: z.string().nullable().optional(),
|
|
platform: agentPlatformSchema,
|
|
token_prefix: z.string(),
|
|
status: agentStatusSchema,
|
|
policy_mode: policyModeSchema,
|
|
policy_generation: z.number().int(),
|
|
last_seen_at: z.string().nullable().optional(),
|
|
last_seen_ip: z.string().nullable().optional(),
|
|
last_apply_at: z.string().nullable().optional(),
|
|
last_apply_status: z.string().nullable().optional(),
|
|
last_apply_error: z.string().nullable().optional(),
|
|
last_apply_prefix_count: z.number().int().nullable().optional(),
|
|
last_apply_packets_dropped: z.number().int().optional(),
|
|
last_apply_packets_accepted: z.number().int().optional(),
|
|
last_apply_kernel_method: z.string().nullable().optional(),
|
|
client_version: z.string().nullable().optional(),
|
|
settings_json: z.string().optional(),
|
|
created_at: z.string(),
|
|
approved_at: z.string().nullable().optional(),
|
|
revoked_at: z.string().nullable().optional(),
|
|
install_curl: z.string().nullable().optional(),
|
|
install_link_id: z.string().nullable().optional(),
|
|
})
|
|
|
|
export const ipListSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
type: ipListTypeSchema,
|
|
config_json: z.string(),
|
|
content_hash: z.string().nullable().optional(),
|
|
refreshed_at: z.string().nullable().optional(),
|
|
last_error: z.string().nullable().optional(),
|
|
entry_count: z.number().int().optional(),
|
|
created_at: z.string(),
|
|
updated_at: z.string(),
|
|
})
|
|
|
|
export const policyRuleSchema = z.object({
|
|
id: z.string(),
|
|
set_id: z.string(),
|
|
priority: z.number().int(),
|
|
action: policyActionSchema,
|
|
enabled: z.boolean().optional().default(true),
|
|
list_id: z.string().nullable().optional(),
|
|
cidr: z.string().nullable().optional(),
|
|
hostname: z.string().nullable().optional(),
|
|
resolved_count: z.number().int().optional(),
|
|
comment: z.string().nullable().optional(),
|
|
created_at: z.string(),
|
|
updated_at: z.string(),
|
|
})
|
|
|
|
export const policySetSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
description: z.string().nullable().optional(),
|
|
enabled: z.boolean(),
|
|
policy_mode: policyModeSchema,
|
|
rules_count: z.number().int().optional(),
|
|
agents_count: z.number().int().optional(),
|
|
created_at: z.string(),
|
|
updated_at: z.string(),
|
|
})
|
|
|
|
export const ipOverrideSchema = z.object({
|
|
id: z.string(),
|
|
agent_id: z.string(),
|
|
cidr: z.string(),
|
|
action: policyActionSchema,
|
|
comment: z.string().nullable().optional(),
|
|
created_at: z.string(),
|
|
})
|
|
|
|
export const createIpListBodySchema = z.object({
|
|
name: z.string().min(1),
|
|
/** Prefer static | json_url | evobgp_community; domains accepted for legacy. */
|
|
type: ipListTypeSchema,
|
|
config: z.record(z.string(), z.unknown()).optional(),
|
|
entries: z.array(z.string()).optional(),
|
|
})
|
|
|
|
export const createPolicySetBodySchema = z.object({
|
|
name: z.string().min(1),
|
|
description: z.string().nullable().optional(),
|
|
enabled: z.boolean().optional().default(true),
|
|
policy_mode: policyModeSchema.optional().default('blacklist'),
|
|
})
|
|
|
|
export const patchPolicySetBodySchema = z.object({
|
|
name: z.string().min(1).optional(),
|
|
description: z.string().nullable().optional(),
|
|
enabled: z.boolean().optional(),
|
|
policy_mode: policyModeSchema.optional(),
|
|
})
|
|
|
|
export const createPolicyRuleBodySchema = z
|
|
.object({
|
|
set_id: z.string().min(1),
|
|
priority: z.number().int().min(1).max(10000).optional(),
|
|
action: policyActionSchema,
|
|
enabled: z.boolean().optional().default(true),
|
|
list_id: z.string().nullable().optional(),
|
|
cidr: z.string().nullable().optional(),
|
|
hostname: z.string().nullable().optional(),
|
|
comment: z.string().nullable().optional(),
|
|
})
|
|
.superRefine((v, ctx) => {
|
|
const sources = [v.list_id, v.cidr, v.hostname].filter(
|
|
(x) => typeof x === 'string' && x.trim().length > 0,
|
|
)
|
|
if (sources.length !== 1) {
|
|
ctx.addIssue({
|
|
code: 'custom',
|
|
message: 'Укажите ровно один источник: list_id, cidr или hostname',
|
|
})
|
|
}
|
|
})
|
|
|
|
export const patchPolicyRuleBodySchema = z.object({
|
|
enabled: z.boolean().optional(),
|
|
action: policyActionSchema.optional(),
|
|
comment: z.string().nullable().optional(),
|
|
priority: z.number().int().min(1).max(10000).optional(),
|
|
})
|
|
|
|
export const reorderPolicyRulesBodySchema = z.object({
|
|
ordered_ids: z.array(z.string()).min(1),
|
|
})
|
|
|
|
export const putAgentPolicySetsBodySchema = z.object({
|
|
set_ids: z.array(z.string()),
|
|
})
|
|
|
|
export const createOverrideBodySchema = z.object({
|
|
cidr: z.string().min(1),
|
|
action: policyActionSchema,
|
|
comment: z.string().nullable().optional(),
|
|
})
|
|
|
|
export const patchAgentBodySchema = z.object({
|
|
name: z.string().min(1).optional(),
|
|
policy_mode: policyModeSchema.optional(),
|
|
settings: z.record(z.string(), z.unknown()).optional(),
|
|
})
|
|
|
|
export const cloneFromBodySchema = z.object({
|
|
include_overrides: z.boolean().optional().default(false),
|
|
})
|
|
|
|
export const enrollBodySchema = z.object({
|
|
name: z.string().min(1),
|
|
hostname: z.string().optional(),
|
|
platform: agentPlatformSchema.optional().default('linux'),
|
|
token: z.string().min(16),
|
|
client_version: z.string().optional(),
|
|
install_link_id: z.string().optional(),
|
|
})
|
|
|
|
export const applyReportBodySchema = z.object({
|
|
status: z.string(),
|
|
prefix_count: z.number().int().optional(),
|
|
packets_dropped: z.number().int().optional(),
|
|
packets_accepted: z.number().int().optional(),
|
|
kernel_method: z.string().optional(),
|
|
error: z.string().optional(),
|
|
source: z.string().optional(),
|
|
})
|
|
|
|
export const agentPolicySchema = z.object({
|
|
generation: z.number().int(),
|
|
hash: z.string(),
|
|
policy_mode: policyModeSchema,
|
|
deny_cidrs: z.array(z.string()),
|
|
allow_cidrs: z.array(z.string()),
|
|
sync_interval_sec: z.number().int(),
|
|
})
|
|
|
|
export const dashboardStatsSchema = z.object({
|
|
agents_total: z.number().int(),
|
|
agents_approved: z.number().int(),
|
|
agents_online: z.number().int(),
|
|
agents_pending: z.number().int(),
|
|
packets_dropped: z.number().int(),
|
|
packets_accepted: z.number().int(),
|
|
lists_total: z.number().int(),
|
|
})
|
|
|
|
export const createInstallLinkBodySchema = z.object({
|
|
name: z.string().min(1),
|
|
platform: agentPlatformSchema.optional().default('linux'),
|
|
})
|
|
|
|
export const installLinkSchema = z.object({
|
|
id: z.string(),
|
|
slug: z.string(),
|
|
client_name: z.string(),
|
|
platform: agentPlatformSchema,
|
|
agent_id: z.string().nullable().optional(),
|
|
created_at: z.string(),
|
|
revoked_at: z.string().nullable().optional(),
|
|
last_used_at: z.string().nullable().optional(),
|
|
use_count: z.number().int(),
|
|
urls: z
|
|
.object({
|
|
by_id: z.string(),
|
|
by_slug: z.string(),
|
|
})
|
|
.optional(),
|
|
curl: z
|
|
.object({
|
|
by_id: z.string(),
|
|
by_slug: z.string(),
|
|
})
|
|
.optional(),
|
|
})
|
|
|
|
export type Agent = z.infer<typeof agentSchema>
|
|
export type IpList = z.infer<typeof ipListSchema>
|
|
export type PolicyRule = z.infer<typeof policyRuleSchema>
|
|
export type PolicySet = z.infer<typeof policySetSchema>
|
|
export type IpOverride = z.infer<typeof ipOverrideSchema>
|
|
export type AgentPolicy = z.infer<typeof agentPolicySchema>
|
|
export type DashboardStats = z.infer<typeof dashboardStatsSchema>
|
|
export type InstallLink = z.infer<typeof installLinkSchema>
|