diff --git a/server/admin/app/ra/components/Image.tsx b/server/admin/app/ra/components/Image.tsx
new file mode 100644
index 00000000..e75cec46
--- /dev/null
+++ b/server/admin/app/ra/components/Image.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+import { parseUrlStr } from '../utils';
+
+export const Image: React.FC<{ src: string }> = React.memo((props) => {
+ return
;
+});
+Image.displayName = 'Image';
diff --git a/server/admin/app/ra/i18n/custom.ts b/server/admin/app/ra/i18n/custom.ts
index 1bc4a6b4..bf8bedb2 100644
--- a/server/admin/app/ra/i18n/custom.ts
+++ b/server/admin/app/ra/i18n/custom.ts
@@ -7,6 +7,9 @@ export const englishCustom = {
permission: 'Permission',
confirmTitle: 'Are you sure you want to perform this operation?',
confirmContent: 'This action cannot be undone',
+ errorOccurred: 'some errors occurred',
+ operateSuccess: 'Operate Success',
+ operateFailed: 'Operate Failed',
},
menu: {
network: 'Tailchat Network',
@@ -66,6 +69,8 @@ export const englishCustom = {
config: {
uploadFileLimit: 'Upload file limit (KB)',
emailVerification: 'Mandatory Email Verification',
+ serverName: 'Server Name',
+ serverEntryImage: 'Server Entry Page Image',
},
},
};
@@ -79,6 +84,9 @@ export const chineseCustom = {
permission: '权限',
confirmTitle: '确认要进行该操作么?',
confirmContent: '该操作不可撤回',
+ errorOccurred: '发生了一些错误',
+ operateSuccess: '操作成功',
+ operateFailed: '操作失败',
},
menu: {
network: 'Tailchat 网络',
@@ -136,6 +144,8 @@ export const chineseCustom = {
config: {
uploadFileLimit: '上传文件限制(KB)',
emailVerification: '邮箱强制验证',
+ serverName: '服务器名',
+ serverEntryImage: '服务器登录图',
},
},
};
diff --git a/server/admin/app/ra/routes/system/index.tsx b/server/admin/app/ra/routes/system/index.tsx
index d35da90f..a949e075 100644
--- a/server/admin/app/ra/routes/system/index.tsx
+++ b/server/admin/app/ra/routes/system/index.tsx
@@ -1,10 +1,11 @@
import React, { PropsWithChildren } from 'react';
import { request } from '../../request';
import { useRequest } from 'ahooks';
-import { CircularProgress, Box, Grid } from '@mui/material';
-import { useTranslate } from 'react-admin';
+import { CircularProgress, Box, Grid, Input } from '@mui/material';
+import { useTranslate, useDelete, useNotify } from 'react-admin';
import DoneIcon from '@mui/icons-material/Done';
import ClearIcon from '@mui/icons-material/Clear';
+import { useEditValue } from '../../utils/hooks';
const SystemItem: React.FC<
PropsWithChildren<{
@@ -29,16 +30,48 @@ SystemItem.displayName = 'SystemItem';
*/
export const SystemConfig: React.FC = React.memo(() => {
const translate = useTranslate();
- const { data: config, loading } = useRequest(async () => {
- const { data } = await request('/config/client');
+ const notify = useNotify();
+ const {
+ data: config,
+ loading,
+ error,
+ } = useRequest(async () => {
+ const { data } = await request.get('/config/client');
return data.config ?? {};
});
+ const [serverName, setServerName, saveServerName] = useEditValue(
+ config?.serverName,
+ async (val) => {
+ if (val === config?.serverName) {
+ return;
+ }
+
+ try {
+ await request.patch('/config/client', {
+ key: 'serverName',
+ value: val,
+ });
+ notify('custom.common.operateSuccess', {
+ type: 'info',
+ });
+ } catch (err) {
+ notify('custom.common.operateFailed', {
+ type: 'info',
+ });
+ }
+ }
+ );
+
if (loading) {
return ;
}
+ if (error) {
+ return
{translate('custom.common.errorOccurred')}
;
+ }
+
return (
{
)}
+
+
+ setServerName(e.target.value)}
+ onBlur={() => saveServerName()}
+ placeholder="Tailchat"
+ />
+
);
});
diff --git a/server/admin/app/ra/utils/hooks.ts b/server/admin/app/ra/utils/hooks.ts
new file mode 100644
index 00000000..6363ba3b
--- /dev/null
+++ b/server/admin/app/ra/utils/hooks.ts
@@ -0,0 +1,15 @@
+import { useCallback, useLayoutEffect, useState } from 'react';
+
+export function useEditValue(value: T, onChange: (val: T) => void) {
+ const [inner, setInner] = useState(value);
+
+ useLayoutEffect(() => {
+ setInner(value);
+ }, [value]);
+
+ const onSave = useCallback(() => {
+ onChange(inner);
+ }, [inner, onChange]);
+
+ return [inner, setInner, onSave] as const;
+}
diff --git a/server/admin/app/ra/utils/index.ts b/server/admin/app/ra/utils/index.ts
new file mode 100644
index 00000000..12bae4e7
--- /dev/null
+++ b/server/admin/app/ra/utils/index.ts
@@ -0,0 +1,8 @@
+/**
+ * parse url, and replace some constants with variable
+ * @param originUrl 原始Url
+ * @returns 解析后的url
+ */
+export function parseUrlStr(originUrl: string): string {
+ return String(originUrl).replace('{BACKEND}', window.location.origin);
+}
diff --git a/server/admin/app/routes/admin/$.tsx b/server/admin/app/routes/admin/$.tsx
index a72cadb5..6a7549ae 100644
--- a/server/admin/app/routes/admin/$.tsx
+++ b/server/admin/app/routes/admin/$.tsx
@@ -1,5 +1,5 @@
-import { App } from '~/ra/App';
-import styles from '~/styles/app.css';
+import { App } from '../../ra/App';
+import styles from '../../styles/app.css';
export function links() {
return [{ rel: 'stylesheet', href: styles }];
diff --git a/server/admin/app/routes/admin/index.tsx b/server/admin/app/routes/admin/index.tsx
index a72cadb5..6a7549ae 100644
--- a/server/admin/app/routes/admin/index.tsx
+++ b/server/admin/app/routes/admin/index.tsx
@@ -1,5 +1,5 @@
-import { App } from '~/ra/App';
-import styles from '~/styles/app.css';
+import { App } from '../../ra/App';
+import styles from '../../styles/app.css';
export function links() {
return [{ rel: 'stylesheet', href: styles }];
diff --git a/server/admin/app/server/index.ts b/server/admin/app/server/index.ts
index 15e2744e..ee9dc17c 100644
--- a/server/admin/app/server/index.ts
+++ b/server/admin/app/server/index.ts
@@ -7,8 +7,13 @@ import mongoose from 'mongoose';
import bodyParser from 'body-parser';
import { apiRouter } from './router/api';
+if (!process.env.MONGO_URL) {
+ console.error('Require env: MONGO_URL');
+ process.exit(1);
+}
+
// 链接数据库
-mongoose.connect(process.env.MONGO_URL!, (error: any) => {
+mongoose.connect(process.env.MONGO_URL, (error: any) => {
if (!error) {
return console.info('Datebase connected');
}
@@ -56,6 +61,11 @@ app.all(
})
);
+app.use((err, req, res, next) => {
+ res.status(500);
+ res.json({ error: err.message });
+});
+
const port = process.env.ADMIN_PORT || 3000;
app.listen(port, () => {
diff --git a/server/admin/app/server/router/config.ts b/server/admin/app/server/router/config.ts
index 992ffdac..134bc0a7 100644
--- a/server/admin/app/server/router/config.ts
+++ b/server/admin/app/server/router/config.ts
@@ -8,12 +8,31 @@ import { auth } from '../middleware/auth';
const router = Router();
-router.get('/client', auth(), async (req, res) => {
- const config = await broker.call('config.client');
+router.get('/client', auth(), async (req, res, next) => {
+ try {
+ const config = await broker.call('config.client');
- res.json({
- config,
- });
+ res.json({
+ config,
+ });
+ } catch (err) {
+ next(err);
+ }
+});
+
+router.patch('/client', auth(), async (req, res, next) => {
+ try {
+ await broker.call('config.setClientConfig', {
+ key: req.body.key,
+ value: req.body.value,
+ });
+
+ res.json({
+ success: true,
+ });
+ } catch (err) {
+ next(err);
+ }
});
export { router as configRouter };
diff --git a/server/admin/tsconfig.json b/server/admin/tsconfig.json
index 969727bd..f22bfbed 100644
--- a/server/admin/tsconfig.json
+++ b/server/admin/tsconfig.json
@@ -16,13 +16,8 @@
"forceConsistentCasingInFileNames": true,
"importsNotUsedAsValues": "error",
"experimentalDecorators": true,
- "baseUrl": ".",
- "paths": {
- "~/*": ["./app/*"]
- },
"jsx": "react-jsx",
-
- // Remix takes care of building everything in `remix build`.
- "noEmit": true
+ "noEmit": true,
+ "baseUrl": "."
}
}
diff --git a/server/models/config.ts b/server/models/config.ts
index f9ddd766..e4558bff 100644
--- a/server/models/config.ts
+++ b/server/models/config.ts
@@ -5,6 +5,7 @@ import {
modelOptions,
Severity,
ReturnModelType,
+ index,
} from '@typegoose/typegoose';
import type { Base } from '@typegoose/typegoose/lib/defaultClasses';
import type { Types } from 'mongoose';
@@ -14,6 +15,7 @@ import type { Types } from 'mongoose';
allowMixed: Severity.ALLOW,
},
})
+@index({ name: 1 })
export class Config implements Base {
_id: Types.ObjectId;
id: string;
@@ -47,14 +49,17 @@ export class Config implements Base {
key: string,
value: any
): Promise {
- await this.updateOne(
+ await this.findOneAndUpdate(
{
name: Config.globalClientConfigName,
},
{
$set: {
- [key]: value,
+ [`data.${key}`]: value,
},
+ },
+ {
+ upsert: true,
}
);
}
diff --git a/server/packages/sdk/src/services/base.ts b/server/packages/sdk/src/services/base.ts
index bb48f62b..67fca5af 100644
--- a/server/packages/sdk/src/services/base.ts
+++ b/server/packages/sdk/src/services/base.ts
@@ -407,9 +407,13 @@ export abstract class TcService extends Service {
* NOTICE: 这里使用Redis作为缓存管理器,因此不需要通知所有的service
*/
async cleanActionCache(actionName: string, keys: string[] = []) {
- await this.broker.cacher.clean(
- `${this.serviceName}.${actionName}:${keys.join('|')}**`
- );
+ if (keys.length === 0) {
+ await this.broker.cacher.clean(`${this.serviceName}.${actionName}`);
+ } else {
+ await this.broker.cacher.clean(
+ `${this.serviceName}.${actionName}:${keys.join('|')}**`
+ );
+ }
}
/**
diff --git a/server/services/core/config.service.ts b/server/services/core/config.service.ts
index 2dc952a7..3cb2d865 100644
--- a/server/services/core/config.service.ts
+++ b/server/services/core/config.service.ts
@@ -71,7 +71,7 @@ class ConfigService extends TcService {
* NOTICE: 返回内容比较简单,因此不建议增加缓存
*/
async client(ctx: TcPureContext) {
- const persistConfig = this.adapter.model.getAllClientPersistConfig();
+ const persistConfig = await this.adapter.model.getAllClientPersistConfig();
return {
uploadFileLimit: config.storage.limit,