mirror of
https://github.com/DanielLavrushin/b4.git
synced 2026-07-09 16:00:05 +00:00
Merge pull request #218 from DanielLavrushin/1-61-1-fixes
This commit is contained in:
commit
8313e099af
9 changed files with 53 additions and 28 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# B4 - Bye Bye Big Bro
|
||||
|
||||
## [1.61.0] - 2026-05-09
|
||||
## [1.61.1] - 2026-05-09
|
||||
|
||||
- ADDED: **Custom payload for UDP fake packets** - new "Fake Packet Payload" picker in the UDP fake settings of each set. Choose a captured `.bin` (uploaded in Settings → Payloads, or auto-captured from live QUIC traffic) to use as the body of fake UDP packets. Empty = zero fill (previous behavior). The Settings → Payloads upload form now has an explicit TLS/QUIC protocol selector.
|
||||
- ADDED: **Auto-generated QUIC Initial payload** - new "(auto: QUIC Initial)" option in the UDP Fake Packet Payload picker. b4 generates a fresh randomized QUIC Initial packet for every fake, with random connection IDs each time, so no upload is needed and the bytes can't be fingerprinted by repetition. Recommended packet size for this mode is 1200 bytes. Works with any Faking Strategy (None / TTL / Checksum).
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ import {
|
|||
stripPort,
|
||||
} from "@utils";
|
||||
import { colors } from "@design";
|
||||
import { useWebSocket } from "../../context/B4WsProvider";
|
||||
import { useWebSocket } from "@context/B4WsProvider";
|
||||
import { AddIpModal } from "./AddIpModal";
|
||||
import { B4Config, B4SetConfig } from "@models/config";
|
||||
import { useSnackbar } from "@context/SnackbarProvider";
|
||||
import { devicesApi } from "@b4.devices";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
interface RipeNetworkInfo {
|
||||
asns: string[];
|
||||
|
|
@ -259,14 +260,14 @@ export function ConnectionsPage() {
|
|||
e.preventDefault();
|
||||
clearDomains();
|
||||
resetDomainsBadge();
|
||||
showSuccess(t("connections.page.clearedAll"));
|
||||
showSuccess(i18n.t("connections.page.clearedAll"));
|
||||
} else if (e.key === "p" || e.key === "Pause") {
|
||||
e.preventDefault();
|
||||
setPauseDomains(!pauseDomains);
|
||||
showSuccess(
|
||||
pauseDomains
|
||||
? t("connections.page.resumed")
|
||||
: t("connections.page.paused"),
|
||||
? i18n.t("connections.page.resumed")
|
||||
: i18n.t("connections.page.paused"),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -274,7 +275,6 @@ export function ConnectionsPage() {
|
|||
clearDomains,
|
||||
resetDomainsBadge,
|
||||
showSuccess,
|
||||
t,
|
||||
setPauseDomains,
|
||||
pauseDomains,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { colors, fonts, glows } from "@design";
|
|||
import { useWebSocket } from "@context/B4WsProvider";
|
||||
import { useSnackbar } from "@context/SnackbarProvider";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
export function LogsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -67,14 +68,14 @@ export function LogsPage() {
|
|||
if ((e.ctrlKey && e.key === "x") || e.key === "Delete") {
|
||||
e.preventDefault();
|
||||
clearLogs();
|
||||
showSuccess(t("logs.cleared"));
|
||||
showSuccess(i18n.t("logs.cleared"));
|
||||
} else if (e.key === "p" || e.key === "Pause") {
|
||||
e.preventDefault();
|
||||
setPauseLogs(!pauseLogs);
|
||||
showSuccess(pauseLogs ? t("logs.resumed") : t("logs.paused"));
|
||||
showSuccess(pauseLogs ? i18n.t("logs.resumed") : i18n.t("logs.paused"));
|
||||
}
|
||||
},
|
||||
[clearLogs, pauseLogs, setPauseLogs, showSuccess, t],
|
||||
[clearLogs, pauseLogs, setPauseLogs, showSuccess],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Navigate, Route, Routes, useNavigate, useParams } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "@/i18n";
|
||||
import { SetEditorPage } from "./Editor";
|
||||
import { SetStats, SetWithStats, SetsManager } from "./Manager";
|
||||
|
||||
|
|
@ -123,14 +124,14 @@ export function SetsPage() {
|
|||
};
|
||||
setConfig(data);
|
||||
} catch {
|
||||
showError(t("core.configLoadError"));
|
||||
showError(i18n.t("core.configLoadError"));
|
||||
} finally {
|
||||
if (!initialLoadDone.current) {
|
||||
setLoading(false);
|
||||
initialLoadDone.current = true;
|
||||
}
|
||||
}
|
||||
}, [showError, t]);
|
||||
}, [showError]);
|
||||
|
||||
useEffect(() => {
|
||||
loadConfig().catch(() => {});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import i18n, { setLanguage } from "../../i18n";
|
||||
|
||||
import {
|
||||
ApiIcon,
|
||||
|
|
@ -90,7 +91,7 @@ enum TABS {
|
|||
|
||||
export function SettingsPage() {
|
||||
const { showError, showSuccess } = useSnackbar();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const [config, setConfig] = useState<B4Config | null>(null);
|
||||
const [originalConfig, setOriginalConfig] = useState<B4Config | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
@ -240,21 +241,25 @@ export function SettingsPage() {
|
|||
const data = await configApi.get();
|
||||
setConfig(data);
|
||||
setOriginalConfig(structuredClone(data));
|
||||
const lang = data.system.web_server.language;
|
||||
if (lang && lang !== i18n.language) {
|
||||
void i18n.changeLanguage(lang);
|
||||
localStorage.setItem("b4-language", lang);
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error loading configuration:", error);
|
||||
showErrorRef.current(t("core.configLoadError"));
|
||||
showErrorRef.current(i18n.t("core.configLoadError"));
|
||||
return null;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [i18n, t]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadConfig().catch(() => {});
|
||||
let bootstrapped = false;
|
||||
loadConfig()
|
||||
.then((data) => {
|
||||
if (bootstrapped || !data) return;
|
||||
bootstrapped = true;
|
||||
setLanguage(data.system.web_server.language ?? "en");
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [loadConfig]);
|
||||
|
||||
const { refresh: refreshAiStatus } = useAiStatus();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
Chip,
|
||||
} from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "@/i18n";
|
||||
import {
|
||||
InfoIcon,
|
||||
CheckIcon,
|
||||
|
|
@ -135,11 +136,11 @@ export const SystemInfoDialog = ({ open, onClose }: SystemInfoDialogProps) => {
|
|||
.then((r) => r.json())
|
||||
.then((json: { success: boolean; data: Diagnostics }) => {
|
||||
if (json.success) setData(json.data);
|
||||
else setError(t("settings.SystemInfo.loadFailed"));
|
||||
else setError(i18n.t("settings.SystemInfo.loadFailed"));
|
||||
})
|
||||
.catch(() => setError(t("settings.SystemInfo.connectFailed")))
|
||||
.catch(() => setError(i18n.t("settings.SystemInfo.connectFailed")))
|
||||
.finally(() => setLoading(false));
|
||||
}, [open, t]);
|
||||
}, [open]);
|
||||
|
||||
const statusChip = (status: string) => {
|
||||
const isOk = status === "loaded" || status === "built-in";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { setLanguage } from "../../i18n";
|
||||
import { ApiIcon } from "@b4.icons";
|
||||
import {
|
||||
B4Alert,
|
||||
|
|
@ -24,13 +25,12 @@ export const WebServerSettings = ({
|
|||
config,
|
||||
onChange,
|
||||
}: WebServerSettingsProps) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleLanguageChange = (e: { target: { value: string | number } }) => {
|
||||
const lang = String(e.target.value);
|
||||
onChange("system.web_server.language", lang);
|
||||
void i18n.changeLanguage(lang);
|
||||
localStorage.setItem("b4-language", lang);
|
||||
setLanguage(lang);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -4,18 +4,34 @@ import { initReactI18next } from "react-i18next";
|
|||
import en from "./en.json";
|
||||
import ru from "./ru.json";
|
||||
|
||||
const cachedLang = localStorage.getItem("b4-language") || "en";
|
||||
const SUPPORTED = ["en", "ru"] as const;
|
||||
type Lang = (typeof SUPPORTED)[number];
|
||||
|
||||
export const isSupportedLang = (v: unknown): v is Lang =>
|
||||
typeof v === "string" && (SUPPORTED as readonly string[]).includes(v);
|
||||
|
||||
const initial: Lang = (() => {
|
||||
const cached = localStorage.getItem("b4-language");
|
||||
return isSupportedLang(cached) ? cached : "en";
|
||||
})();
|
||||
|
||||
void i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
en: { translation: en },
|
||||
ru: { translation: ru },
|
||||
},
|
||||
lng: cachedLang,
|
||||
lng: initial,
|
||||
fallbackLng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const setLanguage = (lang: string) => {
|
||||
if (!isSupportedLang(lang)) return;
|
||||
if (i18n.language === lang) return;
|
||||
void i18n.changeLanguage(lang);
|
||||
localStorage.setItem("b4-language", lang);
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"skipLibCheck": true,
|
||||
"noImplicitAny": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@design": ["./src/design/index.ts"],
|
||||
"@hooks/*": ["./src/hooks/*"],
|
||||
"@utils": ["./src/utils/index.ts"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue