diff --git a/packages/lib/src/config/classes.ts b/packages/lib/src/config/classes.ts index ac1c4261..f6ec27a5 100644 --- a/packages/lib/src/config/classes.ts +++ b/packages/lib/src/config/classes.ts @@ -118,15 +118,17 @@ export interface FieldDefinition { */ access?: ConfigLocation[]; /** - * If present treat the config value as a credential and only allow reading - * from the locations specified. + * If present treat the config value as a credential. + * Only the locations specified will be able to read the current value. + * Access can be used to specify which locations are able to overwrite the value. */ credential?: ConfigLocation[]; /** - * True if graphical interfaces should prevent the user modifying the value - * This is client side and does not provide any validation logic + * If present treat the config value as readonly. + * Only the locations specified will be able to update the config value. + * Access can be used to specify which locations are able to read the value. */ - readonly?: boolean; + readonly?: ConfigLocation[]; /** * True if graphical interfaces should hide the config value */ @@ -240,11 +242,14 @@ export class Config< */ _checkAccess(name: string, def: FieldDefinition, location: ConfigLocation, mode: ConfigAccess, error: boolean) { // If credential array is present it acts as read permission and the access array as write permission, - // if not present access array acts as a combined read/write permission check. + // If readonly array is present it acts as write permission and the access array as the read permission. + // If neither is present access array acts as a combined read/write permission check. if ( def.credential && mode & ConfigAccess.read && !(def.credential).includes(location) + || def.readonly && mode & ConfigAccess.write && !(def.readonly).includes(location) || ( (!def.credential || mode & ConfigAccess.write) + && (!def.readonly || mode & ConfigAccess.read) && !(def.access ?? this.defaultAccess).includes(location) ) ) { @@ -304,7 +309,7 @@ export class Config< for (let [name, value] of Object.entries(this.fields)) { let def = this.constructor.fieldDefinitions[name]; if ( - !this._checkAccess(name, def, this.location, ConfigAccess.write, false) + this.location !== "control" && !this._checkAccess(name, def, this.location, ConfigAccess.write, false) || this.location !== remote && !this._checkAccess(name, def, remote, ConfigAccess.read, false) ) { continue; @@ -353,7 +358,7 @@ export class Config< } if ( - !this._checkAccess(name, def, remote, ConfigAccess.write, false) + this.location !== "control" && !this._checkAccess(name, def, remote, ConfigAccess.write, false) ) { continue; } diff --git a/packages/web_ui/src/BaseWebPlugin.ts b/packages/web_ui/src/BaseWebPlugin.ts index 286ef9af..4ce654ae 100644 --- a/packages/web_ui/src/BaseWebPlugin.ts +++ b/packages/web_ui/src/BaseWebPlugin.ts @@ -24,6 +24,7 @@ export interface PluginLoginForm { } export interface InputComponentProps { + disabled?: boolean, fieldDefinition: FieldDefinition, value: null | boolean | number | string, onChange: (value: null | boolean | number | string) => void, diff --git a/packages/web_ui/src/components/BaseConfigTree.tsx b/packages/web_ui/src/components/BaseConfigTree.tsx index 6cd5174b..710aead8 100644 --- a/packages/web_ui/src/components/BaseConfigTree.tsx +++ b/packages/web_ui/src/components/BaseConfigTree.tsx @@ -32,24 +32,24 @@ function getInitialValues(config: lib.Config, props: BaseConfigTreeProps) { return initialValues; } -function renderInput(inputComponents: Record, def: lib.FieldDefinition) { +function renderInput(inputComponents: Record, def: lib.FieldDefinition, readonly: boolean) { if (def.inputComponent && Object.prototype.hasOwnProperty.call(inputComponents, def.inputComponent)) { // Field.Item will provide the value and onChange props to the component. type InputPartial = React.ComponentClass>; const CustomInput = inputComponents[def.inputComponent] as unknown as InputPartial; - return ; + return ; } if (def.type === "boolean") { - return ; + return ; } if (def.type === "string") { if (def.credential) { - return ; + return ; } - return ; + return ; } if (def.type === "number") { - return ; + return ; } return `Unknown type ${def.type}`; @@ -264,16 +264,17 @@ function computeTreeData( }; for (let [field, def] of Object.entries(groupDefs)) { - const canWrite = config.canAccess(`${groupName}.${field}`, lib.ConfigAccess.write); - const canRead = config.canAccess(`${groupName}.${field}`, lib.ConfigAccess.read); - if (!canWrite && !canRead) { - continue; - } if (def.hidden === true) { continue; } - let fieldName = `${groupName}.${field}`; + const fieldName = `${groupName}.${field}`; + const canWrite = config.canAccess(fieldName, lib.ConfigAccess.write); + const canRead = config.canAccess(fieldName, lib.ConfigAccess.read); + if (!canWrite && !canRead) { + continue; + } + let value = config.get(fieldName) as any; let childNode: ChildNode = { key: fieldName, @@ -314,7 +315,7 @@ function computeTreeData( return Promise.resolve(); }}]} > - + , }); @@ -322,47 +323,53 @@ function computeTreeData( propsMap.set(propPath, [fieldName, prop]); } - let newPropPath = `${groupName}.${field}:add`; - childNode.children.push({ - key: newPropPath, - title: - - - : - - - - - , - }); + let propPath = `${groupName}.${field}.${propName}`; + form.setFieldsValue({ + [`${newPropPath}.name`]: "", + [`${newPropPath}.value`]: "", + [propPath]: propValue, + }); + form.validateFields([propPath]); + onValuesChange({ [propPath]: propValue }); + }} + > + Add + + , + }); + } + + if (!childNode.children.length) { + continue; + } } else { childNode.title = - {renderInput(control.inputComponents, def)} + {renderInput(control.inputComponents, def, !canWrite)} ; initialValues[fieldName] = value; } diff --git a/packages/web_ui/src/components/InputModPack.tsx b/packages/web_ui/src/components/InputModPack.tsx index fd16a5cf..953a999e 100644 --- a/packages/web_ui/src/components/InputModPack.tsx +++ b/packages/web_ui/src/components/InputModPack.tsx @@ -15,6 +15,6 @@ export default function InputModPack(props: InputComponentProps) { value: modPack.id, }))} allowClear={props.fieldDefinition.optional} + disabled={props.disabled} />; } - diff --git a/packages/web_ui/src/components/InputRole.tsx b/packages/web_ui/src/components/InputRole.tsx index 94712e54..174f0b42 100644 --- a/packages/web_ui/src/components/InputRole.tsx +++ b/packages/web_ui/src/components/InputRole.tsx @@ -26,5 +26,6 @@ export default function InputRole(props: InputComponentProps) { value: role.id, }))} allowClear={props.fieldDefinition.optional} + disabled={props.disabled} />; }