feat(core): add admin panel page (#7115)

The path is `/admin-panel`
This commit is contained in:
JimmFly
2024-05-31 09:42:22 +00:00
parent 004390f40c
commit b13151b480
15 changed files with 821 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const root = style({
height: '100vh',
width: '100vw',
display: 'flex',
flexDirection: 'column',
fontSize: cssVar('fontBase'),
position: 'relative',
});
export const container = style({
display: 'flex',
width: '100%',
height: '100%',
});
export const sideBar = style({
width: '300px',
display: 'flex',
flexDirection: 'column',
borderRight: `1px solid ${cssVar('borderColor')}`,
padding: '12px 8px',
height: '100%',
background: cssVar('backgroundPrimaryColor'),
zIndex: 3,
});
export const scrollArea = style({
padding: '24px 0 160px',
background: cssVar('backgroundPrimaryColor'),
});
export const main = style({
display: 'flex',
width: '100%',
flexDirection: 'column',
overflow: 'auto',
alignItems: 'center',
});
export const moduleContainer = style({
width: '100%',
display: 'flex',
flexDirection: 'column',
padding: '16px',
maxWidth: '800px',
margin: 'auto',
gap: 16,
});
export const module = style({
fontSize: cssVar('fontH5'),
fontWeight: 'bold',
marginBottom: 8,
textTransform: 'capitalize',
padding: '16px 0',
borderBottom: `0.5px solid ${cssVar('borderColor')}`,
});

View File

@@ -0,0 +1,165 @@
import { Scrollable } from '@affine/component';
import type { RuntimeConfigType } from '@affine/graphql';
import { FeatureType, getUserFeaturesQuery } from '@affine/graphql';
import { useCallback, useMemo, useState } from 'react';
import {
AdminPanelHeader,
CollapsibleItem,
formatValue,
formatValueForInput,
isEqual,
type ModifiedValues,
renderInput,
RuntimeSettingRow,
useGetServerRuntimeConfig,
} from '../components/admin-panel';
import { useUpdateServerRuntimeConfigs } from '../components/admin-panel/use-update-server-runtime-config';
import { useNavigateHelper } from '../hooks/use-navigate-helper';
import { useQuery } from '../hooks/use-query';
import * as styles from './admin-panel.css';
export const AdminPanel = () => {
const { serverRuntimeConfig, moduleList, configGroup } =
useGetServerRuntimeConfig();
const [currentModule, setCurrentModule] = useState<string>(
moduleList[0].moduleName
);
const { trigger } = useUpdateServerRuntimeConfigs();
const [configValues, setConfigValues] = useState(
serverRuntimeConfig.reduce(
(acc, config) => {
acc[config.id] = config.value;
return acc;
},
{} as Record<string, any>
)
);
const handleInputChange = useCallback(
(key: string, value: any, type: RuntimeConfigType) => {
const newValue = formatValueForInput(value, type);
setConfigValues(prevValues => ({
...prevValues,
[key]: newValue,
}));
},
[]
);
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]);
return (
<div className={styles.root}>
<div className={styles.container}>
<div className={styles.sideBar}>
{moduleList.map(module => (
<CollapsibleItem
key={module.moduleName}
items={module.keys}
title={module.moduleName}
currentModule={currentModule}
changeModule={setCurrentModule}
/>
))}
</div>
<div className={styles.main}>
<AdminPanelHeader
modifiedValues={modifiedValues}
onConfirm={handleSave}
/>
<Scrollable.Root>
<Scrollable.Viewport>
<div className={styles.scrollArea}>
{configGroup
.filter(group => group.moduleName === currentModule)
.map(group => {
const { moduleName, configs } = group;
return (
<div
id={moduleName}
className={styles.moduleContainer}
key={moduleName}
>
<div className={styles.module}>{moduleName}</div>
{configs?.map(config => {
const { id, key, type, description, updatedAt } =
config;
const title = `${key} (${id})`;
const isValueEqual = isEqual(
config.value,
configValues[id]
);
const formatServerValue = formatValue(config.value);
const formatCurrentValue = formatValue(
configValues[id]
);
return (
<RuntimeSettingRow
key={id}
id={key}
title={title}
description={description}
lastUpdatedTime={updatedAt}
operation={renderInput(
type,
configValues[id],
value => handleInputChange(id, value, type)
)}
>
<div style={{ opacity: isValueEqual ? 0 : 1 }}>
{formatServerValue} =&gt; {formatCurrentValue}
</div>
</RuntimeSettingRow>
);
})}
</div>
);
})}
</div>
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
</div>
</div>
</div>
);
};
export const Component = () => {
const { data } = useQuery({
query: getUserFeaturesQuery,
});
const { jumpTo404 } = useNavigateHelper();
const userFeatures = data?.currentUser?.features;
if (!userFeatures || !userFeatures.includes(FeatureType.Admin)) {
jumpTo404();
return null;
}
return <AdminPanel />;
};