mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
refactor(server): config system (#11081)
This commit is contained in:
@@ -1,75 +1,47 @@
|
||||
import { Button } from '@affine/admin/components/ui/button';
|
||||
import { ScrollArea } from '@affine/admin/components/ui/scroll-area';
|
||||
import { Separator } from '@affine/admin/components/ui/separator';
|
||||
import type { RuntimeConfigType } from '@affine/graphql';
|
||||
import { get } from 'lodash-es';
|
||||
import { CheckIcon } from 'lucide-react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { Header } from '../header';
|
||||
import { useNav } from '../nav/context';
|
||||
import {
|
||||
ALL_CONFIG,
|
||||
ALL_CONFIGURABLE_MODULES,
|
||||
type ConfigDescriptor,
|
||||
} from './config';
|
||||
import { ConfigInput } from './config-input';
|
||||
import { ConfirmChanges } from './confirm-changes';
|
||||
import { RuntimeSettingRow } from './runtime-setting-row';
|
||||
import { useGetServerRuntimeConfig } from './use-get-server-runtime-config';
|
||||
import { useUpdateServerRuntimeConfigs } from './use-update-server-runtime-config';
|
||||
import {
|
||||
formatValue,
|
||||
formatValueForInput,
|
||||
isEqual,
|
||||
renderInput,
|
||||
} from './utils';
|
||||
|
||||
export type ModifiedValues = {
|
||||
id: string;
|
||||
expiredValue: any;
|
||||
newValue: any;
|
||||
};
|
||||
import { useAppConfig } from './use-app-config';
|
||||
|
||||
export function SettingsPage() {
|
||||
const { trigger } = useUpdateServerRuntimeConfigs();
|
||||
const { serverRuntimeConfig } = useGetServerRuntimeConfig();
|
||||
const { appConfig, update, save, updates } = useAppConfig();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [configValues, setConfigValues] = useState(
|
||||
serverRuntimeConfig.reduce(
|
||||
(acc, config) => {
|
||||
acc[config.id] = config.value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
)
|
||||
);
|
||||
const modifiedValues: ModifiedValues[] = useMemo(() => {
|
||||
return serverRuntimeConfig
|
||||
.filter(config => !isEqual(config.value, configValues[config.id]))
|
||||
.map(config => ({
|
||||
id: config.id,
|
||||
key: config.key,
|
||||
expiredValue: config.value,
|
||||
newValue: configValues[config.id],
|
||||
}));
|
||||
}, [configValues, serverRuntimeConfig]);
|
||||
const handleSave = useCallback(() => {
|
||||
// post value example: { "key1": "newValue1","key2": "newValue2"}
|
||||
const updates: Record<string, any> = {};
|
||||
|
||||
modifiedValues.forEach(item => {
|
||||
if (item.id && item.newValue !== undefined) {
|
||||
updates[item.id] = item.newValue;
|
||||
}
|
||||
});
|
||||
trigger({ updates });
|
||||
}, [modifiedValues, trigger]);
|
||||
|
||||
const disableSave = modifiedValues.length === 0;
|
||||
const onOpen = useCallback(() => setOpen(true), [setOpen]);
|
||||
const onClose = useCallback(() => setOpen(false), [setOpen]);
|
||||
const onConfirm = useCallback(() => {
|
||||
|
||||
const disableSave = Object.keys(updates).length === 0;
|
||||
|
||||
const saveChanges = useCallback(() => {
|
||||
if (disableSave) {
|
||||
return;
|
||||
}
|
||||
handleSave();
|
||||
onClose();
|
||||
}, [disableSave, handleSave, onClose]);
|
||||
save(
|
||||
Object.entries(updates).map(([key, { to }]) => {
|
||||
const splitAt = key.indexOf('.');
|
||||
const [module, field] = [key.slice(0, splitAt), key.slice(splitAt + 1)];
|
||||
return {
|
||||
module,
|
||||
key: field,
|
||||
value: to,
|
||||
};
|
||||
})
|
||||
);
|
||||
setOpen(false);
|
||||
}, [save, disableSave, updates]);
|
||||
|
||||
return (
|
||||
<div className=" h-screen flex-1 flex-col flex">
|
||||
<Header
|
||||
@@ -87,101 +59,68 @@ export function SettingsPage() {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<AdminPanel
|
||||
configValues={configValues}
|
||||
setConfigValues={setConfigValues}
|
||||
/>
|
||||
<AdminPanel onUpdate={update} appConfig={appConfig} />
|
||||
<ConfirmChanges
|
||||
modifiedValues={modifiedValues}
|
||||
updates={updates}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
onConfirm={saveChanges}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const AdminPanel = ({
|
||||
setConfigValues,
|
||||
configValues,
|
||||
appConfig,
|
||||
onUpdate,
|
||||
}: {
|
||||
setConfigValues: Dispatch<SetStateAction<Record<string, any>>>;
|
||||
configValues: Record<string, any>;
|
||||
appConfig: Record<string, any>;
|
||||
onUpdate: (module: string, field: string, value: any) => void;
|
||||
}) => {
|
||||
const { configGroup } = useGetServerRuntimeConfig();
|
||||
|
||||
const { currentModule } = useNav();
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(key: string, value: any, type: RuntimeConfigType) => {
|
||||
const newValue = formatValueForInput(value, type);
|
||||
setConfigValues(prevValues => ({
|
||||
...prevValues,
|
||||
[key]: newValue,
|
||||
}));
|
||||
},
|
||||
[setConfigValues]
|
||||
);
|
||||
|
||||
return (
|
||||
<ScrollArea>
|
||||
<div className="flex flex-col h-full gap-3 py-5 px-6 w-full max-w-[800px] mx-auto">
|
||||
{configGroup
|
||||
.filter(group => group.moduleName === currentModule)
|
||||
.map(group => {
|
||||
const { moduleName, configs } = group;
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col gap-5"
|
||||
id={moduleName}
|
||||
key={moduleName}
|
||||
>
|
||||
<div className="text-xl font-semibold">{moduleName}</div>
|
||||
{configs?.map((config, index) => {
|
||||
const { id, type, description, updatedAt } = config;
|
||||
const isValueEqual = isEqual(config.value, configValues[id]);
|
||||
const formatServerValue = formatValue(config.value);
|
||||
const formatCurrentValue = formatValue(configValues[id]);
|
||||
return (
|
||||
<div key={id} className="flex flex-col gap-10">
|
||||
{index !== 0 && <Separator />}
|
||||
<RuntimeSettingRow
|
||||
key={id}
|
||||
id={id}
|
||||
description={description}
|
||||
lastUpdatedTime={updatedAt}
|
||||
operation={renderInput(type, configValues[id], value =>
|
||||
handleInputChange(id, value, type)
|
||||
)}
|
||||
>
|
||||
<div style={{ opacity: isValueEqual ? 0 : 1 }}>
|
||||
<span
|
||||
className="line-through"
|
||||
style={{
|
||||
color: 'rgba(198, 34, 34, 1)',
|
||||
backgroundColor: 'rgba(254, 213, 213, 1)',
|
||||
}}
|
||||
>
|
||||
{formatServerValue}
|
||||
</span>{' '}
|
||||
=>{' '}
|
||||
<span
|
||||
style={{
|
||||
color: 'rgba(20, 147, 67, 1)',
|
||||
backgroundColor: 'rgba(225, 250, 177, 1)',
|
||||
}}
|
||||
>
|
||||
{formatCurrentValue}
|
||||
</span>
|
||||
</div>
|
||||
</RuntimeSettingRow>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{ALL_CONFIGURABLE_MODULES.filter(
|
||||
module => module === currentModule
|
||||
).map(module => {
|
||||
const fields = Object.keys(ALL_CONFIG[module]);
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col gap-5"
|
||||
id={`config-module-${module}`}
|
||||
key={module}
|
||||
>
|
||||
<div className="text-xl font-semibold">{module}</div>
|
||||
{fields.map((field, index) => {
|
||||
// @ts-expect-error allow
|
||||
const { desc, type } = ALL_CONFIG[module][
|
||||
field
|
||||
] as ConfigDescriptor;
|
||||
|
||||
return (
|
||||
<div key={field} className="flex flex-col gap-10">
|
||||
{index !== 0 && <Separator />}
|
||||
<RuntimeSettingRow
|
||||
key={field}
|
||||
id={field}
|
||||
description={desc}
|
||||
>
|
||||
<ConfigInput
|
||||
module={module}
|
||||
field={field}
|
||||
type={type}
|
||||
defaultValue={get(appConfig[module], field)}
|
||||
onChange={onUpdate}
|
||||
/>
|
||||
</RuntimeSettingRow>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user