数据流架构完成

This commit is contained in:
cnwhy
2023-11-08 19:12:21 +08:00
parent 5a6ae7b921
commit b24efcbb62
13 changed files with 7428 additions and 41 deletions
+6850 -9
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -10,9 +10,16 @@
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@ant-design/pro-components": "^2.6.35",
"antd": "^5.11.0",
"axios": "^1.6.0",
"events": "^3.3.0",
"i": "^0.3.7",
"npm": "^10.2.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
+36 -29
View File
@@ -1,36 +1,43 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import Ctx from './uitls/ctx';
import { useState, useEffect, useCallback } from "react";
import Ctx from "./uitls/ctx";
import { ProLayout } from "@ant-design/pro-components";
import { init, logout, useGolstCofnig } from "./uitls/server";
import { getConfig } from "./api";
import Home from './Pages/Home';
import "./App.css";
function App() {
const [count, setCount] = useState(0)
const gostInfo = useGolstCofnig();
const [gostConfig, setGostConfig] = useState<any>(null);
useEffect(() => {
init();
}, []);
useEffect(() => {
if (gostInfo) {
updateConfig();
}
}, [gostInfo]);
const updateConfig = useCallback(() => {
return getConfig().then((data) => {
setGostConfig(data);
return data;
});
}, []);
return (
<Ctx.Provider value={{}}>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
<Ctx.Provider
value={{
gostConfig,
updateConfig,
logout,
}}
>
{gostInfo ? <ProLayout></ProLayout> : <Home></Home>}
</Ctx.Provider>
)
);
}
export default App
export default App;
+38
View File
@@ -0,0 +1,38 @@
import React from "react";
import { BetaSchemaForm } from "@ant-design/pro-components";
import type {
ProFormColumnsType,
ProFormLayoutType,
} from "@ant-design/pro-components";
type DataItem = { name: string; state: string };
const columns: ProFormColumnsType<DataItem>[] = [
{
title: "gost API 地址",
dataIndex: "addr",
valueType: "text",
},
{
title: "usrname",
dataIndex: "addr",
valueType: "text",
},
{
title: "password",
dataIndex: "addr",
valueType: "password",
},
];
const Home: React.FC = () => {
return (
<div>
<BetaSchemaForm<DataItem>
layoutType="Form"
columns={columns}
></BetaSchemaForm>
</div>
);
};
export default Home;
+49
View File
@@ -0,0 +1,49 @@
import require from "../uitls/require";
import type * as Gost from "./types";
const apis = {
config: "/config",
admissions: "/config/admissions",
authers: "/config/authers",
bypasses: "/config/bypasses",
chains: "/config/chains",
climiters: "/config/climiters",
limiters: "/config/limiters",
rlimiters: "/config/rlimiters",
hops: "/config/hops",
hosts: "/config/hosts",
ingresses: "/config/ingresses",
resolvers: "/config/resolvers",
services: "/config/services",
};
const getRESTfulApi = <T = any>(basePath: string) => {
type G = Partial<T>;
return {
post: (data: G) => require.post(basePath, data),
put: (id: string, data: G) => require.put(`${basePath}/${id}`, data),
delete: (id: string) => require.delete(`${basePath}/${id}`),
};
};
export const admissions = getRESTfulApi<Gost.AdmissionConfig>(
apis["admissions"]
);
export const authers = getRESTfulApi<Gost.AdmissionConfig>(apis["authers"]);
export const bypasses = getRESTfulApi<Gost.BypassConfig>(apis["bypasses"]);
export const chains = getRESTfulApi<Gost.ChainConfig>(apis["chains"]);
export const climiters = getRESTfulApi<Gost.LimiterConfig>(apis["climiters"]);
export const limiters = getRESTfulApi<Gost.LimiterConfig>(apis["limiters"]);
export const rlimiters = getRESTfulApi<Gost.LimiterConfig>(apis["rlimiters"]);
export const hops = getRESTfulApi<Gost.HopConfig>(apis["hops"]);
export const hosts = getRESTfulApi<Gost.HostsConfig>(apis["hosts"]);
export const ingresses = getRESTfulApi<Gost.IngressConfig>(apis["ingresses"]);
export const resolvers = getRESTfulApi<Gost.ResolverConfig>(apis["resolvers"]);
export const services = getRESTfulApi<Gost.ServiceConfig>(apis["services"]);
// 获取当前config
export const getConfig = () =>
require.get(apis.config);
// 保存当前config(使之重启也生效)
export const saveCofnig = () => require.post(apis.config)
+292
View File
@@ -0,0 +1,292 @@
export type APIConfig = {
accesslog: boolean;
addr: string;
auth: AuthConfig;
auther: string;
pathPrefix: string;
};
export type AdmissionConfig = {
file: FileLoader;
http: HTTPLoader;
matchers: string[];
name: string;
redis: RedisLoader;
reload: Duration;
// description: "DEPRECATED by whitelist since beta.4";
reverse: boolean;
whitelist: boolean;
};
export type AuthConfig = {
password: string;
username: string;
};
export type AutherConfig = {
auths: AuthConfig[];
file: FileLoader;
http: HTTPLoader;
name: string;
redis: RedisLoader;
reload: Duration;
};
export type BypassConfig = {
file: FileLoader;
http: HTTPLoader;
matchers: string[];
name: string;
redis: RedisLoader;
reload: Duration;
// description: "DEPRECATED by whitelist since beta.4";
reverse: boolean;
whitelist: boolean;
};
export type ChainConfig = {
// description: 'REMOVED since beta.6\nSelector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`';
hops: HopConfig[];
metadata: Record<string, any>;
name: string;
};
export type ChainGroupConfig = {
chains: string[];
selector: SelectorConfig;
};
export type Config = {
admissions: AdmissionConfig[];
api: APIConfig;
authers: AutherConfig[];
bypasses: BypassConfig[];
chains: ChainConfig[];
climiters: LimiterConfig[];
hops: HopConfig[];
hosts: HostsConfig[];
ingresses: IngressConfig[];
limiters: LimiterConfig[];
log: LogConfig;
metrics: MetricsConfig;
profiling: ProfilingConfig;
recorders: RecorderConfig[];
resolvers: ResolverConfig[];
rlimiters: LimiterConfig[];
services: ServiceConfig[];
tls: TLSConfig;
};
export type ConnectorConfig = {
auth: AuthConfig;
metadata: Record<string, any>;
tls: TLSConfig;
type: string;
};
export type DialerConfig = {
auth: AuthConfig;
metadata: Record<string, any>;
tls: TLSConfig;
type: string;
};
// description: "A Duration represents the elapsed time between two instants\nas an int64 nanosecond count. The representation limits the\nlargest representable duration to approximately 290 years.";
export type Duration = number;
export type FileLoader = {
path: string;
};
export type FileRecorder = {
path: string;
sep: string;
};
export type ForwardNodeConfig = {
addr: string;
bypass: string;
bypasses: string[];
host: string;
name: string;
protocol: string;
};
export type ForwarderConfig = {
name: string;
nodes: ForwardNodeConfig[];
selector: SelectorConfig;
// description: "DEPRECATED by nodes since beta.4";
targets: string[];
};
export type HTTPLoader = {
timeout: Duration;
url: string;
};
export type HandlerConfig = {
auth: AuthConfig;
auther: string;
authers: string[];
chain: string;
chainGroup: ChainGroupConfig;
ingress: string;
metadata: Record<string, any>;
retries: number;
tls: TLSConfig;
type: string;
};
export type HopConfig = {
bypass: string;
bypasses: string[];
hosts: string;
interface: string;
name: string;
nodes: NodeConfig[];
resolver: string;
selector: SelectorConfig;
sockopts: SockOptsConfig;
};
export type HostMappingConfig = {
aliases: string[];
hostname: string;
ip: string;
};
export type HostsConfig = {
file: FileLoader;
http: HTTPLoader;
mappings: HostMappingConfig[];
name: string;
redis: RedisLoader;
reload: Duration;
};
export type IngressConfig = {
file: FileLoader;
http: HTTPLoader;
name: string;
redis: RedisLoader;
reload: Duration;
rules: IngressRuleConfig[];
};
export type IngressRuleConfig = {
endpoint: string;
hostname: string;
};
export type LimiterConfig = {
file: FileLoader;
http: HTTPLoader;
limits: string[];
name: string;
redis: RedisLoader;
reload: Duration;
};
export type ListenerConfig = {
auth: AuthConfig;
auther: string;
authers: string[];
chain: string;
chainGroup: ChainGroupConfig;
metadata: Record<string, any>;
tls: TLSConfig;
type: string;
};
export type LogConfig = {
format: string;
level: string;
output: string;
rotation: LogRotationConfig;
};
export type LogRotationConfig = {
// description: "Compress determines if the rotated log files should be compressed\nusing gzip. The default is not to perform compression.";
compress: boolean;
// description: "LocalTime determines if the time used for formatting the timestamps in\nbackup files is the computer's local time. The default is to use UTC\ntime.";
localTime: boolean;
// description: "MaxAge is the maximum number of days to retain old log files based on the\ntimestamp encoded in their filename. Note that a day is defined as 24\nhours and may not exactly correspond to calendar days due to daylight\nsavings, leap seconds, etc. The default is not to remove old log files\nbased on age.";
maxAge: number;
// description: "MaxBackups is the maximum number of old log files to retain. The default\nis to retain all old log files (though MaxAge may still cause them to get\ndeleted.)";
maxBackups: number;
// description: "MaxSize is the maximum size in megabytes of the log file before it gets\nrotated. It defaults to 100 megabytes.";
maxSize: number;
};
export type MetricsConfig = {
addr: string;
path: string;
};
export type NameserverConfig = {
addr: string;
chain: string;
clientIP: string;
hostname: string;
prefer: string;
timeout: Duration;
ttl: Duration;
};
export type NodeConfig = {
addr: string;
bypass: string;
bypasses: string[];
connector: ConnectorConfig;
dialer: DialerConfig;
host: string;
hosts: string;
interface: string;
metadata: Record<string, any>;
name: string;
protocol: string;
resolver: string;
sockopts: SockOptsConfig;
};
export type ProfilingConfig = {
addr: string;
};
export type RecorderConfig = {
file: FileRecorder;
name: string;
redis: RedisRecorder;
};
export type RecorderObject = {
name: string;
record: string;
};
export type RedisLoader = {
addr: string;
db: number;
key: string;
password: string;
type: string;
};
export type RedisRecorder = {
addr: string;
db: number;
key: string;
password: string;
type: string;
};
export type ResolverConfig = {
name: string;
nameservers: NameserverConfig[];
};
export type SelectorConfig = {
failTimeout: Duration;
maxFails: number;
strategy: string;
};
export type ServiceConfig = {
addr: string;
admission: string;
admissions: string[];
bypass: string;
bypasses: string[];
climiter: string;
forwarder: ForwarderConfig;
handler: HandlerConfig;
hosts: string;
// description: "DEPRECATED by metadata.interface since beta.5";
interface: string;
limiter: string;
listener: ListenerConfig;
metadata: Record<string, any>;
name: string;
recorders: RecorderObject[];
resolver: string;
rlimiter: string;
sockopts: SockOptsConfig;
};
export type SockOptsConfig = {
mark: number;
};
export type TLSConfig = {
caFile: string;
certFile: string;
commonName: string;
keyFile: string;
organization: string;
secure: boolean;
serverName: string;
validity: Duration;
};
View File
View File
+2 -1
View File
@@ -1,7 +1,8 @@
import React from "react";
import { Config } from "../api/types";
type GostCtx = {
gostConfig?: object;
gostConfig?: Partial<Config>;
updateConfig?: () => PromiseLike<object>;
logout?: () => void;
};
+57
View File
@@ -0,0 +1,57 @@
import React, { useState, useEffect } from "react";
import Events from "events";
type userObject<T = any> = {
(v?: T): T;
set(v: T): void;
get(): T;
};
/**
* 不受控转受控
*/
function getUseValue<T = any>(): userObject<T>;
function getUseValue<T = any>(defaultValue: T): userObject<T>;
function getUseValue<T>(get: () => T, set: (val: T) => void): userObject<T>;
function getUseValue<T>(
get: () => T,
set: (val: T) => void,
defaultValue: T
): userObject<T>;
function getUseValue<T>(get?: any, set?: any, defaultValue?: any) {
if (arguments.length < 2) {
let _value: T = arguments[0];
get = () => _value;
set = (v: T) => {
_value = v;
};
}
const valueEvent = new Events();
valueEvent.on("setValue", function (event: T) {
set(event);
valueEvent.emit("upValue", event);
});
if (defaultValue) {
set(defaultValue);
}
const useValue = (_value?: T) => {
const [value, setValue] = useState<T | undefined>(get ? get() : undefined);
useEffect(() => {
_value && valueEvent.emit("setValue", _value);
valueEvent.on("upValue", setValue);
return () => {
valueEvent.off("upValue", setValue);
};
}, []);
return value;
};
useValue.set = (v: T) => {
valueEvent.emit("setValue", v);
};
useValue.get = () => {
return get?.();
};
return useValue;
}
export default getUseValue;
+7 -1
View File
@@ -1,5 +1,11 @@
import axios from "axios";
import {getGost} from './server';
const require = axios.create()
require.interceptors.request.use((config)=>{
const gost = getGost();
config.baseURL = gost?.addr
config.auth = gost?.auth;
return config;
})
export default require;
+81
View File
@@ -0,0 +1,81 @@
import { v4 } from "uuid";
import getUseValue from "./getUseValue";
import axios from "axios";
const gostServerKey = "__GOST_SERVER__";
const uselocalServerKey = "__USE_SERVER__";
const localServersKey = "__GOST_SERVERS__";
type GostApiConfig = {
addr: string;
auth?: {
username: string;
password: string;
};
};
export const useGolstCofnig = getUseValue<GostApiConfig | null>();
Object.defineProperty(window, gostServerKey, {
get: useGolstCofnig.get,
set: useGolstCofnig.set,
});
export const getGost = (): GostApiConfig | null => useGolstCofnig.get();
export const init = async () => {
// 内存
if (window[gostServerKey]) return true;
// sessionStorage
const serverJson = sessionStorage.getItem(gostServerKey);
if (serverJson) {
const server = JSON.parse(serverJson);
await login(server);
return true;
}
// 本地保存的服务器信息
if (window[uselocalServerKey]) {
const server = await getLocalServer(window[uselocalServerKey]);
await login(server);
}
};
const verify = async (arg: GostApiConfig) => {
return axios.head(arg.addr + "/config");
};
export const login = async (arg: GostApiConfig, saveLocal?: false) => {
await verify(arg);
window[gostServerKey] = arg;
window.sessionStorage.setItem(gostServerKey, JSON.stringify(arg));
if (saveLocal) {
save2Local(arg, v4());
}
};
export const logout = async () => {
useGolstCofnig.set(null);
window.sessionStorage.removeItem(gostServerKey);
};
export const save2Local = async (arg: GostApiConfig, id: string) => {
let servers: any = {};
try {
let serversJson = localStorage.getItem(localServersKey);
servers = serversJson ? JSON.parse(serversJson) : [];
} catch (e) {}
servers[id] = arg;
localStorage.setItem(localServersKey, JSON.stringify(servers));
};
export const getLocalServer = async (id: string): Promise<GostApiConfig> => {
let servers: any = {};
try {
let serversJson = localStorage.getItem(localServersKey);
servers = serversJson ? JSON.parse(serversJson) : [];
} catch (e) {}
return servers[id];
};
// 把链接信息保存到本要,下次可以继续使用
+8
View File
@@ -1 +1,9 @@
/// <reference types="vite/client" />
declare module 'uuid';
declare module 'events';
interface Window {
__GOST_SERVER__: any;
__USE_SERVER__: any;
}