131 lines
3.4 KiB
JavaScript
131 lines
3.4 KiB
JavaScript
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>
|
||
);
|
||
}
|