mirror of
https://github.com/mitmproxy/mitmproxy.git
synced 2024-11-26 23:00:40 +00:00
Feature/dns and upstream (#7110)
* move reverse row into reverse component * add upstream mode * update test state * add dns mode * add tests * [autofix.ci] apply automated fixes * review changes * [autofix.ci] apply automated fixes * wording nits * nit --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Maximilian Hils <git@maximilianhils.com>
This commit is contained in:
parent
332f222994
commit
92faf3959f
@ -81,6 +81,15 @@
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.mode-upstream-input {
|
||||
border: 1px solid #ccc;
|
||||
margin-left: 10px;
|
||||
margin-right: 5px;
|
||||
border-radius: 4px;
|
||||
min-width: 70px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.mode-reverse-dropdown {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
116
web/src/js/__tests__/ducks/modes/dnsSpec.tsx
Normal file
116
web/src/js/__tests__/ducks/modes/dnsSpec.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import dnsReducer, {
|
||||
initialState,
|
||||
setListenHost,
|
||||
setListenPort,
|
||||
setActive,
|
||||
} from "./../../../ducks/modes/dns";
|
||||
import {
|
||||
RECEIVE as STATE_RECEIVE,
|
||||
BackendState,
|
||||
} from "../../../ducks/backendState";
|
||||
import { TStore } from "../tutils";
|
||||
import fetchMock, { enableFetchMocks } from "jest-fetch-mock";
|
||||
import { PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
describe("dnsSlice", () => {
|
||||
it("should have working setters", async () => {
|
||||
enableFetchMocks();
|
||||
const store = TStore();
|
||||
|
||||
expect(store.getState().modes.dns[0]).toEqual({
|
||||
active: false,
|
||||
});
|
||||
|
||||
const server = store.getState().modes.dns[0];
|
||||
await store.dispatch(setActive({ value: true, server }));
|
||||
await store.dispatch(setListenHost({ value: "127.0.0.1", server }));
|
||||
await store.dispatch(setListenPort({ value: 4444, server }));
|
||||
|
||||
expect(store.getState().modes.dns[0]).toEqual({
|
||||
active: true,
|
||||
listen_host: "127.0.0.1",
|
||||
listen_port: 4444,
|
||||
});
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("should handle error when setting dns mode", async () => {
|
||||
fetchMock.mockReject(new Error("invalid spec"));
|
||||
const store = TStore();
|
||||
|
||||
const server = store.getState().modes.dns[0];
|
||||
await store.dispatch(setActive({ value: false, server }));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(store.getState().modes.dns[0].error).toBe("invalid spec");
|
||||
});
|
||||
|
||||
it("should handle error when setting listen port", async () => {
|
||||
fetchMock.mockReject(new Error("invalid spec"));
|
||||
const store = TStore();
|
||||
|
||||
const server = store.getState().modes.dns[0];
|
||||
await store.dispatch(setListenPort({ value: 4444, server }));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(store.getState().modes.dns[0].error).toBe("invalid spec");
|
||||
});
|
||||
|
||||
it("should handle error when setting listen host", async () => {
|
||||
fetchMock.mockReject(new Error("invalid spec"));
|
||||
const store = TStore();
|
||||
|
||||
const server = store.getState().modes.dns[0];
|
||||
await store.dispatch(setListenHost({ value: "localhost", server }));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(store.getState().modes.dns[0].error).toBe("invalid spec");
|
||||
});
|
||||
|
||||
it("should handle RECEIVE_STATE with an active dns proxy", () => {
|
||||
const action = {
|
||||
type: STATE_RECEIVE.type,
|
||||
payload: {
|
||||
servers: {
|
||||
"dns@localhost:8081": {
|
||||
description: "HTTP(S) proxy",
|
||||
full_spec: "dns@localhost:8081",
|
||||
is_running: true,
|
||||
last_exception: null,
|
||||
listen_addrs: [
|
||||
["127.0.0.1", 8081],
|
||||
["::1", 8081],
|
||||
],
|
||||
type: "dns",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as PayloadAction<Partial<BackendState>>;
|
||||
const newState = dnsReducer(initialState, action);
|
||||
expect(newState).toEqual([
|
||||
{
|
||||
active: true,
|
||||
listen_host: "localhost",
|
||||
listen_port: 8081,
|
||||
ui_id: newState[0].ui_id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should handle RECEIVE_STATE with no active dns proxy", () => {
|
||||
const action = {
|
||||
type: STATE_RECEIVE.type,
|
||||
payload: {
|
||||
servers: {},
|
||||
},
|
||||
} as PayloadAction<Partial<BackendState>>;
|
||||
const newState = dnsReducer(initialState, action);
|
||||
expect(newState).toEqual([
|
||||
{
|
||||
active: false,
|
||||
ui_id: newState[0].ui_id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
153
web/src/js/__tests__/ducks/modes/upstreamSpec.tsx
Normal file
153
web/src/js/__tests__/ducks/modes/upstreamSpec.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import fetchMock, { enableFetchMocks } from "jest-fetch-mock";
|
||||
import upstreamReducer, {
|
||||
initialState,
|
||||
setDestination,
|
||||
setActive,
|
||||
setListenHost,
|
||||
setListenPort,
|
||||
} from "../../../ducks/modes/upstream";
|
||||
import {
|
||||
RECEIVE as STATE_RECEIVE,
|
||||
BackendState,
|
||||
} from "../../../ducks/backendState";
|
||||
import { TStore } from "../tutils";
|
||||
import { PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
describe("upstreamSlice", () => {
|
||||
it("should have working setters", async () => {
|
||||
enableFetchMocks();
|
||||
const store = TStore();
|
||||
|
||||
expect(store.getState().modes.upstream[0]).toEqual({
|
||||
active: false,
|
||||
destination: "example.com",
|
||||
});
|
||||
|
||||
await store.dispatch(
|
||||
setActive({
|
||||
value: true,
|
||||
server: store.getState().modes.upstream[0],
|
||||
}),
|
||||
);
|
||||
await store.dispatch(
|
||||
setListenHost({
|
||||
value: "127.0.0.1",
|
||||
server: store.getState().modes.upstream[0],
|
||||
}),
|
||||
);
|
||||
await store.dispatch(
|
||||
setListenPort({
|
||||
value: 4444,
|
||||
server: store.getState().modes.upstream[0],
|
||||
}),
|
||||
);
|
||||
await store.dispatch(
|
||||
setDestination({
|
||||
value: "example.com:8085",
|
||||
server: store.getState().modes.upstream[0],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(store.getState().modes.upstream[0]).toEqual({
|
||||
active: true,
|
||||
listen_host: "127.0.0.1",
|
||||
listen_port: 4444,
|
||||
destination: "example.com:8085",
|
||||
});
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it("should handle error when setting upstream mode", async () => {
|
||||
fetchMock.mockReject(new Error("invalid spec"));
|
||||
const store = TStore();
|
||||
|
||||
const server = store.getState().modes.upstream[0];
|
||||
await store.dispatch(setActive({ value: true, server }));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(store.getState().modes.upstream[0].error).toBe("invalid spec");
|
||||
});
|
||||
|
||||
it("should handle error when setting listen port", async () => {
|
||||
fetchMock.mockReject(new Error("invalid spec"));
|
||||
const store = TStore();
|
||||
|
||||
const server = store.getState().modes.upstream[0];
|
||||
await store.dispatch(setListenPort({ value: 4444, server }));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(store.getState().modes.upstream[0].error).toBe("invalid spec");
|
||||
});
|
||||
|
||||
it("should handle error when setting listen host", async () => {
|
||||
fetchMock.mockReject(new Error("invalid spec"));
|
||||
const store = TStore();
|
||||
|
||||
const server = store.getState().modes.upstream[0];
|
||||
await store.dispatch(setListenHost({ value: "localhost", server }));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(store.getState().modes.upstream[0].error).toBe("invalid spec");
|
||||
});
|
||||
|
||||
it("should handle error when setting destination", async () => {
|
||||
fetchMock.mockReject(new Error("invalid spec"));
|
||||
const store = TStore();
|
||||
|
||||
const server = store.getState().modes.upstream[0];
|
||||
await store.dispatch(setDestination({ value: "example.com", server }));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(store.getState().modes.upstream[0].error).toBe("invalid spec");
|
||||
});
|
||||
|
||||
it("should handle RECEIVE_STATE with an active upstream proxy", () => {
|
||||
const action = {
|
||||
type: STATE_RECEIVE.type,
|
||||
payload: {
|
||||
servers: {
|
||||
"upstream:https://example.com:8085@localhost:8080": {
|
||||
description: "HTTP(S) proxy (upstream mode)",
|
||||
full_spec:
|
||||
"upstream:https://example.com:8085@localhost:8080",
|
||||
is_running: true,
|
||||
last_exception: null,
|
||||
listen_addrs: [
|
||||
["127.0.0.1", 8080],
|
||||
["::1", 8080, 0, 0],
|
||||
],
|
||||
type: "upstream",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as PayloadAction<Partial<BackendState>>;
|
||||
const newState = upstreamReducer(initialState, action);
|
||||
expect(newState).toEqual([
|
||||
{
|
||||
active: true,
|
||||
destination: "https://example.com:8085",
|
||||
listen_host: "localhost",
|
||||
listen_port: 8080,
|
||||
ui_id: newState[0].ui_id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should handle RECEIVE_STATE with no active upstream proxy", () => {
|
||||
const action = {
|
||||
type: STATE_RECEIVE.type,
|
||||
payload: {
|
||||
servers: {},
|
||||
},
|
||||
} as PayloadAction<Partial<BackendState>>;
|
||||
const newState = upstreamReducer(initialState, action);
|
||||
expect(newState).toEqual([
|
||||
{
|
||||
active: false,
|
||||
ui_id: newState[0].ui_id,
|
||||
destination: "",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
@ -167,6 +167,17 @@ export const testState: RootState = {
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
upstream: [
|
||||
{
|
||||
active: false,
|
||||
destination: "example.com",
|
||||
},
|
||||
],
|
||||
dns: [
|
||||
{
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
31
web/src/js/__tests__/modes/dnsSpec.ts
Normal file
31
web/src/js/__tests__/modes/dnsSpec.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { ModesState } from "../../ducks/modes";
|
||||
import { parseSpec } from "../../modes";
|
||||
import { getSpec, parseRaw, DnsState } from "../../modes/dns";
|
||||
|
||||
describe("getSpec dns mode", () => {
|
||||
it("should return the correct mode config", () => {
|
||||
const modes = {
|
||||
dns: [
|
||||
{
|
||||
active: true,
|
||||
listen_host: "localhost",
|
||||
listen_port: 8082,
|
||||
},
|
||||
],
|
||||
} as ModesState;
|
||||
const mode = getSpec(modes.dns[0]);
|
||||
expect(mode).toBe("dns@localhost:8082");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseRaw dns mode", () => {
|
||||
it("should parse", () => {
|
||||
const parsed = parseRaw(parseSpec("dns@localhost:8082"));
|
||||
expect(parsed).toEqual({
|
||||
active: true,
|
||||
ui_id: parsed.ui_id,
|
||||
listen_host: "localhost",
|
||||
listen_port: 8082,
|
||||
} as DnsState);
|
||||
});
|
||||
});
|
35
web/src/js/__tests__/modes/upstreamSpec.ts
Normal file
35
web/src/js/__tests__/modes/upstreamSpec.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ModesState } from "../../ducks/modes";
|
||||
import { parseSpec } from "../../modes";
|
||||
import { getSpec, parseRaw, UpstreamState } from "../../modes/upstream";
|
||||
|
||||
describe("getSpec upstream mode", () => {
|
||||
it("should return the correct mode config", () => {
|
||||
const modes = {
|
||||
upstream: [
|
||||
{
|
||||
active: true,
|
||||
destination: "https://example.com:8085",
|
||||
listen_host: "localhost",
|
||||
listen_port: 8082,
|
||||
},
|
||||
],
|
||||
} as ModesState;
|
||||
const mode = getSpec(modes.upstream[0]);
|
||||
expect(mode).toBe("upstream:https://example.com:8085@localhost:8082");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseRaw upstream mode", () => {
|
||||
it("should parse", () => {
|
||||
const parsed = parseRaw(
|
||||
parseSpec("upstream:https://example.com:8085@localhost:8082"),
|
||||
);
|
||||
expect(parsed).toEqual({
|
||||
active: true,
|
||||
ui_id: parsed.ui_id,
|
||||
destination: "https://example.com:8085",
|
||||
listen_host: "localhost",
|
||||
listen_port: 8082,
|
||||
} as UpstreamState);
|
||||
});
|
||||
});
|
@ -6,6 +6,8 @@ import Reverse from "./Modes/Reverse";
|
||||
import { useAppSelector } from "../ducks";
|
||||
import Transparent from "./Modes/Transparent";
|
||||
import Socks from "./Modes/Socks";
|
||||
import Upstream from "./Modes/Upstream";
|
||||
import Dns from "./Modes/Dns";
|
||||
|
||||
export default function Modes() {
|
||||
const platform = useAppSelector((state) => state.backendState.platform);
|
||||
@ -24,8 +26,9 @@ export default function Modes() {
|
||||
<h3>Advanced</h3>
|
||||
<div className="modes-container">
|
||||
<Socks />
|
||||
<Upstream />
|
||||
<Dns />
|
||||
{!platform.startsWith("win32") ? <Transparent /> : undefined}
|
||||
<i>Remaining modes are coming soon...</i>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
90
web/src/js/components/Modes/Dns.tsx
Normal file
90
web/src/js/components/Modes/Dns.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import * as React from "react";
|
||||
import { useAppDispatch, useAppSelector } from "../../ducks";
|
||||
import { ServerInfo } from "../../ducks/backendState";
|
||||
import ValueEditor from "../editors/ValueEditor";
|
||||
import { ServerStatus } from "./CaptureSetup";
|
||||
import { ModeToggle } from "./ModeToggle";
|
||||
import { Popover } from "./Popover";
|
||||
import { setActive, setListenHost, setListenPort } from "../../ducks/modes/dns";
|
||||
import { DnsState, getSpec } from "../../modes/dns";
|
||||
|
||||
export default function Dns() {
|
||||
const serverState = useAppSelector((state) => state.modes.dns);
|
||||
const backendState = useAppSelector((state) => state.backendState.servers);
|
||||
|
||||
const servers = serverState.map((server) => {
|
||||
return (
|
||||
<DnsRow
|
||||
key={server.ui_id}
|
||||
server={server}
|
||||
backendState={backendState[getSpec(server)]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="mode-title">DNS Server</h4>
|
||||
<p className="mode-description">
|
||||
A recursive DNS resolver using the host's DNS
|
||||
configuration.
|
||||
</p>
|
||||
{servers}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DnsRow({
|
||||
server,
|
||||
backendState,
|
||||
}: {
|
||||
server: DnsState;
|
||||
backendState?: ServerInfo;
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const error = server.error || backendState?.last_exception || undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModeToggle
|
||||
value={server.active}
|
||||
onChange={() =>
|
||||
dispatch(setActive({ server, value: !server.active }))
|
||||
}
|
||||
>
|
||||
Run DNS Server
|
||||
<Popover>
|
||||
<p>Listen Host</p>
|
||||
<ValueEditor
|
||||
className="mode-input"
|
||||
content={server.listen_host || ""}
|
||||
onEditDone={(host) =>
|
||||
dispatch(setListenHost({ server, value: host }))
|
||||
}
|
||||
/>
|
||||
|
||||
<p>Listen Port</p>
|
||||
<ValueEditor
|
||||
className="mode-input"
|
||||
content={
|
||||
server.listen_port
|
||||
? server.listen_port.toString()
|
||||
: ""
|
||||
}
|
||||
placeholder="8080"
|
||||
onEditDone={(port) =>
|
||||
dispatch(
|
||||
setListenPort({
|
||||
server,
|
||||
value: parseInt(port),
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Popover>
|
||||
</ModeToggle>
|
||||
<ServerStatus error={error} backendState={backendState} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -48,8 +48,6 @@ function RegularRow({
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
server.listen_host && console.warn("TODO: implement listen_host");
|
||||
|
||||
const error = server.error || backendState?.last_exception || undefined;
|
||||
|
||||
return (
|
||||
|
@ -1,8 +1,27 @@
|
||||
import * as React from "react";
|
||||
import ReverseToggleRow from "./ReverseToggleRow";
|
||||
import { useAppDispatch, useAppSelector } from "../../ducks";
|
||||
import { addServer } from "../../ducks/modes/reverse";
|
||||
import { getSpec } from "../../modes/reverse";
|
||||
import {
|
||||
addServer,
|
||||
removeServer,
|
||||
setActive,
|
||||
setDestination,
|
||||
setListenHost,
|
||||
setListenPort,
|
||||
setProtocol,
|
||||
} from "../../ducks/modes/reverse";
|
||||
import { getSpec, ReverseState } from "../../modes/reverse";
|
||||
import { ReverseProxyProtocols } from "../../backends/consts";
|
||||
import { ServerInfo } from "../../ducks/backendState";
|
||||
import Dropdown, { MenuItem } from "../common/Dropdown";
|
||||
import ValueEditor from "../editors/ValueEditor";
|
||||
import { ServerStatus } from "./CaptureSetup";
|
||||
import { ModeToggle } from "./ModeToggle";
|
||||
|
||||
interface ReverseToggleRowProps {
|
||||
removable: boolean;
|
||||
server: ReverseState;
|
||||
backendState?: ServerInfo;
|
||||
}
|
||||
|
||||
export default function Reverse() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -36,3 +55,97 @@ export default function Reverse() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ReverseToggleRow({
|
||||
removable,
|
||||
server,
|
||||
backendState,
|
||||
}: ReverseToggleRowProps) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const protocols = Object.values(ReverseProxyProtocols);
|
||||
|
||||
const inner = (
|
||||
<span>
|
||||
<b>{server.protocol} </b>
|
||||
<span className="caret" />
|
||||
</span>
|
||||
);
|
||||
|
||||
const deleteServer = async () => {
|
||||
if (server.active) {
|
||||
await dispatch(setActive({ server, value: false })).unwrap();
|
||||
}
|
||||
await dispatch(removeServer(server));
|
||||
};
|
||||
|
||||
const error = server.error || backendState?.last_exception || undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModeToggle
|
||||
value={server.active}
|
||||
onChange={() => {
|
||||
dispatch(setActive({ server, value: !server.active }));
|
||||
}}
|
||||
>
|
||||
Forward
|
||||
<Dropdown
|
||||
text={inner}
|
||||
className="btn btn-default btn-xs mode-reverse-dropdown"
|
||||
options={{ placement: "bottom" }}
|
||||
>
|
||||
{protocols.map((prot) => (
|
||||
<MenuItem
|
||||
key={prot}
|
||||
onClick={() =>
|
||||
dispatch(setProtocol({ server, value: prot }))
|
||||
}
|
||||
>
|
||||
{prot}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Dropdown>{" "}
|
||||
traffic from{" "}
|
||||
<ValueEditor
|
||||
className="mode-reverse-input"
|
||||
content={server.listen_host || ""}
|
||||
onEditDone={(value) =>
|
||||
dispatch(setListenHost({ server, value }))
|
||||
}
|
||||
placeholder="*"
|
||||
/>
|
||||
<ValueEditor
|
||||
className="mode-reverse-input"
|
||||
content={String(server.listen_port || "")}
|
||||
onEditDone={(value) =>
|
||||
dispatch(
|
||||
setListenPort({
|
||||
server,
|
||||
value: value as unknown as number,
|
||||
}),
|
||||
)
|
||||
}
|
||||
placeholder="8080"
|
||||
/>{" "}
|
||||
to{" "}
|
||||
<ValueEditor
|
||||
className="mode-reverse-input"
|
||||
content={server.destination?.toString() || ""}
|
||||
onEditDone={(value) =>
|
||||
dispatch(setDestination({ server, value }))
|
||||
}
|
||||
placeholder="example.com"
|
||||
/>
|
||||
{removable && (
|
||||
<i
|
||||
className="fa fa-fw fa-trash fa-lg"
|
||||
aria-hidden="true"
|
||||
onClick={deleteServer}
|
||||
></i>
|
||||
)}
|
||||
</ModeToggle>
|
||||
<ServerStatus error={error} backendState={backendState} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,117 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { ModeToggle } from "./ModeToggle";
|
||||
import Dropdown, { MenuItem } from "../common/Dropdown";
|
||||
import ValueEditor from "../editors/ValueEditor";
|
||||
import { useAppDispatch } from "../../ducks";
|
||||
import {
|
||||
removeServer,
|
||||
setActive,
|
||||
setDestination,
|
||||
setListenHost,
|
||||
setListenPort,
|
||||
setProtocol,
|
||||
} from "../../ducks/modes/reverse";
|
||||
import { ReverseProxyProtocols } from "../../backends/consts";
|
||||
import { ReverseState } from "../../modes/reverse";
|
||||
import { ServerStatus } from "./CaptureSetup";
|
||||
import { ServerInfo } from "../../ducks/backendState";
|
||||
|
||||
interface ReverseToggleRowProps {
|
||||
removable: boolean;
|
||||
server: ReverseState;
|
||||
backendState?: ServerInfo;
|
||||
}
|
||||
|
||||
export default function ReverseToggleRow({
|
||||
removable,
|
||||
server,
|
||||
backendState,
|
||||
}: ReverseToggleRowProps) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const protocols = Object.values(ReverseProxyProtocols);
|
||||
|
||||
const inner = (
|
||||
<span>
|
||||
<b>{server.protocol} </b>
|
||||
<span className="caret" />
|
||||
</span>
|
||||
);
|
||||
|
||||
const deleteServer = async () => {
|
||||
if (server.active) {
|
||||
await dispatch(setActive({ server, value: false })).unwrap();
|
||||
}
|
||||
await dispatch(removeServer(server));
|
||||
};
|
||||
|
||||
const error = server.error || backendState?.last_exception || undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModeToggle
|
||||
value={server.active}
|
||||
onChange={() => {
|
||||
dispatch(setActive({ server, value: !server.active }));
|
||||
}}
|
||||
>
|
||||
Forward
|
||||
<Dropdown
|
||||
text={inner}
|
||||
className="btn btn-default btn-xs mode-reverse-dropdown"
|
||||
options={{ placement: "bottom" }}
|
||||
>
|
||||
{protocols.map((prot) => (
|
||||
<MenuItem
|
||||
key={prot}
|
||||
onClick={() =>
|
||||
dispatch(setProtocol({ server, value: prot }))
|
||||
}
|
||||
>
|
||||
{prot}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Dropdown>{" "}
|
||||
traffic from{" "}
|
||||
<ValueEditor
|
||||
className="mode-reverse-input"
|
||||
content={server.listen_host || ""}
|
||||
onEditDone={(value) =>
|
||||
dispatch(setListenHost({ server, value }))
|
||||
}
|
||||
placeholder="*"
|
||||
/>
|
||||
<ValueEditor
|
||||
className="mode-reverse-input"
|
||||
content={String(server.listen_port || "")}
|
||||
onEditDone={(value) =>
|
||||
dispatch(
|
||||
setListenPort({
|
||||
server,
|
||||
value: value as unknown as number,
|
||||
}),
|
||||
)
|
||||
}
|
||||
placeholder="8080"
|
||||
/>{" "}
|
||||
to{" "}
|
||||
<ValueEditor
|
||||
className="mode-reverse-input"
|
||||
content={server.destination?.toString() || ""}
|
||||
onEditDone={(value) =>
|
||||
dispatch(setDestination({ server, value }))
|
||||
}
|
||||
placeholder="example.com"
|
||||
/>
|
||||
{removable && (
|
||||
<i
|
||||
className="fa fa-fw fa-trash fa-lg"
|
||||
aria-hidden="true"
|
||||
onClick={deleteServer}
|
||||
></i>
|
||||
)}
|
||||
</ModeToggle>
|
||||
<ServerStatus error={error} backendState={backendState} />
|
||||
</div>
|
||||
);
|
||||
}
|
104
web/src/js/components/Modes/Upstream.tsx
Normal file
104
web/src/js/components/Modes/Upstream.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import * as React from "react";
|
||||
import { useAppDispatch, useAppSelector } from "../../ducks";
|
||||
import { getSpec, UpstreamState } from "../../modes/upstream";
|
||||
import { ServerInfo } from "../../ducks/backendState";
|
||||
import {
|
||||
setDestination,
|
||||
setActive,
|
||||
setListenHost,
|
||||
setListenPort,
|
||||
} from "../../ducks/modes/upstream";
|
||||
import ValueEditor from "../editors/ValueEditor";
|
||||
import { ServerStatus } from "./CaptureSetup";
|
||||
import { ModeToggle } from "./ModeToggle";
|
||||
import { Popover } from "./Popover";
|
||||
|
||||
export default function Upstream() {
|
||||
const serverState = useAppSelector((state) => state.modes.upstream);
|
||||
const backendState = useAppSelector((state) => state.backendState.servers);
|
||||
|
||||
const servers = serverState.map((server) => {
|
||||
return (
|
||||
<UpstreamRow
|
||||
key={server.ui_id}
|
||||
server={server}
|
||||
backendState={backendState[getSpec(server)]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="mode-title">
|
||||
Explicit HTTP(S) Proxy (With Upstream Proxy)
|
||||
</h4>
|
||||
<p className="mode-description">
|
||||
All requests are forwarded to a second HTTP(S) proxy server.
|
||||
</p>
|
||||
{servers}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UpstreamRow({
|
||||
server,
|
||||
backendState,
|
||||
}: {
|
||||
server: UpstreamState;
|
||||
backendState?: ServerInfo;
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const error = server.error || backendState?.last_exception || undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModeToggle
|
||||
value={server.active}
|
||||
onChange={() => {
|
||||
dispatch(setActive({ server, value: !server.active }));
|
||||
}}
|
||||
>
|
||||
Run HTTP/S Proxy and forward requests to
|
||||
<ValueEditor
|
||||
className="mode-upstream-input"
|
||||
content={server.destination?.toString() || ""}
|
||||
onEditDone={(value) =>
|
||||
dispatch(setDestination({ server, value }))
|
||||
}
|
||||
placeholder="http://example.com:8080"
|
||||
/>
|
||||
<Popover>
|
||||
<p>Listen Host</p>
|
||||
<ValueEditor
|
||||
className="mode-input"
|
||||
content={server.listen_host || ""}
|
||||
onEditDone={(host) =>
|
||||
dispatch(setListenHost({ server, value: host }))
|
||||
}
|
||||
/>
|
||||
|
||||
<p>Listen Port</p>
|
||||
<ValueEditor
|
||||
className="mode-input"
|
||||
content={
|
||||
server.listen_port
|
||||
? server.listen_port.toString()
|
||||
: ""
|
||||
}
|
||||
placeholder="8080"
|
||||
onEditDone={(port) =>
|
||||
dispatch(
|
||||
setListenPort({
|
||||
server,
|
||||
value: parseInt(port),
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Popover>
|
||||
</ModeToggle>
|
||||
<ServerStatus error={error} backendState={backendState} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -5,6 +5,8 @@ import wireguardReducer from "./modes/wireguard";
|
||||
import reverseReducer from "./modes/reverse";
|
||||
import transparentReducer from "./modes/transparent";
|
||||
import socksReducer from "./modes/socks";
|
||||
import upstreamReducer from "./modes/upstream";
|
||||
import dnsReducer from "./modes/dns";
|
||||
|
||||
const modes = combineReducers({
|
||||
regular: regularReducer,
|
||||
@ -13,6 +15,8 @@ const modes = combineReducers({
|
||||
reverse: reverseReducer,
|
||||
transparent: transparentReducer,
|
||||
socks: socksReducer,
|
||||
upstream: upstreamReducer,
|
||||
dns: dnsReducer,
|
||||
//add new modes here
|
||||
});
|
||||
|
||||
|
37
web/src/js/ducks/modes/dns.ts
Normal file
37
web/src/js/ducks/modes/dns.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { DnsState, parseRaw } from "../../modes/dns";
|
||||
import {
|
||||
RECEIVE as RECEIVE_STATE,
|
||||
UPDATE as UPDATE_STATE,
|
||||
} from "../backendState";
|
||||
import { addSetter, createModeUpdateThunk, updateState } from "./utils";
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export const setActive = createModeUpdateThunk<boolean>("modes/dns/setActive");
|
||||
export const setListenHost = createModeUpdateThunk<string | undefined>(
|
||||
"modes/dns/setListenHost",
|
||||
);
|
||||
export const setListenPort = createModeUpdateThunk<number | undefined>(
|
||||
"modes/dns/setListenPort",
|
||||
);
|
||||
|
||||
export const initialState: DnsState[] = [
|
||||
{
|
||||
active: true,
|
||||
ui_id: Math.random(),
|
||||
},
|
||||
];
|
||||
|
||||
export const dnsSlice = createSlice({
|
||||
name: "modes/dns",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
addSetter(builder, "active", setActive);
|
||||
addSetter(builder, "listen_host", setListenHost);
|
||||
addSetter(builder, "listen_port", setListenPort);
|
||||
builder.addCase(RECEIVE_STATE, updateState("dns", parseRaw));
|
||||
builder.addCase(UPDATE_STATE, updateState("dns", parseRaw));
|
||||
},
|
||||
});
|
||||
|
||||
export default dnsSlice.reducer;
|
47
web/src/js/ducks/modes/upstream.ts
Normal file
47
web/src/js/ducks/modes/upstream.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { createModeUpdateThunk, addSetter, updateState } from "./utils";
|
||||
import {
|
||||
RECEIVE as RECEIVE_STATE,
|
||||
UPDATE as UPDATE_STATE,
|
||||
} from "../backendState";
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { parseRaw } from "../../modes/upstream";
|
||||
import { UpstreamState } from "../../modes/upstream";
|
||||
|
||||
export const setActive = createModeUpdateThunk<boolean>(
|
||||
"modes/upstream/setActive",
|
||||
);
|
||||
export const setListenHost = createModeUpdateThunk<string | undefined>(
|
||||
"modes/upstream/setListenHost",
|
||||
);
|
||||
export const setListenPort = createModeUpdateThunk<number | undefined>(
|
||||
"modes/upstream/setListenPort",
|
||||
);
|
||||
|
||||
export const setDestination = createModeUpdateThunk<string>(
|
||||
"modes/upstream/setDestination",
|
||||
);
|
||||
|
||||
export const initialState: UpstreamState[] = [
|
||||
{
|
||||
active: false,
|
||||
destination: "",
|
||||
ui_id: Math.random(),
|
||||
},
|
||||
];
|
||||
|
||||
export const upstreamSlice = createSlice({
|
||||
name: "modes/upstream",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
addSetter(builder, "active", setActive);
|
||||
addSetter(builder, "listen_host", setListenHost);
|
||||
addSetter(builder, "listen_port", setListenPort);
|
||||
addSetter(builder, "destination", setDestination);
|
||||
|
||||
builder.addCase(RECEIVE_STATE, updateState("upstream", parseRaw));
|
||||
builder.addCase(UPDATE_STATE, updateState("upstream", parseRaw));
|
||||
},
|
||||
});
|
||||
|
||||
export default upstreamSlice.reducer;
|
@ -4,6 +4,8 @@ import { getSpec as getWireguardSpec } from "../../modes/wireguard";
|
||||
import { getSpec as getReverseSpec } from "../../modes/reverse";
|
||||
import { getSpec as getTransparentSpec } from "../../modes/transparent";
|
||||
import { getSpec as getSocksSpec } from "../../modes/socks";
|
||||
import { getSpec as getUpstreamSpec } from "../../modes/upstream";
|
||||
import { getSpec as getDnsSpec } from "../../modes/dns";
|
||||
import { fetchApi } from "../../utils";
|
||||
import { BackendState } from "../backendState";
|
||||
import {
|
||||
@ -31,6 +33,8 @@ export async function updateModes(_, thunkAPI) {
|
||||
...modes.reverse.filter(isActiveMode).map(getReverseSpec),
|
||||
...modes.transparent.filter(isActiveMode).map(getTransparentSpec),
|
||||
...modes.socks.filter(isActiveMode).map(getSocksSpec),
|
||||
...modes.upstream.filter(isActiveMode).map(getUpstreamSpec),
|
||||
...modes.dns.filter(isActiveMode).map(getDnsSpec),
|
||||
//add new modes here
|
||||
];
|
||||
const response = await fetchApi.put("/options", {
|
||||
|
17
web/src/js/modes/dns.ts
Normal file
17
web/src/js/modes/dns.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { includeListenAddress, ModeState, RawSpecParts } from ".";
|
||||
|
||||
export interface DnsState extends ModeState {}
|
||||
|
||||
export const getSpec = (m: DnsState): string => {
|
||||
return includeListenAddress("dns", m);
|
||||
};
|
||||
|
||||
export const parseRaw = ({
|
||||
listen_host,
|
||||
listen_port,
|
||||
}: RawSpecParts): DnsState => ({
|
||||
ui_id: Math.random(),
|
||||
active: true,
|
||||
listen_host,
|
||||
listen_port,
|
||||
});
|
29
web/src/js/modes/upstream.ts
Normal file
29
web/src/js/modes/upstream.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { includeListenAddress, ModeState, RawSpecParts } from ".";
|
||||
|
||||
export interface UpstreamState extends ModeState {
|
||||
destination: string;
|
||||
}
|
||||
|
||||
export const defaultReverseState = (): UpstreamState => ({
|
||||
active: false,
|
||||
destination: "",
|
||||
ui_id: Math.random(),
|
||||
});
|
||||
|
||||
export const getSpec = (state: UpstreamState): string => {
|
||||
return includeListenAddress(`upstream:${state.destination}`, state);
|
||||
};
|
||||
|
||||
export const parseRaw = ({
|
||||
data,
|
||||
listen_host,
|
||||
listen_port,
|
||||
}: RawSpecParts): UpstreamState => {
|
||||
return {
|
||||
ui_id: Math.random(),
|
||||
active: true,
|
||||
destination: data || "",
|
||||
listen_host,
|
||||
listen_port,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user