mirror of
https://github.com/go-gost/gost-ui.git
synced 2024-08-11 17:53:29 +00:00
增加复制功能, 优化交互
This commit is contained in:
+5
-180
@@ -1,40 +1,14 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
ConfigProvider,
|
||||
Form,
|
||||
Layout,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
theme,
|
||||
} from "antd";
|
||||
import { ProCard } from "@ant-design/pro-components";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { ConfigProvider, theme } from "antd";
|
||||
import Ctx from "./uitls/ctx";
|
||||
import { init, logout, useGolstCofnig } from "./uitls/server";
|
||||
import * as API from "./api";
|
||||
import Home from "./Pages/Home";
|
||||
import "./App.css";
|
||||
import { configEvent } from "./uitls/events";
|
||||
import { download, jsonFormat } from "./uitls";
|
||||
import ListCard from "./components/ListCard";
|
||||
import ChainCard from "./components/ListCard/Chains";
|
||||
import ServiceCard from "./components/ListCard/Services";
|
||||
import zhCN from "antd/locale/zh_CN";
|
||||
|
||||
const colSpan = {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 12,
|
||||
lg: 12,
|
||||
xl: 12,
|
||||
xxl: 8,
|
||||
};
|
||||
const colSpan1 = {
|
||||
sm: 24,
|
||||
xxl: 8,
|
||||
};
|
||||
import zhCN from "antd/locale/zh_CN";
|
||||
import Manage from "./Pages/Manage";
|
||||
|
||||
function App() {
|
||||
const gostInfo = useGolstCofnig();
|
||||
@@ -88,156 +62,7 @@ function App() {
|
||||
theme={{ algorithm: isDark ? theme.darkAlgorithm : undefined }}
|
||||
locale={zhCN}
|
||||
>
|
||||
{gostInfo ? (
|
||||
<Layout style={{ height: "100vh", overflow: "hidden" }}>
|
||||
<Layout.Header style={{ color: "#FFF" }}>
|
||||
<Row
|
||||
align="middle"
|
||||
justify="space-between"
|
||||
wrap={false}
|
||||
// style={{ padding: "0 15px" }}
|
||||
>
|
||||
<Col color="">
|
||||
<Space>
|
||||
<Select placeholder="快捷操作"></Select>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col>{gostInfo.addr}</Col>
|
||||
<Col>
|
||||
<Space>
|
||||
<Button
|
||||
// type="link"
|
||||
onClick={() => {
|
||||
API.saveCofnig();
|
||||
}}
|
||||
>
|
||||
保存配置
|
||||
</Button>
|
||||
<Button
|
||||
// type="link"
|
||||
onClick={() => {
|
||||
download(jsonFormat(gostConfig), "gost.json");
|
||||
}}
|
||||
>
|
||||
下载当前配置
|
||||
</Button>
|
||||
<Button type="link" onClick={logout}>
|
||||
切换连接
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Layout.Header>
|
||||
<Layout.Content style={{ height: "100%", overflow: "auto" }}>
|
||||
<Row style={{ padding: 16, overflow: "hidden" }}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...colSpan}>
|
||||
<ServiceCard></ServiceCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ChainCard></ChainCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="认证器(Auther)"
|
||||
subTitle="认证器"
|
||||
name="authers"
|
||||
api={API.authers}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="准入控制器(Admission)"
|
||||
subTitle="准入控制器"
|
||||
name="admissions"
|
||||
api={API.admissions}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="分流器(Bypass)"
|
||||
subTitle="分流器"
|
||||
name="bypass"
|
||||
api={API.bypasses}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="主机映射器(Hosts)"
|
||||
subTitle="主机映射器"
|
||||
name="hosts"
|
||||
api={API.hosts}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="跳跃点(Hop)"
|
||||
subTitle="跳跃点"
|
||||
name="hop"
|
||||
api={API.hops}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="Ingress"
|
||||
subTitle="Ingress"
|
||||
name="ingresses"
|
||||
api={API.ingresses}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="域名解析器(Resolver)"
|
||||
subTitle="Resolver"
|
||||
name="resolvers"
|
||||
api={API.resolvers}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ProCard boxShadow title="限速限流">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...colSpan1}>
|
||||
<ListCard
|
||||
title="流量速率限制"
|
||||
subTitle=""
|
||||
name="limiters"
|
||||
api={API.limiters}
|
||||
bordered
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan1}>
|
||||
<ListCard
|
||||
title="请求速率限制"
|
||||
subTitle=""
|
||||
name="rlimiters"
|
||||
api={API.rlimiters}
|
||||
bordered
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan1}>
|
||||
<ListCard
|
||||
title="并发连接数限制"
|
||||
subTitle=""
|
||||
name="climiters"
|
||||
api={API.climiters}
|
||||
bordered
|
||||
></ListCard>
|
||||
</Col>
|
||||
</Row>
|
||||
</ProCard>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ProCard boxShadow title="All Config JSON">
|
||||
<pre>{jsonFormat(gostConfig)}</pre>
|
||||
</ProCard>
|
||||
</Col>
|
||||
</Row>
|
||||
</Row>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
) : (
|
||||
<Home></Home>
|
||||
)}
|
||||
{gostInfo ? <Manage /> : <Home />}
|
||||
</ConfigProvider>
|
||||
</Ctx.Provider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import { Button, Col, Layout, Row, Select, Space } from "antd";
|
||||
import { logout, useGolstCofnig } from "../uitls/server";
|
||||
import { download, jsonFormat } from "../uitls";
|
||||
import Ctx from "../uitls/ctx";
|
||||
import { useContext } from "react";
|
||||
import * as API from "../api";
|
||||
import ListCard from "../components/ListCard";
|
||||
import ChainCard from "../components/ListCard/Chains";
|
||||
import ServiceCard from "../components/ListCard/Services";
|
||||
import { ProCard } from "@ant-design/pro-components";
|
||||
|
||||
const colSpan = {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 12,
|
||||
lg: 12,
|
||||
xl: 12,
|
||||
xxl: 8,
|
||||
};
|
||||
const colSpan1 = {
|
||||
sm: 24,
|
||||
xxl: 8,
|
||||
};
|
||||
|
||||
const Manage = () => {
|
||||
const gostInfo = useGolstCofnig()!;
|
||||
const { gostConfig } = useContext(Ctx);
|
||||
return (
|
||||
<Layout style={{ height: "100vh", overflow: "hidden" }}>
|
||||
<Layout.Header style={{ color: "#FFF" }}>
|
||||
<Row
|
||||
align="middle"
|
||||
justify="space-between"
|
||||
wrap={false}
|
||||
// style={{ padding: "0 15px" }}
|
||||
>
|
||||
<Col color="">
|
||||
<Space>
|
||||
<Select placeholder="快捷操作"></Select>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col>{gostInfo.addr}</Col>
|
||||
<Col>
|
||||
<Space>
|
||||
<Button
|
||||
// type="link"
|
||||
onClick={() => {
|
||||
API.saveCofnig();
|
||||
}}
|
||||
>
|
||||
保存配置
|
||||
</Button>
|
||||
<Button
|
||||
// type="link"
|
||||
onClick={() => {
|
||||
download(jsonFormat(gostConfig!), "gost.json");
|
||||
}}
|
||||
>
|
||||
下载当前配置
|
||||
</Button>
|
||||
<Button type="link" onClick={logout}>
|
||||
切换连接
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Layout.Header>
|
||||
<Layout.Content style={{ height: "100%", overflow: "auto" }}>
|
||||
<Row style={{ padding: 16, overflow: "hidden" }}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...colSpan}>
|
||||
<ServiceCard></ServiceCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ChainCard></ChainCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="认证器(Auther)"
|
||||
subTitle="认证器"
|
||||
name="authers"
|
||||
api={API.authers}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="准入控制器(Admission)"
|
||||
subTitle="准入控制器"
|
||||
name="admissions"
|
||||
api={API.admissions}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="分流器(Bypass)"
|
||||
subTitle="分流器"
|
||||
name="bypass"
|
||||
api={API.bypasses}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="主机映射器(Hosts)"
|
||||
subTitle="主机映射器"
|
||||
name="hosts"
|
||||
api={API.hosts}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="跳跃点(Hop)"
|
||||
subTitle="跳跃点"
|
||||
name="hop"
|
||||
api={API.hops}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="Ingress"
|
||||
subTitle="Ingress"
|
||||
name="ingresses"
|
||||
api={API.ingresses}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan}>
|
||||
<ListCard
|
||||
title="域名解析器(Resolver)"
|
||||
subTitle="Resolver"
|
||||
name="resolvers"
|
||||
api={API.resolvers}
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ProCard boxShadow title="限速限流">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...colSpan1}>
|
||||
<ListCard
|
||||
title="流量速率限制"
|
||||
subTitle=""
|
||||
name="limiters"
|
||||
api={API.limiters}
|
||||
bordered
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan1}>
|
||||
<ListCard
|
||||
title="请求速率限制"
|
||||
subTitle=""
|
||||
name="rlimiters"
|
||||
api={API.rlimiters}
|
||||
bordered
|
||||
></ListCard>
|
||||
</Col>
|
||||
<Col {...colSpan1}>
|
||||
<ListCard
|
||||
title="并发连接数限制"
|
||||
subTitle=""
|
||||
name="climiters"
|
||||
api={API.climiters}
|
||||
bordered
|
||||
></ListCard>
|
||||
</Col>
|
||||
</Row>
|
||||
</ProCard>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ProCard boxShadow title="All Config JSON">
|
||||
<pre>{jsonFormat(gostConfig!)}</pre>
|
||||
</ProCard>
|
||||
</Col>
|
||||
</Row>
|
||||
</Row>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
export default Manage;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useContext, useMemo, useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import JsonForm from "./Json";
|
||||
import templates from "../../uitls/templates";
|
||||
import { Button, message } from "antd";
|
||||
import { Button } from "antd";
|
||||
import { getRESTfulApi } from "../../api";
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import Ctx, { CardCtx } from "../../uitls/ctx";
|
||||
import { CardCtx } from "../../uitls/ctx";
|
||||
import { jsonParse } from "../../uitls";
|
||||
import { GostCommit } from "../../api/local";
|
||||
import { UseTemplates } from "../ListCard/hooks";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
@@ -17,48 +17,20 @@ type Props = {
|
||||
};
|
||||
|
||||
const AddButton: React.FC<Props> = (props) => {
|
||||
const { name, title, api, localApi } = props;
|
||||
const { gostConfig } = useContext(Ctx);
|
||||
const { localList, updateLocalList } = useContext(CardCtx);
|
||||
const dataList = (gostConfig as any)?.[name] || [];
|
||||
const dataSource = [...dataList, ...localList];
|
||||
const ts = useMemo(() => {
|
||||
return templates[name];
|
||||
}, [name]);
|
||||
|
||||
const addService = async (servic: any) => {
|
||||
const data = jsonParse(servic);
|
||||
await api.post(data);
|
||||
};
|
||||
|
||||
const { name, title } = props;
|
||||
const { comm } = useContext(CardCtx);
|
||||
const templates = UseTemplates({ name });
|
||||
|
||||
return (
|
||||
<JsonForm
|
||||
title={`添加 ${title || ""}`}
|
||||
templates={ts}
|
||||
templates={templates}
|
||||
trigger={<Button icon={<PlusOutlined />} size="small" />}
|
||||
onFinish={async (values: any) => {
|
||||
// const { value } = values;
|
||||
// await addService(value);
|
||||
// return true;
|
||||
|
||||
const { value } = values;
|
||||
const json = jsonParse(value);
|
||||
let addName = json.name || `${name}-0`;
|
||||
let rename = json.name ? false : true;
|
||||
const hasName = () => {
|
||||
return dataSource?.find((item: any) => {
|
||||
return item.name === addName;
|
||||
});
|
||||
};
|
||||
while (hasName()) {
|
||||
addName = (addName as string).replace(/\d*$/, (a) => {
|
||||
return String(a == "" ? "-0" : Number(a) + 1);
|
||||
});
|
||||
rename = true;
|
||||
}
|
||||
await addService(JSON.stringify({ ...json, name: addName }));
|
||||
rename && message.info(`已自动处理 name 为 "${addName}"`);
|
||||
return true;
|
||||
await comm!.addValue(json);
|
||||
return true
|
||||
}}
|
||||
></JsonForm>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button, Dropdown, Form, Space } from "antd";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { jsonFormat, jsonStringFormat, jsonParse } from "../../uitls";
|
||||
import MonacoEditor, { MonacoEditorProps } from "react-monaco-editor";
|
||||
import "./userMonacoWorker";
|
||||
import "../../uitls/userMonacoWorker";
|
||||
|
||||
type Template = {
|
||||
label: string;
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { Button, Popconfirm, Space, Table } from "antd";
|
||||
import Ctx from "../../uitls/ctx";
|
||||
import * as API from "../../api";
|
||||
import JsonForm from "../Forms/Json";
|
||||
import { ChainConfig } from "../../api/types";
|
||||
import { jsonFormat, jsonParse } from "../../uitls";
|
||||
import templates from "../../uitls/templates";
|
||||
|
||||
const Chains: React.FC = () => {
|
||||
const { gostConfig, updateConfig } = useContext(Ctx);
|
||||
const { chains } = gostConfig || {};
|
||||
const [json, setJson] = useState<any>(null);
|
||||
const [config, setConfig] = useState("");
|
||||
const ts = useMemo(() => {
|
||||
return templates["chains"];
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (json) {
|
||||
setConfig(JSON.stringify(json));
|
||||
}
|
||||
}, [json]);
|
||||
|
||||
const addService = async (servic: any) => {
|
||||
const data = jsonParse(servic);
|
||||
await API.chains.post(data);
|
||||
};
|
||||
|
||||
const updateService = async (id: string, servic: any) => {
|
||||
const data = jsonParse(servic);
|
||||
await API.chains.put(id, data);
|
||||
};
|
||||
|
||||
const deleteService = async (servic: any) => {
|
||||
if (json === servic) {
|
||||
setJson(null);
|
||||
}
|
||||
await API.chains.delete(servic.name);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: 230, overflow: "auto" }}>
|
||||
<Table
|
||||
size="small"
|
||||
dataSource={chains}
|
||||
columns={[
|
||||
{ title: "Name", dataIndex: "name", width: 100 },
|
||||
{
|
||||
title: "详情",
|
||||
ellipsis: true,
|
||||
render: (value, record: ChainConfig, index) => {
|
||||
const { hops } = record;
|
||||
const _hops = hops.map((hop) => {
|
||||
const { nodes } = hop;
|
||||
const _nodes = nodes.map((node) => {
|
||||
const {
|
||||
addr,
|
||||
connector: { type: connectorType, auth },
|
||||
dialer: { type: dialerType },
|
||||
} = node;
|
||||
const _auth = auth
|
||||
? auth.username + ":" + auth.password + "@"
|
||||
: "";
|
||||
return `${connectorType}${
|
||||
dialerType ? "+" + dialerType : ""
|
||||
}://${_auth}${addr}`;
|
||||
});
|
||||
return _nodes.join(",");
|
||||
});
|
||||
return _hops.join(" -> ");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 120,
|
||||
render: (value, record, index) => {
|
||||
return (
|
||||
<Space size={"small"}>
|
||||
<JsonForm
|
||||
templates={ts}
|
||||
trigger={
|
||||
<Button type="link" size={"small"}>
|
||||
修改
|
||||
</Button>
|
||||
}
|
||||
initialValues={{ value: jsonFormat(record) }}
|
||||
onFinish={async (values: any) => {
|
||||
const { value } = values;
|
||||
await updateService(record.name, value);
|
||||
return true;
|
||||
}}
|
||||
></JsonForm>
|
||||
<Popconfirm
|
||||
title="警告"
|
||||
description="确定要删除吗?"
|
||||
onConfirm={() => deleteService(record)}
|
||||
>
|
||||
<Button type="link" size={"small"}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
></Table>
|
||||
<div>
|
||||
<JsonForm
|
||||
title="添加 Chains"
|
||||
templates={ts}
|
||||
trigger={<Button>新增</Button>}
|
||||
onFinish={async (values: any) => {
|
||||
const { value } = values;
|
||||
await addService(value);
|
||||
return true;
|
||||
}}
|
||||
></JsonForm>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Chains;
|
||||
@@ -1,19 +1,19 @@
|
||||
import { useContext, useMemo, useRef } from "react";
|
||||
import { useContext } from "react";
|
||||
import { Button, Popconfirm, Space, Table } from "antd";
|
||||
import { red, green } from "@ant-design/colors";
|
||||
import Ctx from "../../uitls/ctx";
|
||||
import { getRESTfulApi } from "../../api";
|
||||
import JsonForm from "../Forms/Json";
|
||||
import { jsonFormat, jsonParse } from "../../uitls";
|
||||
import templates from "../../uitls/templates";
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
CopyOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
StopOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { GostCommit } from "../../api/local";
|
||||
import { CardCtx } from "../../uitls/ctx";
|
||||
import { UseListData, UseTemplates } from "../ListCard/hooks";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
@@ -37,48 +37,9 @@ const PublicList: React.FC<Props> = (props) => {
|
||||
keyName = "name",
|
||||
renderConfig = defaultRenderConfig,
|
||||
} = props;
|
||||
const { gostConfig } = useContext(Ctx);
|
||||
const { localList, updateLocalList } = useContext(CardCtx);
|
||||
const dataList = (gostConfig as any)?.[name] || [];
|
||||
const dataSource = [...dataList, ...localList];
|
||||
const ts = useMemo(() => {
|
||||
return templates[name];
|
||||
}, [name]);
|
||||
|
||||
const comm = useRef({
|
||||
updateValue: async (id: string, value: any) => {
|
||||
const data = jsonParse(value);
|
||||
await api.put(id, data);
|
||||
},
|
||||
deleteValue: async (value: any) => {
|
||||
await api.delete(value.name);
|
||||
},
|
||||
dispatch: async (value: any) => {
|
||||
const { deleteValue } = comm.current;
|
||||
if (!localApi) return;
|
||||
await deleteValue(value);
|
||||
await localApi.add(value);
|
||||
updateLocalList?.();
|
||||
},
|
||||
enable: async (value: any) => {
|
||||
if (!localApi) return;
|
||||
// console.log("enable", value);
|
||||
await api.post(value);
|
||||
await localApi.delete(value.name);
|
||||
updateLocalList?.();
|
||||
},
|
||||
updateLocal: async (key: string, value: any) => {
|
||||
if (!localApi) return;
|
||||
const data = jsonParse(value);
|
||||
await localApi.put(key, { ...data, name: key });
|
||||
updateLocalList?.();
|
||||
},
|
||||
deleteLocal: async (value: any) => {
|
||||
if (!localApi) return;
|
||||
await localApi.delete(value.name);
|
||||
updateLocalList?.();
|
||||
},
|
||||
});
|
||||
const { localList, comm } = useContext(CardCtx);
|
||||
const { dataList, dataSource } = UseListData({ localList, name });
|
||||
const templates = UseTemplates({ name });
|
||||
|
||||
return (
|
||||
<div style={{ height: 348, overflow: "auto" }}>
|
||||
@@ -95,7 +56,7 @@ const PublicList: React.FC<Props> = (props) => {
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 90,
|
||||
width: name === "services" ? 120 : 90,
|
||||
align: "right",
|
||||
dataIndex: keyName,
|
||||
render: (value, record, index) => {
|
||||
@@ -107,7 +68,9 @@ const PublicList: React.FC<Props> = (props) => {
|
||||
enable,
|
||||
updateLocal,
|
||||
deleteLocal,
|
||||
} = comm.current;
|
||||
addValue,
|
||||
// } = comm.current;
|
||||
} = comm!;
|
||||
const isEnable = dataList.includes(record);
|
||||
return (
|
||||
<Space size={2}>
|
||||
@@ -144,7 +107,7 @@ const PublicList: React.FC<Props> = (props) => {
|
||||
) : null}
|
||||
<JsonForm
|
||||
title={`修改 ${value || ""}`}
|
||||
templates={ts}
|
||||
templates={templates}
|
||||
trigger={
|
||||
<Button
|
||||
title="修改"
|
||||
@@ -165,6 +128,25 @@ const PublicList: React.FC<Props> = (props) => {
|
||||
}
|
||||
}}
|
||||
></JsonForm>
|
||||
<JsonForm
|
||||
title={`复制自 ${value || ""}`}
|
||||
templates={templates}
|
||||
trigger={
|
||||
<Button
|
||||
title="复制"
|
||||
icon={<CopyOutlined />}
|
||||
type="link"
|
||||
size={"small"}
|
||||
/>
|
||||
}
|
||||
initialValues={{ value: jsonFormat(record) }}
|
||||
onFinish={async (values: any) => {
|
||||
const { value } = values;
|
||||
const json = jsonParse(value);
|
||||
await comm!.addValue(json);
|
||||
return true;
|
||||
}}
|
||||
></JsonForm>
|
||||
<Popconfirm
|
||||
title="警告"
|
||||
description="确定要删除吗?"
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { Button, Popconfirm, Space, Table, message } from "antd";
|
||||
import qs from "qs";
|
||||
// import { ProTable, ProCard } from "@ant-design/pro-components";
|
||||
import Ctx from "../../uitls/ctx";
|
||||
import * as API from "../../api";
|
||||
import JsonForm from "../Forms/Json";
|
||||
import { ServiceConfig } from "../../api/types";
|
||||
import templates from "../../uitls/templates";
|
||||
import { jsonFormat, jsonParse } from "../../uitls";
|
||||
|
||||
const Services: React.FC = () => {
|
||||
const { gostConfig, updateConfig } = useContext(Ctx);
|
||||
const { services: dataList } = gostConfig || {};
|
||||
const [json, setJson] = useState<any>(null);
|
||||
const [config, setConfig] = useState("");
|
||||
const ts = useMemo(() => {
|
||||
return templates["services"];
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (json) {
|
||||
setConfig(JSON.stringify(json));
|
||||
}
|
||||
}, [json]);
|
||||
|
||||
const submit = async (v?: string) => {
|
||||
const data = jsonParse(v || config);
|
||||
if (json) {
|
||||
await API.services.put(data.name, data);
|
||||
} else {
|
||||
await API.services.post(data);
|
||||
}
|
||||
};
|
||||
|
||||
const addService = async (servic: any) => {
|
||||
const data = jsonParse(servic);
|
||||
await API.services.post(data);
|
||||
};
|
||||
|
||||
const updateService = async (id: string, servic: any) => {
|
||||
const data = jsonParse(servic);
|
||||
await API.services.put(id, data);
|
||||
};
|
||||
|
||||
const deleteService = async (servic: any) => {
|
||||
if (json === servic) {
|
||||
setJson(null);
|
||||
}
|
||||
await API.services.delete(servic.name);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: 230, overflow: "auto" }}>
|
||||
<Table
|
||||
size="small"
|
||||
dataSource={dataList}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: "Name", dataIndex: "name", width: 100 },
|
||||
{
|
||||
title: "详情",
|
||||
ellipsis: true,
|
||||
render: (value, record: ServiceConfig, index) => {
|
||||
const { handler, listener, addr, forwarder } = record;
|
||||
const xy =
|
||||
handler.type === listener.type
|
||||
? handler.type
|
||||
: handler.type + "+" + listener.type;
|
||||
const auth = handler.auth
|
||||
? handler.auth.username + ":" + handler.auth.password + "@"
|
||||
: "";
|
||||
const metadata = handler.metadata
|
||||
? qs.stringify(handler.metadata)
|
||||
: "";
|
||||
const targets =
|
||||
forwarder?.nodes.map((item) => item.addr).join(",") || "";
|
||||
|
||||
return `${xy}://${auth}${addr}${targets ? "/" + targets : ""}${
|
||||
metadata ? "?" + metadata : ""
|
||||
}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 120,
|
||||
render: (value, record, index) => {
|
||||
return (
|
||||
<Space size={"small"}>
|
||||
<JsonForm
|
||||
layoutType="ModalForm"
|
||||
templates={ts}
|
||||
trigger={
|
||||
<Button type="link" size={"small"}>
|
||||
修改
|
||||
</Button>
|
||||
}
|
||||
initialValues={{ value: jsonFormat(record) }}
|
||||
onFinish={async (values: any) => {
|
||||
const { value } = values;
|
||||
await updateService(record.name, value);
|
||||
return true;
|
||||
}}
|
||||
></JsonForm>
|
||||
<Popconfirm
|
||||
title="警告"
|
||||
description="确定要删除吗?"
|
||||
onConfirm={() => deleteService(record)}
|
||||
// okText="Yes"
|
||||
// cancelText="No"
|
||||
>
|
||||
<Button type="link" size={"small"}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
></Table>
|
||||
{/* <div>
|
||||
<JsonForm
|
||||
title="添加 Services"
|
||||
templates={ts}
|
||||
trigger={<Button>{"新增"}</Button>}
|
||||
// initialValues={{ value: "" }}
|
||||
onFinish={async (values: any) => {
|
||||
const { value } = values;
|
||||
const json = jsonParse(value);
|
||||
let addName = json.name || "services-0";
|
||||
let rename = json.name ? false : true;
|
||||
const hasName = () => {
|
||||
return dataList?.find((item) => {
|
||||
return item.name === addName;
|
||||
});
|
||||
};
|
||||
while (hasName()) {
|
||||
addName = (addName as string).replace(/\d*$/, (a) => {
|
||||
return String(a == "" ? "-0" : Number(a) + 1);
|
||||
});
|
||||
rename = true;
|
||||
}
|
||||
await addService(JSON.stringify({ ...json, name: addName }));
|
||||
rename && message.info(`name 已自动处理为 "${addName}"`);
|
||||
return true;
|
||||
}}
|
||||
></JsonForm>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Services;
|
||||
@@ -0,0 +1,33 @@
|
||||
import Ctx, { CardCtx } from "../../uitls/ctx";
|
||||
import ConfigTemplates from "../../uitls/templates";
|
||||
import { useContext, useMemo } from "react";
|
||||
|
||||
type UseListDataProps = {
|
||||
name: string;
|
||||
localList?: any[];
|
||||
};
|
||||
|
||||
export const UseTemplates = (props: { name: string }) => {
|
||||
const { name } = props;
|
||||
const templates = useMemo(() => {
|
||||
return ConfigTemplates[name];
|
||||
}, [name]);
|
||||
return templates;
|
||||
};
|
||||
|
||||
export const UseListData = (props: UseListDataProps) => {
|
||||
const { localList = [], name } = props;
|
||||
const { gostConfig } = useContext(Ctx);
|
||||
const dataList = useMemo(
|
||||
() => (gostConfig as any)?.[name] || [],
|
||||
[gostConfig, name]
|
||||
);
|
||||
const dataSource = useMemo(
|
||||
() => [...dataList, ...localList],
|
||||
[dataList, localList]
|
||||
);
|
||||
return {
|
||||
dataList,
|
||||
dataSource,
|
||||
};
|
||||
};
|
||||
@@ -1,10 +1,18 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { getRESTfulApi } from "../../api";
|
||||
import { ProCard } from "@ant-design/pro-components";
|
||||
import PublicList from "../List/Public";
|
||||
import AddButton from "../Forms/AddButton";
|
||||
import { GostCommit } from "../../api/local";
|
||||
import { CardCtx } from "../../uitls/ctx";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { CardCtx, Comm } from "../../uitls/ctx";
|
||||
import { jsonParse } from "../../uitls";
|
||||
import { UseListData } from "./hooks";
|
||||
import { Modal, notification } from "antd";
|
||||
|
||||
export type ListCardProps = {
|
||||
name: string;
|
||||
@@ -46,12 +54,89 @@ const ListCard: React.FC<ListCardProps> = (props) => {
|
||||
setLocalList(data);
|
||||
});
|
||||
}, [localApi]);
|
||||
const addService = useCallback(
|
||||
async (servic: any) => {
|
||||
const data = jsonParse(servic);
|
||||
await api.post(data);
|
||||
},
|
||||
[api]
|
||||
);
|
||||
useEffect(() => {
|
||||
updateLocalList();
|
||||
}, [updateLocalList]);
|
||||
|
||||
const { dataSource } = UseListData({ name, localList });
|
||||
|
||||
const comm = useMemo<Comm>(
|
||||
() => ({
|
||||
updateValue: async (id: string, value: any) => {
|
||||
const data = jsonParse(value);
|
||||
await api.put(id, data);
|
||||
},
|
||||
deleteValue: async (value: any) => {
|
||||
await api.delete(value.name);
|
||||
},
|
||||
addValue: async (json: any) => {
|
||||
let addName = json.name || `${name}-0`;
|
||||
const hasName = () => {
|
||||
return dataSource?.find((item: any) => {
|
||||
return item.name === addName;
|
||||
});
|
||||
};
|
||||
if (hasName()) {
|
||||
const confirmed = await new Promise((resolve, reject) => {
|
||||
Modal.confirm({
|
||||
title: "name无效",
|
||||
content: "是否自动分配name",
|
||||
zIndex: 2000,
|
||||
onOk: () => resolve(true),
|
||||
onCancel: () => resolve(false),
|
||||
});
|
||||
});
|
||||
if (!confirmed) throw false;
|
||||
}
|
||||
while (hasName()) {
|
||||
addName = (addName as string).replace(/\d*$/, (a) => {
|
||||
return String(a == "" ? "-0" : Number(a) + 1);
|
||||
});
|
||||
}
|
||||
await addService(JSON.stringify({ ...json, name: addName }));
|
||||
(json.name !== addName) &&
|
||||
notification.info({
|
||||
description: `新分配 name 为 "${addName}"`,
|
||||
message: "自动修正提醒",
|
||||
});
|
||||
},
|
||||
dispatch: async (value: any) => {
|
||||
if (!localApi) return;
|
||||
await api.delete(value.name);
|
||||
await localApi.add(value);
|
||||
updateLocalList?.();
|
||||
},
|
||||
enable: async (value: any) => {
|
||||
if (!localApi) return;
|
||||
await api.post(value);
|
||||
await localApi.delete(value.name);
|
||||
|
||||
updateLocalList?.();
|
||||
},
|
||||
updateLocal: async (key: string, value: any) => {
|
||||
if (!localApi) return;
|
||||
const data = jsonParse(value);
|
||||
await localApi.put(key, { ...data, name: key });
|
||||
updateLocalList?.();
|
||||
},
|
||||
deleteLocal: async (value: any) => {
|
||||
if (!localApi) return;
|
||||
await localApi.delete(value.name);
|
||||
updateLocalList?.();
|
||||
},
|
||||
}),
|
||||
[addService, api, dataSource, localApi, name, updateLocalList]
|
||||
);
|
||||
|
||||
return (
|
||||
<CardCtx.Provider value={{ localList, updateLocalList }}>
|
||||
<CardCtx.Provider value={{ localList, updateLocalList, name, comm }}>
|
||||
<ProCard
|
||||
boxShadow={boxShadow}
|
||||
bordered={bordered}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Select } from "antd"
|
||||
|
||||
|
||||
// 快捷操作
|
||||
const Quicksetup:React.FC = ()=>{
|
||||
return <Select>
|
||||
|
||||
|
||||
+14
-1
@@ -6,10 +6,23 @@ type GostCtx = {
|
||||
updateConfig?: () => PromiseLike<object>;
|
||||
logout?: () => void;
|
||||
};
|
||||
|
||||
export type Comm = {
|
||||
updateValue: (id: string, value: any) => Promise<void>;
|
||||
deleteValue: (value: any) => Promise<void>;
|
||||
addValue: (json: any) => Promise<void>;
|
||||
dispatch: (value: any) => Promise<void>;
|
||||
enable: (value: any) => Promise<void>;
|
||||
updateLocal: (key: string, value: any) => Promise<void>;
|
||||
deleteLocal: (value: any) => Promise<void>;
|
||||
};
|
||||
|
||||
const Ctx = React.createContext<GostCtx>({});
|
||||
export default Ctx;
|
||||
|
||||
export const CardCtx = React.createContext<{
|
||||
name: string;
|
||||
localList: any[];
|
||||
updateLocalList?: () => void;
|
||||
}>({localList:[]});
|
||||
comm?: Comm;
|
||||
}>({ localList: [], name: "" });
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
/* eslint-disable */
|
||||
import * as monaco from "monaco-editor";
|
||||
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
|
||||
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
|
||||
// import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
|
||||
// import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
|
||||
// import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
|
||||
// @ts-ignore
|
||||
// import { language as yamlLanguage } from "monaco-editor/esm/vs/basic-languages/yaml/yaml";
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
self.MonacoEnvironment = {
|
||||
getWorker(_: any, label: string) {
|
||||
console.log('[label]',label);
|
||||
// setLocaleData(zh_CN.contents);
|
||||
if (label === "json") {
|
||||
return new jsonWorker();
|
||||
}
|
||||
// if (label === "css" || label === "scss" || label === "less") {
|
||||
// return new cssWorker();
|
||||
// }
|
||||
// if (label === "html" || label === "handlebars" || label === "razor") {
|
||||
// return new htmlWorker();
|
||||
// }
|
||||
// if (label === "typescript" || label === "javascript") {
|
||||
// return new tsWorker();
|
||||
// }
|
||||
return new editorWorker();
|
||||
},
|
||||
};
|
||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
allowComments: true,
|
||||
trailingCommas: 'warning',
|
||||
})
|
||||
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
|
||||
|
||||
/* eslint-disable */
|
||||
import * as monaco from "monaco-editor";
|
||||
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
|
||||
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
|
||||
// import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
|
||||
// import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
|
||||
// import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
|
||||
// @ts-ignore
|
||||
// import { language as yamlLanguage } from "monaco-editor/esm/vs/basic-languages/yaml/yaml";
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
self.MonacoEnvironment = {
|
||||
getWorker(_: any, label: string) {
|
||||
console.log('[label]',label);
|
||||
// setLocaleData(zh_CN.contents);
|
||||
if (label === "json") {
|
||||
return new jsonWorker();
|
||||
}
|
||||
// if (label === "css" || label === "scss" || label === "less") {
|
||||
// return new cssWorker();
|
||||
// }
|
||||
// if (label === "html" || label === "handlebars" || label === "razor") {
|
||||
// return new htmlWorker();
|
||||
// }
|
||||
// if (label === "typescript" || label === "javascript") {
|
||||
// return new tsWorker();
|
||||
// }
|
||||
return new editorWorker();
|
||||
},
|
||||
};
|
||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
allowComments: true,
|
||||
trailingCommas: 'warning',
|
||||
})
|
||||
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
|
||||
|
||||
Reference in New Issue
Block a user