From d1b78931a4774fc5ec904975a2de3f086e8868c3 Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Fri, 9 Aug 2024 15:27:51 -0500 Subject: [PATCH] Fix render issues - Client pages needed 'use client' directive --- web_ui/frontend/app/config/Config.tsx | 215 +++++++++++++++++ .../app/config/components/ConfigDisplay.tsx | 6 +- .../app/config/components/TableOfContents.tsx | 4 +- web_ui/frontend/app/config/page.tsx | 228 +----------------- .../configuration/Fields/IntegerField.tsx | 15 +- .../ObjectField/AuthorizationTemplateForm.tsx | 4 +- .../CustomRegistrationFieldForm.tsx | 2 - .../Fields/ObjectField/ExportForm.tsx | 4 +- .../Fields/ObjectField/IPMappingForm.tsx | 8 +- .../OIDCAuthenticationRequirementForm.tsx | 7 +- .../configuration/Fields/StringField.tsx | 13 +- 11 files changed, 265 insertions(+), 241 deletions(-) create mode 100644 web_ui/frontend/app/config/Config.tsx diff --git a/web_ui/frontend/app/config/Config.tsx b/web_ui/frontend/app/config/Config.tsx new file mode 100644 index 000000000..b2c1d2662 --- /dev/null +++ b/web_ui/frontend/app/config/Config.tsx @@ -0,0 +1,215 @@ +/*************************************************************** + * + * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************/ + +'use client'; + +import { + Box, + Grid, + Typography, + Snackbar, + Button, + IconButton, +} from '@mui/material'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { + AppRegistration, + AssistantDirection, + TripOrigin, + Cached, + Download, +} from '@mui/icons-material'; +import useSWR from 'swr'; +import { merge, isMatch, isEqual } from 'lodash'; +import * as yaml from 'js-yaml'; +import { ButtonLink, Sidebar } from '@/components/layout/Sidebar'; +import { Main } from '@/components/layout/Main'; +import { submitConfigChange } from '@/components/configuration/util'; +import { + ParameterMetadataList, + ParameterMetadataRecord, + ParameterValueRecord, +} from '@/components/configuration'; +import { stripNulls, flattenObject } from './util'; +import StatusSnackBar, { + StatusSnackBarProps, +} from '@/components/StatusSnackBar'; +import { ServerType } from '@/index'; +import { getEnabledServers } from '@/helpers/util'; +import DownloadButton from '@/components/DownloadButton'; +import { PaddedContent } from '@/components/layout'; +import { ConfigDisplay, TableOfContents } from '@/app/config/components'; +import _metadata from '@/public/data/parameters.json'; +import AuthenticatedContent from '@/components/layout/AuthenticatedContent'; + +function Config({ metadata }: { metadata: ParameterMetadataRecord }) { + const [status, setStatus] = useState( + undefined + ); + const [patch, _setPatch] = useState({}); + + const { + data: serverConfig, + mutate, + error, + } = useSWR('getConfig', getConfig); + const { data: enabledServers } = useSWR( + 'getEnabledServers', + getEnabledServers, + { + fallbackData: ['origin', 'registry', 'director', 'cache'], + } + ); + + const setPatch = useCallback( + (fieldPatch: any) => { + _setPatch((p: any) => { + return { ...p, ...fieldPatch }; + }); + }, + [_setPatch] + ); + + const updatesPending = useMemo(() => { + return !Object.keys(patch).every((key) => + isEqual(patch[key], serverConfig?.[key]) + ); + }, [serverConfig, patch]); + + return ( + <> + + {enabledServers && enabledServers.includes('origin') && ( + + + + )} + {enabledServers && enabledServers.includes('director') && ( + + + + )} + {enabledServers && enabledServers.includes('registry') && ( + + + + )} + {enabledServers && enabledServers.includes('cache') && ( + + + + )} + +
+ + + + + Configuration + {serverConfig && ( + + + + + + )} + + + + + + + + + + } + /> + {status && } + + + + + + + + + +
+ + ); +} + +const getConfig = async (): Promise => { + let response = await fetch('/api/v1.0/config'); + let data = await response.json(); + let flatData = flattenObject(data); + return flatData; +}; + +export default Config; diff --git a/web_ui/frontend/app/config/components/ConfigDisplay.tsx b/web_ui/frontend/app/config/components/ConfigDisplay.tsx index c301e50bb..819086163 100644 --- a/web_ui/frontend/app/config/components/ConfigDisplay.tsx +++ b/web_ui/frontend/app/config/components/ConfigDisplay.tsx @@ -1,3 +1,5 @@ +'use client'; + import React, { memo } from 'react'; import { Box, Button, Typography } from '@mui/material'; import { QuestionMark } from '@mui/icons-material'; @@ -24,7 +26,9 @@ export interface ConfigDisplayProps { onChange: (patch: any) => void; } -export function ConfigDisplay({ +export const ConfigDisplay = memo(NonMemoizedConfigDisplay); + +export function NonMemoizedConfigDisplay({ config, metadata, patch, diff --git a/web_ui/frontend/app/config/components/TableOfContents.tsx b/web_ui/frontend/app/config/components/TableOfContents.tsx index a1dfc4844..dfd0ef8e9 100644 --- a/web_ui/frontend/app/config/components/TableOfContents.tsx +++ b/web_ui/frontend/app/config/components/TableOfContents.tsx @@ -1,6 +1,6 @@ +'use client'; + import { - Config, - ParameterInputProps, ParameterMetadata, ParameterMetadataRecord, } from '@/components/configuration/index'; diff --git a/web_ui/frontend/app/config/page.tsx b/web_ui/frontend/app/config/page.tsx index ac711ff29..e68aca808 100644 --- a/web_ui/frontend/app/config/page.tsx +++ b/web_ui/frontend/app/config/page.tsx @@ -1,226 +1,18 @@ -/*************************************************************** - * - * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research - * - * Licensed under the Apache License, Version 2.0 (the "License"); you - * may not use this file except in compliance with the License. You may - * obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ***************************************************************/ +import { merge } from 'lodash'; -'use client'; - -import { - Box, - Grid, - Typography, - Snackbar, - Button, - IconButton, -} from '@mui/material'; -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { - AppRegistration, - AssistantDirection, - TripOrigin, - Cached, - Download, -} from '@mui/icons-material'; -import useSWR from 'swr'; -import { merge, isMatch, isEqual } from 'lodash'; -import * as yaml from 'js-yaml'; -import { ButtonLink, Sidebar } from '@/components/layout/Sidebar'; -import { Main } from '@/components/layout/Main'; -import { submitConfigChange } from '@/components/configuration/util'; -import { - ParameterMetadataList, - ParameterValueRecord, -} from '@/components/configuration'; -import { stripNulls, flattenObject } from './util'; -import StatusSnackBar, { - StatusSnackBarProps, -} from '@/components/StatusSnackBar'; -import { ServerType } from '@/index'; -import { getEnabledServers } from '@/helpers/util'; -import DownloadButton from '@/components/DownloadButton'; -import { PaddedContent } from '@/components/layout'; -import { - ConfigDisplay as NonMemoizedConfigDisplay, - TableOfContents, -} from '@/app/config/components'; +import Config from './Config'; import _metadata from '@/public/data/parameters.json'; -import AuthenticatedContent from '@/components/layout/AuthenticatedContent'; - -// Memoize expensive components -const ConfigDisplay = memo(NonMemoizedConfigDisplay); +import { ParameterMetadataList } from '@/components/configuration'; -function Config() { +const getMetadata = async () => { const metadataList = _metadata as unknown as ParameterMetadataList; // @ts-ignore - const metadata = useMemo(() => merge(...metadataList), [metadataList]); - - const [status, setStatus] = useState( - undefined - ); - const [patch, _setPatch] = useState({}); - - const { - data: serverConfig, - mutate, - error, - } = useSWR('getConfig', getConfig); - const { data: enabledServers } = useSWR( - 'getEnabledServers', - getEnabledServers, - { - fallbackData: ['origin', 'registry', 'director', 'cache'], - } - ); - - const setPatch = useCallback( - (fieldPatch: any) => { - _setPatch((p: any) => { - return {...p, ...fieldPatch} - }) - }, - [_setPatch] - ); - - const updatesPending = useMemo(() => { - return !Object.keys(patch).every((key) => - isEqual(patch[key], serverConfig?.[key]) - ); - }, [serverConfig, patch]); - - return ( - <> - - {enabledServers && enabledServers.includes('origin') && ( - - - - )} - {enabledServers && enabledServers.includes('director') && ( - - - - )} - {enabledServers && enabledServers.includes('registry') && ( - - - - )} - {enabledServers && enabledServers.includes('cache') && ( - - - - )} - -
- - - - - Configuration - {serverConfig && ( - - - - - - )} - - - - -
- - - - - - - } - /> - {status && } -
- - - - - -
-
-
-
- - ); -} + return merge(...metadataList); +}; -const getConfig = async (): Promise => { - let response = await fetch('/api/v1.0/config'); - let data = await response.json(); - let flatData = flattenObject(data); - return flatData; +const Page = async () => { + const metadata = await getMetadata(); + return ; }; -export default Config; +export default Page; diff --git a/web_ui/frontend/components/configuration/Fields/IntegerField.tsx b/web_ui/frontend/components/configuration/Fields/IntegerField.tsx index 31fb10de6..dc98524a2 100644 --- a/web_ui/frontend/components/configuration/Fields/IntegerField.tsx +++ b/web_ui/frontend/components/configuration/Fields/IntegerField.tsx @@ -9,7 +9,8 @@ import React, { useMemo, useCallback, SetStateAction, - ChangeEvent, useEffect, + ChangeEvent, + useEffect, } from 'react'; import { createId, buildPatch } from '../util'; @@ -38,7 +39,9 @@ const IntegerField = ({ }: IntegerFieldProps) => { const id = useMemo(() => createId(name), [name]); - const [bufferValue, setBufferValue] = React.useState(value.toString()); + const [bufferValue, setBufferValue] = React.useState( + value.toString() + ); const error = useMemo(() => { return verifyInteger(bufferValue) ? undefined : 'Value must be a integer'; @@ -46,7 +49,7 @@ const IntegerField = ({ useEffect(() => { setBufferValue(value.toString()); - }, [value]) + }, [value]); return ( { setBufferValue(e.target.value); - if(verifyInteger(e.target.value)) { - onChange(parseInt(e.target.value)) + if (verifyInteger(e.target.value)) { + onChange(parseInt(e.target.value)); } }} onBlur={(e) => { - setBufferValue(value.toString()) + setBufferValue(value.toString()); }} error={error !== undefined} helperText={error} diff --git a/web_ui/frontend/components/configuration/Fields/ObjectField/AuthorizationTemplateForm.tsx b/web_ui/frontend/components/configuration/Fields/ObjectField/AuthorizationTemplateForm.tsx index 533f440be..a4156aba4 100644 --- a/web_ui/frontend/components/configuration/Fields/ObjectField/AuthorizationTemplateForm.tsx +++ b/web_ui/frontend/components/configuration/Fields/ObjectField/AuthorizationTemplateForm.tsx @@ -56,7 +56,9 @@ const AuthorizationTemplateForm = ({ } /> - + ); }; diff --git a/web_ui/frontend/components/configuration/Fields/ObjectField/CustomRegistrationFieldForm.tsx b/web_ui/frontend/components/configuration/Fields/ObjectField/CustomRegistrationFieldForm.tsx index 3c07c4cb4..807c135f6 100644 --- a/web_ui/frontend/components/configuration/Fields/ObjectField/CustomRegistrationFieldForm.tsx +++ b/web_ui/frontend/components/configuration/Fields/ObjectField/CustomRegistrationFieldForm.tsx @@ -13,8 +13,6 @@ import { BooleanField, FormProps, } from '@/components/configuration'; -import { Simulate } from 'react-dom/test-utils'; -import submit = Simulate.submit; const verifyForm = (x: CustomRegistrationField) => { return ( diff --git a/web_ui/frontend/components/configuration/Fields/ObjectField/ExportForm.tsx b/web_ui/frontend/components/configuration/Fields/ObjectField/ExportForm.tsx index c4942630e..83f0187c9 100644 --- a/web_ui/frontend/components/configuration/Fields/ObjectField/ExportForm.tsx +++ b/web_ui/frontend/components/configuration/Fields/ObjectField/ExportForm.tsx @@ -86,7 +86,9 @@ const ExportForm = ({ onSubmit, value }: FormProps) => { } /> - + ); }; diff --git a/web_ui/frontend/components/configuration/Fields/ObjectField/IPMappingForm.tsx b/web_ui/frontend/components/configuration/Fields/ObjectField/IPMappingForm.tsx index a9078fe56..58eef26f6 100644 --- a/web_ui/frontend/components/configuration/Fields/ObjectField/IPMappingForm.tsx +++ b/web_ui/frontend/components/configuration/Fields/ObjectField/IPMappingForm.tsx @@ -33,7 +33,9 @@ const createDefaultIPMapping = (): IPMappingFine => { const IPMappingForm = ({ onSubmit, value }: FormProps) => { const valueAsIPMappingFine: IPMappingFine = - value !== undefined && 'all' in value ? { source: 'all', dest: value.all } : value; + value !== undefined && 'all' in value + ? { source: 'all', dest: value.all } + : value; const [ipMapping, setIPMapping] = React.useState( valueAsIPMappingFine || createDefaultIPMapping() @@ -71,7 +73,9 @@ const IPMappingForm = ({ onSubmit, value }: FormProps) => { verify={verifyIpAddress} /> - + ); }; diff --git a/web_ui/frontend/components/configuration/Fields/ObjectField/OIDCAuthenticationRequirementForm.tsx b/web_ui/frontend/components/configuration/Fields/ObjectField/OIDCAuthenticationRequirementForm.tsx index c95112899..e2de27ac5 100644 --- a/web_ui/frontend/components/configuration/Fields/ObjectField/OIDCAuthenticationRequirementForm.tsx +++ b/web_ui/frontend/components/configuration/Fields/ObjectField/OIDCAuthenticationRequirementForm.tsx @@ -3,7 +3,8 @@ import { Box, Button, TextField } from '@mui/material'; import { FormProps, - OIDCAuthenticationRequirement, StringField, + OIDCAuthenticationRequirement, + StringField, } from '@/components/configuration'; import { String } from 'ts-toolbelt'; @@ -38,14 +39,14 @@ const OIDCAuthenticationRequirementForm = ({ <> setAuthReq({ ...authReq, claim: e })} /> setAuthReq({ ...authReq, value: e })} /> diff --git a/web_ui/frontend/components/configuration/Fields/StringField.tsx b/web_ui/frontend/components/configuration/Fields/StringField.tsx index 7bf497079..39b671b33 100644 --- a/web_ui/frontend/components/configuration/Fields/StringField.tsx +++ b/web_ui/frontend/components/configuration/Fields/StringField.tsx @@ -9,7 +9,8 @@ import React, { useMemo, useCallback, SetStateAction, - ChangeEvent, useEffect, + ChangeEvent, + useEffect, } from 'react'; import { createId, buildPatch } from '../util'; @@ -29,7 +30,6 @@ const StringField = ({ focused, verify, }: StringFieldProps) => { - const id = useMemo(() => createId(name), [name]); // Hold a buffer value so that you can type freely without saving an invalid state @@ -38,7 +38,10 @@ const StringField = ({ setBufferValue(value); }, [value]); - const error = useMemo(() => (verify ? verify(bufferValue) : undefined), [bufferValue]); + const error = useMemo( + () => (verify ? verify(bufferValue) : undefined), + [bufferValue] + ); return (