feat(admin): allow edit client config serverName edit in admin

pull/90/head
moonrailgun 2 years ago
parent 6c26fe1c15
commit 9c7448b7cb

@ -0,0 +1,7 @@
import React from 'react';
import { parseUrlStr } from '../utils';
export const Image: React.FC<{ src: string }> = React.memo((props) => {
return <img src={parseUrlStr(props.src)} />;
});
Image.displayName = 'Image';

@ -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: '服务器登录图',
},
},
};

@ -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 <CircularProgress />;
}
if (error) {
return <div>{translate('custom.common.errorOccurred')}</div>;
}
return (
<Box
sx={{
@ -58,6 +91,15 @@ export const SystemConfig: React.FC = React.memo(() => {
<ClearIcon fontSize="small" />
)}
</SystemItem>
<SystemItem label={translate('custom.config.serverName')}>
<Input
value={serverName}
onChange={(e) => setServerName(e.target.value)}
onBlur={() => saveServerName()}
placeholder="Tailchat"
/>
</SystemItem>
</Box>
);
});

@ -0,0 +1,15 @@
import { useCallback, useLayoutEffect, useState } from 'react';
export function useEditValue<T>(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;
}

@ -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);
}

@ -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 }];

@ -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 }];

@ -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, () => {

@ -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 };

@ -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": "."
}
}

@ -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<void> {
await this.updateOne(
await this.findOneAndUpdate(
{
name: Config.globalClientConfigName,
},
{
$set: {
[key]: value,
[`data.${key}`]: value,
},
},
{
upsert: true,
}
);
}

@ -407,9 +407,13 @@ export abstract class TcService extends Service {
* NOTICE: 使Redisservice
*/
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('|')}**`
);
}
}
/**

@ -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,

Loading…
Cancel
Save