mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-20 06:28:19 +02:00
feat: proxy change-IP link
This commit is contained in:
1 parent
69d3c90a7c
commit
3f07a41fac
25 files changed
+925
-51
No files matched your search
@@ -29,6 +29,7 @@ import {
|
||||
LuLockOpen,
|
||||
LuPlus,
|
||||
LuPuzzle,
|
||||
LuRefreshCcwDot,
|
||||
LuRefreshCw,
|
||||
LuSettings,
|
||||
LuShield,
|
||||
@@ -91,6 +92,7 @@ import { getProfileIcon, getProfileNativeOs } from "@/lib/browser-utils";
|
||||
import { DNS_BLOCKLIST_LEVELS } from "@/lib/dns-blocklist-levels";
|
||||
import { formatRelativeTime } from "@/lib/flag-utils";
|
||||
import { MOTION_EASE_OUT } from "@/lib/motion";
|
||||
import { rotateProxyIps } from "@/lib/proxy-rotation";
|
||||
import type { RemoteHandoffState } from "@/lib/remote-sessions";
|
||||
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -1751,6 +1753,9 @@ function NetworkSectionInline({
|
||||
const [vpnId, setVpnId] = React.useState<string | null>(
|
||||
profile.vpn_id ?? null,
|
||||
);
|
||||
const rotatableProxy = storedProxies.find(
|
||||
(p) => p.id === proxyId && p.rotation_url,
|
||||
);
|
||||
const dnsServerId = React.useId();
|
||||
const [savedDnsServer, setSavedDnsServer] = React.useState(
|
||||
profile.dns_server ?? "",
|
||||
@@ -1863,6 +1868,20 @@ function NetworkSectionInline({
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{rotatableProxy && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="soft"
|
||||
className="shrink-0"
|
||||
data-slot="profile-proxy-rotate"
|
||||
title={t("proxies.rotation.actionTooltip")}
|
||||
disabled={isSaving}
|
||||
onClick={() => void rotateProxyIps([rotatableProxy], t as never)}
|
||||
>
|
||||
<LuRefreshCcwDot className="size-3.5" />
|
||||
{t("proxies.rotation.action")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { translateBackendError } from "@/lib/backend-errors";
|
||||
import { looksLikeRotationUrl } from "@/lib/proxy-rotation";
|
||||
import {
|
||||
isFirstHopEncrypted,
|
||||
isXrayProxyType,
|
||||
@@ -42,6 +43,7 @@ interface ProxyFormData {
|
||||
username: string;
|
||||
password: string;
|
||||
vless_uri: string;
|
||||
rotation_url: string;
|
||||
}
|
||||
|
||||
interface ProxyFormDialogProps {
|
||||
@@ -58,6 +60,7 @@ const DEFAULT_FORM: ProxyFormData = {
|
||||
username: "",
|
||||
password: "",
|
||||
vless_uri: "",
|
||||
rotation_url: "",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -175,6 +178,7 @@ export function ProxyFormDialog({
|
||||
username: editingProxy.proxy_settings.username ?? "",
|
||||
password: editingProxy.proxy_settings.password ?? "",
|
||||
vless_uri: editingProxy.proxy_settings.vless_uri ?? "",
|
||||
rotation_url: editingProxy.rotation_url ?? "",
|
||||
});
|
||||
}, [editingProxy, isOpen, resetForm]);
|
||||
|
||||
@@ -215,6 +219,12 @@ export function ProxyFormDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
const rotationUrl = form.rotation_url.trim();
|
||||
if (rotationUrl && !looksLikeRotationUrl(rotationUrl)) {
|
||||
toast.error(t("backendErrors.proxyRotationUrlInvalid"));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const payload = {
|
||||
@@ -234,9 +244,24 @@ export function ProxyFormDialog({
|
||||
proxyId: editingProxy.id,
|
||||
...payload,
|
||||
});
|
||||
if (rotationUrl !== (editingProxy.rotation_url ?? "")) {
|
||||
await invoke("set_proxy_rotation_url", {
|
||||
proxyId: editingProxy.id,
|
||||
rotationUrl: rotationUrl || null,
|
||||
});
|
||||
}
|
||||
toast.success(t("toasts.success.proxyUpdated"));
|
||||
} else {
|
||||
await invoke("create_stored_proxy", payload);
|
||||
const created = await invoke<StoredProxy>(
|
||||
"create_stored_proxy",
|
||||
payload,
|
||||
);
|
||||
if (rotationUrl) {
|
||||
await invoke("set_proxy_rotation_url", {
|
||||
proxyId: created.id,
|
||||
rotationUrl,
|
||||
});
|
||||
}
|
||||
toast.success(t("toasts.success.proxyCreated"));
|
||||
}
|
||||
|
||||
@@ -602,6 +627,26 @@ export function ProxyFormDialog({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="proxy-rotation-url">
|
||||
{t("proxies.rotation.label")}
|
||||
</Label>
|
||||
<Input
|
||||
id="proxy-rotation-url"
|
||||
value={form.rotation_url}
|
||||
onChange={(e) => {
|
||||
setForm({ ...form, rotation_url: e.target.value });
|
||||
}}
|
||||
placeholder={t("proxies.rotation.placeholder")}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("proxies.rotation.hint")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { GoPlus } from "react-icons/go";
|
||||
import {
|
||||
LuDownload,
|
||||
LuPencil,
|
||||
LuRefreshCcwDot,
|
||||
LuRefreshCw,
|
||||
LuTrash2,
|
||||
LuUpload,
|
||||
@@ -63,6 +64,7 @@ import { useProfileReferences } from "@/hooks/use-profile-references";
|
||||
import { useProxyEvents } from "@/hooks/use-proxy-events";
|
||||
import { useVpnEvents } from "@/hooks/use-vpn-events";
|
||||
import { parseBackendError, translateBackendError } from "@/lib/backend-errors";
|
||||
import { rotateProxyIps } from "@/lib/proxy-rotation";
|
||||
import { isFirstHopEncrypted, proxyProtocolToken } from "@/lib/proxy-string";
|
||||
import { canonicalProxyType } from "@/lib/proxy-type";
|
||||
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
|
||||
@@ -655,6 +657,26 @@ export function ProxyManagementDialog({
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
<ProxyCheckButton proxy={proxy} />
|
||||
{proxy.rotation_url && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
data-slot="proxy-rotate"
|
||||
aria-label={t("proxies.rotation.action")}
|
||||
onClick={() => {
|
||||
void rotateProxyIps([proxy], t);
|
||||
}}
|
||||
>
|
||||
<LuRefreshCcwDot className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("proxies.rotation.actionTooltip")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
@@ -1386,6 +1408,19 @@ export function ProxyManagementDialog({
|
||||
>
|
||||
<LuRefreshCw />
|
||||
</DataTableActionBarAction>
|
||||
<DataTableActionBarAction
|
||||
tooltip={t("proxies.rotation.action")}
|
||||
onClick={() =>
|
||||
void rotateProxyIps(
|
||||
proxiesTable
|
||||
.getSelectedRowModel()
|
||||
.rows.map((row) => row.original),
|
||||
t,
|
||||
)
|
||||
}
|
||||
>
|
||||
<LuRefreshCcwDot />
|
||||
</DataTableActionBarAction>
|
||||
<DataTableActionBarAction
|
||||
tooltip={t("common.buttons.delete")}
|
||||
onClick={() => {
|
||||
|
||||
Reference in new issue
Block a user