quick commit
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 6m9s
CI/CD Pipeline / Security Scanning (push) Successful in 26s
CI/CD Pipeline / Tests (3.11) (push) Failing after 5m24s
CI/CD Pipeline / Tests (3.12) (push) Failing after 5m23s
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
CI/CD Pipeline / Notification (push) Successful in 1s
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 6m9s
CI/CD Pipeline / Security Scanning (push) Successful in 26s
CI/CD Pipeline / Tests (3.11) (push) Failing after 5m24s
CI/CD Pipeline / Tests (3.12) (push) Failing after 5m23s
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
CI/CD Pipeline / Notification (push) Successful in 1s
This commit is contained in:
120
dashboard/frontend/src/services/api.ts
Normal file
120
dashboard/frontend/src/services/api.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* API client for GuardDen Dashboard
|
||||
*/
|
||||
|
||||
import type {
|
||||
AnalyticsSummary,
|
||||
AutomodRuleConfig,
|
||||
CreateUserNote,
|
||||
Guild,
|
||||
GuildSettings,
|
||||
Me,
|
||||
ModerationStats,
|
||||
PaginatedLogs,
|
||||
UserNote,
|
||||
UserProfile,
|
||||
} from '../types/api';
|
||||
|
||||
const BASE_URL = '';
|
||||
|
||||
async function fetchJson<T>(url: string, options?: RequestInit): Promise<T> {
|
||||
const response = await fetch(BASE_URL + url, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Request failed: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
// Auth API
|
||||
export const authApi = {
|
||||
getMe: () => fetchJson<Me>('/api/me'),
|
||||
};
|
||||
|
||||
// Guilds API
|
||||
export const guildsApi = {
|
||||
list: () => fetchJson<Guild[]>('/api/guilds'),
|
||||
getSettings: (guildId: number) =>
|
||||
fetchJson<GuildSettings>(`/api/guilds/${guildId}/settings`),
|
||||
updateSettings: (guildId: number, settings: GuildSettings) =>
|
||||
fetchJson<GuildSettings>(`/api/guilds/${guildId}/settings`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(settings),
|
||||
}),
|
||||
getAutomodConfig: (guildId: number) =>
|
||||
fetchJson<AutomodRuleConfig>(`/api/guilds/${guildId}/automod`),
|
||||
updateAutomodConfig: (guildId: number, config: AutomodRuleConfig) =>
|
||||
fetchJson<AutomodRuleConfig>(`/api/guilds/${guildId}/automod`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(config),
|
||||
}),
|
||||
exportConfig: (guildId: number) =>
|
||||
fetch(`${BASE_URL}/api/guilds/${guildId}/export`, {
|
||||
credentials: 'include',
|
||||
}).then((res) => res.blob()),
|
||||
};
|
||||
|
||||
// Moderation API
|
||||
export const moderationApi = {
|
||||
getLogs: (guildId?: number, limit = 50, offset = 0) => {
|
||||
const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
|
||||
if (guildId) {
|
||||
params.set('guild_id', String(guildId));
|
||||
}
|
||||
return fetchJson<PaginatedLogs>(`/api/moderation/logs?${params}`);
|
||||
},
|
||||
};
|
||||
|
||||
// Analytics API
|
||||
export const analyticsApi = {
|
||||
getSummary: (guildId?: number, days = 7) => {
|
||||
const params = new URLSearchParams({ days: String(days) });
|
||||
if (guildId) {
|
||||
params.set('guild_id', String(guildId));
|
||||
}
|
||||
return fetchJson<AnalyticsSummary>(`/api/analytics/summary?${params}`);
|
||||
},
|
||||
getModerationStats: (guildId?: number, days = 30) => {
|
||||
const params = new URLSearchParams({ days: String(days) });
|
||||
if (guildId) {
|
||||
params.set('guild_id', String(guildId));
|
||||
}
|
||||
return fetchJson<ModerationStats>(`/api/analytics/moderation-stats?${params}`);
|
||||
},
|
||||
};
|
||||
|
||||
// Users API
|
||||
export const usersApi = {
|
||||
search: (guildId: number, username?: string, minStrikes?: number, limit = 50) => {
|
||||
const params = new URLSearchParams({ guild_id: String(guildId), limit: String(limit) });
|
||||
if (username) {
|
||||
params.set('username', username);
|
||||
}
|
||||
if (minStrikes !== undefined) {
|
||||
params.set('min_strikes', String(minStrikes));
|
||||
}
|
||||
return fetchJson<UserProfile[]>(`/api/users/search?${params}`);
|
||||
},
|
||||
getProfile: (userId: number, guildId: number) =>
|
||||
fetchJson<UserProfile>(`/api/users/${userId}/profile?guild_id=${guildId}`),
|
||||
getNotes: (userId: number, guildId: number) =>
|
||||
fetchJson<UserNote[]>(`/api/users/${userId}/notes?guild_id=${guildId}`),
|
||||
createNote: (userId: number, guildId: number, note: CreateUserNote) =>
|
||||
fetchJson<UserNote>(`/api/users/${userId}/notes?guild_id=${guildId}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(note),
|
||||
}),
|
||||
deleteNote: (userId: number, noteId: number, guildId: number) =>
|
||||
fetchJson<void>(`/api/users/${userId}/notes/${noteId}?guild_id=${guildId}`, {
|
||||
method: 'DELETE',
|
||||
}),
|
||||
};
|
||||
120
dashboard/frontend/src/services/websocket.ts
Normal file
120
dashboard/frontend/src/services/websocket.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* WebSocket service for real-time updates
|
||||
*/
|
||||
|
||||
import type { WebSocketEvent } from '../types/api';
|
||||
|
||||
type EventHandler = (event: WebSocketEvent) => void;
|
||||
|
||||
export class WebSocketService {
|
||||
private ws: WebSocket | null = null;
|
||||
private handlers: Map<string, Set<EventHandler>> = new Map();
|
||||
private reconnectTimeout: number | null = null;
|
||||
private reconnectAttempts = 0;
|
||||
private maxReconnectAttempts = 5;
|
||||
private guildId: number | null = null;
|
||||
|
||||
connect(guildId: number): void {
|
||||
this.guildId = guildId;
|
||||
this.reconnectAttempts = 0;
|
||||
this.doConnect();
|
||||
}
|
||||
|
||||
private doConnect(): void {
|
||||
if (this.guildId === null) return;
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws/events?guild_id=${this.guildId}`;
|
||||
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket connected');
|
||||
this.reconnectAttempts = 0;
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data) as WebSocketEvent;
|
||||
this.emit(data.type, data);
|
||||
this.emit('*', data); // Emit to wildcard handlers
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
console.log('WebSocket closed');
|
||||
this.scheduleReconnect();
|
||||
};
|
||||
}
|
||||
|
||||
private scheduleReconnect(): void {
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
console.error('Max reconnect attempts reached');
|
||||
return;
|
||||
}
|
||||
|
||||
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000);
|
||||
this.reconnectTimeout = window.setTimeout(() => {
|
||||
this.reconnectAttempts++;
|
||||
console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`);
|
||||
this.doConnect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
if (this.reconnectTimeout !== null) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
this.reconnectTimeout = null;
|
||||
}
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
this.guildId = null;
|
||||
}
|
||||
|
||||
on(eventType: string, handler: EventHandler): void {
|
||||
if (!this.handlers.has(eventType)) {
|
||||
this.handlers.set(eventType, new Set());
|
||||
}
|
||||
this.handlers.get(eventType)!.add(handler);
|
||||
}
|
||||
|
||||
off(eventType: string, handler: EventHandler): void {
|
||||
const handlers = this.handlers.get(eventType);
|
||||
if (handlers) {
|
||||
handlers.delete(handler);
|
||||
if (handlers.size === 0) {
|
||||
this.handlers.delete(eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private emit(eventType: string, event: WebSocketEvent): void {
|
||||
const handlers = this.handlers.get(eventType);
|
||||
if (handlers) {
|
||||
handlers.forEach((handler) => handler(event));
|
||||
}
|
||||
}
|
||||
|
||||
send(data: unknown): void {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
ping(): void {
|
||||
this.send('ping');
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const wsService = new WebSocketService();
|
||||
Reference in New Issue
Block a user