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

106
src/app.jsx Normal file
View File

@@ -0,0 +1,106 @@
import React, { useState, useCallback } from "react";
import { render, Box, Text, useApp, useInput } from "ink";
import MainMenu from "./screens/MainMenu.jsx";
import FrpMenu from "./screens/FrpMenu.jsx";
import TokenPrompt from "./screens/TokenPrompt.jsx";
import ProxyForm from "./screens/ProxyForm.jsx";
import ProxyList from "./screens/ProxyList.jsx";
import PlanPreview from "./screens/PlanPreview.jsx";
import RunLog from "./screens/RunLog.jsx";
import FrpConfigForm from "./screens/FrpConfigForm.jsx";
function App() {
const { exit } = useApp();
const [stack, setStack] = useState([{ name: "main" }]);
const screen = stack[stack.length - 1];
const push = useCallback((next) => {
setStack((current) => [...current, next]);
}, []);
const replace = useCallback((next) => {
setStack((current) => [...current.slice(0, -1), next]);
}, []);
const back = useCallback(() => {
setStack((current) => (current.length > 1 ? current.slice(0, -1) : current));
}, []);
const home = useCallback(() => {
setStack([{ name: "main" }]);
}, []);
useInput((input, key) => {
if (key.escape && stack.length > 1 && screen.name !== "run") {
back();
}
if (input === "q" && screen.name === "main") {
exit();
}
});
const nav = { push, replace, back, home, exit };
return (
<Box flexDirection="column" paddingX={1}>
<Header />
<Box flexDirection="column" marginTop={1}>
{renderScreen(screen, nav)}
</Box>
<Footer screen={screen.name} />
</Box>
);
}
function renderScreen(screen, nav) {
switch (screen.name) {
case "main":
return <MainMenu nav={nav} />;
case "frp":
return <FrpMenu nav={nav} />;
case "token":
return <TokenPrompt nav={nav} {...screen.props} />;
case "frp-config-form":
return <FrpConfigForm nav={nav} {...screen.props} />;
case "proxy-form":
return <ProxyForm nav={nav} {...screen.props} />;
case "proxy-list":
return <ProxyList nav={nav} {...screen.props} />;
case "plan":
return <PlanPreview nav={nav} {...screen.props} />;
case "run":
return <RunLog nav={nav} {...screen.props} />;
default:
return <Text>Unknown screen: {screen.name}</Text>;
}
}
function Header() {
return (
<Box borderStyle="round" borderColor="cyan" paddingX={1}>
<Text color="cyan" bold>server-config</Text>
<Text dimColor> zsh · ssh · frp client</Text>
</Box>
);
}
function Footer({ screen }) {
const hint =
screen === "main"
? "↑↓ select · Enter confirm · q quit"
: screen === "run"
? "(Esc disabled while running)"
: "↑↓ select · Enter confirm · Esc back";
return (
<Box marginTop={1}>
<Text dimColor>{hint}</Text>
</Box>
);
}
export function start() {
render(<App />);
}
start();