mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
59
packages/frontend/core/src/pages/admin-panel.css.ts
Normal file
59
packages/frontend/core/src/pages/admin-panel.css.ts
Normal 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')}`,
|
||||
});
|
||||
165
packages/frontend/core/src/pages/admin-panel.tsx
Normal file
165
packages/frontend/core/src/pages/admin-panel.tsx
Normal 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} => {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 />;
|
||||
};
|
||||
Reference in New Issue
Block a user