This commit is contained in:
2026-05-11 17:19:13 +08:00
parent 03a3973014
commit 1b16641e26
22 changed files with 3012 additions and 847 deletions

130
src/screens/ProxyForm.jsx Normal file
View File

@@ -0,0 +1,130 @@
import React, { useState } from "react";
import { Box, Text, useInput } from "ink";
import TextInput from "ink-text-input";
import { frpAddProxyPlan } from "../lib/tasks.js";
const FIELDS = [
{ key: "name", label: "Name (e.g. ssh)" },
{ key: "type", label: "Type" },
{ key: "localIp", label: "Local IP" },
{ key: "localPort", label: "Local port" },
{ key: "remotePort", label: "Remote port" },
];
export default function ProxyForm({ nav }) {
const [values, setValues] = useState({
name: "",
type: "tcp",
localIp: "127.0.0.1",
localPort: "",
remotePort: "",
});
const [restart, setRestart] = useState(true);
const [step, setStep] = useState(0);
const [error, setError] = useState(null);
const totalRows = FIELDS.length + 1;
useInput((input, key) => {
if (key.tab || key.downArrow) {
setStep((s) => Math.min(s + 1, totalRows - 1));
return;
}
if (key.upArrow) {
setStep((s) => Math.max(s - 1, 0));
return;
}
if (step === totalRows - 1) {
if (input === " ") {
setRestart((r) => !r);
return;
}
if (key.return) {
submit();
return;
}
}
if (key.return) {
if (step < totalRows - 1) {
setStep((s) => s + 1);
return;
}
submit();
}
});
const update = (key) => (value) => {
setValues((v) => ({ ...v, [key]: value }));
};
const submit = () => {
setError(null);
const localPort = Number(values.localPort);
const remotePort = Number(values.remotePort);
if (!Number.isInteger(localPort) || localPort < 1 || localPort > 65535) {
setError("Local port must be an integer in [1, 65535].");
return;
}
if (!Number.isInteger(remotePort) || remotePort < 1 || remotePort > 65535) {
setError("Remote port must be an integer in [1, 65535].");
return;
}
try {
const plan = frpAddProxyPlan({
name: values.name.trim(),
type: values.type.trim() || "tcp",
localIp: values.localIp.trim() || "127.0.0.1",
localPort,
remotePort,
restart,
});
nav.replace({ name: "plan", props: { plan, origin: "frp" } });
} catch (err) {
setError(err.message);
}
};
return (
<Box flexDirection="column">
<Text>Add frp proxy:</Text>
<Box flexDirection="column" marginTop={1}>
{FIELDS.map((field, index) => (
<Box key={field.key}>
<Box width={20}>
<Text color={index === step ? "cyan" : undefined}>
{index === step ? " " : " "}
{field.label}
</Text>
</Box>
<TextInput
value={values[field.key]}
onChange={update(field.key)}
focus={index === step}
/>
</Box>
))}
<Box>
<Box width={20}>
<Text color={step === FIELDS.length ? "cyan" : undefined}>
{step === FIELDS.length ? " " : " "}
Restart frpc
</Text>
</Box>
<Text>[{restart ? "x" : " "}] (Space to toggle)</Text>
</Box>
</Box>
{error ? (
<Box marginTop={1}>
<Text color="red">{error}</Text>
</Box>
) : null}
<Box marginTop={1}>
<Text dimColor>Tab/ next · prev · Space toggles restart · Enter on last submits</Text>
</Box>
</Box>
);
}