mirror of
https://github.com/go-gost/gost-ui.git
synced 2024-08-11 17:53:29 +00:00
数据流架构完成
This commit is contained in:
Generated
+6850
-9
File diff suppressed because it is too large
Load Diff
+8
-1
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
};
|
||||
+2
-1
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
// 把链接信息保存到本要,下次可以继续使用
|
||||
Vendored
+8
@@ -1 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module 'uuid';
|
||||
declare module 'events';
|
||||
|
||||
interface Window {
|
||||
__GOST_SERVER__: any;
|
||||
__USE_SERVER__: any;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user