From b13151b48068e04d8b1f0b17019b9008fe7d3067 Mon Sep 17 00:00:00 2001 From: JimmFly <447268514@qq.com> Date: Fri, 31 May 2024 09:42:22 +0000 Subject: [PATCH] feat(core): add admin panel page (#7115) The path is `/admin-panel` --- .../admin-panel/admin-panel-header.tsx | 71 ++++++++ .../admin-panel/collapsible-item.tsx | 74 ++++++++ .../src/components/admin-panel/index.css.ts | 157 +++++++++++++++++ .../core/src/components/admin-panel/index.ts | 5 + .../admin-panel/runtime-setting-row.tsx | 36 ++++ .../use-get-server-runtime-config.ts | 57 ++++++ .../use-update-server-runtime-config.ts | 41 +++++ .../core/src/components/admin-panel/utils.tsx | 61 +++++++ .../core/src/pages/admin-panel.css.ts | 59 +++++++ .../frontend/core/src/pages/admin-panel.tsx | 165 ++++++++++++++++++ packages/frontend/core/src/router.tsx | 4 + .../src/graphql/get-server-runtime-config.gql | 11 ++ .../frontend/graphql/src/graphql/index.ts | 33 ++++ .../graphql/update-server-runtime-configs.gql | 6 + packages/frontend/graphql/src/schema.ts | 41 +++++ 15 files changed, 821 insertions(+) create mode 100644 packages/frontend/core/src/components/admin-panel/admin-panel-header.tsx create mode 100644 packages/frontend/core/src/components/admin-panel/collapsible-item.tsx create mode 100644 packages/frontend/core/src/components/admin-panel/index.css.ts create mode 100644 packages/frontend/core/src/components/admin-panel/index.ts create mode 100644 packages/frontend/core/src/components/admin-panel/runtime-setting-row.tsx create mode 100644 packages/frontend/core/src/components/admin-panel/use-get-server-runtime-config.ts create mode 100644 packages/frontend/core/src/components/admin-panel/use-update-server-runtime-config.ts create mode 100644 packages/frontend/core/src/components/admin-panel/utils.tsx create mode 100644 packages/frontend/core/src/pages/admin-panel.css.ts create mode 100644 packages/frontend/core/src/pages/admin-panel.tsx create mode 100644 packages/frontend/graphql/src/graphql/get-server-runtime-config.gql create mode 100644 packages/frontend/graphql/src/graphql/update-server-runtime-configs.gql diff --git a/packages/frontend/core/src/components/admin-panel/admin-panel-header.tsx b/packages/frontend/core/src/components/admin-panel/admin-panel-header.tsx new file mode 100644 index 0000000000..6e7c3b2b58 --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/admin-panel-header.tsx @@ -0,0 +1,71 @@ +import { Button, useConfirmModal } from '@affine/component'; +import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper'; +import { ArrowRightBigIcon, Logo1Icon } from '@blocksuite/icons'; +import { useCallback } from 'react'; + +import * as styles from './index.css'; +import { formatValue } from './utils'; + +export type ModifiedValues = { + id: string; + key: string; + expiredValue: any; + newValue: any; +}; + +export const AdminPanelHeader = ({ + modifiedValues, + onConfirm, +}: { + modifiedValues: ModifiedValues[]; + onConfirm: () => void; +}) => { + const { openConfirmModal } = useConfirmModal(); + const { jumpToIndex } = useNavigateHelper(); + + const handleJumpToIndex = useCallback(() => jumpToIndex(), [jumpToIndex]); + + return ( +
+ +
+ After editing, please click the Save button on the right. + +
+
+ +
+
+ ); +}; diff --git a/packages/frontend/core/src/components/admin-panel/collapsible-item.tsx b/packages/frontend/core/src/components/admin-panel/collapsible-item.tsx new file mode 100644 index 0000000000..196bac76fb --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/collapsible-item.tsx @@ -0,0 +1,74 @@ +import { ArrowDownSmallIcon } from '@blocksuite/icons'; +import * as Collapsible from '@radix-ui/react-collapsible'; +import { useCallback, useState } from 'react'; + +import * as styles from './index.css'; + +export const CollapsibleItem = ({ + items, + initialOpen = false, + title, + currentModule, + changeModule, +}: { + title: string; + items: string[]; + initialOpen?: boolean; + currentModule?: string; + changeModule?: (module: string) => void; +}) => { + const [open, setOpen] = useState(initialOpen); + + const handleClick = useCallback( + (id: string, event?: React.MouseEvent) => { + event?.preventDefault(); + const targetElement = document.getElementById(id); + if (targetElement) { + targetElement.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + } + changeModule?.(title); + }, + [changeModule, title] + ); + + return ( + +
+ + + + handleClick(title, e)} + > + {title} + +
+ +
+ {items.map((item, index) => ( + handleClick(item)} + > + + {item} + + + ))} +
+
+ ); +}; diff --git a/packages/frontend/core/src/components/admin-panel/index.css.ts b/packages/frontend/core/src/components/admin-panel/index.css.ts new file mode 100644 index 0000000000..82d406fe52 --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/index.css.ts @@ -0,0 +1,157 @@ +import { cssVar } from '@toeverything/theme'; +import { style } from '@vanilla-extract/css'; + +export const header = style({ + width: '100%', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '12px 16px', + borderBottom: `1px solid ${cssVar('borderColor')}`, + gap: 8, + position: 'sticky', + backgroundColor: cssVar('backgroundPrimaryColor'), + zIndex: 2, +}); + +export const logo = style({ + fontSize: 32, + cursor: 'pointer', +}); + +export const title = style({ + fontSize: cssVar('fontH3'), + fontWeight: 'bold', + display: 'flex', + alignItems: 'center', + gap: 8, + whiteSpace: 'pre-wrap', + textAlign: 'center', + justifyContent: 'center', +}); + +export const outLine = style({ + width: '100%', + display: 'flex', + flexDirection: 'column', + flexWrap: 'wrap', +}); + +export const outLineHeader = style({ + display: 'flex', + width: '100%', + cursor: 'pointer', + alignItems: 'center', + wordBreak: 'break-all', + wordWrap: 'break-word', + fontSize: cssVar('fontBase'), + fontWeight: 'bold', + textTransform: 'capitalize', + ':hover': { + backgroundColor: cssVar('hoverColor'), + }, + selectors: { + '&[data-active=true]': { + background: cssVar('hoverColor'), + }, + }, + color: cssVar('textPrimaryColor'), + borderRadius: 4, + padding: 4, + marginBottom: 4, + ':visited': { + color: cssVar('textPrimaryColor'), + }, +}); + +export const arrowIcon = style({ + transform: 'rotate(-90deg)', + selectors: { + '&[data-open=true]': { + transform: 'rotate(0deg) translateY(3px) translateX(-3px)', + }, + }, + transition: 'transform 0.2s', +}); + +export const collapsibleContainer = style({ + display: 'flex', + width: '100%', + flexDirection: 'column', + wordBreak: 'break-all', + wordWrap: 'break-word', +}); + +export const outLineContent = style({ + fontSize: cssVar('fontSm'), + cursor: 'pointer', + borderRadius: 4, + padding: '4px 18px', + color: cssVar('textPrimaryColor'), + textTransform: 'capitalize', + ':hover': { + backgroundColor: cssVar('hoverColor'), + }, +}); + +export const navText = style({ + width: '100%', + color: cssVar('textPrimaryColor'), + padding: '4px 0px', + ':visited': { + color: cssVar('textPrimaryColor'), + }, +}); + +export const settingItem = style({ + maxWidth: '800px', + minHeight: '100px', + display: 'flex', + gap: 8, + padding: '16px 0', + borderBottom: `0.5px solid ${cssVar('borderColor')}`, +}); + +export const LeftItem = style({ + display: 'flex', + flexDirection: 'column', + gap: 4, + width: '60%', +}); + +export const RightItem = style({ + display: 'flex', + flexDirection: 'column', + gap: 8, + fontSize: cssVar('fontXs'), + color: cssVar('textPrimaryColor'), + width: '40%', + alignItems: 'flex-end', +}); + +export const settingItemTitle = style({ + fontSize: cssVar('fontBase'), + fontWeight: 'bold', + wordBreak: 'break-all', + wordWrap: 'break-word', + marginBottom: 8, +}); + +export const settingItemDescription = style({ + fontSize: cssVar('fontXs'), + color: cssVar('textSecondaryColor'), +}); + +export const changedValues = style({ + background: cssVar('backgroundSecondaryColor'), + display: 'flex', + flexDirection: 'column', + gap: 4, + minHeight: '64px', + borderRadius: 4, + padding: '12px 16px 16px 12px', + marginTop: 8, + overflow: 'hidden', + color: cssVar('textPrimaryColor'), + fontSize: cssVar('fontSm'), +}); diff --git a/packages/frontend/core/src/components/admin-panel/index.ts b/packages/frontend/core/src/components/admin-panel/index.ts new file mode 100644 index 0000000000..e85d9a3f4f --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/index.ts @@ -0,0 +1,5 @@ +export * from './admin-panel-header'; +export * from './collapsible-item'; +export * from './runtime-setting-row'; +export * from './use-get-server-runtime-config'; +export * from './utils'; diff --git a/packages/frontend/core/src/components/admin-panel/runtime-setting-row.tsx b/packages/frontend/core/src/components/admin-panel/runtime-setting-row.tsx new file mode 100644 index 0000000000..f298ea9a6c --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/runtime-setting-row.tsx @@ -0,0 +1,36 @@ +import { type ReactNode } from 'react'; + +import * as styles from './index.css'; + +export const RuntimeSettingRow = ({ + id, + title, + description, + lastUpdatedTime, + operation, + children, +}: { + id: string; + title: string; + description: string; + lastUpdatedTime: string; + operation: ReactNode; + children: ReactNode; +}) => { + const formatTime = new Date(lastUpdatedTime).toLocaleString(); + return ( +
+
+
{title}
+
{description}
+
+ last updated at: {formatTime} +
+
+
+ {operation} + {children} +
+
+ ); +}; diff --git a/packages/frontend/core/src/components/admin-panel/use-get-server-runtime-config.ts b/packages/frontend/core/src/components/admin-panel/use-get-server-runtime-config.ts new file mode 100644 index 0000000000..333d3604fe --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/use-get-server-runtime-config.ts @@ -0,0 +1,57 @@ +import { useQuery } from '@affine/core/hooks/use-query'; +import { getServerRuntimeConfigQuery } from '@affine/graphql'; +import { useMemo } from 'react'; + +export const useGetServerRuntimeConfig = () => { + const { data } = useQuery({ + query: getServerRuntimeConfigQuery, + }); + + const serverRuntimeConfig = useMemo( + () => + data?.serverRuntimeConfig.sort((a, b) => a.id.localeCompare(b.id)) ?? [], + [data] + ); + + // collect all the modules and config keys in each module + const moduleList = useMemo(() => { + const moduleMap: { [key: string]: string[] } = {}; + + serverRuntimeConfig.forEach(config => { + if (!moduleMap[config.module]) { + moduleMap[config.module] = []; + } + moduleMap[config.module].push(config.key); + }); + + return Object.keys(moduleMap) + .sort((a, b) => a.localeCompare(b)) + .map(moduleName => ({ + moduleName, + keys: moduleMap[moduleName].sort((a, b) => a.localeCompare(b)), + })); + }, [serverRuntimeConfig]); + + // group config by module name + const configGroup = useMemo(() => { + const configMap = new Map(); + + serverRuntimeConfig.forEach(config => { + if (!configMap.has(config.module)) { + configMap.set(config.module, []); + } + configMap.get(config.module)?.push(config); + }); + + return Array.from(configMap.entries()).map(([moduleName, configs]) => ({ + moduleName, + configs, + })); + }, [serverRuntimeConfig]); + + return { + serverRuntimeConfig, + moduleList, + configGroup, + }; +}; diff --git a/packages/frontend/core/src/components/admin-panel/use-update-server-runtime-config.ts b/packages/frontend/core/src/components/admin-panel/use-update-server-runtime-config.ts new file mode 100644 index 0000000000..ac3ee7f819 --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/use-update-server-runtime-config.ts @@ -0,0 +1,41 @@ +import { notify } from '@affine/component'; +import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; +import { + useMutateQueryResource, + useMutation, +} from '@affine/core/hooks/use-mutation'; +import { + getServerRuntimeConfigQuery, + updateServerRuntimeConfigsMutation, +} from '@affine/graphql'; + +export const useUpdateServerRuntimeConfigs = () => { + const { trigger, isMutating } = useMutation({ + mutation: updateServerRuntimeConfigsMutation, + }); + const revalidate = useMutateQueryResource(); + + return { + trigger: useAsyncCallback( + async (values: any) => { + try { + await trigger(values); + await revalidate(getServerRuntimeConfigQuery); + notify.success({ + title: 'Saved successfully', + message: 'Runtime configurations have been saved successfully.', + }); + } catch (e) { + notify.error({ + title: 'Failed to save', + message: + 'Failed to save runtime configurations, please try again later.', + }); + console.error(e); + } + }, + [revalidate, trigger] + ), + isMutating, + }; +}; diff --git a/packages/frontend/core/src/components/admin-panel/utils.tsx b/packages/frontend/core/src/components/admin-panel/utils.tsx new file mode 100644 index 0000000000..67d8311845 --- /dev/null +++ b/packages/frontend/core/src/components/admin-panel/utils.tsx @@ -0,0 +1,61 @@ +import { Input, Switch } from '@affine/component'; +import type { RuntimeConfigType } from '@affine/graphql'; + +export const renderInput = ( + type: RuntimeConfigType, + value: any, + onChange: (value?: any) => void +) => { + switch (type) { + case 'Boolean': + return ; + case 'String': + return ( + + ); + case 'Number': + return ( +
+ +
+ ); + //TODO: add more types + default: + return null; + } +}; + +export const isEqual = (a: any, b: any) => { + if (typeof a !== typeof b) return false; + if (typeof a === 'object') return JSON.stringify(a) === JSON.stringify(b); + return a === b; +}; + +export const formatValue = (value: any) => { + if (typeof value === 'object') return JSON.stringify(value); + return value.toString(); +}; + +export const formatValueForInput = (value: any, type: RuntimeConfigType) => { + let newValue = null; + switch (type) { + case 'Boolean': + newValue = !!value; + break; + case 'String': + newValue = value; + break; + case 'Number': + newValue = Number(value); + break; + case 'Array': + newValue = value.split(','); + break; + case 'Object': + newValue = JSON.parse(value); + break; + default: + break; + } + return newValue; +}; diff --git a/packages/frontend/core/src/pages/admin-panel.css.ts b/packages/frontend/core/src/pages/admin-panel.css.ts new file mode 100644 index 0000000000..450168c4df --- /dev/null +++ b/packages/frontend/core/src/pages/admin-panel.css.ts @@ -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')}`, +}); diff --git a/packages/frontend/core/src/pages/admin-panel.tsx b/packages/frontend/core/src/pages/admin-panel.tsx new file mode 100644 index 0000000000..1ef87fb8f5 --- /dev/null +++ b/packages/frontend/core/src/pages/admin-panel.tsx @@ -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( + moduleList[0].moduleName + ); + + const { trigger } = useUpdateServerRuntimeConfigs(); + + const [configValues, setConfigValues] = useState( + serverRuntimeConfig.reduce( + (acc, config) => { + acc[config.id] = config.value; + return acc; + }, + {} as Record + ) + ); + + 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 = {}; + + modifiedValues.forEach(item => { + if (item.id && item.newValue !== undefined) { + updates[item.id] = item.newValue; + } + }); + trigger({ updates }); + }, [modifiedValues, trigger]); + + return ( +
+
+
+ {moduleList.map(module => ( + + ))} +
+
+ + + +
+ {configGroup + .filter(group => group.moduleName === currentModule) + .map(group => { + const { moduleName, configs } = group; + return ( +
+
{moduleName}
+ {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 ( + handleInputChange(id, value, type) + )} + > +
+ {formatServerValue} => {formatCurrentValue} +
+
+ ); + })} +
+ ); + })} +
+
+ +
+
+
+
+ ); +}; + +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 ; +}; diff --git a/packages/frontend/core/src/router.tsx b/packages/frontend/core/src/router.tsx index 04e38b6a48..d2a05d68da 100644 --- a/packages/frontend/core/src/router.tsx +++ b/packages/frontend/core/src/router.tsx @@ -48,6 +48,10 @@ export const topLevelRoutes = [ path: '/404', lazy: () => import('./pages/404'), }, + { + path: '/admin-panel', + lazy: () => import('./pages/admin-panel'), + }, { path: '/auth/:authType', lazy: () => import('./pages/auth'), diff --git a/packages/frontend/graphql/src/graphql/get-server-runtime-config.gql b/packages/frontend/graphql/src/graphql/get-server-runtime-config.gql new file mode 100644 index 0000000000..9229b3c7e8 --- /dev/null +++ b/packages/frontend/graphql/src/graphql/get-server-runtime-config.gql @@ -0,0 +1,11 @@ +query getServerRuntimeConfig { + serverRuntimeConfig { + id + module + key + description + value + type + updatedAt + } +} diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts index f5a117cb5f..59781c32aa 100644 --- a/packages/frontend/graphql/src/graphql/index.ts +++ b/packages/frontend/graphql/src/graphql/index.ts @@ -385,6 +385,25 @@ query oauthProviders { }`, }; +export const getServerRuntimeConfigQuery = { + id: 'getServerRuntimeConfigQuery' as const, + operationName: 'getServerRuntimeConfig', + definitionName: 'serverRuntimeConfig', + containsFile: false, + query: ` +query getServerRuntimeConfig { + serverRuntimeConfig { + id + module + key + description + value + type + updatedAt + } +}`, +}; + export const getUserFeaturesQuery = { id: 'getUserFeaturesQuery' as const, operationName: 'getUserFeatures', @@ -818,6 +837,20 @@ query subscription { }`, }; +export const updateServerRuntimeConfigsMutation = { + id: 'updateServerRuntimeConfigsMutation' as const, + operationName: 'updateServerRuntimeConfigs', + definitionName: 'updateRuntimeConfigs', + containsFile: false, + query: ` +mutation updateServerRuntimeConfigs($updates: JSONObject!) { + updateRuntimeConfigs(updates: $updates) { + key + value + } +}`, +}; + export const updateSubscriptionMutation = { id: 'updateSubscriptionMutation' as const, operationName: 'updateSubscription', diff --git a/packages/frontend/graphql/src/graphql/update-server-runtime-configs.gql b/packages/frontend/graphql/src/graphql/update-server-runtime-configs.gql new file mode 100644 index 0000000000..0ab081950c --- /dev/null +++ b/packages/frontend/graphql/src/graphql/update-server-runtime-configs.gql @@ -0,0 +1,6 @@ +mutation updateServerRuntimeConfigs($updates: JSONObject!) { + updateRuntimeConfigs(updates: $updates) { + key + value + } +} diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index bfe9381f93..31ede1dcbc 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -533,6 +533,24 @@ export type OauthProvidersQuery = { }; }; +export type GetServerRuntimeConfigQueryVariables = Exact<{ + [key: string]: never; +}>; + +export type GetServerRuntimeConfigQuery = { + __typename?: 'Query'; + serverRuntimeConfig: Array<{ + __typename?: 'ServerRuntimeConfigType'; + id: string; + module: string; + key: string; + description: string; + value: Record; + type: RuntimeConfigType; + updatedAt: string; + }>; +}; + export type GetUserFeaturesQueryVariables = Exact<{ [key: string]: never }>; export type GetUserFeaturesQuery = { @@ -916,6 +934,19 @@ export type SubscriptionQuery = { } | null; }; +export type UpdateServerRuntimeConfigsMutationVariables = Exact<{ + updates: Scalars['JSONObject']['input']; +}>; + +export type UpdateServerRuntimeConfigsMutation = { + __typename?: 'Mutation'; + updateRuntimeConfigs: Array<{ + __typename?: 'ServerRuntimeConfigType'; + key: string; + value: Record; + }>; +}; + export type UpdateSubscriptionMutationVariables = Exact<{ idempotencyKey: Scalars['String']['input']; plan?: InputMaybe; @@ -1139,6 +1170,11 @@ export type Queries = variables: OauthProvidersQueryVariables; response: OauthProvidersQuery; } + | { + name: 'getServerRuntimeConfigQuery'; + variables: GetServerRuntimeConfigQueryVariables; + response: GetServerRuntimeConfigQuery; + } | { name: 'getUserFeaturesQuery'; variables: GetUserFeaturesQueryVariables; @@ -1371,6 +1407,11 @@ export type Mutations = variables: SetWorkspacePublicByIdMutationVariables; response: SetWorkspacePublicByIdMutation; } + | { + name: 'updateServerRuntimeConfigsMutation'; + variables: UpdateServerRuntimeConfigsMutationVariables; + response: UpdateServerRuntimeConfigsMutation; + } | { name: 'updateSubscriptionMutation'; variables: UpdateSubscriptionMutationVariables;