feat: implement phases 3-5 of Arma 3 UX enhancement plan

Phase 3 - Mod display names + split-pane selector:
- Parse mod.cpp/meta.cpp for display_name and workshop_id
- Rewrite ModList as two-pane available/selected interface

Phase 4 - Player kick/ban from Players tab:
- Add get_by_slot() to PlayerRepository
- Add get_rcon_client() class method to ThreadRegistry
- Add /players/{slot_id}/kick and /ban endpoints
- Rewrite PlayerTable with kick/ban modals and ban presets

Phase 5 - Historical log file browser:
- Add list_log_files() and get_log_file_path() to RPTParser
- Add logfiles_router with GET/download/DELETE endpoints
- Update LogViewer with collapsible log files section (download + delete)
This commit is contained in:
Tran G. (Revernomad) Khoa
2026-04-17 20:47:37 +07:00
parent fe3bd81cae
commit 5a62d21def
14 changed files with 890 additions and 88 deletions

View File

@@ -390,4 +390,58 @@ export function useSendCommand(serverId: number) {
mutationFn: (command: string) =>
apiClient.post(`/api/servers/${serverId}/rcon/command`, { command }),
});
}
export function useKickPlayer(serverId: number) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ slotId, reason }: { slotId: number; reason: string }) =>
apiClient.post(`/api/servers/${serverId}/players/${slotId}/kick`, { reason }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["players", serverId] }),
});
}
export function useBanPlayer(serverId: number) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ slotId, reason, durationMinutes }: { slotId: number; reason: string; durationMinutes?: number }) =>
apiClient.post(`/api/servers/${serverId}/players/${slotId}/ban`, {
reason,
duration_minutes: durationMinutes ?? null,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["players", serverId] });
queryClient.invalidateQueries({ queryKey: ["bans", serverId] });
},
});
}
export interface LogFile {
filename: string;
size_bytes: number;
modified_at: number;
}
export function useServerLogFiles(serverId: number) {
return useQuery({
queryKey: ["servers", serverId, "logfiles"],
queryFn: async () => {
const res = await apiClient.get<{ success: boolean; data: LogFile[] }>(
`/api/servers/${serverId}/logfiles`,
);
return res.data.data;
},
enabled: serverId > 0,
refetchInterval: 30_000,
});
}
export function useDeleteLogFile(serverId: number) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (filename: string) =>
apiClient.delete(`/api/servers/${serverId}/logfiles/${encodeURIComponent(filename)}`),
onSuccess: () =>
queryClient.invalidateQueries({ queryKey: ["servers", serverId, "logfiles"] }),
});
}