feat: implement access list management with CRUD operations and IP testing
- Added API integration for access lists including listing, creating, updating, deleting, and testing IPs against access lists. - Created AccessListForm component for creating and editing access lists with validation. - Developed AccessListSelector component for selecting access lists with detailed display of selected ACL. - Implemented hooks for managing access lists and handling API interactions. - Added tests for AccessListSelector and useAccessLists hooks to ensure functionality. - Enhanced AccessLists page with UI for managing access lists, including create, edit, delete, and test IP features.
This commit is contained in:
179
frontend/src/api/__tests__/accessLists.test.ts
Normal file
179
frontend/src/api/__tests__/accessLists.test.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { accessListsApi } from '../accessLists';
|
||||
import client from '../client';
|
||||
import type { AccessList } from '../accessLists';
|
||||
|
||||
// Mock the client module
|
||||
vi.mock('../client', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('accessListsApi', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('list', () => {
|
||||
it('should fetch all access lists', async () => {
|
||||
const mockLists: AccessList[] = [
|
||||
{
|
||||
id: 1,
|
||||
uuid: 'test-uuid',
|
||||
name: 'Test ACL',
|
||||
description: 'Test description',
|
||||
type: 'whitelist',
|
||||
ip_rules: '[{"cidr":"192.168.1.0/24"}]',
|
||||
country_codes: '',
|
||||
local_network_only: false,
|
||||
enabled: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
vi.mocked(client.get).mockResolvedValueOnce({ data: mockLists });
|
||||
|
||||
const result = await accessListsApi.list();
|
||||
|
||||
expect(client.get).toHaveBeenCalledWith<[string]>('/access-lists');
|
||||
expect(result).toEqual(mockLists);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should fetch access list by ID', async () => {
|
||||
const mockList: AccessList = {
|
||||
id: 1,
|
||||
uuid: 'test-uuid',
|
||||
name: 'Test ACL',
|
||||
description: 'Test description',
|
||||
type: 'whitelist',
|
||||
ip_rules: '[{"cidr":"192.168.1.0/24"}]',
|
||||
country_codes: '',
|
||||
local_network_only: false,
|
||||
enabled: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
vi.mocked(client.get).mockResolvedValueOnce({ data: mockList });
|
||||
|
||||
const result = await accessListsApi.get(1);
|
||||
|
||||
expect(client.get).toHaveBeenCalledWith<[string]>('/access-lists/1');
|
||||
expect(result).toEqual(mockList);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new access list', async () => {
|
||||
const newList = {
|
||||
name: 'New ACL',
|
||||
description: 'New description',
|
||||
type: 'whitelist' as const,
|
||||
ip_rules: '[{"cidr":"10.0.0.0/8"}]',
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
const mockResponse: AccessList = {
|
||||
id: 1,
|
||||
uuid: 'new-uuid',
|
||||
...newList,
|
||||
country_codes: '',
|
||||
local_network_only: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ data: mockResponse });
|
||||
|
||||
const result = await accessListsApi.create(newList);
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith<[string, typeof newList]>('/access-lists', newList);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an access list', async () => {
|
||||
const updates = {
|
||||
name: 'Updated ACL',
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
const mockResponse: AccessList = {
|
||||
id: 1,
|
||||
uuid: 'test-uuid',
|
||||
name: 'Updated ACL',
|
||||
description: 'Test description',
|
||||
type: 'whitelist',
|
||||
ip_rules: '[{"cidr":"192.168.1.0/24"}]',
|
||||
country_codes: '',
|
||||
local_network_only: false,
|
||||
enabled: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
vi.mocked(client.put).mockResolvedValueOnce({ data: mockResponse });
|
||||
|
||||
const result = await accessListsApi.update(1, updates);
|
||||
|
||||
expect(client.put).toHaveBeenCalledWith<[string, typeof updates]>('/access-lists/1', updates);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an access list', async () => {
|
||||
vi.mocked(client.delete).mockResolvedValueOnce({ data: undefined });
|
||||
|
||||
await accessListsApi.delete(1);
|
||||
|
||||
expect(client.delete).toHaveBeenCalledWith<[string]>('/access-lists/1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('testIP', () => {
|
||||
it('should test an IP against an access list', async () => {
|
||||
const mockResponse = {
|
||||
allowed: true,
|
||||
reason: 'IP matches whitelist rule',
|
||||
};
|
||||
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ data: mockResponse });
|
||||
|
||||
const result = await accessListsApi.testIP(1, '192.168.1.100');
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith<[string, { ip_address: string }]>('/access-lists/1/test', {
|
||||
ip_address: '192.168.1.100',
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTemplates', () => {
|
||||
it('should fetch access list templates', async () => {
|
||||
const mockTemplates = [
|
||||
{
|
||||
name: 'Private Networks',
|
||||
description: 'RFC1918 private networks',
|
||||
type: 'whitelist' as const,
|
||||
ip_rules: '[{"cidr":"10.0.0.0/8"},{"cidr":"172.16.0.0/12"},{"cidr":"192.168.0.0/16"}]',
|
||||
},
|
||||
];
|
||||
|
||||
vi.mocked(client.get).mockResolvedValueOnce({ data: mockTemplates });
|
||||
|
||||
const result = await accessListsApi.getTemplates();
|
||||
|
||||
expect(client.get).toHaveBeenCalledWith<[string]>('/access-lists/templates');
|
||||
expect(result).toEqual(mockTemplates);
|
||||
});
|
||||
});
|
||||
});
|
||||
106
frontend/src/api/accessLists.ts
Normal file
106
frontend/src/api/accessLists.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import client from './client';
|
||||
|
||||
export interface AccessListRule {
|
||||
cidr: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface AccessList {
|
||||
id: number;
|
||||
uuid: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: 'whitelist' | 'blacklist' | 'geo_whitelist' | 'geo_blacklist';
|
||||
ip_rules: string; // JSON string of AccessListRule[]
|
||||
country_codes: string; // Comma-separated
|
||||
local_network_only: boolean;
|
||||
enabled: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateAccessListRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
type: 'whitelist' | 'blacklist' | 'geo_whitelist' | 'geo_blacklist';
|
||||
ip_rules?: string;
|
||||
country_codes?: string;
|
||||
local_network_only?: boolean;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface TestIPRequest {
|
||||
ip_address: string;
|
||||
}
|
||||
|
||||
export interface TestIPResponse {
|
||||
allowed: boolean;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface AccessListTemplate {
|
||||
name: string;
|
||||
description: string;
|
||||
type: string;
|
||||
local_network_only?: boolean;
|
||||
country_codes?: string;
|
||||
}
|
||||
|
||||
export const accessListsApi = {
|
||||
/**
|
||||
* Fetch all access lists
|
||||
*/
|
||||
async list(): Promise<AccessList[]> {
|
||||
const response = await client.get<AccessList[]>('/access-lists');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a single access list by ID
|
||||
*/
|
||||
async get(id: number): Promise<AccessList> {
|
||||
const response = await client.get<AccessList>(`/access-lists/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new access list
|
||||
*/
|
||||
async create(data: CreateAccessListRequest): Promise<AccessList> {
|
||||
const response = await client.post<AccessList>('/access-lists', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an existing access list
|
||||
*/
|
||||
async update(id: number, data: Partial<CreateAccessListRequest>): Promise<AccessList> {
|
||||
const response = await client.put<AccessList>(`/access-lists/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete an access list
|
||||
*/
|
||||
async delete(id: number): Promise<void> {
|
||||
await client.delete(`/access-lists/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if an IP address would be allowed/blocked
|
||||
*/
|
||||
async testIP(id: number, ipAddress: string): Promise<TestIPResponse> {
|
||||
const response = await client.post<TestIPResponse>(`/access-lists/${id}/test`, {
|
||||
ip_address: ipAddress,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get predefined ACL templates
|
||||
*/
|
||||
async getTemplates(): Promise<AccessListTemplate[]> {
|
||||
const response = await client.get<AccessListTemplate[]>('/access-lists/templates');
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -38,6 +38,7 @@ export interface ProxyHost {
|
||||
enabled: boolean;
|
||||
certificate_id?: number | null;
|
||||
certificate?: Certificate | null;
|
||||
access_list_id?: number | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user