优化
This commit is contained in:
11
client/web/plugins/com.msgbyte.openapi/manifest.json
Normal file
11
client/web/plugins/com.msgbyte.openapi/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"label": "Openapi Platform Plugin",
|
||||
"label.zh-CN": "开放平台插件",
|
||||
"name": "com.msgbyte.openapi",
|
||||
"url": "/plugins/com.msgbyte.openapi/index.js",
|
||||
"version": "0.0.0",
|
||||
"author": "msgbyte",
|
||||
"description": "Provide the operating capability of the open platform for the application",
|
||||
"description.zh-CN": "为应用提供开放平台的操作能力",
|
||||
"requireRestart": true
|
||||
}
|
||||
7
client/web/plugins/com.msgbyte.openapi/package.json
Normal file
7
client/web/plugins/com.msgbyte.openapi/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@plugins/com.msgbyte.openapi",
|
||||
"main": "src/index.ts",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
DefaultFullModalInputEditorRender,
|
||||
FullModalField,
|
||||
Switch,
|
||||
} from '@capital/component';
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import { Translate } from '../../translate';
|
||||
import { useOpenAppAction } from './useOpenAppAction';
|
||||
|
||||
const Bot: React.FC = React.memo(() => {
|
||||
const { capability, bot } = useOpenAppInfo();
|
||||
const { loading, handleChangeAppCapability, handleUpdateBotInfo } =
|
||||
useOpenAppAction();
|
||||
|
||||
return (
|
||||
<div className="plugin-openapi-app-info_bot">
|
||||
<FullModalField
|
||||
title={Translate.enableBotCapability}
|
||||
content={
|
||||
<Switch
|
||||
disabled={loading}
|
||||
checked={capability.includes('bot')}
|
||||
onChange={(checked) => handleChangeAppCapability('bot', checked)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{capability.includes('bot') && (
|
||||
<FullModalField
|
||||
title={Translate.bot.callback}
|
||||
tip={Translate.bot.callbackTip}
|
||||
value={bot?.callbackUrl}
|
||||
editable={true}
|
||||
renderEditor={DefaultFullModalInputEditorRender}
|
||||
onSave={(str: string) =>
|
||||
handleUpdateBotInfo('callbackUrl', String(str))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Bot.displayName = 'Bot';
|
||||
|
||||
export default Bot;
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
FullModalField,
|
||||
DefaultFullModalTextAreaEditorRender,
|
||||
Switch,
|
||||
} from '@capital/component';
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import { useOpenAppAction } from './useOpenAppAction';
|
||||
import { Translate } from '../../translate';
|
||||
|
||||
const OAuth: React.FC = React.memo(() => {
|
||||
const { capability, oauth } = useOpenAppInfo();
|
||||
const { loading, handleChangeAppCapability, handleUpdateOAuthInfo } =
|
||||
useOpenAppAction();
|
||||
|
||||
return (
|
||||
<div className="plugin-openapi-app-info_oauth">
|
||||
<FullModalField
|
||||
title={Translate.oauth.open}
|
||||
content={
|
||||
<Switch
|
||||
disabled={loading}
|
||||
checked={capability.includes('oauth')}
|
||||
onChange={(checked) => handleChangeAppCapability('oauth', checked)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{capability.includes('oauth') && (
|
||||
<FullModalField
|
||||
title={Translate.oauth.allowedCallbackUrls}
|
||||
tip={Translate.oauth.allowedCallbackUrlsTip}
|
||||
content={
|
||||
<>
|
||||
{(oauth?.redirectUrls ?? []).map((url, i) => (
|
||||
<p key={i}>{url}</p>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
value={(oauth?.redirectUrls ?? []).join('\n')}
|
||||
editable={true}
|
||||
renderEditor={DefaultFullModalTextAreaEditorRender}
|
||||
onSave={(str: string) =>
|
||||
handleUpdateOAuthInfo(
|
||||
'redirectUrls',
|
||||
String(str)
|
||||
.split('\n')
|
||||
.map((t) => t.trim())
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
OAuth.displayName = 'OAuth';
|
||||
|
||||
export default OAuth;
|
||||
@@ -0,0 +1,5 @@
|
||||
.plugin-openapi-app-info_profile {
|
||||
h2 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import React from 'react';
|
||||
import {
|
||||
FullModalField,
|
||||
Divider,
|
||||
SensitiveText,
|
||||
Button,
|
||||
Avatar,
|
||||
AvatarUploader,
|
||||
DefaultFullModalInputEditorRender,
|
||||
} from '@capital/component';
|
||||
import { Translate } from '../../translate';
|
||||
import { useOpenAppAction } from './useOpenAppAction';
|
||||
import styled from 'styled-components';
|
||||
import './Profile.less';
|
||||
|
||||
const TwoColumnContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* 基础信息
|
||||
*/
|
||||
const Profile: React.FC = React.memo(() => {
|
||||
const { appId, appSecret, appName, appDesc, appIcon } = useOpenAppInfo();
|
||||
|
||||
const { handleSetAppInfo, handleDeleteApp } = useOpenAppAction();
|
||||
|
||||
return (
|
||||
<div className="plugin-openapi-app-info_profile">
|
||||
<h2>{Translate.app.basicInfo}</h2>
|
||||
|
||||
<TwoColumnContainer>
|
||||
<div>
|
||||
<FullModalField
|
||||
title={Translate.app.appName}
|
||||
value={appName}
|
||||
editable={true}
|
||||
renderEditor={DefaultFullModalInputEditorRender}
|
||||
onSave={(val) => handleSetAppInfo('appName', val)}
|
||||
/>
|
||||
|
||||
<FullModalField
|
||||
title={Translate.app.appDesc}
|
||||
value={appDesc}
|
||||
editable={true}
|
||||
renderEditor={DefaultFullModalInputEditorRender}
|
||||
onSave={(val) => handleSetAppInfo('appDesc', val)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<AvatarUploader
|
||||
onUploadSuccess={(fileInfo) => {
|
||||
handleSetAppInfo('appIcon', fileInfo.url);
|
||||
}}
|
||||
>
|
||||
<Avatar name={appName} src={appIcon} size={72} />
|
||||
</AvatarUploader>
|
||||
</div>
|
||||
</TwoColumnContainer>
|
||||
|
||||
<Divider />
|
||||
|
||||
<h2>{Translate.app.appcret}</h2>
|
||||
|
||||
<div>
|
||||
<FullModalField title="App ID" content={appId} />
|
||||
<FullModalField
|
||||
title="App Secret"
|
||||
content={<SensitiveText text={appSecret} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Button type="primary" danger={true} onClick={handleDeleteApp}>
|
||||
{Translate.delete}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Profile.displayName = 'Profile';
|
||||
|
||||
export default Profile;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import React from 'react';
|
||||
|
||||
const Summary: React.FC = React.memo(() => {
|
||||
const { refresh, ...other } = useOpenAppInfo();
|
||||
|
||||
return <pre>{JSON.stringify(other, undefined, 2)}</pre>;
|
||||
});
|
||||
Summary.displayName = 'Summary';
|
||||
|
||||
export default Summary;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const Webpage: React.FC = React.memo(() => {
|
||||
return <div>开发中</div>;
|
||||
});
|
||||
Webpage.displayName = 'Webpage';
|
||||
|
||||
export default Webpage;
|
||||
@@ -0,0 +1,8 @@
|
||||
.plugin-openapi-app-info {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
.plugin-openapi-app-info_body {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Icon, SidebarView } from '@capital/component';
|
||||
import { Loadable, useEvent } from '@capital/common';
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import { Translate } from '../../translate';
|
||||
import styled from 'styled-components';
|
||||
import './index.less';
|
||||
|
||||
const MenuTitle = styled.div`
|
||||
display: flex;
|
||||
|
||||
.iconify {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
// const Summary = Loadable(() => import('./Summary'));
|
||||
const Profile = Loadable(() => import('./Profile'));
|
||||
const Bot = Loadable(() => import('./Bot'));
|
||||
const Webpage = Loadable(() => import('./Webpage'));
|
||||
const OAuth = Loadable(() => import('./OAuth'));
|
||||
|
||||
const AppInfo: React.FC = React.memo(() => {
|
||||
const { appName, onSelectApp } = useOpenAppInfo();
|
||||
|
||||
const handleBack = useEvent(() => {
|
||||
onSelectApp(null);
|
||||
});
|
||||
|
||||
const menu = useMemo(
|
||||
() => [
|
||||
{
|
||||
type: 'group',
|
||||
title: (
|
||||
<MenuTitle>
|
||||
<Icon icon="mdi:arrow-left" onClick={handleBack} /> {appName}
|
||||
</MenuTitle>
|
||||
),
|
||||
children: [
|
||||
// {
|
||||
// type: 'item',
|
||||
// title: '总览',
|
||||
// content: <Summary />,
|
||||
// isDev: true,
|
||||
// },
|
||||
{
|
||||
type: 'item',
|
||||
title: Translate.app.basicInfo,
|
||||
content: <Profile />,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: Translate.app.bot,
|
||||
content: <Bot />,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: Translate.app.webpage,
|
||||
content: <Webpage />,
|
||||
isDev: true,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: Translate.app.oauth,
|
||||
content: <OAuth />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="plugin-openapi-app-info">
|
||||
<SidebarView menu={menu} defaultContentPath="0.children.0.content" />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
AppInfo.displayName = 'AppInfo';
|
||||
|
||||
export default AppInfo;
|
||||
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
openReconfirmModal,
|
||||
postRequest,
|
||||
showErrorToasts,
|
||||
useAsyncFn,
|
||||
useAsyncRequest,
|
||||
useEvent,
|
||||
} from '@capital/common';
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import type { OpenAppBot, OpenAppCapability, OpenAppOAuth } from '../types';
|
||||
|
||||
/**
|
||||
* 开放应用操作
|
||||
*/
|
||||
export function useOpenAppAction() {
|
||||
const { refresh, appId, capability, onSelectApp } = useOpenAppInfo();
|
||||
|
||||
const [{ loading }, handleChangeAppCapability] = useAsyncRequest(
|
||||
async (targetCapability: OpenAppCapability, checked: boolean) => {
|
||||
const newCapability: OpenAppCapability[] = [...capability];
|
||||
const findIndex = newCapability.findIndex((c) => c === targetCapability);
|
||||
|
||||
if (checked) {
|
||||
if (findIndex === -1) {
|
||||
newCapability.push(targetCapability);
|
||||
}
|
||||
} else {
|
||||
if (findIndex !== -1) {
|
||||
newCapability.splice(findIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
await postRequest('/openapi/app/setAppCapability', {
|
||||
appId,
|
||||
capability: newCapability,
|
||||
});
|
||||
await refresh();
|
||||
},
|
||||
[appId, capability, refresh]
|
||||
);
|
||||
|
||||
const [, handleSetAppInfo] = useAsyncRequest(
|
||||
async (fieldName: string, fieldValue: string) => {
|
||||
await postRequest('/openapi/app/setAppInfo', {
|
||||
appId,
|
||||
fieldName,
|
||||
fieldValue,
|
||||
});
|
||||
await refresh();
|
||||
},
|
||||
[appId, refresh]
|
||||
);
|
||||
|
||||
const [, handleUpdateOAuthInfo] = useAsyncRequest(
|
||||
async <T extends keyof OpenAppOAuth>(name: T, value: OpenAppOAuth[T]) => {
|
||||
await postRequest('/openapi/app/setAppOAuthInfo', {
|
||||
appId,
|
||||
fieldName: name,
|
||||
fieldValue: value,
|
||||
});
|
||||
await refresh();
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const [, handleUpdateBotInfo] = useAsyncRequest(
|
||||
async <T extends keyof OpenAppBot>(name: T, value: OpenAppBot[T]) => {
|
||||
await postRequest('/openapi/app/setAppBotInfo', {
|
||||
appId,
|
||||
fieldName: name,
|
||||
fieldValue: value,
|
||||
});
|
||||
await refresh();
|
||||
},
|
||||
[appId, refresh]
|
||||
);
|
||||
|
||||
const handleDeleteApp = useEvent(() => {
|
||||
openReconfirmModal({
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await postRequest('/openapi/app/delete', {
|
||||
appId,
|
||||
});
|
||||
onSelectApp(null);
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
showErrorToasts(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
handleSetAppInfo,
|
||||
handleDeleteApp,
|
||||
handleChangeAppCapability,
|
||||
handleUpdateOAuthInfo,
|
||||
handleUpdateBotInfo,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { OpenApp } from './types';
|
||||
|
||||
interface OpenAppInfoContextProps extends OpenApp {
|
||||
refresh: () => Promise<void>;
|
||||
onSelectApp: (appId: string | null) => void;
|
||||
}
|
||||
|
||||
const OpenAppInfoContext = React.createContext<OpenAppInfoContextProps>(null);
|
||||
OpenAppInfoContext.displayName = 'OpenAppInfoContext';
|
||||
|
||||
export const OpenAppInfoProvider: React.FC<
|
||||
React.PropsWithChildren<{
|
||||
appInfo: OpenApp;
|
||||
refresh: OpenAppInfoContextProps['refresh'];
|
||||
onSelectApp: OpenAppInfoContextProps['onSelectApp'];
|
||||
}>
|
||||
> = (props) => {
|
||||
return (
|
||||
<OpenAppInfoContext.Provider
|
||||
value={{
|
||||
...props.appInfo,
|
||||
refresh: props.refresh,
|
||||
onSelectApp: props.onSelectApp,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</OpenAppInfoContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useOpenAppInfo() {
|
||||
return useContext(OpenAppInfoContext);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.plugin-openapi-main-panel {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { openModal, closeModal } from '@capital/common';
|
||||
import { Space, Table, Button, Loading } from '@capital/component';
|
||||
import { OpenApp } from './types';
|
||||
import AppInfo from './AppInfo';
|
||||
import { OpenAppInfoProvider } from './context';
|
||||
import { CreateOpenApp } from '../modals/CreateOpenApp';
|
||||
import { ServiceChecker } from '../components/ServiceChecker';
|
||||
import { useOpenAppList } from './useOpenAppList';
|
||||
import { Translate } from '../translate';
|
||||
import './index.less';
|
||||
|
||||
const OpenApiMainPanel: React.FC = React.memo(() => {
|
||||
const { loading, allApps, refresh, appInfo, handleSetSelectedApp } =
|
||||
useOpenAppList();
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: Translate.name,
|
||||
dataIndex: 'appName',
|
||||
},
|
||||
{
|
||||
title: Translate.operation,
|
||||
key: 'action',
|
||||
render: (_, record: OpenApp) => (
|
||||
<Space>
|
||||
<Button onClick={() => handleSetSelectedApp(record._id)}>
|
||||
{Translate.enter}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const handleCreateOpenApp = () => {
|
||||
const key = openModal(
|
||||
<CreateOpenApp
|
||||
onSuccess={() => {
|
||||
refresh();
|
||||
closeModal(key);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Loading spinning={loading} style={{ height: '100%' }}>
|
||||
<div className="plugin-openapi-main-panel">
|
||||
{appInfo ? (
|
||||
<OpenAppInfoProvider
|
||||
appInfo={appInfo}
|
||||
onSelectApp={handleSetSelectedApp}
|
||||
refresh={refresh}
|
||||
>
|
||||
<AppInfo />
|
||||
</OpenAppInfoProvider>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
style={{ marginBottom: 10 }}
|
||||
type="primary"
|
||||
onClick={handleCreateOpenApp}
|
||||
>
|
||||
{Translate.createApplication}
|
||||
</Button>
|
||||
<Table columns={columns} dataSource={allApps} pagination={false} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Loading>
|
||||
);
|
||||
});
|
||||
OpenApiMainPanel.displayName = 'OpenApiMainPanel';
|
||||
|
||||
const OpenApiMainPanelWrapper = () => {
|
||||
return (
|
||||
<ServiceChecker>
|
||||
<OpenApiMainPanel />
|
||||
</ServiceChecker>
|
||||
);
|
||||
};
|
||||
|
||||
export default OpenApiMainPanelWrapper;
|
||||
@@ -0,0 +1,29 @@
|
||||
const openAppCapability = [
|
||||
'bot', // 机器人
|
||||
'webpage', // 网页
|
||||
'oauth', // 第三方登录
|
||||
] as const;
|
||||
|
||||
export type OpenAppCapability = typeof openAppCapability[number];
|
||||
|
||||
export interface OpenAppOAuth {
|
||||
redirectUrls: string[];
|
||||
}
|
||||
|
||||
export interface OpenAppBot {
|
||||
callbackUrl: string;
|
||||
}
|
||||
|
||||
export interface OpenApp {
|
||||
_id: string;
|
||||
appId: string;
|
||||
appSecret: string;
|
||||
appName: string;
|
||||
appDesc: string;
|
||||
appIcon: string;
|
||||
capability: OpenAppCapability[];
|
||||
oauth?: OpenAppOAuth;
|
||||
bot?: OpenAppBot;
|
||||
|
||||
owner: string;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
postRequest,
|
||||
appendUrlSearch,
|
||||
useAsyncRefresh,
|
||||
useLocation,
|
||||
urlSearchParse,
|
||||
isValidStr,
|
||||
useNavigate,
|
||||
} from '@capital/common';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { OpenApp } from './types';
|
||||
|
||||
/**
|
||||
* 开放应用列表
|
||||
*/
|
||||
export function useOpenAppList() {
|
||||
const [selectedAppId, setSelectedAppId] = useState<string | null>(null);
|
||||
const {
|
||||
loading,
|
||||
value: allApps = [],
|
||||
refresh,
|
||||
} = useAsyncRefresh(async (): Promise<OpenApp[]> => {
|
||||
const { data } = await postRequest('/openapi/app/all');
|
||||
|
||||
return data ?? [];
|
||||
}, []);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
// 仅初始化的时候才处理
|
||||
const { appId } = urlSearchParse(location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
});
|
||||
|
||||
if (isValidStr(appId)) {
|
||||
setSelectedAppId(appId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
allApps,
|
||||
refresh,
|
||||
appInfo: allApps.find((a) => a._id === selectedAppId),
|
||||
/**
|
||||
* 设置当前选中的开放app
|
||||
*/
|
||||
handleSetSelectedApp(appId: string | null) {
|
||||
navigate({
|
||||
search: appendUrlSearch({
|
||||
appId,
|
||||
}),
|
||||
});
|
||||
setSelectedAppId(appId);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { LoadingSpinner } from '@capital/component';
|
||||
import { fetchAvailableServices, useAsync } from '@capital/common';
|
||||
import React from 'react';
|
||||
import { Translate } from '../translate';
|
||||
|
||||
/**
|
||||
* 服务监测
|
||||
*/
|
||||
export const ServiceChecker: React.FC<React.PropsWithChildren> = React.memo(
|
||||
(props) => {
|
||||
const { loading, value: enabled } = useAsync(async () => {
|
||||
const services = await fetchAvailableServices();
|
||||
return services.includes('openapi.app');
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
return <div>{Translate.noservice}</div>;
|
||||
}
|
||||
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
);
|
||||
ServiceChecker.displayName = 'ServiceChecker';
|
||||
18
client/web/plugins/com.msgbyte.openapi/src/index.ts
Normal file
18
client/web/plugins/com.msgbyte.openapi/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Loadable, regCustomPanel, regPluginRootRoute } from '@capital/common';
|
||||
import { Translate } from './translate';
|
||||
|
||||
const MainPanel = Loadable(() => import('./MainPanel'));
|
||||
|
||||
regCustomPanel({
|
||||
position: 'setting',
|
||||
icon: '',
|
||||
name: 'com.msgbyte.openapi/mainPanel',
|
||||
label: Translate.openapi,
|
||||
render: MainPanel,
|
||||
});
|
||||
|
||||
regPluginRootRoute({
|
||||
name: 'com.msgbyte.openapi/route',
|
||||
path: '/openapi',
|
||||
component: MainPanel,
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
createFastFormSchema,
|
||||
fieldSchema,
|
||||
ModalWrapper,
|
||||
postRequest,
|
||||
showToasts,
|
||||
showErrorToasts,
|
||||
} from '@capital/common';
|
||||
import { WebFastForm } from '@capital/component';
|
||||
import React from 'react';
|
||||
import { Translate } from '../translate';
|
||||
|
||||
const schema = createFastFormSchema({
|
||||
appName: fieldSchema
|
||||
.string()
|
||||
.required(Translate.appNameCannotBeEmpty)
|
||||
.max(20, Translate.appNameTooLong),
|
||||
appDesc: fieldSchema.string(),
|
||||
});
|
||||
|
||||
const fields = [
|
||||
{ type: 'text', name: 'appName', label: Translate.app.appName },
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'appDesc',
|
||||
label: Translate.app.appDesc,
|
||||
},
|
||||
];
|
||||
|
||||
interface CreateOpenAppProps {
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
export const CreateOpenApp: React.FC<CreateOpenAppProps> = React.memo(
|
||||
(props) => {
|
||||
const handleSubmit = async (values: any) => {
|
||||
try {
|
||||
await postRequest('/openapi/app/create', {
|
||||
...values,
|
||||
appIcon: '',
|
||||
});
|
||||
|
||||
showToasts(Translate.createApplicationSuccess, 'success');
|
||||
props.onSuccess?.();
|
||||
} catch (e) {
|
||||
showErrorToasts(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper title={Translate.createApplication}>
|
||||
<WebFastForm schema={schema} fields={fields} onSubmit={handleSubmit} />
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
CreateOpenApp.displayName = 'CreateOpenApp';
|
||||
101
client/web/plugins/com.msgbyte.openapi/src/translate.ts
Normal file
101
client/web/plugins/com.msgbyte.openapi/src/translate.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { localTrans } from '@capital/common';
|
||||
|
||||
export const Translate = {
|
||||
openapi: localTrans({ 'zh-CN': '开放平台', 'en-US': 'Open Api' }),
|
||||
noservice: localTrans({
|
||||
'zh-CN': '管理员没有开放 Openapi 服务',
|
||||
'en-US': 'The administrator did not open the Openapi service',
|
||||
}),
|
||||
enableBotCapability: localTrans({
|
||||
'zh-CN': '开启机器人能力',
|
||||
'en-US': 'Enable Bot Capability',
|
||||
}),
|
||||
name: localTrans({
|
||||
'zh-CN': '名称',
|
||||
'en-US': 'Name',
|
||||
}),
|
||||
operation: localTrans({
|
||||
'zh-CN': '操作',
|
||||
'en-US': 'Operation',
|
||||
}),
|
||||
delete: localTrans({
|
||||
'zh-CN': '删除',
|
||||
'en-US': 'Delete',
|
||||
}),
|
||||
enter: localTrans({
|
||||
'zh-CN': '进入',
|
||||
'en-US': 'Enter',
|
||||
}),
|
||||
createApplication: localTrans({
|
||||
'zh-CN': '创建应用',
|
||||
'en-US': 'Create Application',
|
||||
}),
|
||||
createApplicationSuccess: localTrans({
|
||||
'zh-CN': '创建应用成功',
|
||||
'en-US': 'Create Application Success',
|
||||
}),
|
||||
appNameCannotBeEmpty: localTrans({
|
||||
'zh-CN': '应用名不能为空',
|
||||
'en-US': 'App Name Cannot be Empty',
|
||||
}),
|
||||
appNameTooLong: localTrans({
|
||||
'zh-CN': '应用名过长',
|
||||
'en-US': 'App Name too Long',
|
||||
}),
|
||||
app: {
|
||||
basicInfo: localTrans({
|
||||
'zh-CN': '基础信息',
|
||||
'en-US': 'Basic Info',
|
||||
}),
|
||||
appName: localTrans({
|
||||
'zh-CN': '应用名称',
|
||||
'en-US': 'App Name',
|
||||
}),
|
||||
appDesc: localTrans({
|
||||
'zh-CN': '应用描述',
|
||||
'en-US': 'App Description',
|
||||
}),
|
||||
bot: localTrans({
|
||||
'zh-CN': '机器人',
|
||||
'en-US': 'Bot',
|
||||
}),
|
||||
webpage: localTrans({
|
||||
'zh-CN': '网页',
|
||||
'en-US': 'Web Page',
|
||||
}),
|
||||
oauth: localTrans({
|
||||
'zh-CN': '第三方登录',
|
||||
'en-US': 'OAuth',
|
||||
}),
|
||||
appcret: localTrans({
|
||||
'zh-CN': '应用凭证',
|
||||
'en-US': 'Application Credentials',
|
||||
}),
|
||||
},
|
||||
bot: {
|
||||
callback: localTrans({
|
||||
'zh-CN': '消息回调地址',
|
||||
'en-US': 'Callback Url',
|
||||
}),
|
||||
callbackTip: localTrans({
|
||||
'zh-CN':
|
||||
'机器人被 @ 的时候会向该地址发送请求(收件箱接受到新内容时会发送回调)',
|
||||
'en-US':
|
||||
'The bot will send a request to this address when it is mentioned (callback will be sent when the inbox receives new content)',
|
||||
}),
|
||||
},
|
||||
oauth: {
|
||||
open: localTrans({
|
||||
'zh-CN': '开启 OAuth',
|
||||
'en-US': 'Open OAuth',
|
||||
}),
|
||||
allowedCallbackUrls: localTrans({
|
||||
'zh-CN': '允许的回调地址',
|
||||
'en-US': 'Allowed Callback Urls',
|
||||
}),
|
||||
allowedCallbackUrlsTip: localTrans({
|
||||
'zh-CN': '多个回调地址单独一行',
|
||||
'en-US': 'Multiple callback addresses on a single line',
|
||||
}),
|
||||
},
|
||||
};
|
||||
9
client/web/plugins/com.msgbyte.openapi/tsconfig.json
Normal file
9
client/web/plugins/com.msgbyte.openapi/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"paths": {
|
||||
"@capital/*": ["../../src/plugin/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user