feat(iplimit): gate IP limit on fail2ban and reset stale limits

Per-client IP limit only enforces where fail2ban is installed, so the panel now reports enforceability and disables the field otherwise:

- Add GET /panel/api/server/fail2banStatus (enabled/installed/usable/windows), cached 30s.
- ClientFormModal and ClientBulkAddModal disable the IP Limit input when not usable and show a hover tooltip; Windows gets a platform-specific message instead of the bash-menu hint.
- One-time migration ResetIpLimitNoFail2ban zeroes existing client limitIp (inbound settings JSON + clients table) on hosts without fail2ban, where the limit never applied.
- Drop the recurring '[LimitIP] Fail2Ban is not installed' warning.
- Add limitIpFail2banMissing/limitIpFail2banWindows/limitIpDisabled across all 13 locales.
This commit is contained in:
MHSanaei
2026-06-22 23:15:58 +02:00
parent 718b7e16e1
commit ce8b1bed77
23 changed files with 315 additions and 16 deletions
@@ -0,0 +1,41 @@
import { useQuery } from '@tanstack/react-query';
import { HttpUtil } from '@/utils';
import { keys } from '@/api/queryKeys';
export interface Fail2banStatus {
enabled: boolean;
installed: boolean;
usable: boolean;
windows: boolean;
}
const FAIL_OPEN_STATUS: Fail2banStatus = {
enabled: true,
installed: true,
usable: true,
windows: false,
};
async function fetchFail2banStatus(): Promise<Fail2banStatus> {
const msg = await HttpUtil.get<Fail2banStatus>('/panel/api/server/fail2banStatus', undefined, { silent: true });
if (!msg?.success || !msg.obj) throw new Error(msg?.msg || 'Failed to fetch fail2ban status');
return { ...FAIL_OPEN_STATUS, ...msg.obj };
}
export function getLimitIpNotice(status: Fail2banStatus, t: (key: string) => string): string {
if (status.usable) return '';
if (!status.enabled) return t('pages.clients.limitIpDisabled');
if (status.windows) return t('pages.clients.limitIpFail2banWindows');
return t('pages.clients.limitIpFail2banMissing');
}
export function useFail2banStatusQuery() {
const query = useQuery({
queryKey: keys.server.fail2banStatus(),
queryFn: fetchFail2banStatus,
staleTime: 60_000,
});
return query.data ?? FAIL_OPEN_STATUS;
}
+1
View File
@@ -1,6 +1,7 @@
export const keys = {
server: {
status: () => ['server', 'status'] as const,
fail2banStatus: () => ['server', 'fail2banStatus'] as const,
},
nodes: {
root: () => ['nodes'] as const,