Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 124 additions & 48 deletions admin-ui/app/routes/Apps/Gluu/GluuInlineInput.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import React, { useState, useContext } from 'react'
import React, { useState, useContext, useCallback, useEffect, useMemo } from 'react'
import GluuLabel from './GluuLabel'
import GluuToogle from './GluuToogle'
import { useTranslation } from 'react-i18next'
import { Typeahead } from 'react-bootstrap-typeahead'
import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle'
import { Col, FormGroup, Input, Button } from 'Components'
import { ThemeContext } from 'Context/theme/themeContext'
import customColors from '@/customColors'
import type { JsonPatch } from 'JansConfigApi'

function GluuInlineInput({
interface GluuInlineInputProps {
label: string
name: string
type?: string
value?: string | number | boolean | string[]
required?: boolean
lsize?: number
rsize?: number
isBoolean?: boolean
isArray?: boolean
handler: (patch: JsonPatch) => void
options?: string[]
path?: string
doc_category?: string
disabled?: boolean
id?: string
parentIsArray?: boolean
}

interface ThemeContextValue {
state: {
theme: string
}
}

const GluuInlineInput = ({
label,
name,
type = 'text',
Expand All @@ -22,52 +47,98 @@ function GluuInlineInput({
options,
path,
doc_category = 'json_properties',
}: any) {
const { t } = useTranslation()
const theme: any = useContext(ThemeContext)
disabled = false,
}: GluuInlineInputProps) => {
const theme = useContext(ThemeContext) as ThemeContextValue
const selectedTheme = theme.state.theme
const VALUE = 'value'
const PATH = 'path'
const [show, setShow] = useState(false)
const [correctValue, setCorrectValue] = useState([])
const [data, setData] = useState(value)
const onValueChanged = (e: any) => {
if (isBoolean) {
setData(e.target.checked)
} else {
setData(e.target.value)
const [correctValue, setCorrectValue] = useState<string[]>([])
const [data, setData] = useState<string | number | boolean>(value as string | number | boolean)

useEffect(() => {
if (value !== undefined) {
setData(value as string | number | boolean)
}
setShow(true)
}
const handleTypeAheadChange = (selectedOptions: any) => {
const object = selectedOptions.filter((data: any) => typeof data == 'object')
const arrayItems = selectedOptions.filter((data: any) => typeof data != 'object')
for (const i in object) {
if (!object[i]['tokenEndpointAuthMethodsSupported']) {
arrayItems.push(object[i][name])
}, [value])

const onValueChanged = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (disabled) {
return
}
if (isBoolean) {
setData(e.target.checked)
} else {
arrayItems.push(object[i]['tokenEndpointAuthMethodsSupported'])
setData(e.target.value)
}
setShow(true)
},
[disabled, isBoolean],
)

const handleTypeAheadChange = useCallback(
(selectedOptions: (string | Record<string, string>)[]) => {
if (disabled) {
return
}
const object = selectedOptions.filter(
(item): item is Record<string, string> => typeof item === 'object',
)
const arrayItems = selectedOptions.filter(
(item): item is string => typeof item !== 'object',
) as string[]

for (const obj of object) {
if (!obj.tokenEndpointAuthMethodsSupported) {
const value = obj[name]
if (typeof value === 'string') {
arrayItems.push(value)
}
} else {
const value = obj.tokenEndpointAuthMethodsSupported
if (typeof value === 'string') {
arrayItems.push(value)
}
}
}
setCorrectValue(arrayItems)
setShow(true)
},
[disabled, name],
)

const onAccept = useCallback(() => {
if (disabled) {
return
}
setCorrectValue(arrayItems)
setShow(true)
}
const onAccept = () => {
const patch: any = {}
patch[PATH] = path
if (isArray) {
patch[VALUE] = correctValue
} else {
patch[VALUE] = data
}
patch['op'] = 'replace'
const patch: JsonPatch = {
op: 'replace',
path: path || '',
value: isArray ? correctValue : data,
} as JsonPatch
handler(patch)
setShow(!show)
}
const onCancel = () => {
setShow((prev) => !prev)
}, [disabled, path, isArray, correctValue, data, handler])

const onCancel = useCallback(() => {
setCorrectValue([])
setShow(!show)
}
setShow((prev) => !prev)
}, [])

const disabledStyle = useMemo(
() => (disabled ? { cursor: 'not-allowed', opacity: 0.6 } : {}),
[disabled],
)

const filteredValue = useMemo(
() => (Array.isArray(value) ? value.filter((item) => item != null) : []),
[value],
)

const filteredOptions = useMemo(
() => (Array.isArray(options) ? options.filter((item) => item != null) : []),
[options],
)
return (
<FormGroup row>
<Col sm={10}>
Expand All @@ -85,9 +156,11 @@ function GluuInlineInput({
id={name}
data-testid={name}
name={name}
type={type}
defaultValue={data}
type={type as 'text' | 'number' | 'email' | 'password' | 'tel' | 'url'}
defaultValue={String(data)}
onChange={onValueChanged}
disabled={disabled}
style={disabledStyle}
/>
)}
{isBoolean && (
Expand All @@ -96,7 +169,8 @@ function GluuInlineInput({
data-testid={name}
name={name}
handler={onValueChanged}
value={value}
value={value as boolean}
disabled={disabled}
/>
)}
{isArray && (
Expand All @@ -108,23 +182,24 @@ function GluuInlineInput({
labelKey={name}
onChange={handleTypeAheadChange}
multiple={true}
defaultSelected={Array.isArray(value) ? value.filter((item) => item != null) : []}
options={Array.isArray(options) ? options.filter((item) => item != null) : []}
defaultSelected={filteredValue}
options={filteredOptions}
disabled={disabled}
/>
)}
</Col>
</FormGroup>
</Col>
<Col sm={2}>
{show && (
{show && !disabled && (
<>
<Button
color={`primary-${selectedTheme}`}
style={applicationStyle.buttonStyle}
size="sm"
onClick={onAccept}
>
<i className="fa fa-check me-2"></i>
<i className="fa fa-check me-2" />
</Button>
<Button
style={{
Expand All @@ -143,4 +218,5 @@ function GluuInlineInput({
</FormGroup>
)
}

export default GluuInlineInput
10 changes: 5 additions & 5 deletions admin-ui/app/routes/Apps/Gluu/GluuTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ TabPanel.displayName = 'TabPanel'
const isNavigationTab = (tab: TabItem | null): tab is NavigationTab => {
return Boolean(
tab &&
typeof tab === 'object' &&
'name' in tab &&
'path' in tab &&
typeof tab.path === 'string' &&
tab.path.trim().length > 0,
typeof tab === 'object' &&
'name' in tab &&
'path' in tab &&
typeof tab.path === 'string' &&
tab.path.trim().length > 0,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import JsonPropertyBuilderConfigApi from './JsonPropertyBuilderConfigApi'
import { toast } from 'react-toastify'
import type { ApiAppConfiguration, JsonPatch } from './types'
import { useAppNavigation, ROUTES } from '@/helpers/navigation'
import { READ_ONLY_FIELDS } from './utils'
import type { GluuCommitDialogOperation, JsonValue } from 'Routes/Apps/Gluu/types'

interface ApiConfigFormProps {
configuration: ApiAppConfiguration
Expand All @@ -20,7 +22,7 @@ interface SpecSchema {
components: {
schemas: {
ApiAppConfiguration: {
properties: Record<string, unknown>
properties: Record<string, string | number | boolean | null | undefined>
}
}
}
Expand All @@ -29,22 +31,28 @@ interface SpecSchema {
const { properties: schema } = (spec as unknown as SpecSchema).components?.schemas
?.ApiAppConfiguration ?? { properties: {} }

const CONFIG_API_RESOURCE_ID = ADMIN_UI_RESOURCES.ConfigApiConfiguration

const ApiConfigForm: React.FC<ApiConfigFormProps> = ({ configuration, onSubmit }) => {
const { authorizeHelper, hasCedarWritePermission } = useCedarling()
const { navigateToRoute } = useAppNavigation()
const [modal, setModal] = useState(false)
const [patches, setPatches] = useState<JsonPatch[]>([])

const operations = patches
const configApiResourceId = ADMIN_UI_RESOURCES.ConfigApiConfiguration
const configApiScopes = useMemo(
() => CEDAR_RESOURCE_SCOPES[configApiResourceId] || [],
[configApiResourceId],
)
const configApiScopes = useMemo(() => CEDAR_RESOURCE_SCOPES[CONFIG_API_RESOURCE_ID] || [], [])

const canWriteConfigApi = useMemo(
() => hasCedarWritePermission(configApiResourceId),
[hasCedarWritePermission, configApiResourceId],
() => hasCedarWritePermission(CONFIG_API_RESOURCE_ID),
[hasCedarWritePermission],
)

const operations: GluuCommitDialogOperation[] = useMemo(
() =>
patches.map((patch) => ({
path: patch.path as string,
value: patch.value as JsonValue,
})),
[patches],
)

useEffect(() => {
Expand All @@ -53,6 +61,10 @@ const ApiConfigForm: React.FC<ApiConfigFormProps> = ({ configuration, onSubmit }
}
}, [authorizeHelper, configApiScopes])

const patchHandler = useCallback((patch: JsonPatch) => {
setPatches((existingPatches) => [...existingPatches, patch])
}, [])

const toggle = useCallback(() => {
if (patches?.length > 0) {
setModal((prev) => !prev)
Expand All @@ -69,27 +81,29 @@ const ApiConfigForm: React.FC<ApiConfigFormProps> = ({ configuration, onSubmit }
[toggle, onSubmit, patches],
)

const patchHandler = (patch: JsonPatch) => {
setPatches((existingPatches) => [...existingPatches, patch])
}

const handleBack = () => {
const handleBack = useCallback(() => {
navigateToRoute(ROUTES.HOME_DASHBOARD)
}
}, [navigateToRoute])

return (
<>
{Object.keys(configuration).map((propKey) => (
<JsonPropertyBuilderConfigApi
key={propKey}
propKey={propKey}
propValue={configuration[propKey as keyof ApiAppConfiguration]}
lSize={6}
handler={patchHandler}
schema={schema[propKey] as { type?: string; items?: { type?: string; enum?: string[] } }}
doc_category="config_api_properties"
/>
))}
{Object.keys(configuration).map((propKey) => {
const isDisabled = READ_ONLY_FIELDS.includes(propKey)
return (
<JsonPropertyBuilderConfigApi
key={propKey}
propKey={propKey}
propValue={configuration[propKey as keyof ApiAppConfiguration]}
lSize={6}
handler={patchHandler}
schema={
schema[propKey] as { type?: string; items?: { type?: string; enum?: string[] } }
}
doc_category="config_api_properties"
disabled={isDisabled}
/>
)
})}

<FormGroup row />
{canWriteConfigApi && (
Expand Down
Loading