feat: Add Wild Central web app (Central-only UI)
Extract Central pages from the wild-cloud web app into a standalone React app for Wild Central. Includes: - Central overview, DNS, DHCP, Firewall, VPN, Ingress, CrowdSec pages - Simplified sidebar with Central-only navigation - Branding updated to "Wild Central" - All Cloud-specific pages, components, hooks, and API services removed - TypeScript type-check and production build pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
270
web/src/services/api/networking.ts
Normal file
270
web/src/services/api/networking.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { apiClient } from './client';
|
||||
|
||||
// HAProxy
|
||||
|
||||
export interface HaproxyInstanceRoute {
|
||||
name: string;
|
||||
domain: string;
|
||||
backendIP: string;
|
||||
extraDomains?: string[];
|
||||
}
|
||||
|
||||
export interface HaproxyStatus {
|
||||
status: string;
|
||||
pid?: number;
|
||||
configFile?: string;
|
||||
lastRestart?: string;
|
||||
routes?: HaproxyInstanceRoute[];
|
||||
}
|
||||
|
||||
export interface HaproxyGenerateResult {
|
||||
message: string;
|
||||
config?: string;
|
||||
routes?: HaproxyInstanceRoute[];
|
||||
customRoutes?: number;
|
||||
}
|
||||
|
||||
export const haproxyApi = {
|
||||
async getStatus(): Promise<HaproxyStatus> {
|
||||
return apiClient.get('/api/v1/haproxy/status');
|
||||
},
|
||||
|
||||
async getConfig(): Promise<{ content: string; config_file: string }> {
|
||||
return apiClient.get('/api/v1/haproxy/config');
|
||||
},
|
||||
|
||||
async generate(): Promise<HaproxyGenerateResult> {
|
||||
return apiClient.post('/api/v1/haproxy/generate');
|
||||
},
|
||||
|
||||
async restart(): Promise<{ message: string }> {
|
||||
return apiClient.post('/api/v1/haproxy/restart');
|
||||
},
|
||||
|
||||
async getStats(): Promise<{ backends: BackendStat[] }> {
|
||||
return apiClient.get('/api/v1/haproxy/stats');
|
||||
},
|
||||
};
|
||||
|
||||
export interface BackendStat {
|
||||
name: string;
|
||||
status: string;
|
||||
activeConns: number;
|
||||
rate: number;
|
||||
peakRate: number;
|
||||
totalConns: number;
|
||||
errors: number;
|
||||
}
|
||||
|
||||
// DDNS
|
||||
|
||||
export interface DdnsRecordStatus {
|
||||
name: string;
|
||||
ip?: string;
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface DdnsStatus {
|
||||
enabled: boolean;
|
||||
currentIP?: string;
|
||||
lastChecked?: string;
|
||||
lastUpdated?: string;
|
||||
lastError?: string;
|
||||
records?: DdnsRecordStatus[];
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const ddnsApi = {
|
||||
async getStatus(): Promise<DdnsStatus> {
|
||||
return apiClient.get('/api/v1/ddns/status');
|
||||
},
|
||||
|
||||
async trigger(): Promise<{ message: string }> {
|
||||
return apiClient.post('/api/v1/ddns/trigger');
|
||||
},
|
||||
};
|
||||
|
||||
// DHCP
|
||||
|
||||
export interface DhcpLease {
|
||||
expiry: string;
|
||||
mac: string;
|
||||
ip: string;
|
||||
hostname?: string;
|
||||
}
|
||||
|
||||
export interface DhcpStaticLease {
|
||||
mac: string;
|
||||
ip: string;
|
||||
hostname?: string;
|
||||
}
|
||||
|
||||
export const dhcpApi = {
|
||||
async getLeases(): Promise<{ leases: DhcpLease[] }> {
|
||||
return apiClient.get('/api/v1/dnsmasq/dhcp/leases');
|
||||
},
|
||||
|
||||
async addStatic(lease: DhcpStaticLease): Promise<{ message: string }> {
|
||||
return apiClient.post('/api/v1/dnsmasq/dhcp/static', lease);
|
||||
},
|
||||
|
||||
async deleteStatic(mac: string): Promise<{ message: string }> {
|
||||
return apiClient.delete(`/api/v1/dnsmasq/dhcp/static/${encodeURIComponent(mac)}`);
|
||||
},
|
||||
};
|
||||
|
||||
// nftables
|
||||
|
||||
export interface NftablesStatus {
|
||||
rulesFile: string;
|
||||
rules: string;
|
||||
}
|
||||
|
||||
export interface NetworkInterface {
|
||||
name: string;
|
||||
addresses: string[];
|
||||
}
|
||||
|
||||
export const nftablesApi = {
|
||||
async getStatus(): Promise<NftablesStatus> {
|
||||
return apiClient.get('/api/v1/nftables/status');
|
||||
},
|
||||
|
||||
async apply(): Promise<{ message: string }> {
|
||||
return apiClient.post('/api/v1/nftables/apply');
|
||||
},
|
||||
|
||||
async getInterfaces(): Promise<{ interfaces: NetworkInterface[] }> {
|
||||
return apiClient.get('/api/v1/nftables/interfaces');
|
||||
},
|
||||
};
|
||||
|
||||
// CrowdSec LAPI
|
||||
|
||||
export interface CrowdSecMachine {
|
||||
machineId: string;
|
||||
isValidated: boolean;
|
||||
last_push?: string;
|
||||
last_heartbeat?: string;
|
||||
ipAddress?: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface CrowdSecBanSummary {
|
||||
total: number;
|
||||
byReason: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface CrowdSecBouncer {
|
||||
name: string;
|
||||
revoked: boolean;
|
||||
ipAddress?: string;
|
||||
type?: string;
|
||||
version?: string;
|
||||
lastPull?: string;
|
||||
}
|
||||
|
||||
export interface CrowdSecDecision {
|
||||
id: number;
|
||||
value: string;
|
||||
type: string;
|
||||
scope: string;
|
||||
reason: string;
|
||||
origin: string;
|
||||
duration?: string;
|
||||
}
|
||||
|
||||
export interface CrowdSecAlert {
|
||||
id: number;
|
||||
scenario: string;
|
||||
message: string;
|
||||
createdAt: string;
|
||||
machineId?: string;
|
||||
sourceIP?: string;
|
||||
country?: string;
|
||||
asName?: string;
|
||||
}
|
||||
|
||||
export interface AddDecisionRequest {
|
||||
ip: string;
|
||||
type: 'ban' | 'allow';
|
||||
reason?: string;
|
||||
duration?: string;
|
||||
}
|
||||
|
||||
export interface CrowdSecStatus {
|
||||
active: boolean;
|
||||
firewallBouncer: boolean;
|
||||
machines: CrowdSecMachine[];
|
||||
bouncers: CrowdSecBouncer[];
|
||||
}
|
||||
|
||||
export interface CrowdSecProvisionResult {
|
||||
message: string;
|
||||
centralLapiUrl: string;
|
||||
agentUsername: string;
|
||||
}
|
||||
|
||||
export const crowdSecApi = {
|
||||
async getStatus(): Promise<CrowdSecStatus> {
|
||||
return apiClient.get('/api/v1/crowdsec/status');
|
||||
},
|
||||
|
||||
async getDecisions(): Promise<{ decisions: CrowdSecDecision[] }> {
|
||||
return apiClient.get('/api/v1/crowdsec/decisions');
|
||||
},
|
||||
|
||||
async deleteDecision(id: number): Promise<{ message: string }> {
|
||||
return apiClient.delete(`/api/v1/crowdsec/decisions/${id}`);
|
||||
},
|
||||
|
||||
async getMachines(): Promise<{ machines: CrowdSecMachine[] }> {
|
||||
return apiClient.get('/api/v1/crowdsec/machines');
|
||||
},
|
||||
|
||||
async deleteMachine(name: string): Promise<{ message: string }> {
|
||||
return apiClient.delete(`/api/v1/crowdsec/machines/${encodeURIComponent(name)}`);
|
||||
},
|
||||
|
||||
async getBouncers(): Promise<{ bouncers: CrowdSecBouncer[] }> {
|
||||
return apiClient.get('/api/v1/crowdsec/bouncers');
|
||||
},
|
||||
|
||||
async deleteBouncer(name: string): Promise<{ message: string }> {
|
||||
return apiClient.delete(`/api/v1/crowdsec/bouncers/${encodeURIComponent(name)}`);
|
||||
},
|
||||
|
||||
async getSummary(): Promise<CrowdSecBanSummary> {
|
||||
return apiClient.get('/api/v1/crowdsec/summary');
|
||||
},
|
||||
|
||||
async getAlerts(limit = 50): Promise<{ alerts: CrowdSecAlert[] }> {
|
||||
return apiClient.get(`/api/v1/crowdsec/alerts?limit=${limit}`);
|
||||
},
|
||||
|
||||
async addDecision(req: AddDecisionRequest): Promise<{ message: string }> {
|
||||
return apiClient.post('/api/v1/crowdsec/decisions', req);
|
||||
},
|
||||
|
||||
async deleteDecisionByIP(ip: string): Promise<{ message: string }> {
|
||||
return apiClient.delete(`/api/v1/crowdsec/decisions/ip/${encodeURIComponent(ip)}`);
|
||||
},
|
||||
|
||||
async provision(instanceName: string): Promise<CrowdSecProvisionResult> {
|
||||
return apiClient.post(`/api/v1/crowdsec/provision/${encodeURIComponent(instanceName)}`);
|
||||
},
|
||||
};
|
||||
|
||||
// Global Secrets
|
||||
|
||||
export const secretsApi = {
|
||||
async get(raw?: boolean): Promise<Record<string, unknown>> {
|
||||
const path = raw ? '/api/v1/secrets?raw=true' : '/api/v1/secrets';
|
||||
return apiClient.get(path);
|
||||
},
|
||||
|
||||
async update(values: Record<string, unknown>): Promise<{ message: string }> {
|
||||
return apiClient.put('/api/v1/secrets', values);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user