diff --git a/src/cockpit/389-console/buildAndRun.sh b/src/cockpit/389-console/buildAndRun.sh index d46f94d621..ef78830ae6 100755 --- a/src/cockpit/389-console/buildAndRun.sh +++ b/src/cockpit/389-console/buildAndRun.sh @@ -12,5 +12,5 @@ if [ $? != 0 ]; then fi printf "\nBuilding and watching ...\n" -ESBUILD_WATCH=true ./build.js +./build.js --watch # npm run watch diff --git a/src/cockpit/389-console/pkg/lib/cockpit-po-plugin.js b/src/cockpit/389-console/pkg/lib/cockpit-po-plugin.js index 639dafcedc..6b1e8c6567 100644 --- a/src/cockpit/389-console/pkg/lib/cockpit-po-plugin.js +++ b/src/cockpit/389-console/pkg/lib/cockpit-po-plugin.js @@ -28,6 +28,11 @@ function get_po_files() { } function get_plural_expr(statement) { + if (statement === undefined) { + console.error("Plural-Forms header is missing in the PO file"); + return "'(n) => n != 1'"; // Default plural expression for English + } + try { /* Check that the plural forms isn't being sneaky since we build a function here */ Jed.PF.parse(statement); @@ -62,7 +67,7 @@ function buildFile(po_file, subdir, filename, filter) { const chunks = [ '{\n', ' "": {\n', - ` "plural-forms": ${get_plural_expr(parsed.headers['Plural-Forms'])},\n`, + ` "plural-forms": ${get_plural_expr(parsed.headers['plural-forms'])},\n`, ` "language": "${parsed.headers.Language}",\n`, ` "language-direction": "${dir}"\n`, ' }' diff --git a/src/cockpit/389-console/src/LDAPEditor.jsx b/src/cockpit/389-console/src/LDAPEditor.jsx index 12b0143842..de581175c4 100644 --- a/src/cockpit/389-console/src/LDAPEditor.jsx +++ b/src/cockpit/389-console/src/LDAPEditor.jsx @@ -16,6 +16,7 @@ import { Breadcrumb, BreadcrumbItem, Button, + Icon , Modal, ModalVariant, Spinner, @@ -43,10 +44,7 @@ import EditorTableView from './lib/ldap_editor/tableView.jsx'; import EditorTreeView from './lib/ldap_editor/treeView.jsx'; import { SearchDatabase } from './lib/ldap_editor/search.jsx'; import GenericWizard from './lib/ldap_editor/wizards/genericWizard.jsx'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faSyncAlt -} from '@fortawesome/free-solid-svg-icons'; +import { SyncAltIcon } from '@patternfly/react-icons'; import '@fortawesome/fontawesome-svg-core/styles.css'; import { log_cmd } from "./lib/tools.jsx"; @@ -127,7 +125,7 @@ export class LDAPEditor extends React.Component { }; // Actions when user clicks on the Entry Details menu - this.onToggleEntryMenu = isOpen => { + this.onToggleEntryMenu = (_event, isOpen) => { this.setState({ entryMenuIsOpen: isOpen }); @@ -1188,12 +1186,12 @@ export class LDAPEditor extends React.Component { </BreadcrumbItem> ))} </Breadcrumb> - <FontAwesomeIcon - className="ds-left-margin ds-refresh" - icon={faSyncAlt} - title={_("Refresh")} - onClick={this.handleReload} - /> + <Icon> + <SyncAltIcon + className="ds-left-margin ds-refresh" + onClick={this.handleReload} + /> + </Icon> </div> <div className={this.state.searching ? "ds-margin-top-xlg ds-center" : "ds-hidden"}> <TextContent> diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx index fce4cd5dd4..121404dcd4 100644 --- a/src/cockpit/389-console/src/database.jsx +++ b/src/cockpit/389-console/src/database.jsx @@ -17,6 +17,8 @@ import { FormSelect, FormSelectOption, FormHelperText, + HelperText, + HelperTextItem, Modal, ModalVariant, Spinner, @@ -718,7 +720,7 @@ export class Database extends React.Component { }); } - onHandleSelectChange(value, event) { + onHandleSelectChange(_event, value) { let noInit = false; let addSuffix = false; let addSample = false; @@ -738,7 +740,7 @@ export class Database extends React.Component { }); } - onHandleChange(str, e) { + onHandleChange(e, str) { // Handle the Create Suffix modal changes const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; let valueErr = false; @@ -1488,9 +1490,6 @@ class CreateSuffixModal extends React.Component { label={_("Suffix DN")} fieldId="createSuffix" title={_("Database suffix, like 'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN).")} - helperTextInvalid={_("The DN of the suffix is invalid")} - helperTextInvalidIcon={<ExclamationCircleIcon />} - validated={error.createSuffix ? "error" : "noval"} > <TextInput isRequired @@ -1501,29 +1500,34 @@ class CreateSuffixModal extends React.Component { onChange={handleChange} validated={error.createSuffix ? "error" : "noval"} /> - <FormHelperText isError isHidden={!error.createSuffix}> - {_("Required field")} + <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={error.createSuffix ? "error" : "default"}> + {error.createBeName ? 'The DN of the suffix is invalid' : 'Required field'} + </HelperTextItem> + </HelperText> </FormHelperText> </FormGroup> <FormGroup label={_("Database Name")} fieldId="suffixName" title={_("The name for the backend database, like 'userroot'. The name can be a combination of alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed, and the name must be unique across all backends.")} - helperTextInvalid={_("You must enter a name for the database")} - helperTextInvalidIcon={<ExclamationCircleIcon />} - validated={error.createBeName ? "error" : "noval"} > <TextInput isRequired type="text" id="createBeName" - aria-describedby="createSuffix" + aria-describedby="createBeName" name="suffixName" onChange={handleChange} validated={error.createBeName ? "error" : "noval"} /> - <FormHelperText isError isHidden={!error.createBeName}> - {_("Required field")} + <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={error.createBeName ? "error" : "default"}> + {error.createBeName ? 'You must enter a name for the database' : 'Required field'} + </HelperTextItem> + </HelperText> </FormHelperText> </FormGroup> <FormGroup diff --git a/src/cockpit/389-console/src/ds.jsx b/src/cockpit/389-console/src/ds.jsx index df188699c9..65350a7122 100644 --- a/src/cockpit/389-console/src/ds.jsx +++ b/src/cockpit/389-console/src/ds.jsx @@ -11,30 +11,33 @@ import { ManageBackupsModal, SchemaReloadModal, CreateInstanceModal } from "./ds import { LDAPEditor } from "./LDAPEditor.jsx"; import { log_cmd } from "./lib/tools.jsx"; import { - Alert, - AlertGroup, - AlertActionCloseButton, - AlertVariant, - Button, - Dropdown, - DropdownToggle, - DropdownItem, - DropdownPosition, - DropdownSeparator, - Grid, GridItem, - FormSelect, - FormSelectOption, - PageSectionVariants, - Progress, - ProgressMeasureLocation, - Spinner, - Tab, - Tabs, - TabTitleText, - Text, - TextContent, - TextVariants -} from "@patternfly/react-core"; + Alert, + AlertGroup, + AlertActionCloseButton, + AlertVariant, + Button, + Grid, + GridItem, + FormSelect, + FormSelectOption, + PageSectionVariants, + Progress, + ProgressMeasureLocation, + Spinner, + Tab, + Tabs, + TabTitleText, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; +import { + Dropdown, + DropdownToggle, + DropdownItem, + DropdownPosition, + DropdownSeparator +} from '@patternfly/react-core/deprecated'; import { CaretDownIcon } from '@patternfly/react-icons/dist/esm/icons/caret-down-icon'; const _ = cockpit.gettext; @@ -109,7 +112,7 @@ export class DSInstance extends React.Component { }; // Dropdown tasks - this.handleToggle = dropdownIsOpen => { + this.handleToggle = (_event, dropdownIsOpen) => { this.setState({ dropdownIsOpen }); @@ -483,7 +486,7 @@ export class DSInstance extends React.Component { }); } - handleServerIdChange(e) { + handleServerIdChange(_event, e) { this.setState({ pageLoadingState: { state: "loading", jsx: "" }, progressValue: 25 @@ -691,7 +694,12 @@ export class DSInstance extends React.Component { <span className="spinner spinner-lg spinner-inline" /> </p> <div className="ds-margin-top-lg"> - <Progress value={progressValue} label={`${progressValue}%`} measureLocation={ProgressMeasureLocation.inside} /> + <Progress + value={progressValue} + label={`${progressValue}%`} + measureLocation={ProgressMeasureLocation.inside} + aria-label="Directory Server Configuration loading progress" + /> </div> </div> </div> @@ -764,7 +772,12 @@ export class DSInstance extends React.Component { position={DropdownPosition.right} onSelect={this.handleDropdown} toggle={ - <DropdownToggle onToggle={this.handleToggle} toggleIndicator={CaretDownIcon} isPrimary id="ds-dropdown"> + <DropdownToggle + onToggle={(event, isOpen) => this.handleToggle(event, isOpen)} + toggleIndicator={CaretDownIcon} + variant="primary" + id="ds-dropdown" + > {_("Actions")} </DropdownToggle> } diff --git a/src/cockpit/389-console/src/dsModals.jsx b/src/cockpit/389-console/src/dsModals.jsx index 67a235070c..8b39324efb 100644 --- a/src/cockpit/389-console/src/dsModals.jsx +++ b/src/cockpit/389-console/src/dsModals.jsx @@ -414,12 +414,12 @@ export class CreateInstanceModal extends React.Component { id="createServerId" aria-describedby="horizontal-form-name-helper" name="createServerId" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={errObj.createServerId ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errObj.createServerId}> + <FormHelperText > {this.state.createServerIdMsg} </FormHelperText> </GridItem> @@ -442,7 +442,7 @@ export class CreateInstanceModal extends React.Component { plusBtnAriaLabel="plus" widthChars={8} /> - <FormHelperText className="ds-info-color" isHidden={createPort !== 0}> + <FormHelperText className="ds-info-color" > {_("Port 0 will disable non-TLS connections")} </FormHelperText> </GridItem> @@ -475,7 +475,7 @@ export class CreateInstanceModal extends React.Component { <Checkbox id="createTLSCert" isChecked={createTLSCert} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} /> @@ -492,12 +492,12 @@ export class CreateInstanceModal extends React.Component { id="createDM" aria-describedby="horizontal-form-name-helper" name="createDM" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={errObj.createDM ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errObj.createDM}> + <FormHelperText > {_("Enter a valid DN")} </FormHelperText> </GridItem> @@ -513,12 +513,12 @@ export class CreateInstanceModal extends React.Component { id="createDMPassword" aria-describedby="horizontal-form-name-helper" name="createDMPassword" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={errObj.createDMPassword ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errObj.createDMPassword}> + <FormHelperText > {_("Password must be set and it must match the confirmation password.")} </FormHelperText> </GridItem> @@ -534,12 +534,12 @@ export class CreateInstanceModal extends React.Component { id="createDMPasswordConfirm" aria-describedby="horizontal-form-name-helper" name="createDMPasswordConfirm" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={errObj.createDMPasswordConfirm ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errObj.createDMPasswordConfirm}> + <FormHelperText > {_("Confirmation password must be set and it must match the first password.")} </FormHelperText> </GridItem> @@ -549,7 +549,7 @@ export class CreateInstanceModal extends React.Component { id="createDBCheckbox" label={_("Create Database")} isChecked={createDBCheckbox} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} /> @@ -568,12 +568,12 @@ export class CreateInstanceModal extends React.Component { aria-describedby="horizontal-form-name-helper" name="createDBSuffix" isDisabled={!createDBCheckbox} - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={errObj.createDBSuffix ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errObj.createDBSuffix}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -591,12 +591,12 @@ export class CreateInstanceModal extends React.Component { aria-describedby="horizontal-form-name-helper" name="createDBName" isDisabled={!createDBCheckbox} - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={errObj.createDBName ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errObj.createDBName}> + <FormHelperText > {_("Name is required")} </FormHelperText> </GridItem> @@ -609,7 +609,7 @@ export class CreateInstanceModal extends React.Component { <FormSelect id="createInitDB" value={createInitDB} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" @@ -721,7 +721,7 @@ export class SchemaReloadModal extends React.Component { id="reloadSchemaDir" aria-describedby="horizontal-form-name-helper" name="reloadSchemaDir" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} /> diff --git a/src/cockpit/389-console/src/lib/database/attrEncryption.jsx b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx index e6e20eabac..996f9192de 100644 --- a/src/cockpit/389-console/src/lib/database/attrEncryption.jsx +++ b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx @@ -3,13 +3,15 @@ import React from "react"; import { DoubleConfirmModal } from "../notifications.jsx"; import { EncryptedAttrTable } from "./databaseTables.jsx"; import { - Button, - Grid, - GridItem, - Select, - SelectVariant, - SelectOption, -} from "@patternfly/react-core"; + Button, + Grid, + GridItem +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import { log_cmd } from "../tools.jsx"; @@ -191,7 +193,7 @@ export class AttrEncryption extends React.Component { <GridItem span={6}> <Select variant={SelectVariant.typeahead} - onToggle={this.handleSelectToggle} + onToggle={(_event, isSelectOpen) => this.handleSelectToggle(isSelectOpen)} onSelect={this.handleSelect} onClear={this.handleSelectClear} selections={addAttr} diff --git a/src/cockpit/389-console/src/lib/database/backups.jsx b/src/cockpit/389-console/src/lib/database/backups.jsx index 8d70b48f7f..cfe0547b7d 100644 --- a/src/cockpit/389-console/src/lib/database/backups.jsx +++ b/src/cockpit/389-console/src/lib/database/backups.jsx @@ -830,7 +830,7 @@ class ExportModal extends React.Component { {_("Select Suffix")} </GridItem> <GridItem span={9}> - <FormSelect id="ldifSuffix" value={ldifSuffix} onChange={(value, event) => { handleChange(event) }} aria-label="FormSelect Input"> + <FormSelect id="ldifSuffix" value={ldifSuffix} onChange={(event, value) => { handleChange(event) }} aria-label="FormSelect Input"> {suffixList} </FormSelect> </GridItem> @@ -848,7 +848,7 @@ class ExportModal extends React.Component { aria-describedby="horizontal-form-name-helper" name="ldifName" value={ldifName} - onChange={(value, event) => { handleChange(event) }} + onChange={(event, value) => { handleChange(event) }} validated={error.ldifName ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -861,7 +861,7 @@ class ExportModal extends React.Component { id="includeReplData" className="ds-indent ds-margin-top" isChecked={this.props.includeReplData} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} title={_("Include the replication metadata needed to restore or initialize another replica.")} @@ -939,7 +939,7 @@ export class BackupModal extends React.Component { id="backupName" aria-describedby="horizontal-form-name-helper" name="ldifName" - onChange={(value, event) => { handleChange(event) }} + onChange={(event, value) => { handleChange(event) }} validated={error.backupName ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> diff --git a/src/cockpit/389-console/src/lib/database/chaining.jsx b/src/cockpit/389-console/src/lib/database/chaining.jsx index 2438b84731..1e881da586 100644 --- a/src/cockpit/389-console/src/lib/database/chaining.jsx +++ b/src/cockpit/389-console/src/lib/database/chaining.jsx @@ -3,34 +3,35 @@ import React from "react"; import { DoubleConfirmModal } from "../notifications.jsx"; import { log_cmd, callCmdStreamPassword } from "../tools.jsx"; import { - Button, - Checkbox, - ExpandableSection, - Form, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectOption, - SelectVariant, - SimpleList, - SimpleListItem, - Tab, - Tabs, - TabTitleText, - TextInput, - Text, - TextContent, - TextVariants, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + ExpandableSection, + Form, + Grid, + GridItem, + Modal, + ModalVariant, + SimpleList, + SimpleListItem, + Tab, + Tabs, + TabTitleText, + TextInput, + Text, + TextContent, + TextVariants, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - faLink, - faSyncAlt -} from '@fortawesome/free-solid-svg-icons'; + SyncAltIcon, + LinkIcon +} from '@patternfly/react-icons'; const _ = cockpit.gettext; @@ -100,7 +101,7 @@ export class ChainingDatabaseConfig extends React.Component { _defUseStartTLS: this.props.data.defUseStartTLS, }; - this.handleToggle = (isExpanded) => { + this.handleToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -582,7 +583,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defSizeLimit" aria-describedby="defSizeLimit" name="defSizeLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -602,7 +603,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defConcurOpLimit" aria-describedby="defConcurOpLimit" name="defConcurOpLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -622,7 +623,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defTimeLimit" aria-describedby="defTimeLimit" name="defTimeLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -642,7 +643,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defConnLife" aria-describedby="defConnLife" name="defConnLife" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -662,7 +663,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defBindConnLimit" aria-describedby="defBindConnLimit" name="defBindConnLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -682,7 +683,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defOpConnLimit" aria-describedby="defOpConnLimit" name="defOpConnLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -702,7 +703,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defSearchCheck" aria-describedby="defSearchCheck" name="defSearchCheck" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -722,7 +723,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defConcurLimit" aria-describedby="defConcurLimit" name="defConcurLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -742,7 +743,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defHopLimit" aria-describedby="defHopLimit" name="defHopLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -762,7 +763,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defBindTimeout" aria-describedby="defBindTimeout" name="defBindTimeout" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -782,7 +783,7 @@ export class ChainingDatabaseConfig extends React.Component { id="defBindRetryLimit" aria-describedby="defBindRetryLimit" name="defBindRetryLimit" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -797,7 +798,7 @@ export class ChainingDatabaseConfig extends React.Component { label={_("Check Local ACIs")} id="defCheckAci" isChecked={this.state.defCheckAci} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} aria-label="check aci" @@ -813,7 +814,7 @@ export class ChainingDatabaseConfig extends React.Component { label={_("Send Referral On Scoped Search")} id="defRefOnScoped" isChecked={this.state.defRefOnScoped} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} aria-label="send ref" @@ -829,7 +830,7 @@ export class ChainingDatabaseConfig extends React.Component { label={_("Allow Proxied Authentication")} id="defProxy" isChecked={this.state.defProxy} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} aria-label="prox auth" @@ -845,7 +846,7 @@ export class ChainingDatabaseConfig extends React.Component { label={_("Use StartTLS")} id="defUseStartTLS" isChecked={this.state.defUseStartTLS} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} aria-label="startTLS" @@ -1054,7 +1055,7 @@ export class ChainingConfig extends React.Component { }; } - this.handleSelectToggle = isOpen => { + this.handleSelectToggle = (_event, isOpen) => { this.setState({ isOpen }); @@ -1357,14 +1358,23 @@ export class ChainingConfig extends React.Component { <GridItem span={10} className="ds-word-wrap"> <TextContent> <Text className="ds-suffix-header" component={TextVariants.h3}> - <FontAwesomeIcon size="sm" icon={faLink} /> {this.props.suffix} (<i><font size="3">{this.props.bename}</font></i>) - <FontAwesomeIcon - size="lg" - className="ds-left-margin ds-refresh" - icon={faSyncAlt} - title={_("Refresh database link")} + <> + <Icon> + <LinkIcon size="sm" /> + </Icon>{' '} + {this.props.suffix} ( + <Text component={TextVariants.i} style={{ fontSize: '16px' }}> + {this.props.bename} + </Text> + ) + </> + <Button + variant="plain" + aria-label={_("Refresh database link")} onClick={() => this.props.reload(this.props.suffix)} - /> + > + <SyncAltIcon size="lg" /> + </Button> </Text> </TextContent> </GridItem> @@ -1393,7 +1403,7 @@ export class ChainingConfig extends React.Component { id="nsfarmserverurl" aria-describedby="nsfarmserverurl" name="nsfarmserverurl" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1413,7 +1423,7 @@ export class ChainingConfig extends React.Component { id="nsmultiplexorbinddn" aria-describedby="nsmultiplexorbinddn" name="nsmultiplexorbinddn" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1433,7 +1443,7 @@ export class ChainingConfig extends React.Component { id="nsmultiplexorcredentials" aria-describedby="nsmultiplexorcredentials" name="nsmultiplexorcredentials" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} validated={(error.nsmultiplexorcredentials || !this.state.linkPwdMatch) ? ValidatedOptions.error : ValidatedOptions.default} @@ -1454,7 +1464,7 @@ export class ChainingConfig extends React.Component { id="nsmultiplexorcredentials_confirm" aria-describedby="nsmultiplexorcredentials_confirm" name="nsmultiplexorcredentials_confirm" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} validated={(error.nsmultiplexorcredentials_confirm || !this.state.linkPwdMatch) ? ValidatedOptions.error : ValidatedOptions.default} @@ -1472,7 +1482,7 @@ export class ChainingConfig extends React.Component { <Select variant={SelectVariant.single} aria-label="Select Input" - onToggle={this.handleSelectToggle} + onToggle={(event, isOpen) => this.handleSelectToggle(event, isOpen)} onSelect={this.handleSelect} selections={this.state.nsbindmechanism} isOpen={this.state.isOpen} @@ -1493,7 +1503,7 @@ export class ChainingConfig extends React.Component { label={_("Use StartTLS")} id="nsusestarttls" isChecked={this.state.nsusestarttls} - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} aria-label="check startTLS" @@ -1504,7 +1514,7 @@ export class ChainingConfig extends React.Component { <ExpandableSection className="ds-margin-top-xlg" toggleText={this.state.isExpanded ? _("Hide Advanced Settings") : _("Show Advanced Settings")} - onToggle={this.handleToggle} + onToggle={(event, isOpen) => this.handleToggle(event, isOpen)} isExpanded={this.state.isExpanded} > <div className="ds-margin-top ds-margin-left"> @@ -1522,7 +1532,7 @@ export class ChainingConfig extends React.Component { id="sizelimit" aria-describedby="sizelimit" name="sizelimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1542,7 +1552,7 @@ export class ChainingConfig extends React.Component { id="timelimit" aria-describedby="timelimit" name="timelimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1562,7 +1572,7 @@ export class ChainingConfig extends React.Component { id="bindconnlimit" aria-describedby="bindconnlimit" name="bindconnlimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1582,7 +1592,7 @@ export class ChainingConfig extends React.Component { id="opconnlimit" aria-describedby="opconnlimit" name="opconnlimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1602,7 +1612,7 @@ export class ChainingConfig extends React.Component { id="concurrbindlimit" aria-describedby="concurrbindlimit" name="concurrbindlimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1622,7 +1632,7 @@ export class ChainingConfig extends React.Component { id="bindtimeout" aria-describedby="bindtimeout" name="bindtimeout" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1642,7 +1652,7 @@ export class ChainingConfig extends React.Component { id="bindretrylimit" aria-describedby="bindretrylimit" name="bindretrylimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1662,7 +1672,7 @@ export class ChainingConfig extends React.Component { id="concurroplimit" aria-describedby="concurroplimit" name="concurroplimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1682,7 +1692,7 @@ export class ChainingConfig extends React.Component { id="connlifetime" aria-describedby="connlifetime" name="connlifetime" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1702,7 +1712,7 @@ export class ChainingConfig extends React.Component { id="searchcheckinterval" aria-describedby="searchcheckinterval" name="searchcheckinterval" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1722,7 +1732,7 @@ export class ChainingConfig extends React.Component { id="hoplimit" aria-describedby="hoplimit" name="hoplimit" - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} /> @@ -1737,7 +1747,7 @@ export class ChainingConfig extends React.Component { label={_("Allow Proxied Authentication")} id="nsproxiedauthorization" isChecked={this.state.nsproxiedauthorization} - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} aria-label="send ref" @@ -1753,7 +1763,7 @@ export class ChainingConfig extends React.Component { label={_("Check Local ACIs")} id="nschecklocalaci" isChecked={this.state.nschecklocalaci} - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} aria-label="send ref" @@ -1769,7 +1779,7 @@ export class ChainingConfig extends React.Component { label={_("Send Referral On Scoped Search")} id="nsreferralonscopedsearch" isChecked={this.state.nsreferralonscopedsearch} - onChange={(str, e) => { + onChange={(e, str) => { this.onChange(e); }} aria-label="send ref" diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx index b9cb661e80..b944c08d9c 100644 --- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx +++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx @@ -117,10 +117,10 @@ export class GlobalDatabaseConfig extends React.Component { this.props.enableTree(); } - handleSelectDBLocksMonitoring (val, e) { + handleSelectDBLocksMonitoring (e, val) { this.setState({ dblocksMonitoring: !this.state.dblocksMonitoring - }, this.handleChange(val, e)); + }, this.handleChange(e, val)); } validateSaveBtn() { @@ -148,7 +148,7 @@ export class GlobalDatabaseConfig extends React.Component { }); } - handleChange(str, e) { + handleChange(e, str) { // Generic const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; const attr = e.target.id; @@ -158,7 +158,7 @@ export class GlobalDatabaseConfig extends React.Component { }, () => { this.validateSaveBtn() }); } - handleTimeChange(value) { + handleTimeChange(_event, value) { this.setState({ compacttime: value, }, () => { this.validateSaveBtn() }); @@ -735,7 +735,7 @@ export class GlobalDatabaseConfig extends React.Component { <GridItem span={12}> <Checkbox label={_("Automatic Cache Tuning")} - onChange={this.handleChange} + onChange={(e, str) => this.handleChange(e, str)} isChecked={db_auto_checked} aria-label="uncontrolled checkbox example" id="db_cache_auto" @@ -755,7 +755,7 @@ export class GlobalDatabaseConfig extends React.Component { <Checkbox label={_("Automatic Import Cache Tuning")} title={_("Set import cache to be set automatically")} - onChange={this.handleChange} + onChange={(e, str) => this.handleChange(e, str)} isChecked={import_auto_checked} aria-label="uncontrolled checkbox example" id="import_cache_auto" @@ -828,7 +828,7 @@ export class GlobalDatabaseConfig extends React.Component { label={_("Enable DB Lock Monitoring")} id="dblocksMonitoring" isChecked={this.state.dblocksMonitoring} - onChange={this.handleSelectDBLocksMonitoring} + onChange={(e, val) => this.handleSelectDBLocksMonitoring(e, val)} aria-label="uncontrolled checkbox example" /> </div> @@ -871,7 +871,7 @@ export class GlobalDatabaseConfig extends React.Component { id="txnlogdir" aria-describedby="txnlogdir" name="txnlogdir" - onChange={this.handleChange} + onChange={(e, str) => this.handleChange(e, str)} /> </GridItem> </Grid> @@ -889,7 +889,7 @@ export class GlobalDatabaseConfig extends React.Component { id="dbhomedir" aria-describedby="dbhomedir" name="dbhomedir" - onChange={this.handleChange} + onChange={(e, str) => this.handleChange(e, str)} /> </GridItem> </Grid> @@ -1156,7 +1156,7 @@ export class GlobalDatabaseConfigMDB extends React.Component { }); } - handleChange(str, e) { + handleChange(e, str) { // Generic const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; const attr = e.target.id; diff --git a/src/cockpit/389-console/src/lib/database/databaseModal.jsx b/src/cockpit/389-console/src/lib/database/databaseModal.jsx index 740e4a2ff9..a642359968 100644 --- a/src/cockpit/389-console/src/lib/database/databaseModal.jsx +++ b/src/cockpit/389-console/src/lib/database/databaseModal.jsx @@ -78,7 +78,7 @@ class CreateLinkModal extends React.Component { id="createLinkSuffix" aria-describedby="horizontal-form-name-helper" name="createLinkSuffix" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.createLinkSuffix ? ValidatedOptions.error : ValidatedOptions.default} @@ -88,7 +88,7 @@ class CreateLinkModal extends React.Component { <b><font color="blue">,{suffix}</font></b> </div> </div> - <FormHelperText isError isHidden={!error.createLinkSuffix}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -103,12 +103,12 @@ class CreateLinkModal extends React.Component { id="createLinkName" aria-describedby="horizontal-form-name-helper" name="createLinkName" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.createLinkName ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.createLinkName}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -123,12 +123,12 @@ class CreateLinkModal extends React.Component { id="createNsfarmserverurl" aria-describedby="horizontal-form-name-helper" name="createNsfarmserverurl" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.createNsfarmserverurl ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.createNsfarmserverurl}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -143,12 +143,12 @@ class CreateLinkModal extends React.Component { id="createNsmultiplexorbinddn" aria-describedby="horizontal-form-name-helper" name="createNsmultiplexorbinddn" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.createNsmultiplexorbinddn ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.createNsmultiplexorbinddn}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -163,12 +163,12 @@ class CreateLinkModal extends React.Component { id="createNsmultiplexorcredentials" aria-describedby="horizontal-form-name-helper" name="createNsmultiplexorcredentials" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.createNsmultiplexorcredentials ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.createNsmultiplexorcredentials}> + <FormHelperText > {_("Password does not match")} </FormHelperText> </GridItem> @@ -183,12 +183,12 @@ class CreateLinkModal extends React.Component { id="createNsmultiplexorcredentialsConfirm" aria-describedby="horizontal-form-name-helper" name="createNsmultiplexorcredentialsConfirm" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.createNsmultiplexorcredentialsConfirm ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.createNsmultiplexorcredentialsConfirm}> + <FormHelperText > {_("Password does not match")} </FormHelperText> </GridItem> @@ -211,7 +211,7 @@ class CreateLinkModal extends React.Component { <GridItem span={12}> <Checkbox id="createUseStartTLS" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} isChecked={starttls_checked} @@ -296,7 +296,7 @@ class CreateSubSuffixModal extends React.Component { id="subSuffixValue" aria-describedby="horizontal-form-name-helper" name="subSuffixValue" - onChange={(val, e) => { + onChange={(e, val) => { handleChange(e); }} validated={error.subSuffixValue ? ValidatedOptions.error : ValidatedOptions.default} @@ -306,7 +306,7 @@ class CreateSubSuffixModal extends React.Component { <b><font color="blue">,{suffix}</font></b> </div> </div> - <FormHelperText isError isHidden={!error.subSuffixValue}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -321,12 +321,12 @@ class CreateSubSuffixModal extends React.Component { id="subSuffixBeName" aria-describedby="horizontal-form-name-helper" name="subSuffixBeName" - onChange={(val, e) => { + onChange={(e, val) => { handleChange(e); }} validated={error.subSuffixBeName ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.subSuffixBeName}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -403,7 +403,7 @@ class ExportModal extends React.Component { id="ldifLocation" aria-describedby="horizontal-form-name-helper" name="ldifLocation" - onChange={(val, e) => { + onChange={(e, val) => { handleChange(e); }} validated={error.ldifLocation ? ValidatedOptions.error : ValidatedOptions.default} @@ -414,7 +414,7 @@ class ExportModal extends React.Component { <GridItem span={12}> <Checkbox id="includeReplData" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} isChecked={includeReplData} @@ -474,7 +474,7 @@ class ImportModal extends React.Component { id="ldifLocation" aria-describedby="horizontal-form-name-helper" name="ldifLocation" - onChange={(val, e) => { + onChange={(e, val) => { handleChange(e); }} /> diff --git a/src/cockpit/389-console/src/lib/database/databaseTables.jsx b/src/cockpit/389-console/src/lib/database/databaseTables.jsx index 518f7cca4e..1c4b437161 100644 --- a/src/cockpit/389-console/src/lib/database/databaseTables.jsx +++ b/src/cockpit/389-console/src/lib/database/databaseTables.jsx @@ -5,17 +5,18 @@ import { Grid, GridItem, Pagination, - PaginationVariant, SearchInput, } from '@patternfly/react-core'; import { - expandable, - Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + Table, + Thead, + Tr, + Th, + Tbody, + Td, + ActionsColumn, + ExpandableRowContent, + SortByDirection } from '@patternfly/react-table'; import { TrashAltIcon } from '@patternfly/react-icons/dist/js/icons/trash-alt-icon'; import { ArrowRightIcon } from '@patternfly/react-icons/dist/js/icons/arrow-right-icon'; @@ -34,8 +35,8 @@ class ReferralTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Referral"), transforms: [sortable] }, - { props: { textCenter: true }, title: _("Delete Referral") }, + { title: _("Referral"), sortable: true }, + { title: _("Delete Referral") }, ], }; @@ -73,12 +74,10 @@ class ReferralTable extends React.Component { let rows = []; let columns = this.state.columns; for (const refRow of this.props.rows) { - rows.push({ - cells: [refRow, { props: { textCenter: true }, title: this.getDeleteButton(refRow) }] - }); + rows.push([refRow, this.getDeleteButton(refRow)]); } if (rows.length === 0) { - rows = [{ cells: [_("No Referrals")] }]; + rows = [[_("No Referrals")]]; columns = [{ title: _("Referrals") }]; } this.setState({ @@ -88,17 +87,17 @@ class ReferralTable extends React.Component { } handleSort(_event, index, direction) { - const rows = []; const sortedRefs = [...this.props.rows]; - - // Sort the referrals and build the new rows + sortedRefs.sort(); - if (direction !== SortByDirection.asc) { + if (direction !== 'asc') { sortedRefs.reverse(); } - for (const refRow of sortedRefs) { - rows.push({ cells: [refRow, { props: { textCenter: true }, title: this.getDeleteButton(refRow) }] }); - } + + const rows = sortedRefs.map(refRow => [ + refRow, + this.getDeleteButton(refRow) + ]); this.setState({ sortBy: { @@ -112,27 +111,52 @@ class ReferralTable extends React.Component { render() { const { columns, rows, perPage, page, sortBy } = this.state; + const startIdx = (perPage * page) - perPage; + const displayRows = rows.slice(startIdx, startIdx + perPage); return ( <div className="ds-margin-top-lg"> <Table - className="ds-margin-top" aria-label="referral table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {displayRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td + key={cellIndex} + textCenter={cellIndex === 1} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -152,9 +176,9 @@ class IndexTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Attribute"), transforms: [sortable] }, // name - { title: _("Indexing Types"), transforms: [sortable] }, // types - { title: _("Matching Rules"), transforms: [sortable] }, // matchingrules + { title: _("Attribute"), sortable: true }, + { title: _("Indexing Types"), sortable: true }, + { title: _("Matching Rules"), sortable: true } ], }; @@ -174,36 +198,15 @@ class IndexTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - // Copy the rows so we can handle sorting and searching + componentDidMount() { this.setState({ rows: [...this.props.rows] }); } - actions() { - return [ - { - title: _("Edit Index"), - onClick: (event, rowId, rowData, extra) => - this.props.editIndex(rowData) - }, - { - title: _("Reindex"), - onClick: (event, rowId, rowData, extra) => - this.props.reindexIndex(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Index"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteIndex(rowData[0], rowData) - } - ]; - } - handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -216,20 +219,19 @@ class IndexTable extends React.Component { handleSearchChange(event, value) { let rows = []; const val = value.toLowerCase(); - for (const row of this.props.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2]]); - } + if (val === "") { - // reset rows rows = [...this.props.rows]; + } else { + for (const row of this.props.rows) { + if (row[0].toLowerCase().includes(val) || + row[1].toLowerCase().includes(val) || + row[2].toLowerCase().includes(val)) { + rows.push([row[0], row[1], row[2]]); + } + } } + this.setState({ rows, value, @@ -242,6 +244,25 @@ class IndexTable extends React.Component { let columns = this.state.columns; let has_rows = true; let tableRows; + + const getActionsForRow = (rowData) => [ + { + title: _("Edit Index"), + onClick: () => this.props.editIndex(rowData) + }, + { + title: _("Reindex"), + onClick: () => this.props.reindexIndex(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Index"), + onClick: () => this.props.deleteIndex(rowData[0], rowData) + } + ]; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Indexes") }]; @@ -250,6 +271,7 @@ class IndexTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div className="ds-margin-top-xlg"> <SearchInput @@ -257,29 +279,65 @@ class IndexTable extends React.Component { placeholder={_("Search indexes")} value={this.state.value} onChange={this.handleSearchChange} - onClear={(evt, val) => this.handleSearchChange(evt, '')} + onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table - className="ds-margin-top" - aria-label="glue table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows && this.props.editable ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + <Table + aria-label="index table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && this.props.editable && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + // Handle array-type rows + row.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {Array.isArray(cell) && cell.length === 0 ? + '' : // Handle empty arrays + String(cell) // Convert any value to string + } + </Td> + )) + ) : ( + // Handle object-type rows (for the "No Indexes" case) + row.cells && row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && this.props.editable && ( + <Td isActionCell> + <ActionsColumn + items={getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -299,8 +357,8 @@ class EncryptedAttrTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Encrypted Attribute"), transforms: [sortable] }, - { props: { textCenter: true }, title: _("Delete Attribute") }, + { title: _("Encrypted Attribute"), sortable: true }, + { title: _("Delete Attribute") }, ], }; @@ -338,12 +396,10 @@ class EncryptedAttrTable extends React.Component { let rows = []; let columns = this.state.columns; for (const attrRow of this.props.rows) { - rows.push({ - cells: [attrRow, { props: { textCenter: true }, title: this.getDeleteButton(attrRow) }] - }); + rows.push([attrRow, this.getDeleteButton(attrRow)]); } if (rows.length === 0) { - rows = [{ cells: [_("No Attributes")] }]; + rows = [[_("No Attributes")]]; columns = [{ title: _("Encrypted Attribute") }]; } this.setState({ @@ -353,17 +409,17 @@ class EncryptedAttrTable extends React.Component { } handleSort(_event, index, direction) { - const rows = []; const sortedAttrs = [...this.props.rows]; - // Sort the referrals and build the new rows sortedAttrs.sort(); - if (direction !== SortByDirection.asc) { + if (direction !== 'asc') { sortedAttrs.reverse(); } - for (const attrRow of sortedAttrs) { - rows.push({ cells: [attrRow, { props: { textCenter: true }, title: this.getDeleteButton(attrRow) }] }); - } + + const rows = sortedAttrs.map(attrRow => [ + attrRow, + this.getDeleteButton(attrRow) + ]); this.setState({ sortBy: { @@ -377,27 +433,52 @@ class EncryptedAttrTable extends React.Component { render() { const { columns, rows, perPage, page, sortBy } = this.state; + const startIdx = (perPage * page) - perPage; + const displayRows = rows.slice(startIdx, startIdx + perPage); return ( <div className="ds-margin-top-lg"> <Table - className="ds-margin-top" - aria-label="referral table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + aria-label="encrypted attributes table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {displayRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td + key={cellIndex} + textCenter={cellIndex === 1} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -417,10 +498,10 @@ class LDIFTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("LDIF File"), transforms: [sortable] }, - { title: _("Creation Date"), transforms: [sortable] }, - { title: _("File Size"), transforms: [sortable] }, - { title: '' } + { title: _("LDIF File"), sortable: true }, + { title: _("Creation Date"), sortable: true }, + { title: _("File Size"), sortable: true }, + { title: _("Actions"), screenReaderText: _("LDIF file actions") } ], }; @@ -458,14 +539,10 @@ class LDIFTable extends React.Component { let rows = []; let columns = this.state.columns; for (const ldifRow of this.props.rows) { - rows.push({ - cells: [ - ldifRow[0], ldifRow[1], ldifRow[2] - ] - }); + rows.push(ldifRow); } if (rows.length === 0) { - rows = [{ cells: [_("No LDIF files")] }]; + rows = [[_("No LDIF files")]]; columns = [{ title: _("LDIF File") }]; } this.setState({ @@ -475,69 +552,84 @@ class LDIFTable extends React.Component { } handleSort(_event, index, direction) { - const rows = []; - const sortedLDIF = [...this.props.rows]; - - // Sort the referrals and build the new rows - sortedLDIF.sort(); - if (direction !== SortByDirection.asc) { - sortedLDIF.reverse(); - } - for (const ldifRow of sortedLDIF) { - rows.push({ - cells: - [ - ldifRow[0], ldifRow[1], ldifRow[2] - ] - }); - } - + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, direction }, - rows, + rows: direction === 'asc' ? sortedRows : sortedRows.reverse(), page: 1, }); } - actions() { - return [ - { - title: _("Import LDIF File"), - onClick: (event, rowId, rowData, extra) => - this.props.confirmImport(rowData.cells[0]) - }, - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Import LDIF File"), + onClick: () => this.props.confirmImport(rowData[0]) + } + ]; render() { const { columns, rows, perPage, page, sortBy } = this.state; + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); + const hasRows = this.props.rows.length > 0; return ( <div className="ds-margin-top-lg"> <Table - className="ds-margin-top" aria-label="ldif table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={this.props.rows.length > 0 ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={hasRows && column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + screenReaderText={column.screenReaderText} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + <Td>{row}</Td> + )} + {/* Only render the action column if we have rows */} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination - itemCount={this.props.rows.length} + itemCount={rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -557,10 +649,10 @@ class LDIFManageTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("LDIF File"), transforms: [sortable] }, - { title: _("Suffix"), transforms: [sortable] }, - { title: _("Creation Date"), transforms: [sortable] }, - { title: _("File Size"), transforms: [sortable] }, + { title: _("LDIF File"), sortable: true }, + { title: _("Suffix"), sortable: true }, + { title: _("Creation Date"), sortable: true }, + { title: _("File Size"), sortable: true }, ], }; @@ -583,9 +675,7 @@ class LDIFManageTable extends React.Component { let rows = []; let columns = this.state.columns; for (const ldifRow of this.props.rows) { - rows.push({ - cells: [ldifRow[0], ldifRow[3], ldifRow[1], ldifRow[2]] - }); + rows.push([ldifRow[0], ldifRow[3], ldifRow[1], ldifRow[2]]); } if (rows.length === 0) { rows = [{ cells: [_("No LDIF files")] }]; @@ -598,74 +688,102 @@ class LDIFManageTable extends React.Component { } handleSort(_event, index, direction) { - const rows = []; - const sortedLDIF = [...this.props.rows]; + const sortedLDIF = [...this.state.rows]; + + sortedLDIF.sort((a, b) => { + const aValue = Array.isArray(a) ? a[index] : a.cells[index]; + const bValue = Array.isArray(b) ? b[index] : b.cells[index]; + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + }); - // Sort the referrals and build the new rows - sortedLDIF.sort(); if (direction !== SortByDirection.asc) { sortedLDIF.reverse(); } - for (const ldifRow of sortedLDIF) { - rows.push({ - cells: [ldifRow[0], ldifRow[3], ldifRow[1], ldifRow[2]] - }); - } this.setState({ sortBy: { index, direction }, - rows, + rows: sortedLDIF, page: 1, }); } - actions() { + getActions(rowData) { return [ { title: _("Import LDIF"), - onClick: (event, rowId, rowData, extra) => - this.props.confirmImport(rowData.cells[0], rowData.cells[1]) + onClick: () => this.props.confirmImport(rowData[0], rowData[1]) }, { title: _("Delete LDIF"), - onClick: (event, rowId, rowData, extra) => - this.props.confirmDelete(rowData.cells[0]) + onClick: () => this.props.confirmDelete(rowData[0]) }, ]; } render() { const { columns, rows, perPage, page, sortBy } = this.state; - let hasRows = true; - if (this.props.rows.length === 0) { - hasRows = false; - } + const hasRows = this.props.rows.length > 0; + + // Calculate pagination + const startIdx = (perPage * page) - perPage; + const tableRows = [...rows].splice(startIdx, perPage); + return ( <div className="ds-margin-top-lg"> <Table className="ds-margin-top" aria-label="manage ldif table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={hasRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -685,9 +803,9 @@ class BackupTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Backup"), transforms: [sortable] }, - { title: _("Creation Date"), transforms: [sortable] }, - { title: _("Size"), transforms: [sortable] }, + { title: _("Backup"), sortable: true }, + { title: _("Creation Date"), sortable: true }, + { title: _("Size"), sortable: true }, ], }; @@ -710,12 +828,10 @@ class BackupTable extends React.Component { let rows = []; let columns = this.state.columns; for (const bakRow of this.props.rows) { - rows.push({ - cells: [bakRow[0], bakRow[1], bakRow[2]] - }); + rows.push([bakRow[0], bakRow[1], bakRow[2]]); } if (rows.length === 0) { - rows = [{ cells: [_("No Backups")] }]; + rows = [[_("No Backups")]]; columns = [{ title: _("Backups") }]; } this.setState({ @@ -725,74 +841,102 @@ class BackupTable extends React.Component { } handleSort(_event, index, direction) { - const rows = []; - const sortedBaks = [...this.props.rows]; + const sortedBaks = [...this.state.rows]; + + sortedBaks.sort((a, b) => { + const aValue = Array.isArray(a) ? a[index] : a[0]; + const bValue = Array.isArray(b) ? b[index] : b[0]; + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + }); - // Sort the referrals and build the new rows - sortedBaks.sort(); - if (direction !== SortByDirection.asc) { + if (direction !== 'asc') { sortedBaks.reverse(); } - for (const bakRow of sortedBaks) { - rows.push({ - cells: [bakRow[0], bakRow[1], bakRow[2]] - }); - } this.setState({ sortBy: { index, direction }, - rows, + rows: sortedBaks, page: 1, }); } - actions() { + getActions(rowData) { return [ { title: _("Restore Backup"), - onClick: (event, rowId, rowData, extra) => - this.props.confirmRestore(rowData.cells[0]) + onClick: () => this.props.confirmRestore(rowData[0]) }, { title: _("Delete Backup"), - onClick: (event, rowId, rowData, extra) => - this.props.confirmDelete(rowData.cells[0]) + onClick: () => this.props.confirmDelete(rowData[0]) }, ]; } render() { const { columns, rows, perPage, page, sortBy } = this.state; - let hasRows = true; - if (this.props.rows.length === 0) { - hasRows = false; - } + const hasRows = this.props.rows.length > 0; + + // Calculate pagination + const startIdx = (perPage * page) - perPage; + const tableRows = [...rows].splice(startIdx, perPage); + return ( <div className="ds-margin-top-lg"> <Table className="ds-margin-top" aria-label="backup table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={hasRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="up" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -812,9 +956,9 @@ class PwpTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Target DN"), transforms: [sortable] }, - { title: _("Policy Type"), transforms: [sortable] }, - { title: _("Database Suffix"), transforms: [sortable] }, + { title: _("Target DN"), sortable: true }, + { title: _("Policy Type"), sortable: true }, + { title: _("Database Suffix"), sortable: true }, ], }; @@ -837,9 +981,7 @@ class PwpTable extends React.Component { let rows = []; let columns = this.state.columns; for (const pwpRow of this.props.rows) { - rows.push({ - cells: [pwpRow[0], pwpRow[1], pwpRow[2]] - }); + rows.push([pwpRow[0], pwpRow[1], pwpRow[2]]); } if (rows.length === 0) { rows = [{ cells: [_("No Local Policies")] }]; @@ -852,74 +994,102 @@ class PwpTable extends React.Component { } handleSort(_event, index, direction) { - const rows = []; - const sortedPwp = [...this.props.rows]; + const sortedPwp = [...this.state.rows]; + + sortedPwp.sort((a, b) => { + const aValue = Array.isArray(a) ? a[index] : a.cells[index]; + const bValue = Array.isArray(b) ? b[index] : b.cells[index]; + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + }); - // Sort the referrals and build the new rows - sortedPwp.sort(); if (direction !== SortByDirection.asc) { sortedPwp.reverse(); } - for (const pwpRow of sortedPwp) { - rows.push({ - cells: [pwpRow[0], pwpRow[1], pwpRow[2]] - }); - } this.setState({ sortBy: { index, direction }, - rows, + rows: sortedPwp, page: 1, }); } - actions() { + getActions(rowData) { return [ { title: _("Edit Policy"), - onClick: (event, rowId, rowData, extra) => - this.props.editPolicy(rowData.cells[0]) + onClick: () => this.props.editPolicy(rowData[0]) }, { title: _("Delete policy"), - onClick: (event, rowId, rowData, extra) => - this.props.deletePolicy(rowData.cells[0]) + onClick: () => this.props.deletePolicy(rowData[0]) }, ]; } render() { const { columns, rows, perPage, page, sortBy } = this.state; - let hasRows = true; - if (this.props.rows.length === 0) { - hasRows = false; - } + const hasRows = this.props.rows.length > 0; + + // Calculate pagination + const startIdx = (perPage * page) - perPage; + const tableRows = [...rows].splice(startIdx, perPage); + return ( <div className="ds-margin-top-lg"> <Table className="ds-margin-top" aria-label="pwp table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={hasRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -942,12 +1112,11 @@ class VLVTable extends React.Component { columns: [ { title: _("Name"), - transforms: [sortable], - cellFormatters: [expandable] + sortable: true, }, { title: _("Search Base"), - transforms: [sortable], + sortable: true, }, ], }; @@ -1093,97 +1262,126 @@ class VLVTable extends React.Component { componentDidMount() { let rows = []; - let columns = this.state.columns; - let count = 0; let noRows = true; for (const row of this.props.rows) { - rows.push( - { - isOpen: false, - cells: [row.attrs.cn[0], row.attrs.vlvbase[0]], - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] - }, - ); - count += 2; + rows.push({ + isOpen: false, + cells: [row.attrs.cn[0], row.attrs.vlvbase[0]], + originalData: row // Store original data for expanded content + }); } + if (rows.length === 0) { rows = [{ cells: [_("No VLV Indexes")] }]; - columns = [{ title: _("VLV Indexes") }]; + this.setState({ + columns: [{ title: _("VLV Indexes") }] + }); } else { noRows = false; } + this.setState({ rows, - columns, noRows, }); } - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + rows[rowIndex].isOpen = isExpanding; + this.setState({ rows }); } - actions() { + getActions(rowData) { return [ { title: _("Reindex VLV"), - onClick: (event, rowId, rowData, extra) => - this.props.reindexFunc(rowData.cells[0]) + onClick: () => this.props.reindexFunc(rowData.cells[0]) }, { title: _("Delete VLV"), - onClick: (event, rowId, rowData, extra) => { - this.props.deleteFunc(rowData.cells[0]); - } + onClick: () => this.props.deleteFunc(rowData.cells[0]) } ]; } render() { - const { perPage, page, sortBy, rows, columns } = this.state; - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const { perPage, page, sortBy, rows, columns, noRows } = this.state; + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, perPage); return ( <div className={(this.props.saving || this.props.updating) ? "ds-margin-top-lg ds-disabled" : "ds-margin-top-lg"}> - <Table - className="ds-margin-top" + <Table aria-label="vlv table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - onCollapse={this.handleCollapse} - actions={!this.state.noRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {!noRows && <Th screenReaderText="Row expansion" />} + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + {!noRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + {!noRows && ( + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + )} + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + {!noRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(row)} + /> + </Td> + )} + </Tr> + {row.isOpen && row.originalData && ( + <Tr isExpanded={true}> + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + <ExpandableRowContent> + {this.getExpandedRow(row.originalData)} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination - itemCount={this.state.rows.length / 2} + itemCount={rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> diff --git a/src/cockpit/389-console/src/lib/database/globalPwp.jsx b/src/cockpit/389-console/src/lib/database/globalPwp.jsx index cee0c746a5..02f8366ccc 100644 --- a/src/cockpit/389-console/src/lib/database/globalPwp.jsx +++ b/src/cockpit/389-console/src/lib/database/globalPwp.jsx @@ -2,27 +2,29 @@ import cockpit from "cockpit"; import React from "react"; import { log_cmd } from "../tools.jsx"; import { - Alert, - Button, - Checkbox, - Form, - FormAlert, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Select, - SelectVariant, - SelectOption, - Spinner, - Tab, - Tabs, - TabTitleText, - TextInput, - Text, - TextContent, - TextVariants -} from "@patternfly/react-core"; + Alert, + Button, + Checkbox, + Form, + FormAlert, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Spinner, + Tab, + Tabs, + TabTitleText, + TextInput, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { @@ -813,7 +815,7 @@ export class GlobalPwPolicy extends React.Component { }); } - handleSelectToggle = isSelectOpen => { + handleSelectToggle = (_event, isSelectOpen) => { this.setState({ isSelectOpen }); @@ -853,7 +855,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordminlength" aria-describedby="horizontal-form-name-helper" name="passwordminlength" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -869,7 +871,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordminalphas" aria-describedby="horizontal-form-name-helper" name="passwordminalphas" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -887,7 +889,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmindigits" aria-describedby="horizontal-form-name-helper" name="passwordmindigits" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -903,7 +905,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordminspecials" aria-describedby="horizontal-form-name-helper" name="passwordminspecials" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -921,7 +923,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordminuppers" aria-describedby="horizontal-form-name-helper" name="passwordminuppers" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -937,7 +939,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordminlowers" aria-describedby="horizontal-form-name-helper" name="passwordminlowers" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -955,7 +957,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmin8bit" aria-describedby="horizontal-form-name-helper" name="passwordmin8bit" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -971,7 +973,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmincategories" aria-describedby="horizontal-form-name-helper" name="passwordmincategories" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -989,7 +991,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmaxsequence" aria-describedby="horizontal-form-name-helper" name="passwordmaxsequence" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -1005,7 +1007,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmaxseqsets" aria-describedby="horizontal-form-name-helper" name="passwordmaxseqsets" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -1023,7 +1025,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmaxclasschars" aria-describedby="horizontal-form-name-helper" name="passwordmaxclasschars" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -1041,7 +1043,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordbadwords" aria-describedby="horizontal-form-name-helper" name="passwordbadwords" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -1055,7 +1057,7 @@ export class GlobalPwPolicy extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute to check" - onToggle={this.handleSelectToggle} + onToggle={(_event, isSelectOpen) => this.handleSelectToggle(isSelectOpen)} onClear={this.handleSelectClear} onSelect={this.handleSyntaxChange} selections={this.state.passworduserattributes} @@ -1078,7 +1080,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passworddictcheck" isChecked={this.state.passworddictcheck} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} label={_("Dictionary Check")} @@ -1091,7 +1093,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordpalindrome" isChecked={this.state.passwordpalindrome} className="ds-label" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} label={_("Reject Palindromes")} @@ -1116,7 +1118,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmaxfailure" aria-describedby="horizontal-form-name-helper" name="passwordmaxpasswordmaxfailureclasschars" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} /> @@ -1133,7 +1135,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordresetfailurecount" aria-describedby="horizontal-form-name-helper" name="passwordresetfailurecount" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} /> @@ -1150,7 +1152,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordlockoutduration" aria-describedby="horizontal-form-name-helper" name="passwordlockoutduration" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} /> @@ -1161,7 +1163,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordunlock" isChecked={this.state.passwordunlock} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} label={_("Do Not Lockout Account Forever")} @@ -1186,7 +1188,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordmaxage" aria-describedby="horizontal-form-name-helper" name="passwordmaxage" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} /> @@ -1203,7 +1205,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordgracelimit" aria-describedby="horizontal-form-name-helper" name="passwordgracelimit" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} /> @@ -1220,7 +1222,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordwarning" aria-describedby="horizontal-form-name-helper" name="passwordwarning" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} /> @@ -1231,7 +1233,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordsendexpiringtime" isChecked={this.state.passwordsendexpiringtime} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} label={_("<>Always Send <i>Password Expiring</i> Control</>")} @@ -1245,7 +1247,7 @@ export class GlobalPwPolicy extends React.Component { if (this.state.loading || !this.state.loaded) { pwp_element = ( <div className="ds-margin-top-xlg ds-center"> - <Spinner isSVG size="xl" /> + <Spinner size="xl" /> </div> ); } else { @@ -1259,7 +1261,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="nsslapd-pwpolicy-local" isChecked={this.state['nsslapd-pwpolicy-local']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Allow Local Password Policies")} @@ -1271,7 +1273,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="nsslapd-pwpolicy-inherit-global" isChecked={this.state["nsslapd-pwpolicy-inherit-global"]} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Local Policies Inherit Global Policy")} @@ -1283,7 +1285,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="nsslapd-allow-hashed-passwords" isChecked={this.state["nsslapd-allow-hashed-passwords"]} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Allow Adding Pre-Hashed Passwords")} @@ -1295,7 +1297,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordisglobalpolicy" isChecked={this.state.passwordisglobalpolicy} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Replicate Password Policy State Attributes")} @@ -1307,7 +1309,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordtrackupdatetime" isChecked={this.state.passwordtrackupdatetime} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Track Password Update Time")} @@ -1319,7 +1321,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordchange" isChecked={this.state.passwordchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Allow Users To Change Their Passwords")} @@ -1331,7 +1333,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordmustchange" isChecked={this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("User Must Change Password After Reset")} @@ -1344,7 +1346,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordhistory" isChecked={this.state.passwordhistory} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Keep Password History")} @@ -1357,7 +1359,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordinhistory" aria-describedby="horizontal-form-name-helper" name="passwordinhistory" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} /> @@ -1372,7 +1374,7 @@ export class GlobalPwPolicy extends React.Component { <FormSelect id="passwordstoragescheme" value={this.state.passwordstoragescheme} - onChange={(value, event) => { + onChange={(event, value) => { this.handleGeneralChange(event); }} aria-label="FormSelect Input" @@ -1400,7 +1402,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordminage" aria-describedby="horizontal-form-name-helper" name="passwordminage" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} /> @@ -1419,7 +1421,7 @@ export class GlobalPwPolicy extends React.Component { id="passwordadmindn" aria-describedby="horizontal-form-name-helper" name="passwordadmindn" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} /> @@ -1432,7 +1434,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordadminskipinfoupdate" isChecked={this.state.passwordadminskipinfoupdate} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Do not update target entry's password state attributes")} @@ -1459,7 +1461,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordexp" isChecked={this.state.passwordexp} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} label={_("Enforce Password Expiration")} @@ -1487,7 +1489,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordlockout" isChecked={this.state.passwordlockout} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} label={_("Enable Account Lockout")} @@ -1515,7 +1517,7 @@ export class GlobalPwPolicy extends React.Component { <Checkbox id="passwordchecksyntax" isChecked={this.state.passwordchecksyntax} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} label={_("Enable Password Syntax Checking")} @@ -1562,7 +1564,7 @@ export class GlobalPwPolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="passwordtprmaxuse" isDisabled={!this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleTPRChange(e); }} /> @@ -1584,7 +1586,7 @@ export class GlobalPwPolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="passwordtprdelayexpireat" isDisabled={!this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleTPRChange(e); }} /> @@ -1606,7 +1608,7 @@ export class GlobalPwPolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="passwordtprdelayvalidfrom" isDisabled={!this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleTPRChange(e); }} /> diff --git a/src/cockpit/389-console/src/lib/database/indexes.jsx b/src/cockpit/389-console/src/lib/database/indexes.jsx index 747c1a219e..9af43722dd 100644 --- a/src/cockpit/389-console/src/lib/database/indexes.jsx +++ b/src/cockpit/389-console/src/lib/database/indexes.jsx @@ -4,23 +4,25 @@ import { DoubleConfirmModal } from "../notifications.jsx"; import { IndexTable } from "./databaseTables.jsx"; import { log_cmd } from "../tools.jsx"; import { - Button, - Checkbox, - Form, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - Tab, - Tabs, - TabTitleText, - Text, - TextContent, - TextVariants, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + Grid, + GridItem, + Modal, + ModalVariant, + Tab, + Tabs, + TabTitleText, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; const _ = cockpit.gettext; @@ -88,7 +90,7 @@ export class SuffixIndexes extends React.Component { ); } }; - this.handleAttributeToggle = isAttributeOpen => { + this.handleAttributeToggle = (_event, isAttributeOpen) => { this.setState({ isAttributeOpen }); @@ -100,7 +102,7 @@ export class SuffixIndexes extends React.Component { }); }; - this.handleMatchingruleAddToggle = isMatchingruleOpen => { + this.handleMatchingruleAddToggle = (_event, isMatchingruleOpen) => { this.setState({ isMatchingruleOpen }); @@ -112,7 +114,7 @@ export class SuffixIndexes extends React.Component { }); }; - this.handleMatchingruleEditToggle = isMatchingruleOpen => { + this.handleMatchingruleEditToggle = (_event, isMatchingruleOpen) => { this.setState({ isMatchingruleOpen }); @@ -628,7 +630,7 @@ export class SuffixIndexes extends React.Component { }); } - onSelectToggle = (isExpanded, toggleId) => { + onSelectToggle = (_event, isExpanded, toggleId) => { this.setState({ [toggleId]: isExpanded }); @@ -867,7 +869,7 @@ class AddIndexModal extends React.Component { <Checkbox id="indexTypeEq" isChecked={this.props.indexTypeEq} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Equailty Indexing")} @@ -879,7 +881,7 @@ class AddIndexModal extends React.Component { <Checkbox id="indexTypePres" isChecked={this.props.indexTypePres} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Presence Indexing")} @@ -891,7 +893,7 @@ class AddIndexModal extends React.Component { <Checkbox id="indexTypeSub" isChecked={this.props.indexTypeSub} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Substring Indexing")} @@ -903,7 +905,7 @@ class AddIndexModal extends React.Component { <Checkbox id="indexTypeApprox" isChecked={this.props.indexTypeApprox} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Approximate Indexing")} @@ -947,7 +949,7 @@ class AddIndexModal extends React.Component { <Checkbox id="reindexOnAdd" isChecked={this.props.reindexOnAdd} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Index attribute after creation")} @@ -1008,7 +1010,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypeEq" isChecked={this.props.indexTypeEq} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Equailty Indexing")} @@ -1020,7 +1022,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypePres" isChecked={this.props.indexTypePres} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Presence Indexing")} @@ -1032,7 +1034,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypeSub" isChecked={this.props.indexTypeSub} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Substring Indexing")} @@ -1044,7 +1046,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypeApprox" isChecked={this.props.indexTypeApprox} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Approximate Indexing")} @@ -1058,7 +1060,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypeEq" isChecked={this.props.indexTypeEq} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Equality Indexing")} @@ -1072,7 +1074,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypePres" isChecked={this.props.indexTypePres} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Presence Indexing")} @@ -1086,7 +1088,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypeSub" isChecked={this.props.indexTypeSub} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Substring Indexing")} @@ -1100,7 +1102,7 @@ class EditIndexModal extends React.Component { <Checkbox id="indexTypeApprox" isChecked={this.props.indexTypeApprox} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Approximate Indexing")} @@ -1198,7 +1200,7 @@ class EditIndexModal extends React.Component { <Checkbox id="reindexOnAdd" isChecked={this.props.reindexOnAdd} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Reindex Attribute After Saving")} diff --git a/src/cockpit/389-console/src/lib/database/localPwp.jsx b/src/cockpit/389-console/src/lib/database/localPwp.jsx index 18c5c5ddda..77dc50b728 100644 --- a/src/cockpit/389-console/src/lib/database/localPwp.jsx +++ b/src/cockpit/389-console/src/lib/database/localPwp.jsx @@ -4,30 +4,32 @@ import { log_cmd, valid_dn } from "../tools.jsx"; import { DoubleConfirmModal } from "../notifications.jsx"; import { PwpTable } from "./databaseTables.jsx"; import { - Alert, - Button, - Checkbox, - ExpandableSection, - Form, - FormAlert, - FormHelperText, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Select, - SelectOption, - SelectVariant, - Spinner, - Tab, - Tabs, - TabTitleText, - TextInput, - Text, - TextContent, - TextVariants, - ValidatedOptions -} from "@patternfly/react-core"; + Alert, + Button, + Checkbox, + ExpandableSection, + Form, + FormAlert, + FormHelperText, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Spinner, + Tab, + Tabs, + TabTitleText, + TextInput, + Text, + TextContent, + TextVariants, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSyncAlt @@ -103,27 +105,27 @@ class CreatePolicy extends React.Component { isTPRExpanded: false, }; - this.handleGeneralToggle = (isGeneralExpanded) => { + this.handleGeneralToggle = (_event, isGeneralExpanded) => { this.setState({ isGeneralExpanded }); }; - this.handleLockoutToggle = (isLockoutExpanded) => { + this.handleLockoutToggle = (_event, isLockoutExpanded) => { this.setState({ isLockoutExpanded }); }; - this.handleExpiredToggle = (isExpiredExpanded) => { + this.handleExpiredToggle = (_event, isExpiredExpanded) => { this.setState({ isExpiredExpanded }); }; - this.handleSyntaxToggle = (isSyntaxExpanded) => { + this.handleSyntaxToggle = (_event, isSyntaxExpanded) => { this.setState({ isSyntaxExpanded }); }; - this.handleTPRToggle = (isTPRExpanded) => { + this.handleTPRToggle = (_event, isTPRExpanded) => { this.setState({ isTPRExpanded }); @@ -165,12 +167,12 @@ class CreatePolicy extends React.Component { id="policyDN" aria-describedby="horizontal-form-name-helper" name="policyDN" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} validated={this.props.invalid_dn ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!this.props.invalid_dn && this.props.policyDN !== ""}> + <FormHelperText > {helper_text} </FormHelperText> </GridItem> @@ -179,7 +181,7 @@ class CreatePolicy extends React.Component { <ExpandableSection className="ds-margin-top-lg" toggleText={this.state.isGeneralExpanded ? _("Hide General Settings") : _("Show General Settings")} - onToggle={this.handleGeneralToggle} + onToggle={(event, isOpen) => this.handleGeneralToggle(event, isOpen)} isExpanded={this.state.isGeneralExpanded} > <div className="ds-margin-left"> @@ -191,7 +193,7 @@ class CreatePolicy extends React.Component { <FormSelect id="create_passwordstoragescheme" value={this.props.create_passwordstoragescheme} - onChange={(value, event) => { + onChange={(event, value) => { this.props.handleChange(event); }} aria-label="FormSelect Input" @@ -219,7 +221,7 @@ class CreatePolicy extends React.Component { id="create_passwordminage" aria-describedby="horizontal-form-name-helper" name="create_passwordminage" - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} /> @@ -239,7 +241,7 @@ class CreatePolicy extends React.Component { id="create_passwordadmindn" aria-describedby="horizontal-form-name-helper" name="create_passwordadmindn" - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} /> @@ -253,7 +255,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordadminskipinfoupdate" isChecked={this.props.create_passwordadminskipinfoupdate} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("Do not update target entry's password state attributes")} @@ -265,7 +267,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordtrackupdatetime" isChecked={this.props.create_passwordtrackupdatetime} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("Track Password Update Time")} @@ -277,7 +279,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordchange" isChecked={this.props.create_passwordchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("Allow Users To Change Their Passwords")} @@ -289,7 +291,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordmustchange" isChecked={this.props.create_passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("User Must Change Password After Reset")} @@ -302,7 +304,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordhistory" isChecked={this.props.create_passwordhistory} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("Keep Password History")} @@ -315,7 +317,7 @@ class CreatePolicy extends React.Component { id="create_passwordinhistory" aria-describedby="horizontal-form-name-helper" name="create_passwordinhistory" - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} /> @@ -328,7 +330,7 @@ class CreatePolicy extends React.Component { <ExpandableSection className="ds-margin-top-lg" toggleText={this.state.isExpiredExpanded ? _("Hide Expiration Settings") : _("Show Expiration Settings")} - onToggle={this.handleExpiredToggle} + onToggle={(event, isOpen) => this.handleExpiredToggle(event, isOpen)} isExpanded={this.state.isExpiredExpanded} > <div className="ds-margin-left"> @@ -337,7 +339,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordexp" isChecked={this.props.create_passwordexp} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("Enforce Password Expiration")} @@ -358,7 +360,7 @@ class CreatePolicy extends React.Component { id="create_passwordmaxage" aria-describedby="create_passwordmaxage" name="create_passwordmaxage" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordexp} @@ -375,7 +377,7 @@ class CreatePolicy extends React.Component { id="create_passwordgracelimit" aria-describedby="create_passwordgracelimit" name="create_passwordgracelimit" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordexp} @@ -392,7 +394,7 @@ class CreatePolicy extends React.Component { id="create_passwordwarning" aria-describedby="create_passwordwarning" name="create_passwordwarning" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordexp} @@ -407,7 +409,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordsendexpiringtime" isChecked={this.props.create_passwordsendexpiringtime} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordexp} @@ -423,7 +425,7 @@ class CreatePolicy extends React.Component { <ExpandableSection className="ds-margin-top-lg" toggleText={this.state.isLockoutExpanded ? _("Hide Lockout Settings") : _("Show Lockout Settings")} - onToggle={this.handleLockoutToggle} + onToggle={(event, isOpen) => this.handleLockoutToggle(event, isOpen)} isExpanded={this.state.isLockoutExpanded} > <div className="ds-margin-left"> @@ -432,7 +434,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordlockout" isChecked={this.props.create_passwordlockout} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("Enable Account Lockout")} @@ -450,7 +452,7 @@ class CreatePolicy extends React.Component { id="create_passwordmaxfailure" aria-describedby="create_passwordmaxfailure" name="create_passwordmaxfailure" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordlockout} @@ -467,7 +469,7 @@ class CreatePolicy extends React.Component { id="create_passwordresetfailurecount" aria-describedby="create_passwordresetfailurecount" name="create_passwordresetfailurecount" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordlockout} @@ -484,7 +486,7 @@ class CreatePolicy extends React.Component { id="create_passwordlockoutduration" aria-describedby="create_passwordlockoutduration" name="create_passwordlockoutduration" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordlockout} @@ -496,7 +498,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordunlock" isChecked={this.props.create_passwordunlock} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordlockout} @@ -511,7 +513,7 @@ class CreatePolicy extends React.Component { <ExpandableSection className="ds-margin-top-lg" toggleText={this.state.isSyntaxExpanded ? _("Hide Syntax Settings") : _("Show Syntax Settings")} - onToggle={this.handleSyntaxToggle} + onToggle={(event, isOpen) => this.handleSyntaxToggle(event, isOpen)} isExpanded={this.state.isSyntaxExpanded} > <div className="ds-margin-left"> @@ -520,7 +522,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordchecksyntax" isChecked={this.props.create_passwordchecksyntax} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} label={_("Enable Password Syntax Checking")} @@ -538,7 +540,7 @@ class CreatePolicy extends React.Component { id="create_passwordminlength" aria-describedby="create_passwordminlength" name="create_passwordminlength" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -553,7 +555,7 @@ class CreatePolicy extends React.Component { id="create_passwordminalphas" aria-describedby="create_passwordminalphas" name="create_passwordminalphas" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -570,7 +572,7 @@ class CreatePolicy extends React.Component { id="create_passwordmindigits" aria-describedby="create_passwordmindigits" name="create_passwordmindigits" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -585,7 +587,7 @@ class CreatePolicy extends React.Component { id="create_passwordminspecials" aria-describedby="create_passwordminspecials" name="create_passwordminspecials" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -602,7 +604,7 @@ class CreatePolicy extends React.Component { id="create_passwordminuppers" aria-describedby="create_passwordminuppers" name="create_passwordminuppers" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -617,7 +619,7 @@ class CreatePolicy extends React.Component { id="create_passwordminlowers" aria-describedby="create_passwordminlowers" name="create_passwordminlowers" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -634,7 +636,7 @@ class CreatePolicy extends React.Component { id="create_passwordmin8bit" aria-describedby="create_passwordmin8bit" name="create_passwordmin8bit" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -649,7 +651,7 @@ class CreatePolicy extends React.Component { id="create_passwordmincategories" aria-describedby="create_passwordmincategories" name="create_passwordmincategories" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -666,7 +668,7 @@ class CreatePolicy extends React.Component { id="create_passwordmintokenlength" aria-describedby="create_passwordmintokenlength" name="create_passwordmintokenlength" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -681,7 +683,7 @@ class CreatePolicy extends React.Component { id="create_passwordmaxrepeats" aria-describedby="create_passwordmaxrepeats" name="create_passwordmaxrepeats" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -698,7 +700,7 @@ class CreatePolicy extends React.Component { id="create_passwordmaxsequence" aria-describedby="create_passwordmaxsequence" name="create_passwordmaxsequence" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -713,7 +715,7 @@ class CreatePolicy extends React.Component { id="create_passwordmaxseqsets" aria-describedby="create_passwordmaxseqsets" name="create_passwordmaxseqsets" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -730,7 +732,7 @@ class CreatePolicy extends React.Component { id="create_passwordmaxclasschars" aria-describedby="create_passwordmaxclasschars" name="create_passwordmaxclasschars" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -750,7 +752,7 @@ class CreatePolicy extends React.Component { id="create_passwordbadwords" aria-describedby="create_passwordbadwords" name="create_passwordbadwords" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -765,7 +767,7 @@ class CreatePolicy extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a attribute name to check" - onToggle={this.props.onUserAttrsCreateToggle} + onToggle={(event, isOpen) => this.props.onUserAttrsCreateToggle(event, isOpen)} onSelect={this.props.handleChange} onClear={this.props.onUserAttrsCreateClear} selections={this.props.passworduserattributes} @@ -789,7 +791,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passworddictcheck" isChecked={this.props.create_passworddictcheck} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -802,7 +804,7 @@ class CreatePolicy extends React.Component { <Checkbox id="create_passwordpalindrome" isChecked={this.props.create_passwordpalindrome} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} isDisabled={!this.props.passwordchecksyntax} @@ -816,7 +818,7 @@ class CreatePolicy extends React.Component { <ExpandableSection className="ds-margin-top-lg" toggleText={this.state.isTPRExpanded ? _("Hide Temporary Password Settings") : _("Show Temporary Password Settings")} - onToggle={this.handleTPRToggle} + onToggle={(event, isOpen) => this.handleTPRToggle(event, isOpen)} isExpanded={this.state.isTPRExpanded} > <div className="ds-margin-left"> @@ -844,7 +846,7 @@ class CreatePolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="create_passwordtprmaxuse" isDisabled={!this.props.create_passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} /> @@ -864,7 +866,7 @@ class CreatePolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="create_passwordtprdelayexpireat" isDisabled={!this.props.create_passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} /> @@ -884,7 +886,7 @@ class CreatePolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="create_passwordtprdelayvalidfrom" isDisabled={!this.props.create_passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} /> @@ -1147,7 +1149,7 @@ export class LocalPwPolicy extends React.Component { }; // Check User Attributes Create - this.handleUserAttrsCreateToggle = isUserAttrsCreateOpen => { + this.handleUserAttrsCreateToggle = (_event, isUserAttrsCreateOpen) => { this.setState({ isUserAttrsCreateOpen }); @@ -1185,7 +1187,7 @@ export class LocalPwPolicy extends React.Component { }); }; - this.handleSelectToggle = isSelectOpen => { + this.handleSelectToggle = (_event, isSelectOpen) => { this.setState({ isSelectOpen }); @@ -1257,7 +1259,7 @@ export class LocalPwPolicy extends React.Component { }); } - onCreateSelectChange(value) { + onCreateSelectChange(_event, value) { this.setState({ createPolicyType: value }); @@ -2421,7 +2423,7 @@ export class LocalPwPolicy extends React.Component { id="passwordminlength" aria-describedby="horizontal-form-name-helper" name="passwordminlength" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2437,7 +2439,7 @@ export class LocalPwPolicy extends React.Component { id="passwordminalphas" aria-describedby="horizontal-form-name-helper" name="passwordminalphas" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2455,7 +2457,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmindigits" aria-describedby="horizontal-form-name-helper" name="passwordmindigits" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2471,7 +2473,7 @@ export class LocalPwPolicy extends React.Component { id="passwordminspecials" aria-describedby="horizontal-form-name-helper" name="passwordminspecials" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2489,7 +2491,7 @@ export class LocalPwPolicy extends React.Component { id="passwordminuppers" aria-describedby="horizontal-form-name-helper" name="passwordminuppers" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2505,7 +2507,7 @@ export class LocalPwPolicy extends React.Component { id="passwordminlowers" aria-describedby="horizontal-form-name-helper" name="passwordminlowers" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2523,7 +2525,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmin8bit" aria-describedby="horizontal-form-name-helper" name="passwordmin8bit" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2539,7 +2541,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmincategories" aria-describedby="horizontal-form-name-helper" name="passwordmincategories" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2557,7 +2559,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmaxsequence" aria-describedby="horizontal-form-name-helper" name="passwordmaxsequence" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2573,7 +2575,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmaxseqsets" aria-describedby="horizontal-form-name-helper" name="passwordmaxseqsets" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2591,7 +2593,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmaxclasschars" aria-describedby="horizontal-form-name-helper" name="passwordmaxclasschars" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2609,7 +2611,7 @@ export class LocalPwPolicy extends React.Component { id="passwordbadwords" aria-describedby="horizontal-form-name-helper" name="passwordbadwords" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} /> @@ -2623,7 +2625,7 @@ export class LocalPwPolicy extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute to check" - onToggle={this.handleSelectToggle} + onToggle={(event, isOpen) => this.handleSelectToggle(event, isOpen)} onSelect={this.handleSyntaxChange} onClear={this.handleSelectClear} selections={this.state.passworduserattributes} @@ -2646,7 +2648,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passworddictcheck" isChecked={this.state.passworddictcheck} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} label={_("Dictionary Check")} @@ -2659,7 +2661,7 @@ export class LocalPwPolicy extends React.Component { id="passwordpalindrome" isChecked={this.state.passwordpalindrome} className="ds-label" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} label={_("Reject Palindromes")} @@ -2684,7 +2686,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmaxfailure" aria-describedby="horizontal-form-name-helper" name="passwordmaxpasswordmaxfailureclasschars" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} /> @@ -2701,7 +2703,7 @@ export class LocalPwPolicy extends React.Component { id="passwordresetfailurecount" aria-describedby="horizontal-form-name-helper" name="passwordresetfailurecount" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} /> @@ -2718,7 +2720,7 @@ export class LocalPwPolicy extends React.Component { id="passwordlockoutduration" aria-describedby="horizontal-form-name-helper" name="passwordlockoutduration" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} /> @@ -2729,7 +2731,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordunlock" isChecked={this.state.passwordunlock} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} label={_("Do Not Lockout Account Forever")} @@ -2754,7 +2756,7 @@ export class LocalPwPolicy extends React.Component { id="passwordmaxage" aria-describedby="horizontal-form-name-helper" name="passwordmaxage" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} /> @@ -2771,7 +2773,7 @@ export class LocalPwPolicy extends React.Component { id="passwordgracelimit" aria-describedby="horizontal-form-name-helper" name="passwordgracelimit" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} /> @@ -2788,7 +2790,7 @@ export class LocalPwPolicy extends React.Component { id="passwordwarning" aria-describedby="horizontal-form-name-helper" name="passwordwarning" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} /> @@ -2799,7 +2801,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordsendexpiringtime" isChecked={this.state.passwordsendexpiringtime} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} label={_("<>Always Send <i>Password Expiring</i> Control</>")} @@ -2836,7 +2838,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordtrackupdatetime" isChecked={this.state.passwordtrackupdatetime} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Track Password Update Time")} @@ -2848,7 +2850,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordchange" isChecked={this.state.passwordchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Allow Users To Change Their Passwords")} @@ -2860,7 +2862,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordmustchange" isChecked={this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("User Must Change Password After Reset")} @@ -2873,7 +2875,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordhistory" isChecked={this.state.passwordhistory} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label={_("Keep Password History")} @@ -2886,7 +2888,7 @@ export class LocalPwPolicy extends React.Component { id="passwordinhistory" aria-describedby="horizontal-form-name-helper" name="passwordinhistory" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} /> @@ -2901,7 +2903,7 @@ export class LocalPwPolicy extends React.Component { <FormSelect id="passwordstoragescheme" value={this.state.passwordstoragescheme} - onChange={(value, event) => { + onChange={(event, value) => { this.handleGeneralChange(event); }} aria-label="FormSelect Input" @@ -2929,7 +2931,7 @@ export class LocalPwPolicy extends React.Component { id="passwordminage" aria-describedby="horizontal-form-name-helper" name="passwordminage" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} /> @@ -2948,7 +2950,7 @@ export class LocalPwPolicy extends React.Component { id="passwordadmindn" aria-describedby="horizontal-form-name-helper" name="passwordadmindn" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} /> @@ -2961,7 +2963,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordadminskipinfoupdate" isChecked={this.state.passwordadminskipinfoupdate} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleGeneralChange(e); }} label="Do not update target entry's password state attributes" @@ -2988,7 +2990,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordexp" isChecked={this.state.passwordexp} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleExpChange(e); }} label={_("Enforce Password Expiration")} @@ -3016,7 +3018,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordlockout" isChecked={this.state.passwordlockout} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleLockoutChange(e); }} label={_("Enable Account Lockout")} @@ -3044,7 +3046,7 @@ export class LocalPwPolicy extends React.Component { <Checkbox id="passwordchecksyntax" isChecked={this.state.passwordchecksyntax} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSyntaxChange(e); }} label={_("Enable Password Syntax Checking")} @@ -3091,7 +3093,7 @@ export class LocalPwPolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="passwordtprmaxuse" isDisabled={!this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleTPRChange(e); }} /> @@ -3113,7 +3115,7 @@ export class LocalPwPolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="passwordtprdelayexpireat" isDisabled={!this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleTPRChange(e); }} /> @@ -3135,7 +3137,7 @@ export class LocalPwPolicy extends React.Component { aria-describedby="horizontal-form-name-helper" name="passwordtprdelayvalidfrom" isDisabled={!this.state.passwordmustchange} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleTPRChange(e); }} /> @@ -3217,7 +3219,7 @@ export class LocalPwPolicy extends React.Component { if (this.state.loading || !this.state.loaded) { body = ( <div className="ds-margin-top-xlg ds-center"> - <Spinner isSVG size="xl" /> + <Spinner size="xl" /> </div> ); } diff --git a/src/cockpit/389-console/src/lib/database/referrals.jsx b/src/cockpit/389-console/src/lib/database/referrals.jsx index 304530058c..f091de3227 100644 --- a/src/cockpit/389-console/src/lib/database/referrals.jsx +++ b/src/cockpit/389-console/src/lib/database/referrals.jsx @@ -369,7 +369,7 @@ class AddReferralModal extends React.Component { id="refHost" aria-describedby="horizontal-form-name-helper" name="refHost" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.refHost ? ValidatedOptions.error : ValidatedOptions.default} @@ -386,7 +386,7 @@ class AddReferralModal extends React.Component { id="refPort" aria-describedby="horizontal-form-name-helper" name="refPort" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.refPort ? ValidatedOptions.error : ValidatedOptions.default} @@ -403,7 +403,7 @@ class AddReferralModal extends React.Component { id="refSuffix" aria-describedby="horizontal-form-name-helper" name="refSuffix" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} validated={error.refSuffix ? ValidatedOptions.error : ValidatedOptions.default} @@ -420,7 +420,7 @@ class AddReferralModal extends React.Component { id="refAttrs" aria-describedby="horizontal-form-name-helper" name="refAttrs" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -436,7 +436,7 @@ class AddReferralModal extends React.Component { id="refFilter" aria-describedby="horizontal-form-name-helper" name="refFilter" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> diff --git a/src/cockpit/389-console/src/lib/database/suffix.jsx b/src/cockpit/389-console/src/lib/database/suffix.jsx index 67a73fe01c..fb3c588714 100644 --- a/src/cockpit/389-console/src/lib/database/suffix.jsx +++ b/src/cockpit/389-console/src/lib/database/suffix.jsx @@ -21,17 +21,19 @@ import { CreateLinkModal, } from "./databaseModal.jsx"; import { - Dropdown, - DropdownToggle, - DropdownItem, - DropdownPosition, - DropdownSeparator, - Grid, - GridItem, - Tab, - Tabs, - TabTitleText, -} from "@patternfly/react-core"; + Grid, + GridItem, + Tab, + Tabs, + TabTitleText +} from '@patternfly/react-core'; +import { + Dropdown, + DropdownToggle, + DropdownItem, + DropdownPosition, + DropdownSeparator +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; @@ -110,7 +112,7 @@ export class Suffix extends React.Component { // config.autoAddCss = false; // Dropdown tasks - this.handleToggle = dropdownIsOpen => { + this.handleToggle = (_event, dropdownIsOpen) => { this.setState({ dropdownIsOpen }); @@ -895,7 +897,7 @@ export class Suffix extends React.Component { position={DropdownPosition.right} onSelect={this.handleSelect} toggle={ - <DropdownToggle id="suffix-dropdown" isPrimary onToggle={this.handleToggle}> + <DropdownToggle id="suffix-dropdown" isPrimary onToggle={(event, isOpen) => this.handleToggle(event, isOpen)}> {_("Suffix Tasks")} </DropdownToggle> } diff --git a/src/cockpit/389-console/src/lib/database/suffixConfig.jsx b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx index 305bf191d9..5bb5af39b1 100644 --- a/src/cockpit/389-console/src/lib/database/suffixConfig.jsx +++ b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx @@ -63,7 +63,7 @@ export class SuffixConfig extends React.Component { id="dncachememsize" aria-describedby="dncachememsize" name="dncachememsize" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} /> @@ -85,7 +85,7 @@ export class SuffixConfig extends React.Component { id="cachememsize" aria-describedby="cachememsize" name="cachememsize" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} /> @@ -102,7 +102,7 @@ export class SuffixConfig extends React.Component { id="cachesize" aria-describedby="cachesize" name="cachesize" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} /> @@ -119,7 +119,7 @@ export class SuffixConfig extends React.Component { id="dncachememsize" aria-describedby="dncachememsize" name="dncachememsize" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} /> @@ -150,7 +150,7 @@ export class SuffixConfig extends React.Component { <FormSelect id="dbstate" value={this.props.dbstate} - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} aria-label="FormSelect Input" @@ -168,7 +168,7 @@ export class SuffixConfig extends React.Component { label={_("Database Read-Only Mode")} id="readOnly" isChecked={this.props.readOnly} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} aria-label="send ref" @@ -181,7 +181,7 @@ export class SuffixConfig extends React.Component { label={_("Block Unindexed Searches")} id="requireIndex" isChecked={this.props.requireIndex} - onChange={(checked, e) => { + onChange={(e, checked) => { this.props.handleChange(e); }} aria-label="requireIndex" diff --git a/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx index e8b2dabc84..ecc678ea35 100644 --- a/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx +++ b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx @@ -4,24 +4,26 @@ import { DoubleConfirmModal } from "../notifications.jsx"; import { VLVTable } from "./databaseTables.jsx"; import { log_cmd } from "../tools.jsx"; import { - Button, - Checkbox, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - TextInput, - Text, - TextContent, - TextVariants, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + Text, + TextContent, + TextVariants, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; const _ = cockpit.gettext; @@ -397,7 +399,7 @@ export class VLVIndexes extends React.Component { }); } - handleSelectToggle = (isExpanded, toggleId) => { + handleSelectToggle = (_event, isExpanded, toggleId) => { this.setState({ [toggleId]: isExpanded }); @@ -510,7 +512,7 @@ class AddVLVIndexModal extends React.Component { }; // VLV Sort indexes - this.handleVLVSortToggle = isVLVSortOpen => { + this.handleVLVSortToggle = (_event, isVLVSortOpen) => { this.setState({ isVLVSortOpen }); @@ -593,7 +595,7 @@ class AddVLVIndexModal extends React.Component { variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel={_("Type an attribute names to create a sort index")} className="ds-margin-top-lg" - onToggle={this.handleVLVSortToggle} + onToggle={(event, isOpen) => this.handleVLVSortToggle(event, isOpen)} onClear={this.handleVLVSortClear} onSelect={this.handleTypeaheadChange} maxHeight={1000} @@ -614,7 +616,7 @@ class AddVLVIndexModal extends React.Component { <Checkbox id="reindexVLV" isChecked={this.props.reindexVLV} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Reindex After Saving")} @@ -681,7 +683,7 @@ class AddVLVModal extends React.Component { id="vlvName" aria-describedby="vlvName" name="vlvName" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.vlvName ? ValidatedOptions.error : ValidatedOptions.default} @@ -699,7 +701,7 @@ class AddVLVModal extends React.Component { id="vlvBase" aria-describedby="vlvBase" name="vlvBase" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.vlvBase ? ValidatedOptions.error : ValidatedOptions.default} @@ -716,7 +718,7 @@ class AddVLVModal extends React.Component { id="vlvFilter" aria-describedby="vlvFilter" name="vlvFilter" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} value={this.props.filter} @@ -731,7 +733,7 @@ class AddVLVModal extends React.Component { <GridItem span={10}> <FormSelect value={this.props.vlvScope} - onChange={(value, event) => { + onChange={(event, value) => { handleChange(event); }} id="vlvScope" diff --git a/src/cockpit/389-console/src/lib/ldap_editor/lib/editableTable.jsx b/src/cockpit/389-console/src/lib/ldap_editor/lib/editableTable.jsx index dd646e6f3b..d6ba15c2c4 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/lib/editableTable.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/lib/editableTable.jsx @@ -11,17 +11,25 @@ import { ModalVariant, Radio, TextInput, - ValidatedOptions, + ValidatedOptions, InputGroupItem, } from '@patternfly/react-core'; import { - EditableTextCell, - Table, TableHeader, TableBody, TableVariant, - applyCellEdits, - breakWord, - cancelCellEdits, - validateCellEdits, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + ActionsColumn, + TableVariant, + applyCellEdits, + breakWord, + cancelCellEdits, + validateCellEdits } from '@patternfly/react-table'; import { + CheckIcon, + TimesIcon, EyeIcon, EyeSlashIcon, InfoCircleIcon, @@ -39,6 +47,62 @@ import PropTypes from "prop-types"; const _ = cockpit.gettext; +// Define EditableTextCell as a separate component outside the class +const EditableTextCell = ({ + value, + rowIndex, + cellIndex, + props, + handleTextInputChange, + isDisabled, + inputAriaLabel, + onUpdate +}) => { + const [isEditing, setIsEditing] = React.useState(false); + const [editedValue, setEditedValue] = React.useState(value); + + return isEditing ? ( + <div className="pf-c-inline-edit"> + <TextInput + value={editedValue} + onChange={(_, newValue) => { + setEditedValue(newValue); + handleTextInputChange(newValue, null, rowIndex, cellIndex); + }} + aria-label={inputAriaLabel} + isDisabled={isDisabled} + /> + <div className="pf-c-inline-edit__group"> + <Button + variant="plain" + onClick={() => { + onUpdate(null, 'save', false, rowIndex, null); + setIsEditing(false); + }} + > + <CheckIcon /> + </Button> + <Button + variant="plain" + onClick={() => { + setIsEditing(false); + setEditedValue(value); + }} + > + <TimesIcon /> + </Button> + </div> + </div> + ) : ( + <div + onClick={() => !isDisabled && setIsEditing(true)} + className={!isDisabled ? "pf-c-inline-edit--editable" : ""} + > + {value} + </div> + ); +}; + class EditableTable extends React.Component { constructor (props) { super(props); @@ -88,7 +152,7 @@ class EditableTable extends React.Component { })); }; - this.handlePwdChange = (str, e) => { + this.handlePwdChange = (_event, str) => { this.setState({ pwdValue: str }); @@ -283,11 +347,11 @@ class EditableTable extends React.Component { }; }; - this.handleRadioOnChange = (_, event) => { + this.handleRadioOnChange = (event, _) => { this.setState({ binaryAttributeRadio: event.currentTarget.value }); }; - this.handleBinaryAttributeInput = (str, e) => { + this.handleBinaryAttributeInput = (_event, str) => { const invalidPath = !file_is_path(str); this.setState({ @@ -448,17 +512,16 @@ class EditableTable extends React.Component { cells: [ { title: (value, rowIndex, cellIndex, props) => ( - <> - <EditableTextCell - value={value} - rowIndex={rowIndex} - cellIndex={cellIndex} - props={props} - handleTextInputChange={this.onTextInputChange} - isDisabled - inputAriaLabel={attrName} - /> - </> + <EditableTextCell + value={value} + rowIndex={rowIndex} + cellIndex={cellIndex} + props={props} + handleTextInputChange={this.onTextInputChange} + onUpdate={this.handleUpdateEditableRows} + isDisabled + inputAriaLabel={attrName} + /> ), props: { value: attrName, @@ -467,17 +530,15 @@ class EditableTable extends React.Component { }, { title: (value, rowIndex, cellIndex, props) => ( - <> - <EditableTextCell - // isDisabled={myData.isDisabled} - value={value !== "" ? "********" : <Label color="red" icon={<InfoCircleIcon />}> {_("Empty value!")} </Label>} - rowIndex={rowIndex} - cellIndex={cellIndex} - props={props} - handleTextInputChange={this.onTextInputChange} - inputAriaLabel={'_' + value} // To avoid empty property when value is empty. - /> - </> + <EditableTextCell + value={value !== "" ? "********" : <Label color="red" icon={<InfoCircleIcon />}>{_("Empty value!")}</Label>} + rowIndex={rowIndex} + cellIndex={cellIndex} + props={props} + handleTextInputChange={this.onTextInputChange} + onUpdate={this.handleUpdateEditableRows} + inputAriaLabel={'_' + value} + /> ), props: { value: myData.val, @@ -496,30 +557,28 @@ class EditableTable extends React.Component { cells: [ { title: (value, rowIndex, cellIndex, props) => ( - <> + <div> <EditableTextCell value={value} rowIndex={rowIndex} cellIndex={cellIndex} props={props} handleTextInputChange={this.onTextInputChange} + onUpdate={this.handleUpdateEditableRows} isDisabled inputAriaLabel={attrName} /> - { - (attrId === this.state.namingRowID || myData.namingAttr) && + {(attrId === this.state.namingRowID || myData.namingAttr) && ( <Popover headerContent={<div>{_("Naming Attribute")}</div>} bodyContent={ - <div> - {_("This attribute and value are part of the entry's DN and can not be changed except by doing a Rename (modrdn) operation on the entry.")} - </div> + <div>{_("This attribute and value are part of the entry's DN and can not be changed except by doing a Rename (modrdn) operation on the entry.")}</div> } > <a href="#" className="ds-font-size-sm">{_("Naming Attribute")}</a> </Popover> - } - </> + )} + </div> ), props: { value: attrName, @@ -528,17 +587,15 @@ class EditableTable extends React.Component { }, { title: (value, rowIndex, cellIndex, props) => ( - <> - <EditableTextCell - // isDisabled={myData.isDisabled} - value={value === "" ? <Label color="red" icon={<InfoCircleIcon />}> {_("Empty value!")} </Label> : value} - rowIndex={rowIndex} - cellIndex={cellIndex} - props={props} - handleTextInputChange={this.onTextInputChange} - inputAriaLabel={'_' + value} // To avoid empty property when value is empty. - /> - </> + <EditableTextCell + value={value === "" ? <Label color="red" icon={<InfoCircleIcon />}>{_("Empty value!")}</Label> : value} + rowIndex={rowIndex} + cellIndex={cellIndex} + props={props} + handleTextInputChange={this.onTextInputChange} + onUpdate={this.handleUpdateEditableRows} + inputAriaLabel={'_' + value} + /> ), props: { value: myData.val, @@ -568,120 +625,130 @@ class EditableTable extends React.Component { actionResolver = (rowData, { rowIndex }) => { const myAttr = rowData.cells[0].props.value; const myName = rowData.cells[0].props.name; + + // Early return for DN if (myAttr === "dn") { - // There should not be an actions for the DN return []; } - const namingAction = myName === this.state.namingRowID || this.props.disableNamingChange - ? [] - : (this.props.namingAttr && myAttr !== this.props.namingAttr) || this.props.namingAttr === "" - ? [{ - title: _("Set as Naming Attribute"), - onClick: () => { - const foundEmptyValue = this.state.tableRows.find(el => el.cells[1].props.value === ''); - this.props.enableNextStep(foundEmptyValue === undefined); - - this.setState({ - namingRowID: myName, - rdnAttr: myAttr - }, - () => { - this.props.setNamingRowID(myName); - }); - } - }] - : []; - - const duplicationAction = - this.props.isAttributeSingleValued(myAttr) - ? [] - : [{ - title: _("Add Another Value"), - onClick: (event, rowId, rowData) => { - const myData = { - id: generateUniqueId(), - attr: myAttr, - val: '' - }; - const tableRows = [...this.state.tableRows]; - const newItem = this.generateSingleRow(myData); - - // Insert the duplicate item right below the original. - tableRows.splice(rowIndex + 1, 0, newItem); - - this.setState({ tableRows }, - () => { - const rowDataToSave = this.buildRowDataToSave(); - this.props.saveCurrentRows(rowDataToSave, this.state.namingRowID); - // Disable the next step because a duplicated row has no initial value. - this.props.enableNextStep(false); - }); - } - }]; - - const removalAction = this.state.tableRows.length === 1 || rowData.namingAttr || - ((this.props.isAttributeRequired(myAttr) || rowData.required) && this.hasSingleOccurrence(myAttr)) - ? [] - : [ - { - title: _("Remove this Row"), - onClick: (event, rowId) => { - const tableRows = this.state.tableRows.filter((aRow) => aRow.cells[0].props.name !== myName); - this.setState({ tableRows }, - () => { - const rowDataToSave = this.buildRowDataToSave(); - const foundEmptyValue = tableRows.find(el => el.cells[1].props.value === ''); - this.props.enableNextStep(foundEmptyValue === undefined); - - let namingRowID = this.state.namingRowID; - if (rowId === namingRowID) { - namingRowID = -1; - this.props.setNamingRowID(-1); - this.props.enableNextStep(false); - } - - this.setState({ namingRowID }); - this.props.saveCurrentRows(rowDataToSave, namingRowID); - }); - } + const actions = []; + + // Naming Action + if (!(myName === this.state.namingRowID || this.props.disableNamingChange) && + ((this.props.namingAttr && myAttr !== this.props.namingAttr) || this.props.namingAttr === "")) { + actions.push({ + title: _("Set as Naming Attribute"), + onClick: () => { + const foundEmptyValue = this.state.tableRows.find(el => el.cells[1].props.value === ''); + this.props.enableNextStep?.(foundEmptyValue === undefined); + + this.setState({ + namingRowID: myName, + rdnAttr: myAttr + }, () => { + // Only call setNamingRowID if it exists + if (typeof this.props.setNamingRowID === 'function') { + this.props.setNamingRowID(myName); + } + }); + } + }); + } + + // Add separator if needed + if (actions.length > 0 && (!this.props.isAttributeSingleValued(myAttr) || + !(this.state.tableRows.length === 1 || rowData.namingAttr || + ((this.props.isAttributeRequired(myAttr) || rowData.required) && this.hasSingleOccurrence(myAttr))))) { + actions.push({ isSeparator: true }); + } + + // Duplication Action + if (!this.props.isAttributeSingleValued(myAttr)) { + actions.push({ + title: _("Add Another Value"), + onClick: () => { + const myData = { + id: generateUniqueId(), + attr: myAttr, + val: '' + }; + const tableRows = [...this.state.tableRows]; + const newItem = this.generateSingleRow(myData); + + // Insert the duplicate item right below the original + tableRows.splice(rowIndex + 1, 0, newItem); + + this.setState({ tableRows }, () => { + const rowDataToSave = this.buildRowDataToSave(); + this.props.saveCurrentRows?.(rowDataToSave, this.state.namingRowID); + this.props.enableNextStep?.(false); + }); } - ]; + }); + } + + // Add separator before removal action if needed + if (actions.length > 0 && !(this.state.tableRows.length === 1 || rowData.namingAttr || + ((this.props.isAttributeRequired(myAttr) || rowData.required) && this.hasSingleOccurrence(myAttr)))) { + actions.push({ isSeparator: true }); + } - const nam = namingAction.length > 0; - const dup = duplicationAction.length > 0; - const rem = removalAction.length > 0; - const firstSeparator = nam && (dup || rem) ? [{ isSeparator: true }] : []; - const secondSeparator = dup && rem ? [{ isSeparator: true }] : []; + // Removal Action + if (!(this.state.tableRows.length === 1 || rowData.namingAttr || + ((this.props.isAttributeRequired(myAttr) || rowData.required) && this.hasSingleOccurrence(myAttr)))) { + actions.push({ + title: _("Remove this Row"), + onClick: () => { + const tableRows = this.state.tableRows.filter((aRow) => aRow.cells[0].props.name !== myName); + this.setState({ tableRows }, () => { + const rowDataToSave = this.buildRowDataToSave(); + const foundEmptyValue = tableRows.find(el => el.cells[1].props.value === ''); + this.props.enableNextStep?.(foundEmptyValue === undefined); + + let namingRowID = this.state.namingRowID; + if (myName === namingRowID) { + namingRowID = -1; + // Only call setNamingRowID if it exists + if (typeof this.props.setNamingRowID === 'function') { + this.props.setNamingRowID(-1); + } + this.props.enableNextStep?.(false); + } + this.setState({ namingRowID }); + this.props.saveCurrentRows?.(rowDataToSave, namingRowID); + }); + } + }); + } + + // Handle quick update mode if (this.props.quickUpdate) { - // Allow some actions in quick update. - let actions = []; - if (dup) { - actions = duplicationAction; - if (rem) { - actions.push({ isSeparator: true }); + const quickActions = []; + const canDuplicate = !this.props.isAttributeSingleValued(myAttr); + const canRemove = !(this.state.tableRows.length === 1 || rowData.namingAttr || + ((this.props.isAttributeRequired(myAttr) || rowData.required) && this.hasSingleOccurrence(myAttr))); + + if (canDuplicate) { + quickActions.push(actions.find(action => action.title === _("Add Another Value"))); + if (canRemove) { + quickActions.push({ isSeparator: true }); } } - if (rem) { - actions.push(removalAction[0]); + + if (canRemove) { + quickActions.push(actions.find(action => action.title === _("Remove this Row"))); } else { - actions.push({ + quickActions.push({ title: _("Required Attribute"), isDisabled: true }); } - return actions; - } else { - // Full option list - return [ - ...namingAction, - ...firstSeparator, - ...duplicationAction, - ...secondSeparator, - ...removalAction - ]; + + return quickActions; } + + return actions; }; render () { @@ -761,19 +828,19 @@ class EditableTable extends React.Component { ]} > <InputGroup> - <TextInput + <InputGroupItem isFill ><TextInput name={_("passwordField")} id="passwordField" type={showPassword ? 'text' : 'password'} - onChange={this.handlePwdChange} + onChange={(event, str) => this.handlePwdChange(event, str)} aria-label="password field" - /> - <Button + /></InputGroupItem> + <InputGroupItem><Button variant="control" aria-label="password field icon" onClick={this.handleShowOrHidePassword} icon={showPassword ? <EyeSlashIcon /> : <EyeIcon />} - /> + /></InputGroupItem> </InputGroup> </Modal>} @@ -801,7 +868,7 @@ class EditableTable extends React.Component { value="Upload" className="ds-margin-top" isChecked={uploadSelected} - onChange={this.handleRadioOnChange} + onChange={(event, _) => this.handleRadioOnChange(event, _)} label={_("Upload a file local to the browser.")} description={_("Select a file from the machine on which the browser was launched.")} name="radio-binary-attribute" @@ -811,7 +878,7 @@ class EditableTable extends React.Component { className="ds-margin-top-lg" value="TextInput" isChecked={!uploadSelected} - onChange={this.handleRadioOnChange} + onChange={(event, _) => this.handleRadioOnChange(event, _)} label={_("Write the complete file path.")} description={_("Type the full path of the file on the server host the LDAP server.")} name="radio-binary-attribute" @@ -844,7 +911,7 @@ class EditableTable extends React.Component { { !uploadSelected && <TextInput value={binaryAttributeFilePath} - onChange={this.handleBinaryAttributeInput} + onChange={(event, str) => this.handleBinaryAttributeInput(event, str)} isRequired validated={file_is_path(binaryAttributeFilePath) ? ValidatedOptions.default @@ -853,18 +920,21 @@ class EditableTable extends React.Component { aria-label="File input for a binary attribute." />} </Modal>} - <Table - actionResolver={this.actionResolver} - onRowEdit={this.handleUpdateEditableRows} - dropdownDirection="up" - aria-label="Editable Rows Table" - variant={TableVariant.compact} - cells={this.columns} - rows={tableRows} - className="ds-margin-top" - > - <TableHeader /> - <TableBody /> + <Table aria-label="Editable Rows Table" variant="compact"> + <Thead> + <Tr>{this.columns.map((column, columnIndex) => ( + <Th key={columnIndex}>{column.title}</Th> + ))}<Th>Actions</Th></Tr> + </Thead> + <Tbody>{tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}>{row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{typeof cell.title === 'function' + ? cell.title(cell.props.value, rowIndex, cellIndex, cell.props) + : cell.title}</Td> + ))}<Td> + <ActionsColumn items={this.actionResolver(row, { rowIndex })} /> + </Td></Tr> + ))}</Tbody> </Table> </> ); diff --git a/src/cockpit/389-console/src/lib/ldap_editor/lib/genericPagination.jsx b/src/cockpit/389-console/src/lib/ldap_editor/lib/genericPagination.jsx index f7de3aeb52..1b27aa71c9 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/lib/genericPagination.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/lib/genericPagination.jsx @@ -1,16 +1,24 @@ import cockpit from "cockpit"; import React from 'react'; import { - BadgeToggle, - Grid, - GridItem, - Dropdown, DropdownItem, DropdownPosition, - Pagination, - SearchInput, + Grid, + GridItem, + Pagination, + SearchInput } from '@patternfly/react-core'; import { - Table, TableHeader, TableBody, TableVariant, - SortByDirection + BadgeToggle, + Dropdown, + DropdownItem, + DropdownPosition +} from '@patternfly/react-core/deprecated'; +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td } from '@patternfly/react-table'; const _ = cockpit.gettext; @@ -27,29 +35,12 @@ class GenericPagination extends React.Component { pagedRows: [], searchValue: "", selectedAttrs: [], - // sortedRows: this.props.enableSorting ? [...this.props.rows] : null, sortBy: {}, isDropDownOpen: false, rows: [...this.props.rows], allRows: [...this.props.rows], }; - this.onSort = (_event, index, direction) => { - const mySortedRows = this.state.rows - .sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); - this.setState({ - sortBy: { - index, - direction - }, - rows: direction === SortByDirection.asc - ? mySortedRows - : mySortedRows.reverse() - }, () => this.setState({ // Need to update this.state.rows prior to run getRowsToShow(). - pagedRows: this.getRowsToShow(this.state.page, this.state.perPage) - })); - }; - this.handleSearchChange = (event, value) => { let rows = []; const all = []; @@ -98,52 +89,47 @@ class GenericPagination extends React.Component { }; } - componentDidMount () { - const selectedAttrs = this.props.rows.filter(row => (row.selected)).map(attr => attr.cells[0]); - this.setState({ - rows: [...this.props.rows], - allRows: [...this.props.rows], - selectedAttrs - }, () => this.setState({ - // pagedRows: this.getRowsToShow(this.state.page, this.state.perPage), - pagedRows: this.getRowsToShow(1, this.state.perPage), - page: 1, - itemCount: this.props.rows.length - })); - } + componentDidMount() { + if (this.props.rows && this.props.rows.length > 0) { + const selectedAttrs = this.props.rows + .filter(row => row.selected) + .map(attr => attr.cells[0]); - componentDidUpdate (prevProps) { - if (this.props.tableModificationTime !== prevProps.tableModificationTime) { this.setState({ rows: [...this.props.rows], allRows: [...this.props.rows], - }, () => this.setState({ - // pagedRows: this.getRowsToShow(this.state.page, this.state.perPage), + selectedAttrs, pagedRows: this.getRowsToShow(1, this.state.perPage), page: 1, itemCount: this.props.rows.length - })); + }); } } - onSelectRow = (_event, isSelected, rowId) => { - // const rows = [...this.state.pagedRows]; - // rows[rowId].selected = isSelected; + componentDidUpdate(prevProps) { + if (this.props.tableModificationTime !== prevProps.tableModificationTime) { + this.setState({ + rows: [...this.props.rows] || [], + allRows: [...this.props.rows] || [], + }, () => this.setState({ + pagedRows: this.getRowsToShow(1, this.state.perPage), + page: 1, + itemCount: ([...this.props.rows] || []).length + })); + } + } - // Find the row in the full array and set 'selected' property accordingly - // The rowId cannot be used since it changes with the pagination. - // TODO: Need to make sure the column used has unique values for each row. - // TODO: Pass the colum to use as a property. - const cellValue = this.state.pagedRows[rowId].cells[0]; + onSelectRow = (event, isSelected, rowIndex) => { + const cellValue = this.state.pagedRows[rowIndex].cells[0]; const allItems = [...this.state.rows]; let index = allItems.findIndex(item => item.cells[0] === cellValue); allItems[index].selected = isSelected; - // Update all rows so our selected attributes stay accurate as "search" could mess rows const allRows = [...this.state.allRows]; index = allRows.findIndex(item => item.cells[0] === cellValue); allRows[index].selected = isSelected; const selectedAttrs = allRows.filter(row => (row.selected)).map(attr => attr.cells[0]); + this.setState({ rows: allItems, allRows, @@ -210,7 +196,7 @@ class GenericPagination extends React.Component { position={DropdownPosition.left} onSelect={this.handleDropDownSelect} toggle={ - <BadgeToggle id="toggle-attr-select" badgeProps={badgeProps} onToggle={this.handleDropDownToggle}> + <BadgeToggle id="toggle-attr-select" badgeProps={badgeProps} onToggle={(_event, isOpen) => this.handleDropDownToggle(isOpen)}> {numSelected > 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -220,21 +206,54 @@ class GenericPagination extends React.Component { ); }; - render () { - const { - itemCount, page, perPage, pagedRows, - } = this.state; + onSort = (_event, columnIndex, sortDirection) => { + const mySortedRows = this.state.rows + .sort((a, b) => { + const aValue = a.cells[columnIndex]?.title || a.cells[columnIndex]; + const bValue = b.cells[columnIndex]?.title || b.cells[columnIndex]; + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + }); + this.setState({ + sortBy: { + index: columnIndex, + direction: sortDirection + }, + rows: sortDirection === 'asc' ? mySortedRows : mySortedRows.reverse() + }, () => this.setState({ + pagedRows: this.getRowsToShow(this.state.page, this.state.perPage) + })); + }; - // Enable pagination if the number of rows is higher than 10. + renderCellContent = (cell) => { + if (cell === null || cell === undefined) { + return ''; + } + if (React.isValidElement(cell)) { + return cell; + } + if (typeof cell === 'object') { + if (cell.title) { + return React.isValidElement(cell.title) + ? cell.title + : <span>{String(cell.title)}</span>; + } + return ''; + } + // Handle string valuess(like " o=redhat") + return String(cell).trim(); + }; + + render() { + const { itemCount, page, perPage, pagedRows } = this.state; + const { columns = [], isSelectable = false, isSearchable = false, enableSorting = false } = this.props; const showPagination = itemCount > 10; return ( <Grid> - {this.props.isSelectable && - this.buildAttrDropdown()} - <GridItem span={12} className={this.props.isSelectable ? "ds-margin-top" : ""}> + {isSelectable && this.buildAttrDropdown()} + <GridItem span={12} className={isSelectable ? "ds-margin-top" : ""}> <Grid> - { this.props.isSearchable && + {isSearchable && ( <GridItem span={5}> <SearchInput className="ds-font-size-md" @@ -243,9 +262,10 @@ class GenericPagination extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - </GridItem>} - <GridItem span={this.props.isSearchable ? 7 : 12}> - { showPagination && + </GridItem> + )} + <GridItem span={isSearchable ? 7 : 12}> + {showPagination && ( <Pagination itemCount={itemCount} page={page} @@ -254,25 +274,53 @@ class GenericPagination extends React.Component { onPerPageSelect={this.handlePerPageSelect} isCompact widgetId="pagination-options-menu-generic" - />} + /> + )} </GridItem> </Grid> </GridItem> <GridItem span={12}> - <Table - variant={TableVariant.compact} - rows={pagedRows} - cells={this.props.columns} - actions={this.props.actions} - aria-label="generic table" - caption={this.props.caption} - sortBy={this.props.enableSorting ? this.state.sortBy : null} - onSort={this.props.enableSorting ? this.onSort : null} - canSelectAll={this.props.canSelectAll} - onSelect={this.props.isSelectable ? this.onSelectRow : null} - > - <TableHeader /> - <TableBody /> + <Table aria-label={this.props.ariaLabel || "generic table"} variant="compact"> + <Thead> + <Tr>{isSelectable && <Th/>}{columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={enableSorting ? { + sortBy: this.state.sortBy, + onSort: (_evt, index, direction) => this.onSort(_evt, columnIndex, direction), + columnIndex + } : undefined} + > + {typeof column === 'object' ? column.title : column} + </Th> + ))}</Tr> + </Thead> + <Tbody> + {(pagedRows || []).map((row, rowIndex) => { + const rowCells = Array.isArray(row) ? row : row.cells || []; + return ( + <Tr key={rowIndex}> + {isSelectable && ( + <Td + select={{ + rowIndex, + onSelect: this.onSelectRow, + isSelected: row.selected + }} + /> + )} + {rowCells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columns[cellIndex]?.title || columns[cellIndex]} + > + {this.renderCellContent(cell)} + </Td> + ))} + </Tr> + ); + })} + </Tbody> </Table> </GridItem> </Grid> @@ -280,4 +328,14 @@ class GenericPagination extends React.Component { } } +GenericPagination.defaultProps = { + rows: [], + columns: [], + isSelectable: false, + isSearchable: false, + enableSorting: false, + handleSelectedAttrs: () => {}, + ariaLabel: "generic table" +}; + export default GenericPagination; diff --git a/src/cockpit/389-console/src/lib/ldap_editor/search.jsx b/src/cockpit/389-console/src/lib/ldap_editor/search.jsx index be1586c086..5b22369565 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/search.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/search.jsx @@ -1,36 +1,38 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Grid, - GridItem, - ExpandableSection, - Form, - FormHelperText, - FormSelect, - FormSelectOption, - Label, - Modal, - ModalVariant, - NumberInput, - SearchInput, - Select, - SelectOption, - SelectVariant, - Spinner, - Switch, - Text, - TextContent, - TextInput, - TextList, - TextListItem, - TextVariants, - ToggleGroup, - ToggleGroupItem, - Tooltip, - ValidatedOptions -} from "@patternfly/react-core"; + Button, + Checkbox, + Grid, + GridItem, + ExpandableSection, + Form, + FormHelperText, + FormSelect, + FormSelectOption, + Label, + Modal, + ModalVariant, + NumberInput, + SearchInput, + Spinner, + Switch, + Text, + TextContent, + TextInput, + TextList, + TextListItem, + TextVariants, + ToggleGroup, + ToggleGroupItem, + Tooltip, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import { expandable } from '@patternfly/react-table'; @@ -110,13 +112,13 @@ export class SearchDatabase extends React.Component { this.initialResultText = _("Loading ..."); - this.handleChangeSwitch = checkIfLocked => { + this.handleChangeSwitch = (_event, checkIfLocked) => { this.setState({ checkIfLocked }); }; - this.handleToggle = isExpanded => { + this.handleToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -137,7 +139,7 @@ export class SearchDatabase extends React.Component { }; // Custom filter attributes - this.handleCustomAttrToggle = isCustomAttrOpen => { + this.handleCustomAttrToggle = (_event, isCustomAttrOpen) => { this.setState({ isCustomAttrOpen, }); @@ -234,7 +236,7 @@ export class SearchDatabase extends React.Component { }); }; - this.handleSearchTypeClick = (isSelected, event) => { + this.handleSearchTypeClick = (event, isSelected) => { const id = event.currentTarget.id; this.setState({ searchType: id, @@ -243,7 +245,7 @@ export class SearchDatabase extends React.Component { }); }; - this.handleScopeClick = (isSelected, event) => { + this.handleScopeClick = (event, isSelected) => { const id = event.currentTarget.id; this.setState({ searchScope: id }); }; @@ -534,7 +536,7 @@ export class SearchDatabase extends React.Component { }); } - handleChange (e) { + handleChange (e, _str) { const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value.trim(); this.setState({ [e.target.id]: value, @@ -792,7 +794,7 @@ export class SearchDatabase extends React.Component { <FormSelect id="searchSuffix" value={baseDN} - onChange={(value, event) => { + onChange={(event, value) => { this.handleSuffixChange(event); }} aria-label="FormSelect Input" @@ -821,20 +823,20 @@ export class SearchDatabase extends React.Component { text={_("Text")} buttonId="Search Text" isSelected={this.state.searchType === "Search Text"} - onChange={this.handleSearchTypeClick} + onChange={(event, isSelected) => this.handleSearchTypeClick(event, isSelected)} /> <ToggleGroupItem title={_("Specific LDAP search filter for finding entries.")} text={_("Filter")} buttonId="Search Filter" isSelected={this.state.searchType === "Search Filter"} - onChange={this.handleSearchTypeClick} + onChange={(event, isSelected) => this.handleSearchTypeClick(event, isSelected)} /> </ToggleGroup> <SearchInput placeholder={this.state.searchType === "Search Text" ? _("Enter search text ...") : _("Enter an LDAP search filter ...")} value={this.state.searchText} - onChange={this.handleSearchChange} + onChange={(evt, val) => this.handleSearchChange(evt, val)} onClear={(evt, val) => this.handleSearchChange(evt, '')} onSearch={this.handleSearch} className="ds-search-input" @@ -846,7 +848,7 @@ export class SearchDatabase extends React.Component { <ExpandableSection className="ds-margin-left" toggleText={this.state.isExpanded ? _("Hide Search Criteria") : _("Show Search Criteria")} - onToggle={this.handleToggle} + onToggle={(event, isExpanded) => this.handleToggle(event, isExpanded)} isExpanded={this.state.isExpanded} displaySize={this.state.isExpanded ? "large" : "default"} > @@ -861,14 +863,14 @@ export class SearchDatabase extends React.Component { id="searchBase" aria-describedby="searchBase" name={_("searchBase")} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} validated={!valid_dn(this.state.searchBase) ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> <GridItem span={2} className="ds-left-margin ds-lower-field-md"> - <FormHelperText isError isHidden={valid_dn(this.state.searchBase)}> + <FormHelperText > {_("Invalid DN syntax")} </FormHelperText> </GridItem> @@ -884,21 +886,21 @@ export class SearchDatabase extends React.Component { text={_("Subtree")} buttonId="sub" isSelected={this.state.searchScope === "sub"} - onChange={this.handleScopeClick} + onChange={(evt, val) => this.handleScopeClick(evt, val)} title={_("Search for entries starting at the search base, and including all its child entries")} /> <ToggleGroupItem text={_("One Level")} buttonId="one" isSelected={this.state.searchScope === "one"} - onChange={this.handleScopeClick} + onChange={(evt, val) => this.handleScopeClick(evt, val)} title={_("Search for entries starting at the search base, and include only the first level of child entries")} /> <ToggleGroupItem text={_("Base")} buttonId="base" isSelected={this.state.searchScope === "base"} - onChange={this.handleScopeClick} + onChange={(evt, val) => this.handleScopeClick(evt, val)} title={_("Search for an exact entry (search base). This does not include child entries.")} /> </ToggleGroup> @@ -949,7 +951,7 @@ export class SearchDatabase extends React.Component { {_("Show Locking")} </GridItem> <GridItem span={10}> - <Switch id="no-label-switch-on" aria-label="Message when on" isChecked={this.state.checkIfLocked} onChange={this.handleChangeSwitch} /> + <Switch id="no-label-switch-on" aria-label="Message when on" isChecked={this.state.checkIfLocked} onChange={(event, val) => this.handleChangeSwitch(event, val)} /> </GridItem> </Grid> <div hidden={this.state.searchType === "Search Filter"}> @@ -968,7 +970,7 @@ export class SearchDatabase extends React.Component { label={_("cn")} id="cn" isChecked={this.state.cn} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="cn" @@ -979,7 +981,7 @@ export class SearchDatabase extends React.Component { label={_("uid")} id="uid" isChecked={this.state.uid} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="uid" @@ -990,7 +992,7 @@ export class SearchDatabase extends React.Component { label={_("sn")} id="sn" isChecked={this.state.sn} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="sn" @@ -1003,7 +1005,7 @@ export class SearchDatabase extends React.Component { label={_("givenName")} id="givenName" isChecked={this.state.givenName} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="givenName" @@ -1014,7 +1016,7 @@ export class SearchDatabase extends React.Component { label={_("mail")} id="mail" isChecked={this.state.mail} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="mail" @@ -1025,7 +1027,7 @@ export class SearchDatabase extends React.Component { label={_("displayName")} id="displayName" isChecked={this.state.displayName} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="displayName" @@ -1038,7 +1040,7 @@ export class SearchDatabase extends React.Component { label={_("legalName")} id="legalName" isChecked={this.state.legalName} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="legalName" @@ -1049,7 +1051,7 @@ export class SearchDatabase extends React.Component { label={_("memberOf")} id="memberOf" isChecked={this.state.memberOf} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="memberOf" @@ -1060,7 +1062,7 @@ export class SearchDatabase extends React.Component { label={_("member")} id="member" isChecked={this.state.member} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="member" @@ -1073,7 +1075,7 @@ export class SearchDatabase extends React.Component { label={_("uniqueMember")} id="uniqueMember" isChecked={this.state.uniqueMember} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="uniqueMember" @@ -1085,7 +1087,7 @@ export class SearchDatabase extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type attributes to include in filter ..." - onToggle={this.handleCustomAttrToggle} + onToggle={(event, isOpen) => this.handleCustomAttrToggle(event, isOpen)} onSelect={this.handleCustomAttrChange} onClear={this.handleCustomAttrClear} selections={this.state.customSearchAttrs} diff --git a/src/cockpit/389-console/src/lib/ldap_editor/tableView.jsx b/src/cockpit/389-console/src/lib/ldap_editor/tableView.jsx index cdd5598e32..13bbe7ef04 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/tableView.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/tableView.jsx @@ -8,79 +8,179 @@ import { TextVariants, } from '@patternfly/react-core'; import { - Table, TableHeader, TableBody, TableVariant + Thead, + Tr, + Th, + Tbody, + Td, + ExpandableRowContent, + ActionsColumn } from '@patternfly/react-table'; +import { + Table +} from '@patternfly/react-table/deprecated'; const _ = cockpit.gettext; -class EditorTableView extends React.Component { - constructor (props) { - super(props); - this.state = { - }; - } +const EditorTableView = (props) => { + const { + loading, + columns, + editorTableRows, + onCollapse, + itemCount, + page, + perPage, + onSetPage, + onPerPageSelect, + actionResolver // Added actionResolver from props + } = props; - render () { - const { - loading - } = this.props; + let tableColumns = [...columns]; + let rows = [...editorTableRows]; + let noBackend = false; - let columns = [...this.props.columns]; - let rows = [...this.props.editorTableRows]; - let noBackend = false; - if (rows.length === 0) { - columns = ['Database Suffixes']; - rows = [{ cells: ['No Databases'] }]; - noBackend = true; + if (rows.length === 0) { + tableColumns = ['Database Suffixes']; + rows = [{ cells: ['No Databases'] }]; + noBackend = true; + } + + // Helper function to safely render cell content + const renderCellContent = (cell) => { + if (cell === null || cell === undefined) { + return ''; + } + if (React.isValidElement(cell)) { + return cell; } + if (typeof cell === 'object') { + if (cell.title) { + return React.isValidElement(cell.title) + ? cell.title + : <span>{String(cell.title)}</span>; + } + return ''; + } + return String(cell); + }; - let body = ( - <div className="ds-margin-top-xlg ds-center"> - <TextContent> - <Text component={TextVariants.h3}> - {_("Loading ...")} - </Text> - </TextContent> - <Spinner className="ds-margin-top-lg" size="lg" /> - </div> - ); - if (!loading) { - body = ( - <div> - <Table - variant={TableVariant.compact} - onCollapse={noBackend ? null : this.props.onCollapse} - rows={rows} - cells={columns} - actionResolver={noBackend ? null : this.props.actionResolver} - aria-label="editor table view" - header={this.props.header ? this.props.header : ""} - > - <TableHeader /> - <TableBody /> - </Table> - {!noBackend && - <Pagination - id="ds-addons-editor-view-top" - className="ds-margin-top" - widgetId="pagination-options-menu-top" - itemCount={this.props.itemCount} - page={this.props.page} - perPage={this.props.perPage} - onSetPage={(_evt, value) => this.props.onSetPage(value)} - onPerPageSelect={(_evt, value) => this.props.onPerPageSelect(value)} - dropDirection="up" - />} - </div> - ); + // Filter out the expanded content rows and create a map for them + const [parentRows, expandedContentMap] = React.useMemo(() => { + const parentRowsArray = []; + const expandedMap = new Map(); + + for (let i = 0; i < rows.length; i++) { + const currentRow = rows[i]; + if (i % 2 === 0) { // Parent rows are at even indices + parentRowsArray.push(currentRow); + if (rows[i + 1]) { // Store expanded content if it exists + expandedMap.set(i, rows[i + 1]); + } + } } + + return [parentRowsArray, expandedMap]; + }, [rows]); - return ( - <div className="ds-indent-lg ds-margin-top-lg"> - {body} - </div> - ); - } -} + const loadingBody = ( + <div className="ds-margin-top-xlg ds-center"> + <TextContent> + <Text component={TextVariants.h3}> + {_("Loading ...")} + </Text> + </TextContent> + <Spinner className="ds-margin-top-lg" size="lg" /> + </div> + ); + + const tableBody = ( + <div> + <Table + aria-label="editor table view" + variant='compact' + > + <Thead> + <Tr> + {!noBackend && ( + <Th screenReaderText="Expand/Collapse Row" /> + )} + {tableColumns.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + {!noBackend && actionResolver && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {parentRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + {!noBackend && ( + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => onCollapse(null, rowIndex * 2, !row.isOpen, row) + }} + /> + )} + {row.cells.map((cell, cellIndex) => ( + <Td + key={cellIndex} + dataLabel={tableColumns[cellIndex]?.title || tableColumns[cellIndex]} + > + {renderCellContent(cell)} + </Td> + ))} + {!noBackend && actionResolver && ( + <Td isActionCell> + <ActionsColumn + items={actionResolver(row, { rowIndex: rowIndex * 2 })} + /> + </Td> + )} + </Tr> + {row.isOpen && expandedContentMap.has(rowIndex * 2) && ( + <Tr isExpanded={true}> + <Td /> + <Td + colSpan={tableColumns.length + (noBackend ? 0 : (actionResolver ? 2 : 1))} + noPadding + > + <ExpandableRowContent> + {renderCellContent(expandedContentMap.get(rowIndex * 2).cells[0])} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> + </Table> + {!noBackend && ( + <Pagination + id="ds-addons-editor-view-top" + className="ds-margin-top" + widgetId="pagination-options-menu-top" + itemCount={itemCount} + page={page} + perPage={perPage} + onSetPage={(_evt, value) => onSetPage(value)} + onPerPageSelect={(_evt, value) => onPerPageSelect(value)} + isCompact + dropDirection="up" + /> + )} + </div> + ); + + return ( + <div className="ds-indent-lg ds-margin-top-lg"> + {loading ? loadingBody : tableBody} + </div> + ); +}; export default EditorTableView; diff --git a/src/cockpit/389-console/src/lib/ldap_editor/treeView.jsx b/src/cockpit/389-console/src/lib/ldap_editor/treeView.jsx index 3ed0b10d51..f29f03de88 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/treeView.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/treeView.jsx @@ -1,23 +1,42 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, AlertGroup, AlertActionCloseButton, AlertVariant, - Button, - Bullseye, - Card, CardBody, CardFooter, CardTitle, - CardHeaderMain, CardHeader, CardActions, - Divider, - DropdownItem, Dropdown, DropdownSeparator, - EmptyState, EmptyStateIcon, EmptyStateBody, EmptyStateVariant, - Grid, GridItem, - KebabToggle, - Label, - Spinner, - Title, - TextContent, Text, TextVariants, TextList, - TextListVariants, TextListItem, TextListItemVariants, - Tooltip + Alert, + AlertGroup, + AlertActionCloseButton, + AlertVariant, + Button, + Bullseye, + Card, + CardBody, + CardFooter, + CardTitle, + CardHeader, + Divider, + EmptyState, + EmptyStateIcon, + EmptyStateBody, + EmptyStateVariant, + Grid, + GridItem, + Label, + Spinner, + Title, + TextContent, + Text, + TextVariants, + TextList, + TextListVariants, + TextListItem, + TextListItemVariants, + Tooltip, EmptyStateHeader } from '@patternfly/react-core'; +import { + DropdownItem, + Dropdown, + DropdownSeparator, + KebabToggle +} from '@patternfly/react-core/deprecated'; import { ArrowRightIcon, CatalogIcon, @@ -63,11 +82,8 @@ class EditorTreeView extends React.Component { props: { colSpan: 8 }, title: ( <Bullseye> - <EmptyState variant={EmptyStateVariant.small}> - <EmptyStateIcon icon={ResourcesEmptyIcon} /> - <Title headingLevel="h2" size="lg"> - {_("No entry is selected")} - </Title> + <EmptyState variant={EmptyStateVariant.sm}> + <EmptyStateHeader titleText={<>{_("No entry is selected")}</>} icon={<EmptyStateIcon icon={ResourcesEmptyIcon} />} headingLevel="h2" /> <EmptyStateBody> {_("Select an entry to see its details.")} </EmptyStateBody> @@ -715,10 +731,8 @@ class EditorTreeView extends React.Component { { searching && loadingEntryComponent } { firstClickOnTree && !loading && !searching && isValidData && - <Card isSelectable> - <CardHeader> - <CardActions> - <Dropdown + <Card isSelectable isClickable> + <CardHeader actions={{ actions: <><Dropdown onSelect={this.props.onSelectEntryOptions} toggle={ <KebabToggle @@ -730,13 +744,10 @@ class EditorTreeView extends React.Component { isPlain dropdownItems={dropdownItems} position="right" - /> - </CardActions> - <CardHeaderMain> - {entryIcon} - </CardHeaderMain> + /></>, hasNoOffset: false, className: undefined}} > + <Title headingLevel="h6" size="md"> - <span> {entryDn} </span> + {entryIcon}<span> {entryDn} </span> {(entryState !== "") && (entryStateIcon !== "") && (entryState !== "activated") ? ( <Tooltip @@ -769,11 +780,8 @@ class EditorTreeView extends React.Component { /> { isEmptySuffix && - <EmptyState variant={EmptyStateVariant.small}> - <EmptyStateIcon icon={ResourcesEmptyIcon} /> - <Title headingLevel="h2" size="lg"> - {_("Empty suffix!")} - </Title> + <EmptyState variant={EmptyStateVariant.sm}> + <EmptyStateHeader titleText={<>{_("Empty suffix!")}</>} icon={<EmptyStateIcon icon={ResourcesEmptyIcon} />} headingLevel="h2" /> <EmptyStateBody> <Label variant="outline" color="orange" icon={<InfoCircleIcon />}> {_("The suffix is configured, but it has no entries.")} diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/aci.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/aci.jsx index 07e867a8c1..87594dd226 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/aci.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/aci.jsx @@ -7,13 +7,17 @@ import { Pagination, PaginationVariant, } from '@patternfly/react-core'; import { - expandable, + expandable, + sortable, + SortByDirection, + ExpandableRowContent, + ActionsColumn, Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + Thead, + Tbody, + Tr, + Th, + Td } from '@patternfly/react-table'; import { retrieveAllAcis, modifyLdapEntry @@ -378,22 +382,59 @@ class AciWizard extends React.Component { }); }; - render () { - // We are using an expandable list, so every row has a child row with an - // index that points back to the parent. So when we splice the rows for - // pagination we have to treat each connection as two rows, and we need - // to rewrite the child's parent index to point to the correct location - // in the new spliced array + getActionsForRow = (rowData) => { + // Return empty array if it's the "No ACI's" row + if (rowData.cells.length === 1 && rowData.cells[0] === _("No ACI's")) { + return []; + } + + // Only return actions for parent rows (not expanded rows) + if (!rowData.cells || rowData.parent !== undefined) { + return []; + } + + return [ + { + title: _("Edit ACI"), + onClick: () => this.showEditAci(rowData) + }, + { + isSeparator: true + }, + { + title: _("Remove ACI"), + onClick: () => this.showDeleteConfirm(rowData) + } + ]; + }; + + prepareTableRows(tableRows) { + const parentRows = []; + const expandedContentMap = new Map(); + + for (let i = 0; i < tableRows.length; i += 2) { + const currentRow = tableRows[i]; + if (currentRow) { + parentRows.push(currentRow); + if (tableRows[i + 1]) { + expandedContentMap.set(i, tableRows[i + 1]); + } + } + } + + return { parentRows, expandedContentMap }; + } + + + render() { const { columns, rows, perPage, page, sortBy, showModal, actions, modalSpinning } = this.state; const origRows = [...rows]; const startIdx = ((perPage * page) - perPage) * 2; let tableRows = origRows.splice(startIdx, perPage * 2); - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } - // Edit modal + // Prepare rows without using hooks + const { parentRows, expandedContentMap } = this.prepareTableRows(tableRows); + let btnName = _("Save ACI"); const extraPrimaryProps = {}; if (modalSpinning) { @@ -403,13 +444,16 @@ class AciWizard extends React.Component { const title = _("Manage ACI's For ") + this.props.wizardEntryDn; - // Handle table state let cols = columns; if (rows.length === 0) { tableRows = [{ cells: [_("No ACI's")] }]; cols = [{ title: _("Access Control Instructions") }]; } + // Determine if we should show the actions column + const showActions = rows.length > 0 && !(rows.length === 1 && rows[0].cells.length === 1 && rows[0].cells[0] === _("No ACI's")); + const isEmptyTable = !showActions; + return ( <> <Modal @@ -438,31 +482,89 @@ class AciWizard extends React.Component { </Button> ]} > - <Table - className="ds=margin-top-lg" - aria-label="Expandable table" - cells={cols} - rows={tableRows} - actions={rows.length > 0 ? actions : null} - onCollapse={this.handleCollpase} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - key={tableRows} + <Table + aria-label="Expandable ACI table" + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {!isEmptyTable && ( + <Th + screenReaderText={_("Expand/Collapse Row")} + /> + )} + {cols.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.transforms?.includes(sortable) ? { + sortBy, + onSort: (_evt, index, direction) => this.handleSort(index, direction), + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + {showActions && ( + <Th + screenReaderText={_("Actions")} + /> + )} + </Tr> + </Thead> + <Tbody> + {isEmptyTable ? ( + <Tr> + <Td>{tableRows[0].cells[0]}</Td> + </Tr> + ) : ( + parentRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollpase(null, rowIndex * 2, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + </Tr> + {row.isOpen && expandedContentMap.has(rowIndex * 2) && ( + <Tr isExpanded={true}> + <Td + colSpan={cols.length + 2} + noPadding + > + <ExpandableRowContent> + {expandedContentMap.get(rowIndex * 2).cells[0]} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + )) + )} + </Tbody> </Table> - {rows.length > 0 && + {!isEmptyTable && ( <Pagination itemCount={rows.length / 2} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} - onSetPage={this.handleSetPage} - onPerPageSelect={this.handlePerPageSelect} - />} + variant="bottom" + onSetPage={(_evt, value) => this.handleSetPage(value)} + onPerPageSelect={(_evt, value) => this.handlePerPageSelect(value)} + /> + )} <div className="ds-margin-top-xlg" /> {this.state.isWizardOpen && <AddNewAci @@ -516,7 +618,7 @@ class AciWizard extends React.Component { className="ds-textarea" id="aciTextNew" value={this.state.aciTextNew} - onChange={(str, e) => { this.onChange(e) }} + onChange={(e, str) => { this.onChange(e) }} aria-label="aci text edit area" autoResize resizeOrientation="vertical" @@ -527,7 +629,7 @@ class AciWizard extends React.Component { variant="secondary" onClick={this.handleResetACIText} isDisabled={this.state.aciText === this.state.aciTextNew} - isSmall + size="sm" > {_("Reset ACI")} </Button> @@ -558,7 +660,7 @@ class AciWizard extends React.Component { className="ds-textarea" id="aciTextNew" value={this.state.aciTextNew} - onChange={(str, e) => { this.onChange(e) }} + onChange={(e, str) => { this.onChange(e) }} aria-label="aci text edit area" autoResize resizeOrientation="vertical" diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/cos.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/cos.jsx index 6ca2968566..6cec6e422c 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/cos.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/cos.jsx @@ -23,7 +23,7 @@ class CoSEntryWizard extends React.Component { }; } - handleOnChange = (_, event) => { + handleOnChange = (event, _) => { // console.log('event.currentTarget.value = ' + event.currentTarget.value); this.setState({ getStartedStepRadio: event.currentTarget.value }); }; @@ -57,7 +57,7 @@ class CoSEntryWizard extends React.Component { className="ds-margin-top-lg" value="CoSDefinition" isChecked={this.state.getStartedStepRadio === 'CoSDefinition'} - onChange={this.handleOnChange} + onChange={(event, _) => this.handleOnChange(event, _)} label={_("Create a new CoS Definition")} description={_("The CoS definition entry identifies the type of CoS used. The CoS definition entry is below the branch at which it is effective.")} name="radio-new-step-start" @@ -67,7 +67,7 @@ class CoSEntryWizard extends React.Component { className="ds-margin-top-lg" value="CoSTemplate" isChecked={this.state.getStartedStepRadio === 'CoSTemplate'} - onChange={this.handleOnChange} + onChange={(event, _) => this.handleOnChange(event, _)} label={_("Create a new CoS Template")} description={_("The CoS template entry contains a list of the shared attribute values. Changes to the template entry attribute values are automatically applied to all the entries within the scope of the CoS.")} name="radio-new-step-start" diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/deleteOperation.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/deleteOperation.jsx index 3c07cee8c5..6149872642 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/deleteOperation.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/deleteOperation.jsx @@ -1,16 +1,18 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - Card, - CardBody, - CardTitle, - ClipboardCopy, - ClipboardCopyVariant, - Spinner, - Switch, - Wizard + Alert, + Card, + CardBody, + CardTitle, + ClipboardCopy, + ClipboardCopyVariant, + Spinner, + Switch } from '@patternfly/react-core'; +import { + Wizard +} from '@patternfly/react-core/deprecated'; import { headerCol, } from '@patternfly/react-table'; @@ -94,7 +96,7 @@ class DeleteOperationWizard extends React.Component { } }; - this.handleChangeAck = isAckChecked => { + this.handleChangeAck = (_event, isAckChecked) => { this.setState({ isAckChecked }); }; // End constructor(). @@ -223,7 +225,7 @@ class DeleteOperationWizard extends React.Component { label={_("Yes, I'm sure.")} labelOff={_("No, don't delete.")} isChecked={isAckChecked} - onChange={this.handleChangeAck} + onChange={(event, isChecked) => this.handleChangeAck(event, isChecked)} /> </CardBody> </Card> diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/newEntry.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/newEntry.jsx index 445e5c7d6a..7609498686 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/newEntry.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/newEntry.jsx @@ -55,7 +55,7 @@ variant="info" isInline </CardBody> <CardBody> <Alert -variant="default" isInline +variant="custom" isInline title={cockpit.format(_("The root entry to create is $0"), this.props.entryParentDn)} > {_("Make sure to select an <strong>ObjectClass</strong>that allows or requires the attribute")} @@ -78,7 +78,7 @@ variant="default" isInline <Radio value="User" isChecked={this.state.getStartedStepRadio === 'User'} - onChange={this.handleOnChange} + onChange={(event, _) => this.handleOnChange(_, event)} label={_("Create a new User")} description={_("Add a new User (inetOrgPerson objectClass)")} name="radio-new-step-start" @@ -88,7 +88,7 @@ variant="default" isInline className="ds-margin-top-lg" value="Group" isChecked={this.state.getStartedStepRadio === 'Group'} - onChange={this.handleOnChange} + onChange={(event, _) => this.handleOnChange(_, event)} label={_("Create a new Group")} description={_("Add a new Group (GroupOfNames/GroupOfUniqueNames objectClass)")} name="group" @@ -98,7 +98,7 @@ variant="default" isInline className="ds-margin-top-lg" value="OrganizationalUnit" isChecked={this.state.getStartedStepRadio === 'OrganizationalUnit'} - onChange={this.handleOnChange} + onChange={(event, _) => this.handleOnChange(_, event)} label={_("Create a new Organizational Unit")} description={_("Add a new Organizational Unit")} name="radio-new-step-start" @@ -108,7 +108,7 @@ variant="default" isInline className="ds-margin-top-lg" value="Role" isChecked={this.state.getStartedStepRadio === 'Role'} - onChange={this.handleOnChange} + onChange={(event, _) => this.handleOnChange(_, event)} label={_("Create a new Role")} description={_("Add a new Role (Filtered / Managed / Nested)")} name="radio-new-step-start" @@ -118,7 +118,7 @@ variant="default" isInline className="ds-margin-top-lg" value="Other" isChecked={this.state.getStartedStepRadio === 'Other'} - onChange={this.handleOnChange} + onChange={(event, _) => this.handleOnChange(_, event)} label={_("Create a new custom Entry")} description={_("Add a new entry by selecting ObjectClasses and Attributes")} name="radio-new-step-start" diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciBindRuleTable.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciBindRuleTable.jsx index 5cf0587840..4d3874d9d9 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciBindRuleTable.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciBindRuleTable.jsx @@ -2,9 +2,12 @@ import cockpit from "cockpit"; import React from 'react'; import { Table, - TableHeader, - TableBody, - TableVariant, + Thead, + Tbody, + Tr, + Th, + Td, + ActionsColumn } from '@patternfly/react-table'; const _ = cockpit.gettext; @@ -12,24 +15,25 @@ const _ = cockpit.gettext; class AciBindRuleTable extends React.Component { constructor(props) { super(props); - this.state = { - columns: [_("Bind Rule"), _("Comparator"), _("LDAP URLs")], - actions: [ - { - title: _("Remove Bind Rule"), - onClick: (event, rowId, rowData, extra) => this.props.removeRow(rowId) - } - ], - rows: [], - }; + columns: [ + { title: _("Bind Rule") }, + { title: _("Comparator") }, + { title: _("LDAP URLs") } + ], + rows: [], + }; } - componentDidMount () { - let columns = [_("Bind Rule"), _("Comparator"), _("LDAP URLs")]; + componentDidMount() { + let columns = [ + { title: _("Bind Rule") }, + { title: _("Comparator") }, + { title: _("LDAP URLs") } + ]; let rows = [...this.props.rows]; if (this.props.rows.length === 0) { - columns = [_("Bind Rules")]; + columns = [{ title: _("Bind Rules") }]; rows = [{ cells: [_("No bind rules")] }]; } this.setState({ @@ -38,20 +42,55 @@ class AciBindRuleTable extends React.Component { }); } + getActions = (rowIndex) => [ + { + title: _("Remove Bind Rule"), + onClick: () => this.props.removeRow(rowIndex) + } + ]; + render() { - const { columns, rows, actions } = this.state; + const { columns, rows } = this.state; + const hasRows = this.props.rows.length !== 0; return ( <Table className="ds-margin-top-lg" - actions={this.props.rows.length !== 0 ? actions : null} aria-label="bind rule Table" - variant={TableVariant.compact} - cells={columns} - rows={rows} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, columnIndex) => ( + <Th key={columnIndex}>{column.title}</Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + // Handle array-type rows + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + // Handle object-type rows (for the "No bind rules" case) + row.cells && row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(rowIndex)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> ); } diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx index 4d2f262b50..82845037f4 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/aciNew.jsx @@ -1,34 +1,58 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - Bullseye, - Button, - Card, CardBody, CardTitle, - Checkbox, - Divider, - Drawer, DrawerPanelContent, DrawerContent, - DrawerContentBody, DrawerHead, - DrawerActions, DrawerCloseButton, - DualListSelector, - Form, FormHelperText, FormSelect, FormSelectOption, - Grid, GridItem, - HelperText, HelperTextItem, - Modal, ModalVariant, - SearchInput, - Select, SelectOption, SelectVariant, - Spinner, - Text, TextArea, TextContent, TextInput, TextVariants, - TimePicker, - Tooltip, - ValidatedOptions, - Wizard, + Alert, + Bullseye, + Button, + Card, + CardBody, + CardTitle, + Checkbox, + Divider, + Drawer, + DrawerPanelContent, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerActions, + DrawerCloseButton, + DualListSelector, + Form, + FormHelperText, + FormSelect, + FormSelectOption, + Grid, + GridItem, + HelperText, + HelperTextItem, + Modal, + ModalVariant, + SearchInput, + Spinner, + Text, + TextArea, + TextContent, + TextInput, + TextVariants, + TimePicker, + Tooltip, + ValidatedOptions } from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant, + Wizard +} from '@patternfly/react-core/deprecated'; import { Table, - TableHeader, - TableBody, - sortable, + Thead, + Tbody, + Tr, + Th, + Td, + sortable, + ActionsColumn } from '@patternfly/react-table'; import { getSearchEntries, getAttributesNameAndOid, @@ -338,7 +362,7 @@ class AddNewAci extends React.Component { this.targetsDrawerRef = React.createRef(); // Target attr operator - this.handleToggleTargetAttrOp = isOpenTargetAttrOperator => { + this.handleToggleTargetAttrOp = (_event, isOpenTargetAttrOperator) => { this.setState({ isOpenTargetAttrOperator }); @@ -351,7 +375,7 @@ class AddNewAci extends React.Component { }; // rights - this.handleToggleRights = isOpenRights => { + this.handleToggleRights = (_event, isOpenRights) => { this.setState({ isOpenRights }); @@ -471,13 +495,16 @@ class AddNewAci extends React.Component { this.handleRightsOnSelect = (event, isSelected, rowId) => { let rows; if (rowId === -1) { - rows = this.state.rightsRows.map(oneRow => { - oneRow.selected = isSelected; - return oneRow; - }); + rows = this.state.rightsRows.map(oneRow => ({ + ...oneRow, + selected: isSelected + })); } else { rows = [...this.state.rightsRows]; - rows[rowId].selected = isSelected; + rows[rowId] = { + ...rows[rowId], + selected: isSelected + }; } this.setState({ rightsRows: rows @@ -497,7 +524,7 @@ class AddNewAci extends React.Component { return noDuplicates; }; - this.handleUsersOnListChange = (newAvailableOptions, newChosenOptions) => { + this.handleUsersOnListChange = (_event, newAvailableOptions, newChosenOptions) => { const newAvailNoDups = this.removeDuplicates(newAvailableOptions); const newChosenNoDups = this.removeDuplicates(newChosenOptions); @@ -597,7 +624,7 @@ class AddNewAci extends React.Component { }); }; - this.handleTextChange = (value) => { + this.handleTextChange = (_event, value) => { this.setState({ aciTextNew: value, }); @@ -772,7 +799,7 @@ class AddNewAci extends React.Component { id="newAciName" value={newAciName} type="text" - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="Text input ACI name" autoComplete="off" /> @@ -789,7 +816,7 @@ class AddNewAci extends React.Component { id="target" value={target} type="text" - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="Text input ACI target" autoComplete="off" /> @@ -832,7 +859,7 @@ class AddNewAci extends React.Component { chosenOptions={usersChosenOptions} availableOptionsTitle={_("Available Entries")} chosenOptionsTitle={_("Chosen Entries")} - onListChange={this.handleUsersOnListChange} + onListChange={(event, newAvailableOptions, newChosenOptions) => this.handleUsersOnListChange(event, newAvailableOptions, newChosenOptions)} id="usersSelector" className="ds-aci-dual-select" /> @@ -902,7 +929,7 @@ class AddNewAci extends React.Component { className="ds-margin-top-lg" variant="primary" onClick={this.handleOpenAddBindRule} - isSmall + size="sm" > {_("Add Bind Rule")} </Button> @@ -912,7 +939,8 @@ class AddNewAci extends React.Component { const rightsComponent = ( <> <TextContent> - <Text component={TextVariants.h3}>{_("Choose the Rights to Allow or Deny")} + <Text component={TextVariants.h3}> + {_("Choose the Rights to Allow or Deny")} <Tooltip position="bottom" content={ @@ -931,7 +959,7 @@ class AddNewAci extends React.Component { variant={SelectVariant.single} className="ds-margin-top-lg" aria-label="Select rights" - onToggle={this.handleToggleRights} + onToggle={(event, isOpen) => this.handleToggleRights(event, isOpen)} onSelect={this.handleSelectRights} selections={this.state.rightType} isOpen={this.state.isOpenRights} @@ -939,17 +967,35 @@ class AddNewAci extends React.Component { <SelectOption key="allow" value="allow" /> <SelectOption key="deny" value="deny" /> </Select> - <Table - onSelect={this.handleRightsOnSelect} + <Table aria-label="Selectable Table User Rights" - cells={this.rightsColumns} - rows={rightsRows} variant="compact" borders={false} className="ds-margin-top-lg" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {this.rightsColumns.map((column, columnIndex) => ( + <Th key={columnIndex}>{column.title}</Th> + ))} + </Tr> + </Thead> + <Tbody> + {rightsRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td> + <Checkbox + id={`rights-checkbox-${rowIndex}`} + isChecked={row.selected} + onChange={(checked) => this.handleRightsOnSelect(null, checked, rowIndex)} + aria-label={`Select ${row.cells[0]}`} + /> + {row.cells[0]} + </Td> + <Td>{row.cells[1]}</Td> + </Tr> + ))} + </Tbody> </Table> </> ); @@ -981,7 +1027,7 @@ class AddNewAci extends React.Component { <Select variant={SelectVariant.single} aria-label="Select auth compare operator" - onToggle={this.handleToggleTargetAttrOp} + onToggle={(event, isOpen) => this.handleToggleTargetAttrOp(event, isOpen)} onSelect={this.handleSelectTargetAttrOp} selections={this.state.targetAttrCompOp} isOpen={this.state.isOpenTargetAttrOperator} @@ -1037,11 +1083,11 @@ class AddNewAci extends React.Component { <TextInput id="targetFilter" aria-label="Add target filter" - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} value={targetFilter} validated={targetFilter !== "" && !valid_filter(targetFilter) ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={targetFilter === "" || valid_filter(targetFilter)}> + <FormHelperText > {_("The filter must be enclosed with parentheses")} </FormHelperText> </GridItem> @@ -1077,11 +1123,11 @@ class AddNewAci extends React.Component { <TextInput id="target_from" aria-label="Add target_from" - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} value={target_from} validated={target_from !== "" && !isValidLDAPUrl(target_from) ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={target_from === "" || isValidLDAPUrl(target_from)}> + <FormHelperText > {_("The LDAP URL must start with 'ldap:///'")} </FormHelperText> </GridItem> @@ -1092,11 +1138,11 @@ class AddNewAci extends React.Component { <TextInput id="target_to" aria-label="Add target_to" - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} value={target_to} validated={target_to !== "" && !isValidLDAPUrl(target_to) ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={target_to === "" || isValidLDAPUrl(target_to)}> + <FormHelperText > {_('The LDAP URL must start with "ldap:///"')} </FormHelperText> </GridItem> @@ -1133,7 +1179,7 @@ class AddNewAci extends React.Component { className="" label={_("Sunday")} isChecked={this.state.sunday} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="Sunday" id="sunday" /> @@ -1141,7 +1187,7 @@ class AddNewAci extends React.Component { className="" label={_("Monday")} isChecked={this.state.monday} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="monday" id="monday" /> @@ -1149,7 +1195,7 @@ class AddNewAci extends React.Component { className="" label={_("Tuesday")} isChecked={this.state.tuesday} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="Tuesday" id="tuesday" /> @@ -1157,7 +1203,7 @@ class AddNewAci extends React.Component { className="" label={_("Wednesday")} isChecked={this.state.wednesday} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="wednesday" id="wednesday" /> @@ -1165,7 +1211,7 @@ class AddNewAci extends React.Component { className="" label={_("Thursday")} isChecked={this.state.thursday} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="thursday" id="thursday" /> @@ -1173,7 +1219,7 @@ class AddNewAci extends React.Component { className="" label={_("Friday")} isChecked={this.state.friday} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="friday" id="friday" /> @@ -1181,7 +1227,7 @@ class AddNewAci extends React.Component { className="" label={_("Saturday")} isChecked={this.state.saturday} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="saturday" id="saturday" /> @@ -1200,7 +1246,7 @@ class AddNewAci extends React.Component { <FormSelect id="timeStartCompOp" value={this.state.timeStartCompOp} - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key="=" label="=" value="=" /> @@ -1228,7 +1274,7 @@ class AddNewAci extends React.Component { <FormSelect id="timeEndCompOp" value={this.state.timeEndCompOp} - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key="=" label="=" value="=" /> @@ -1274,7 +1320,7 @@ class AddNewAci extends React.Component { className="ds-textarea" id="aciTextNew" value={aciTextNew} - onChange={this.handleTextChange} + onChange={(event, str) => this.handleTextChange(event, str)} aria-label="aci text edit area" autoResize resizeOrientation="vertical" @@ -1285,7 +1331,7 @@ class AddNewAci extends React.Component { variant="primary" onClick={this.handleResetACIText} isDisabled={aciText === aciTextNew} - isSmall + size="sm" > {_("Undo Changes")} </Button> @@ -1442,7 +1488,7 @@ class AddNewAci extends React.Component { {_("Choose Bind Rule")} </GridItem> <GridItem span={9}> - <FormSelect id="bindRuleType" value={bindRuleType} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="bindRuleType" value={bindRuleType} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> { this.state.specialSelection === "" && !this.state.haveUserAttrRules && <> <FormSelectOption key="userdn" label={_("User DN (userdn)")} value="userdn" title={_("Bind rules for user entries")} /> @@ -1467,7 +1513,7 @@ class AddNewAci extends React.Component { {_("Select Alias URL")} </GridItem> <GridItem span={9}> - <FormSelect id="specialSelection" value={this.state.specialSelection} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="specialSelection" value={this.state.specialSelection} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption key="anyone" label={_("ldap:///anyone")} value="ldap:///anyone" title={_("Grants Anonymous Access")} /> <FormSelectOption key="all" label={_("ldap:///all")} value="ldap:///all" title={_("Grants Access to Authenticated Users")} /> <FormSelectOption key="self" label={_("ldap:///self")} value="ldap:///self" title={_("Enables Users to Access Their Own Entries")} /> @@ -1483,7 +1529,7 @@ class AddNewAci extends React.Component { {_("Choose Comparator")} </GridItem> <GridItem span={9}> - <FormSelect id="bindRuleOperator" value={bindRuleType} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="bindRuleOperator" value={bindRuleType} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption key="=" label="=" value="=" /> <FormSelectOption key="!=" label="!=" value="!=" /> </FormSelect> @@ -1502,7 +1548,7 @@ class AddNewAci extends React.Component { {_("IP Address")} </GridItem> <GridItem span={2}> - <FormSelect id="ipOperator" value={this.state.ipOperator} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="ipOperator" value={this.state.ipOperator} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption key="=" label="=" value="=" /> <FormSelectOption key="!=" label="!=" value="!=" /> </FormSelect> @@ -1511,7 +1557,7 @@ class AddNewAci extends React.Component { <TextInput id="ip" aria-label="Add IP restriction" - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} value={ip} autoComplete="off" validated={ip === "" || (ip !== "" && !isValidIpAddress(ip)) ? ValidatedOptions.error : ValidatedOptions.default} @@ -1531,7 +1577,7 @@ class AddNewAci extends React.Component { {_("Hostname")} </GridItem> <GridItem span={2}> - <FormSelect id="dnsOperator" value={this.state.dnsOperator} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="dnsOperator" value={this.state.dnsOperator} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption key="=" label="=" value="=" /> <FormSelectOption key="!=" label="!=" value="!=" /> </FormSelect> @@ -1540,7 +1586,7 @@ class AddNewAci extends React.Component { <TextInput id="dns" aria-label="Add Hostname restriction" - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} value={dns} validated={dns === "" || (dns !== "" && !isValidHostname(dns)) ? ValidatedOptions.error : ValidatedOptions.default} autoComplete="off" @@ -1562,7 +1608,7 @@ class AddNewAci extends React.Component { <FormSelect id="authMethodOperator" value={this.state.authMethodOperator} - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key="=" label="=" value="=" /> @@ -1570,7 +1616,7 @@ class AddNewAci extends React.Component { </FormSelect> </GridItem> <GridItem span={7} className="ds-left-margin"> - <FormSelect id="authmethod" value={this.state.authmethod} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="authmethod" value={this.state.authmethod} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption key="none" value="none" label="none" /> <FormSelectOption key="simple" value="simple" label="simple" /> <FormSelectOption key="SSL" value="SSL" label="SSL" /> @@ -1588,7 +1634,7 @@ class AddNewAci extends React.Component { <FormSelect id="ssfOperator" value={this.state.ssfOperator} - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key="=" label="=" value="=" /> @@ -1603,7 +1649,7 @@ class AddNewAci extends React.Component { <FormSelect id="ssf" value={this.state.ssf} - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key="0" label="0" value="0" title={_("No connection security restrictions")} /> @@ -1622,7 +1668,7 @@ class AddNewAci extends React.Component { <FormSelect id="userattrOperator" value={this.state.userattrOperator} - onChange={(str, e) => { this.handleChange(e) }} + onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key="=" label="=" value="=" /> @@ -1630,7 +1676,7 @@ class AddNewAci extends React.Component { </FormSelect> </GridItem> <GridItem span={5} className="ds-left-margin"> - <FormSelect id="userattrAttr" value={this.state.userattrAttr} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="userattrAttr" value={this.state.userattrAttr} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption value="" label={_("Select an attribute")} isPlaceholder /> {this.state.attributeList.map((attr, index) => ( <FormSelectOption key={attr} value={attr} label={attr} /> @@ -1638,7 +1684,7 @@ class AddNewAci extends React.Component { </FormSelect> </GridItem> <GridItem span={2} className="ds-left-margin"> - <FormSelect id="userattrBindType" value={this.state.userattrBindType} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="userattrBindType" value={this.state.userattrBindType} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption key="USERDN" value="USERDN" label="USERDN" /> <FormSelectOption key="GROUPDN" value="GROUPDN" label="GROUPDN" /> <FormSelectOption key="ROLEDN" value="ROLEDN" label="ROLEDN" /> @@ -1655,7 +1701,7 @@ class AddNewAci extends React.Component { className="" label="0" isChecked={this.state.userAttrParent0} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="0" id="userAttrParent0" title={_("Default value, this userattr bind rule only applies to the target entry (no child entries).")} @@ -1664,7 +1710,7 @@ class AddNewAci extends React.Component { className="" label="1" isChecked={this.state.userAttrParent1} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="1" id="userAttrParent1" /> @@ -1672,7 +1718,7 @@ class AddNewAci extends React.Component { className="" label="2" isChecked={this.state.userAttrParent2} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="2" id="userAttrParent2" /> @@ -1680,7 +1726,7 @@ class AddNewAci extends React.Component { className="" label="3" isChecked={this.state.userAttrParent3} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="3" id="userAttrParent3" /> @@ -1688,7 +1734,7 @@ class AddNewAci extends React.Component { className="" label="4" isChecked={this.state.userAttrParent4} - onChange={(checked, e) => this.handleChange(e)} + onChange={(e, checked) => this.handleChange(e)} aria-label="4" id="userAttrParent4" /> diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx index 0508c43c53..2028e58153 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosDefinition.jsx @@ -1,37 +1,39 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - BadgeToggle, - Bullseye, - Button, - Card, - CardBody, - CardTitle, - Checkbox, - Dropdown, - DropdownItem, - DropdownPosition, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - Radio, - SearchInput, - SimpleList, - SimpleListItem, - Spinner, - Text, - TextContent, - TextInput, - TextVariants, - Tooltip, - ValidatedOptions, - Wizard, + Alert, + Bullseye, + Button, + Card, + CardBody, + CardTitle, + Checkbox, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + Radio, + SearchInput, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextInput, + TextVariants, + Tooltip, + ValidatedOptions } from '@patternfly/react-core'; +import { + BadgeToggle, + Dropdown, + DropdownItem, + DropdownPosition, + Wizard +} from '@patternfly/react-core/deprecated'; import { headerCol, sortable, @@ -312,7 +314,7 @@ class AddCosDefinition extends React.Component { return noDuplicates; }; - this.handleRadioChange = (_, event) => { + this.handleRadioChange = (event, _) => { this.setState({ cosType: event.currentTarget.id, }); @@ -655,7 +657,7 @@ class AddCosDefinition extends React.Component { onSelect={this.handleAttrDropDownSelect} position={DropdownPosition.left} toggle={ - <BadgeToggle id="toggle-attr-select" onToggle={this.handleAttrDropDownToggle}> + <BadgeToggle id="toggle-attr-select" onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -832,7 +834,7 @@ class AddCosDefinition extends React.Component { id="namingVal" aria-describedby="namingVal" name="namingVal" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} validated={this.state.namingVal === '' ? ValidatedOptions.error : ValidatedOptions.default} @@ -848,7 +850,7 @@ class AddCosDefinition extends React.Component { id="cosDescription" aria-describedby="cosDescription" name="cosDescription" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -867,7 +869,7 @@ class AddCosDefinition extends React.Component { value="pointer" label={_("Pointer")} isChecked={this.state.cosType === 'pointer'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("Identifies the template entry using the template DN only.")} /> <Radio @@ -876,7 +878,7 @@ class AddCosDefinition extends React.Component { value="indirect" label={_("Indirect")} isChecked={this.state.cosType === 'indirect'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("Identifies the template entry using the value of one of the target entry's attributes.")} className="ds-margin-top" /> @@ -886,7 +888,7 @@ class AddCosDefinition extends React.Component { value="classic" label={_("Classic")} isChecked={this.state.cosType === 'classic'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("Identifies the template entry using a combination of the template entry's base DN and the value of one of the target entry's attributes.")} className="ds-margin-top" /> @@ -982,7 +984,7 @@ class AddCosDefinition extends React.Component { </Button> ]} > - <Card isHoverable className="ds-indent ds-margin-bottom-md"> + <Card className="ds-indent ds-margin-bottom-md"> <CardBody> <LdapNavigator treeItems={[...this.props.treeViewRootSuffixes]} @@ -1024,7 +1026,7 @@ class AddCosDefinition extends React.Component { </Button>, ]} > - <Card isHoverable className="ds-indent ds-margin-bottom-md"> + <Card className="ds-indent ds-margin-bottom-md"> <CardBody> <LdapNavigator treeItems={[...this.props.treeViewRootSuffixes]} @@ -1131,7 +1133,7 @@ class AddCosDefinition extends React.Component { id="def" label={_("Default")} isChecked={def} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleCheckboxChange(e, name, "def"); }} /> @@ -1141,7 +1143,7 @@ class AddCosDefinition extends React.Component { id="override" label={_("Override")} isChecked={override} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleCheckboxChange(e, name, "override"); }} /> @@ -1151,7 +1153,7 @@ class AddCosDefinition extends React.Component { id="operational" label={_("Operational")} isChecked={operational} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleCheckboxChange(e, name, "operational"); }} /> @@ -1162,7 +1164,7 @@ class AddCosDefinition extends React.Component { id="opdefault" label={_("Operational-Default")} isChecked={opdefault} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleCheckboxChange(e, name, "opdefault"); }} /> @@ -1172,7 +1174,7 @@ class AddCosDefinition extends React.Component { id="mergeschemes" label={_("Merge-Schemes")} isChecked={mergeschemes} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleCheckboxChange(e, name, "mergeschemes"); }} /> @@ -1210,7 +1212,7 @@ class AddCosDefinition extends React.Component { </TextContent> </GridItem> <GridItem span={5} className="ds-left-margin"> - <FormSelect id="cosspecAttr" value={this.state.cosspecAttr} onChange={(str, e) => { this.handleChange(e) }} aria-label="FormSelect Input"> + <FormSelect id="cosspecAttr" value={this.state.cosspecAttr} onChange={(e, str) => { this.handleChange(e) }} aria-label="FormSelect Input"> <FormSelectOption value="" label={_("Select an attribute")} isPlaceholder /> {this.state.attributeList.map((attr, index) => ( <FormSelectOption key={attr} value={attr} label={attr} /> @@ -1234,7 +1236,7 @@ class AddCosDefinition extends React.Component { isInline title={_("LDIF Content for CoS Creation")} /> - <Card isHoverable> + <Card > <CardBody> {(ldifListItems.length > 0) && <SimpleList aria-label="LDIF data User"> @@ -1265,7 +1267,7 @@ class AddCosDefinition extends React.Component { </div>} </Alert> {resultVariant === 'danger' && - <Card isHoverable> + <Card > <CardTitle> {_("LDIF Data")} </CardTitle> diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosTemplate.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosTemplate.jsx index 811130b9ee..539522d12a 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosTemplate.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addCosTemplate.jsx @@ -1,42 +1,49 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - BadgeToggle, - Bullseye, - Button, - Card, - CardBody, - CardTitle, - Dropdown, - DropdownItem, - DropdownPosition, - Form, - Grid, - GridItem, - NumberInput, - Modal, - ModalVariant, - Pagination, - SearchInput, - SimpleList, - SimpleListItem, - Spinner, - Text, - TextContent, - TextInput, - TextList, - TextListItem, - TextVariants, - Title, - Tooltip, - ValidatedOptions, - Wizard, + Alert, + Bullseye, + Button, + Card, + CardBody, + CardTitle, + Form, + Grid, + GridItem, + NumberInput, + Modal, + ModalVariant, + Pagination, + SearchInput, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextInput, + TextList, + TextListItem, + TextVariants, + Title, + Tooltip, + ValidatedOptions } from '@patternfly/react-core'; import { - Table, TableHeader, TableBody, TableVariant, - breakWord, - headerCol, + BadgeToggle, + Dropdown, + DropdownItem, + DropdownPosition, + Wizard +} from '@patternfly/react-core/deprecated'; +import { + breakWord, + headerCol, + Table, + Thead, + Tr, + Th, + Tbody, + Td, } from '@patternfly/react-table'; import { createLdapEntry, @@ -741,7 +748,7 @@ class AddCosTemplate extends React.Component { onSelect={this.handleOCDropDownSelect} position={DropdownPosition.left} toggle={ - <BadgeToggle id="toggle-oc-select" onToggle={this.handleOCDropDownToggle}> + <BadgeToggle id="toggle-oc-select" onToggle={(_event, isOpen) => this.handleOCDropDownToggle(isOpen)}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -776,7 +783,7 @@ class AddCosTemplate extends React.Component { onSelect={this.handleAttrDropDownSelect} position={DropdownPosition.left} toggle={ - <BadgeToggle id="toggle-attr-select" onToggle={this.handleAttrDropDownToggle}> + <BadgeToggle id="toggle-attr-select" onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -827,7 +834,8 @@ class AddCosTemplate extends React.Component { itemCountAttr, pageAttr, perPageAttr, columnsAttr, pagedRowsAttr, commandOutput, namingAttr, namingVal, cospriority, stepIdReached, ldifArray, resultVariant, editableTableData, validMods, cleanLdifArray, - selectedAttributes, selectedObjectClasses, goBackToCoSDefinition, createTemplateEnd + selectedAttributes, selectedObjectClasses, goBackToCoSDefinition, createTemplateEnd, + isOCDropDownOpen, isAttrDropDownOpen } = this.state; if (createTemplateEnd) { @@ -920,7 +928,7 @@ class AddCosTemplate extends React.Component { id="namingVal" aria-describedby="namingVal" name="namingVal" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} validated={this.state.namingVal === '' ? ValidatedOptions.error : ValidatedOptions.default} @@ -974,54 +982,97 @@ class AddCosTemplate extends React.Component { {_("Select ObjectClasses")} </Text> </TextContent> - {this.buildOCDropdown()} + <Dropdown + className="ds-dropdown-padding" + position="left" + onSelect={this.handleOCDropDownSelect} + toggle={ + <BadgeToggle + id="toggle-oc-select" + badgeProps={{ + className: selectedObjectClasses.length > 0 ? "ds-badge-bgcolor" : undefined, + isRead: selectedObjectClasses.length === 0 + }} + onToggle={(_event, isOpen) => this.handleOCDropDownToggle(isOpen)} + > + {selectedObjectClasses.length > 0 ? + `${selectedObjectClasses.length} ${_("selected")}` : + `0 ${_("selected")}`} + </BadgeToggle> + } + isOpen={isOCDropDownOpen} + dropdownItems={selectedObjectClasses.map((oc) => + <DropdownItem key={oc.cells[0]}>{oc.cells[0]}</DropdownItem> + )} + /> </div> - { loading && + { loading ? ( + <Bullseye className="ds-margin-top-xlg"> + <Title headingLevel="h3" size="lg"> + {_("Loading ObjectClasses ...")} + </Title> + <Spinner className="ds-center" size="lg" /> + </Bullseye> + ) : ( <div> - <Bullseye className="ds-margin-top-xlg" key="add-entry-bulleye"> - <Title headingLevel="h3" size="lg" key="loading-title"> - {_("Loading ObjectClasses ...")} - </Title> - </Bullseye> - <Spinner className="ds-center" size="lg" key="loading-spinner" /> - </div>} - <div className={loading ? "ds-hidden" : ""}> - <Grid className="ds-margin-top-lg"> - <GridItem span={5}> - <SearchInput - className="ds-font-size-md" - placeholder={_("Search Objectclasses")} - value={this.state.searchValue} - onChange={this.handleOCSearchChange} - onClear={(evt, val) => this.handleOCSearchChange(evt, '')} - /> - </GridItem> - <GridItem span={7}> - <Pagination - value="ObjectClassTable" - itemCount={itemCountOc} - page={pageOc} - perPage={perPageOc} - onSetPage={this.handleSetPageOc} - widgetId="pagination-step-objectclass" - onPerPageSelect={this.handlePerPageSelectOc} - variant="top" - isCompact - /> - </GridItem> - </Grid> - <Table - cells={columnsOc} - rows={pagedRowsOc} - canSelectAll={false} - onSelect={this.handleSelectOc} - variant={TableVariant.compact} - aria-label="Pagination All ObjectClasses" - > - <TableHeader /> - <TableBody /> - </Table> - </div> + <Grid className="ds-margin-top-lg"> + <GridItem span={5}> + <SearchInput + className="ds-font-size-md" + placeholder={_("Search Objectclasses")} + value={this.state.searchValue} + onChange={this.handleOCSearchChange} + onClear={(evt) => this.handleOCSearchChange(evt, '')} + /> + </GridItem> + <GridItem span={7}> + <Pagination + itemCount={itemCountOc} + page={pageOc} + perPage={perPageOc} + onSetPage={this.handleSetPageOc} + widgetId="pagination-step-objectclass" + onPerPageSelect={this.handlePerPageSelectOc} + isCompact + /> + </GridItem> + </Grid> + <Table aria-label="Objectclasses Table" variant="compact"> + <Thead> + <Tr> + <Th screenReaderText="Selection column" /> + {columnsOc.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsOc.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelectOc, + isSelected: row.selected, + isDisabled: row.disableSelection + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsOc[cellIndex]?.title || columnsOc[cellIndex]} + > + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ))} + </Tbody> + </Table> + </div> + )} </> ); @@ -1033,7 +1084,29 @@ class AddCosTemplate extends React.Component { {_("Select Attributes")} </Text> </TextContent> - {this.buildAttrDropdown()} + <Dropdown + className="ds-dropdown-padding" + position="left" + onSelect={this.handleAttrDropDownSelect} + toggle={ + <BadgeToggle + id="toggle-attr-select" + badgeProps={{ + className: selectedAttributes.length > 0 ? "ds-badge-bgcolor" : undefined, + isRead: selectedAttributes.length === 0 + }} + onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)} + > + {selectedAttributes.length > 0 ? + `${selectedAttributes.length} ${_("selected")}` : + `0 ${_("selected")}`} + </BadgeToggle> + } + isOpen={isAttrDropDownOpen} + dropdownItems={selectedAttributes.map((attr) => + <DropdownItem key={attr[0]}>{attr[0]}</DropdownItem> + )} + /> </div> <Grid className="ds-margin-top-lg"> <GridItem span={5}> @@ -1042,7 +1115,7 @@ class AddCosTemplate extends React.Component { placeholder={_("Search Attributes")} value={this.state.searchAttrValue} onChange={this.handleAttrSearchChange} - onClear={(evt, val) => this.handleAttrSearchChange(evt, '')} + onClear={(evt) => this.handleAttrSearchChange(evt, '')} /> </GridItem> <GridItem span={7}> @@ -1053,25 +1126,44 @@ class AddCosTemplate extends React.Component { onSetPage={this.handleSetPageAttr} widgetId="pagination-step-attributes" onPerPageSelect={this.handlePerPageSelectAttr} - dropDirection="up" isCompact /> </GridItem> </Grid> - - <Table - className="ds-margin-top" - cells={columnsAttr} - rows={pagedRowsAttr} - onSelect={this.handleSelectAttr} - variant={TableVariant.compact} - aria-label="Pagination Attributes" - canSelectAll={false} - > - <TableHeader /> - <TableBody /> + <Table aria-label="Attributes Table" variant="compact"> + <Thead> + <Tr> + <Th screenReaderText="Selection column" /> + {columnsAttr.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsAttr.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelectAttr, + isSelected: row.selected, + isDisabled: row.disableCheckbox + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsAttr[cellIndex]?.title || columnsAttr[cellIndex]} + > + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> - </> ); diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addGroup.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addGroup.jsx index ba7dd1ce4d..d53581bd19 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addGroup.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addGroup.jsx @@ -1,31 +1,35 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - Button, - Card, - CardBody, - CardTitle, - DualListSelector, - Form, - Grid, - GridItem, - Modal, - ModalVariant, - NumberInput, - Radio, - Select, SelectOption, SelectVariant, - SearchInput, - SimpleList, - SimpleListItem, - Spinner, - Text, - TextContent, - TextInput, - TextVariants, - ValidatedOptions, - Wizard, + Alert, + Button, + Card, + CardBody, + CardTitle, + DualListSelector, + Form, + Grid, + GridItem, + Modal, + ModalVariant, + NumberInput, + Radio, + SearchInput, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextInput, + TextVariants, + ValidatedOptions } from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant, + Wizard +} from '@patternfly/react-core/deprecated'; import LdapNavigator from '../../lib/ldapNavigator.jsx'; import { createLdapEntry, @@ -203,7 +207,7 @@ class AddGroup extends React.Component { return noDuplicates; }; - this.handleUsersOnListChange = (newAvailableOptions, newChosenOptions) => { + this.handleUsersOnListChange = (_event, newAvailableOptions, newChosenOptions) => { const newAvailNoDups = this.removeDuplicates(newAvailableOptions); const newChosenNoDups = this.removeDuplicates(newChosenOptions); @@ -213,14 +217,14 @@ class AddGroup extends React.Component { }); }; - this.handleRadioChange = (_, event) => { + this.handleRadioChange = (event, _) => { this.setState({ memberAttr: event.currentTarget.id, }); }; // Group Type handling - this.handleToggleType = isOpenType => { + this.handleToggleType = (_event, isOpenType) => { this.setState({ isOpenType }); @@ -334,7 +338,7 @@ class AddGroup extends React.Component { variant={SelectVariant.single} className="ds-margin-top-lg" aria-label="Select group type" - onToggle={this.handleToggleType} + onToggle={(event, isOpen) => this.handleToggleType(event, isOpen)} onSelect={this.handleSelectType} selections={this.state.groupType} isOpen={this.state.isOpenType} @@ -373,7 +377,7 @@ class AddGroup extends React.Component { id="groupName" aria-describedby="groupName" name="groupName" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} validated={this.state.groupName === '' ? ValidatedOptions.error : ValidatedOptions.default} @@ -391,7 +395,7 @@ class AddGroup extends React.Component { id="groupDesc" aria-describedby="groupDesc" name="groupDesc" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -412,7 +416,7 @@ class AddGroup extends React.Component { value="member" label={_("member")} isChecked={this.state.memberAttr === 'member'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("This group uses objectclass 'GroupOfNames'.")} /> <Radio @@ -421,7 +425,7 @@ class AddGroup extends React.Component { value="uniquemember" label={_("uniquemember")} isChecked={this.state.memberAttr === 'uniquemember'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("This group uses objectclass 'GroupOfUniqueNames'.")} className="ds-margin-top" /> @@ -498,7 +502,7 @@ class AddGroup extends React.Component { chosenOptions={usersChosenOptions} availableOptionsTitle={_("Available Members")} chosenOptionsTitle={_("Chosen Members")} - onListChange={this.handleUsersOnListChange} + onListChange={(event, newAvailableOptions, newChosenOptions) => this.handleUsersOnListChange(event, newAvailableOptions, newChosenOptions)} id="usersSelector" /> </GridItem> diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addLdapEntry.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addLdapEntry.jsx index fdfd3720c3..43138414c1 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addLdapEntry.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addLdapEntry.jsx @@ -1,24 +1,39 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - BadgeToggle, - Bullseye, - Card, CardBody, CardTitle, - Dropdown, DropdownItem, DropdownPosition, - Grid, GridItem, - Pagination, - SearchInput, - SimpleList, SimpleListItem, - Spinner, - Text, TextContent, TextVariants, - Title, - Wizard, + Alert, + Bullseye, + Card, + CardBody, + CardTitle, + Grid, + GridItem, + Pagination, + SearchInput, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextVariants, + Title } from '@patternfly/react-core'; import { - Table, TableHeader, TableBody, TableVariant, - breakWord, - headerCol, + BadgeToggle, + Dropdown, + DropdownItem, + DropdownPosition, + Wizard +} from '@patternfly/react-core/deprecated'; +import { + breakWord, + headerCol, + Table, + Thead, + Tr, + Th, + Tbody, + Td, } from '@patternfly/react-table'; import { createLdapEntry, @@ -625,7 +640,7 @@ class AddLdapEntry extends React.Component { onSelect={this.handleOCDropDownSelect} position={DropdownPosition.left} toggle={ - <BadgeToggle id="toggle-oc-select" onToggle={this.handleOCDropDownToggle}> + <BadgeToggle id="toggle-oc-select" onToggle={(_event, isOpen) => this.handleOCDropDownToggle(isOpen)}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -660,7 +675,7 @@ class AddLdapEntry extends React.Component { onSelect={this.handleAttrDropDownSelect} position={DropdownPosition.left} toggle={ - <BadgeToggle id="toggle-attr-select" onToggle={this.handleAttrDropDownToggle}> + <BadgeToggle id="toggle-attr-select" onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -712,52 +727,75 @@ class AddLdapEntry extends React.Component { </TextContent> {this.buildOCDropdown()} </div> - { loading && + { loading ? ( <div> - <Bullseye className="ds-margin-top-xlg" key="add-entry-bulleye"> - <Title headingLevel="h3" size="lg" key="loading-title"> + <Bullseye className="ds-margin-top-xlg"> + <Title headingLevel="h3" size="lg"> {_("Loading ObjectClasses ...")} </Title> + <Spinner className="ds-center" size="lg" /> </Bullseye> - <Spinner className="ds-center" size="lg" key="loading-spinner" /> - </div>} - <div className={loading ? "ds-hidden" : ""}> - <Grid className="ds-margin-top-lg"> - <GridItem span={5}> - <SearchInput - className="ds-font-size-md" - placeholder={_("Search Objectclasses")} - value={this.state.searchOCValue} - onChange={this.handleOCSearchChange} - onClear={(evt, val) => this.handleOCSearchChange(evt, '')} - /> - </GridItem> - <GridItem span={7}> - <Pagination - value="ObjectClassTable" - itemCount={itemCountOc} - page={pageOc} - perPage={perPageOc} - onSetPage={this.handleSetPageOc} - widgetId="pagination-step-objectclass" - onPerPageSelect={this.handlePerPageSelectOc} - variant="top" - isCompact - /> - </GridItem> - </Grid> - <Table - cells={columnsOc} - rows={pagedRowsOc} - canSelectAll={false} - onSelect={this.handleSelectOc} - variant={TableVariant.compact} - aria-label="Pagination All ObjectClasses" - > - <TableHeader /> - <TableBody /> - </Table> - </div> + </div> + ) : ( + <div> + <Grid className="ds-margin-top-lg"> + <GridItem span={5}> + <SearchInput + className="ds-font-size-md" + placeholder={_("Search Objectclasses")} + value={this.state.searchOCValue} + onChange={this.handleOCSearchChange} + onClear={(evt) => this.handleOCSearchChange(evt, '')} + /> + </GridItem> + <GridItem span={7}> + <Pagination + itemCount={itemCountOc} + page={pageOc} + perPage={perPageOc} + onSetPage={this.handleSetPageOc} + widgetId="pagination-step-objectclass" + onPerPageSelect={this.handlePerPageSelectOc} + variant="top" + isCompact + /> + </GridItem> + </Grid> + <Table aria-label="Pagination All ObjectClasses" variant="compact"> + <Thead> + <Tr> + <Th screenReaderText="Selection column" /> + {columnsOc.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsOc.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelectOc, + isSelected: row.selected + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsOc[cellIndex]?.title || columnsOc[cellIndex]} + > + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ))} + </Tbody> + </Table> + </div> + )} </> ); @@ -778,7 +816,7 @@ class AddLdapEntry extends React.Component { placeholder={_("Search Attributes")} value={this.state.searchAttrValue} onChange={this.handleAttrSearchChange} - onClear={(evt, val) => this.handleAttrSearchChange(evt, '')} + onClear={(evt) => this.handleAttrSearchChange(evt, '')} /> </GridItem> <GridItem span={7}> @@ -793,17 +831,38 @@ class AddLdapEntry extends React.Component { /> </GridItem> </Grid> - <Table - className="ds-margin-top" - cells={columnsAttr} - rows={pagedRowsAttr} - onSelect={this.handleSelectAttr} - variant={TableVariant.compact} - aria-label="Pagination Attributes" - canSelectAll={false} - > - <TableHeader /> - <TableBody /> + <Table aria-label="Pagination Attributes" variant="compact"> + <Thead> + <Tr> + <Th screenReaderText="Selection column" /> + {columnsAttr.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsAttr.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelectAttr, + isSelected: row.selected + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsAttr[cellIndex]?.title || columnsAttr[cellIndex]} + > + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> </> ); diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addRole.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addRole.jsx index 94f306ee61..abfc3938bc 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addRole.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addRole.jsx @@ -1,40 +1,44 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - BadgeToggle, - Button, - Card, - CardBody, - CardTitle, - Dropdown, - DropdownItem, - DropdownPosition, - DualListSelector, - Form, - Grid, - GridItem, - Modal, - ModalVariant, - Pagination, - Radio, - SearchInput, - SimpleList, - SimpleListItem, - Spinner, - Text, - TextContent, - TextInput, - TextVariants, - ValidatedOptions, - Wizard, + Alert, + Button, + Card, + CardBody, + CardTitle, + DualListSelector, + Form, + Grid, + GridItem, + Modal, + ModalVariant, + Pagination, + Radio, + SearchInput, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextInput, + TextVariants, + ValidatedOptions } from '@patternfly/react-core'; +import { + BadgeToggle, + Dropdown, + DropdownItem, + DropdownPosition, + Wizard +} from '@patternfly/react-core/deprecated'; import { Table, - TableHeader, - TableBody, - TableVariant, - headerCol, + Thead, + Tr, + Th, + Tbody, + Td, + headerCol } from '@patternfly/react-table'; import EditableTable from '../../lib/editableTable.jsx'; import LdapNavigator from '../../lib/ldapNavigator.jsx'; @@ -262,7 +266,7 @@ class AddRole extends React.Component { return noDuplicates; }; - this.handleUsersListChange = (newAvailableOptions, newChosenOptions) => { + this.handleUsersListChange = (_event, newAvailableOptions, newChosenOptions) => { const newAvailNoDups = this.removeDuplicates(newAvailableOptions); const newChosenNoDups = this.removeDuplicates(newChosenOptions); @@ -272,7 +276,7 @@ class AddRole extends React.Component { }); }; - this.handleRadioChange = (_, event) => { + this.handleRadioChange = (event, _) => { this.setState({ roleType: event.currentTarget.id, }); @@ -596,7 +600,7 @@ class AddRole extends React.Component { onSelect={this.handleAttrDropDownSelect} position={DropdownPosition.left} toggle={ - <BadgeToggle id="toggle-attr-select" onToggle={this.handleAttrDropDownToggle}> + <BadgeToggle id="toggle-attr-select" onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -673,7 +677,8 @@ class AddRole extends React.Component { pagedRowsRole, ldifArray, noEmptyValue, namingAttrVal, namingAttr, resultVariant, editableTableData, stepIdReached, namingVal, rolesSearchBaseDn, rolesAvailableOptions, rolesChosenOptions, - showLDAPNavModal, commandOutput, roleType + showLDAPNavModal, commandOutput, roleType, isAttrDropDownOpen, + selectedAttributes } = this.state; const rdnValue = namingVal; @@ -700,7 +705,7 @@ class AddRole extends React.Component { id="namingVal" aria-describedby="namingVal" name="namingVal" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} validated={this.state.namingVal === '' ? ValidatedOptions.error : ValidatedOptions.default} @@ -720,7 +725,7 @@ class AddRole extends React.Component { value="managed" label={_("Managed")} isChecked={this.state.roleType === 'managed'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("This attribute uses objectclass 'RoleOfNames'")} /> <Radio @@ -729,7 +734,7 @@ class AddRole extends React.Component { value="filtered" label={_("Filtered")} isChecked={this.state.roleType === 'filtered'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("This attribute uses objectclass 'RoleOfUniqueNames'")} className="ds-margin-top" /> @@ -739,7 +744,7 @@ class AddRole extends React.Component { value="nested" label={_("Nested")} isChecked={this.state.roleType === 'nested'} - onChange={this.handleRadioChange} + onChange={(event, str) => this.handleRadioChange(event, str)} description={_("This attribute uses objectclass 'RoleOfUniqueNames'")} className="ds-margin-top" /> @@ -789,7 +794,7 @@ class AddRole extends React.Component { chosenOptions={rolesChosenOptions} availableOptionsTitle={_("Available Roles")} chosenOptionsTitle={_("Chosen Roles")} - onListChange={this.handleUsersListChange} + onListChange={(event, newAvailableOptions, newChosenOptions) => this.handleUsersListChange(event, newAvailableOptions, newChosenOptions)} id="usersSelector" /> </GridItem> @@ -834,7 +839,29 @@ class AddRole extends React.Component { {_("Select Entry Attributes")} </Text> </TextContent> - {this.buildAttrDropdown()} + <Dropdown + className="ds-dropdown-padding" + position="left" + onSelect={this.handleAttrDropDownSelect} + toggle={ + <BadgeToggle + id="toggle-attr-select" + badgeProps={{ + className: selectedAttributes.length > 0 ? "ds-badge-bgcolor" : undefined, + isRead: selectedAttributes.length === 0 + }} + onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)} + > + {selectedAttributes.length > 0 ? + `${selectedAttributes.length} ${_("selected")}` : + `0 ${_("selected")}`} + </BadgeToggle> + } + isOpen={isAttrDropDownOpen} + dropdownItems={selectedAttributes.map((attr) => + <DropdownItem key={attr}>{attr}</DropdownItem> + )} + /> </div> <Pagination itemCount={itemCountAddRole} @@ -845,15 +872,42 @@ class AddRole extends React.Component { onPerPageSelect={this.handlePerPageSelectAddRole} isCompact /> - <Table - cells={columnsRole} - rows={pagedRowsRole} - onSelect={this.handleSelect} - variant={TableVariant.compact} - aria-label="Pagination Role Attributes" - > - <TableHeader /> - <TableBody /> + <Table aria-label="Role Attributes Table" variant="compact"> + <Thead> + <Tr> + <Th select={{ + onSelect: (_event, isSelected) => this.handleSelect(_event, isSelected, -1), + isSelected: this.state.allAttributesSelected + }} /> + {columnsRole.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsRole.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelect, + isSelected: row.selected, + isDisabled: row.disableCheckbox + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsRole[cellIndex]?.title || columnsRole[cellIndex]} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> </> ); diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addUser.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addUser.jsx index a0b757690f..7988076e71 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addUser.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/addUser.jsx @@ -1,27 +1,44 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - BadgeToggle, - Card, CardBody, CardTitle, - Dropdown, DropdownItem, DropdownPosition, - Form, - Grid, GridItem, - Label, - Pagination, - SearchInput, - Select, SelectOption, SelectVariant, - SimpleList, SimpleListItem, - Spinner, - Text, TextContent, TextVariants, - Wizard, + Alert, + Card, + CardBody, + CardTitle, + Form, + Grid, + GridItem, + Label, + Pagination, + SearchInput, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextVariants } from '@patternfly/react-core'; +import { + BadgeToggle, + Dropdown, + DropdownItem, + DropdownPosition, + Select, + SelectOption, + SelectVariant, + Wizard +} from '@patternfly/react-core/deprecated'; import { InfoCircleIcon, } from '@patternfly/react-icons'; import { - Table, TableHeader, TableBody, TableVariant, - headerCol, + Table, + Thead, + Tr, + Th, + Tbody, + Td, + headerCol } from '@patternfly/react-table'; import EditableTable from '../../lib/editableTable.jsx'; import { @@ -212,7 +229,7 @@ class AddUser extends React.Component { } }; - this.handleToggleType = isOpenType => { + this.handleToggleType = (_event, isOpenType) => { this.setState({ isOpenType }); @@ -493,7 +510,7 @@ class AddUser extends React.Component { onSelect={this.handleAttrDropDownSelect} position={DropdownPosition.left} toggle={ - <BadgeToggle id="toggle-attr-select" onToggle={this.handleAttrDropDownToggle}> + <BadgeToggle id="toggle-attr-select" onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -583,12 +600,19 @@ class AddUser extends React.Component { </div> <div className="ds-indent"> <Select - variant={SelectVariant.single} - className="ds-margin-top-lg" + id="user-type-select" aria-label="Select user type" - onToggle={this.handleToggleType} + toggle={(toggleRef) => ( + <SelectToggle + ref={toggleRef} + onToggle={(event, isOpen) => this.handleToggleType(event, isOpen)} + isExpanded={this.state.isOpenType} + > + {this.state.accountType} + </SelectToggle> + )} onSelect={this.handleSelectType} - selections={this.state.accountType} + selected={this.state.accountType} isOpen={this.state.isOpenType} > <SelectOption key="user" value="Basic Account" /> @@ -619,7 +643,27 @@ class AddUser extends React.Component { {_("Select Entry Attributes")} </Text> </TextContent> - {this.buildAttrDropdown()} + <Dropdown + className="ds-dropdown-padding" + position="left" + onSelect={this.handleAttrDropDownSelect} + toggle={ + <BadgeToggle + id="toggle-attr-select" + badgeProps={{ + className: this.state.selectedAttributes.length > 0 ? "ds-badge-bgcolor" : undefined, + isRead: this.state.selectedAttributes.length === 0 + }} + onToggle={(_event, isOpen) => this.handleAttrDropDownToggle(isOpen)} + > + {`${this.state.selectedAttributes.length} ${_("selected")}`} + </BadgeToggle> + } + isOpen={this.state.isAttrDropDownOpen} + dropdownItems={this.state.selectedAttributes.map((attr) => + <DropdownItem key={attr}>{attr}</DropdownItem> + )} + /> </div> <Grid className="ds-margin-top-lg"> <GridItem span={5}> @@ -627,15 +671,15 @@ class AddUser extends React.Component { className="ds-font-size-md" placeholder={_("Search Attributes")} value={this.state.searchValue} - onChange={(evt, val) => this.handleAttrSearchChange(val)} + onChange={(_event, value) => this.handleAttrSearchChange(value)} onClear={() => this.handleAttrSearchChange('')} /> </GridItem> <GridItem span={7}> <Pagination - itemCount={itemCountAddUser} - page={pageAddUser} - perPage={perPageAddUser} + itemCount={this.state.itemCountAddUser} + page={this.state.pageAddUser} + perPage={this.state.perPageAddUser} onSetPage={this.handleSetPageAddUser} widgetId="pagination-options-menu-add-user" onPerPageSelect={this.handlePerPageSelectAddUser} @@ -643,16 +687,39 @@ class AddUser extends React.Component { /> </GridItem> </Grid> - <Table - cells={columnsUser} - rows={pagedRowsUser} - onSelect={this.handleSelect} - variant={TableVariant.compact} - aria-label="Pagination User Attributes" - canSelectAll={false} - > - <TableHeader /> - <TableBody /> + <Table aria-label="User Attributes Table" variant="compact"> + <Thead> + <Tr> + <Th screenReaderText="Select Attributes" /> + {this.state.columnsUser.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {this.state.pagedRowsUser.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelect, + isSelected: row.selected, + isDisabled: row.disableCheckbox + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={this.state.columnsUser[cellIndex]?.title || this.state.columnsUser[cellIndex]} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> </> ); diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editGroup.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editGroup.jsx index 5412ea26c3..191a6c1446 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editGroup.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editGroup.jsx @@ -170,7 +170,7 @@ class EditGroup extends React.Component { }); }; - this.handleUsersOnListChange = (newAvailableOptions, newChosenOptions) => { + this.handleUsersOnListChange = (_event, newAvailableOptions, newChosenOptions) => { const newNewAvailOptions = [...newAvailableOptions]; const newNewChosenOptions = []; @@ -512,7 +512,7 @@ class EditGroup extends React.Component { chosenOptions={usersChosenOptions} availableOptionsTitle={_("Available Members")} chosenOptionsTitle={_("Chosen Members")} - onListChange={this.handleUsersOnListChange} + onListChange={(event, newAvailableOptions, newChosenOptions) => this.handleUsersOnListChange(event, newAvailableOptions, newChosenOptions)} id="usersSelector" /> <Button diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editLdapEntry.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editLdapEntry.jsx index 9a4e677261..bd494b52b3 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editLdapEntry.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/editLdapEntry.jsx @@ -1,28 +1,44 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - BadgeToggle, - Bullseye, - Card, CardBody, CardTitle, - Dropdown, DropdownItem, DropdownPosition, - Grid, GridItem, - Label, LabelGroup, - Pagination, - SearchInput, - SimpleList, SimpleListItem, - Spinner, - Text, TextContent, TextVariants, - Title, - Wizard, + Alert, + Bullseye, + Card, + CardBody, + CardTitle, + Grid, + GridItem, + Label, + LabelGroup, + Pagination, + SearchInput, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextVariants, + Title } from '@patternfly/react-core'; +import { + BadgeToggle, + Dropdown, + DropdownItem, + DropdownPosition, + Wizard +} from '@patternfly/react-core/deprecated'; import { InfoCircleIcon, } from '@patternfly/react-icons'; import { - Table, TableHeader, TableBody, TableVariant, - breakWord, - headerCol, + breakWord, + headerCol, + Table, + Thead, + Tr, + Th, + Tbody, + Td, } from '@patternfly/react-table'; import { b64DecodeUnicode, @@ -904,16 +920,16 @@ class EditLdapEntry extends React.Component { } statementRows.push({ cells: [ - { title: (<Label color="orange">_("Replace")</Label>) }, + { title: (<Label color="orange">{_("Replace")}</Label>) }, myAttr, { title: ( <LabelGroup isVertical> <Label variant="outline" color="red"> - <em>_("old:")</em> {myVal} + <em>{_("old:")}</em> {myVal} </Label> - <Label variant="outline" color="blue" isTruncated> - <em>_("new:")</em> {datum.new} + <Label variant="outline" color="blue" > + <em>{_("new:")}</em> {datum.new} </Label> </LabelGroup> ) @@ -979,7 +995,7 @@ class EditLdapEntry extends React.Component { myAttr, { title: ( - <Label variant="outline" color="blue" isTruncated> + <Label variant="outline" color="blue" > {myVal} </Label> ) @@ -1042,7 +1058,7 @@ class EditLdapEntry extends React.Component { myAttr, { title: ( - <Label variant="outline" color="blue" isTruncated> + <Label variant="outline" color="blue" > {myVal} </Label> ) @@ -1066,11 +1082,11 @@ class EditLdapEntry extends React.Component { cleanLdifArray.push('objectClass: ' + oldOC); statementRows.push({ cells: [ - { title: (<Label color="red">_("Delete")</Label>) }, + { title: (<Label color="red">{_("Delete")}</Label>) }, 'objectClass', { title: ( - <Label variant="outline" color="blue" isTruncated> + <Label variant="outline" color="blue" > {oldOC} </Label> ) @@ -1096,7 +1112,7 @@ class EditLdapEntry extends React.Component { 'objectClass', { title: ( - <Label variant="outline" color="blue" isTruncated> + <Label variant="outline" color="blue" > {newOC} </Label> ) @@ -1122,9 +1138,9 @@ class EditLdapEntry extends React.Component { }; handleOCDropDownSelect = event => { - this.setState((prevState, props) => { - return { isOCDropDownOpen: !prevState.isOCDropDownOpen }; - }); + this.setState((prevState) => ({ + isOCDropDownOpen: !prevState.isOCDropDownOpen + })); }; buildOCDropdown = () => { @@ -1140,7 +1156,7 @@ class EditLdapEntry extends React.Component { <Dropdown className="ds-dropdown-padding" onSelect={this.handleOCDropDownSelect} - position={DropdownPosition.left} + position="left" toggle={ <BadgeToggle id="toggle-oc-select" onToggle={this.handleOCDropDownToggle}> {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} @@ -1159,14 +1175,14 @@ class EditLdapEntry extends React.Component { }; handleAttrDropDownSelect = event => { - this.setState((prevState, props) => { - return { isAttrDropDownOpen: !prevState.isAttrDropDownOpen }; - }); + this.setState((prevState) => ({ + isAttrDropDownOpen: !prevState.isAttrDropDownOpen + })); }; buildAttrDropdown = () => { const { isAttrDropDownOpen, selectedAttributes } = this.state; - const numSelected = this.state.selectedAttributes.length; + const numSelected = selectedAttributes.length; const attrs = selectedAttributes.map((attr) => attr[0]); attrs.sort(); const items = attrs.map((attr) => @@ -1177,9 +1193,12 @@ class EditLdapEntry extends React.Component { <Dropdown className="ds-dropdown-padding" onSelect={this.handleAttrDropDownSelect} - position={DropdownPosition.left} + position="left" toggle={ - <BadgeToggle id="toggle-attr-select" onToggle={this.handleAttrDropDownToggle}> + <BadgeToggle + id="toggle-attr-select" + onToggle={this.handleAttrDropDownToggle} + > {numSelected !== 0 ? <>{numSelected} {_("selected")} </> : <>0 {_("selected")} </>} </BadgeToggle> } @@ -1207,52 +1226,73 @@ class EditLdapEntry extends React.Component { </TextContent> {this.buildOCDropdown()} </div> - { loading && + {loading ? ( + <Bullseye className="ds-margin-top-xlg"> + <Title headingLevel="h3" size="lg"> + {_("Loading ...")} + </Title> + <Spinner className="ds-center" size="lg" /> + </Bullseye> + ) : ( <div> - <Bullseye className="ds-margin-top-xlg" key="add-entry-bulleye"> - <Title headingLevel="h3" size="lg" key="loading-title"> - {_("Loading ...")} - </Title> - </Bullseye> - <Spinner className="ds-center" size="lg" key="loading-spinner" /> - </div>} - <div className={loading ? "ds-hidden" : ""}> - <Grid className="ds-margin-top-lg"> - <GridItem span={5}> - <SearchInput - className="ds-font-size-md" - placeholder={_("Search Objectclasses")} - value={this.state.searchOCValue} - onChange={this.handleOCSearchChange} - onClear={(evt, val) => this.handleOCSearchChange(evt, '')} - /> - </GridItem> - <GridItem span={7}> - <Pagination - value="ObjectClassTable" - itemCount={this.state.itemCountOc} - page={this.state.pageOc} - perPage={this.state.perPageOc} - onSetPage={this.handleSetPageOc} - widgetId="pagination-step-objectclass" - onPerPageSelect={this.handlePerPageSelectOc} - variant="top" - isCompact - /> - </GridItem> - </Grid> - <Table - cells={columnsOc} - rows={pagedRowsOc} - canSelectAll={false} - onSelect={this.handleSelectOc} - variant={TableVariant.compact} - aria-label="Pagination All ObjectClasses" - > - <TableHeader /> - <TableBody /> - </Table> - </div> + <Grid className="ds-margin-top-lg"> + <GridItem span={5}> + <SearchInput + className="ds-font-size-md" + placeholder={_("Search Objectclasses")} + value={this.state.searchOCValue} + onChange={this.handleOCSearchChange} + onClear={(evt) => this.handleOCSearchChange(evt, '')} + /> + </GridItem> + <GridItem span={7}> + <Pagination + itemCount={this.state.itemCountOc} + page={this.state.pageOc} + perPage={this.state.perPageOc} + onSetPage={this.handleSetPageOc} + widgetId="pagination-step-objectclass" + onPerPageSelect={this.handlePerPageSelectOc} + variant="top" + isCompact + /> + </GridItem> + </Grid> + <Table aria-label="Pagination All ObjectClasses" variant="compact"> + <Thead> + <Tr> + <Th screenReaderText="Selection column" /> + {columnsOc.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsOc.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelectOc, + isSelected: row.selected + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsOc[cellIndex]?.title || columnsOc[cellIndex]} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> + </Table> + </div> + )} </> ); @@ -1273,7 +1313,7 @@ class EditLdapEntry extends React.Component { placeholder={_("Search Attributes")} value={this.state.searchValue} onChange={this.handleAttrSearchChange} - onClear={(evt, val) => this.handleAttrSearchChange(evt, '')} + onClear={(evt) => this.handleAttrSearchChange(evt, '')} /> </GridItem> <GridItem span={7}> @@ -1289,19 +1329,43 @@ class EditLdapEntry extends React.Component { /> </GridItem> </Grid> - <Table + <Table + aria-label="Pagination Attributes" + variant="compact" className="ds-margin-top" - cells={columnsAttr} - rows={pagedRowsAttr} - onSelect={this.handleSelectAttr} - variant={TableVariant.compact} - aria-label="Pagination Attributes" - canSelectAll={false} > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Selection column" /> + {columnsAttr.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsAttr.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelectAttr, + isSelected: row.selected + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsAttr[cellIndex]?.title || columnsAttr[cellIndex]} + > + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> - </> ); @@ -1427,11 +1491,40 @@ class EditLdapEntry extends React.Component { <Table aria-label="Statement Table" variant="compact" - cells={this.operationColumns} - rows={statementRows} + borders={true} > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {this.operationColumns.map((column, columnIndex) => ( + <React.Fragment key={columnIndex}> + {typeof column === 'object' ? ( + <Th>{column.title}</Th> + ) : ( + <Th>{column}</Th> + )} + </React.Fragment> + ))} + </Tr> + </Thead> + <Tbody> + {statementRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.cells.map((cell, cellIndex) => ( + <React.Fragment key={cellIndex}> + <Td> + {cell.title ? + // For cells with React element content + cell.title.props.children + : + // For simple string cells + cell + } + </Td> + </React.Fragment> + ))} + </Tr> + ))} + </Tbody> </Table> ), canJumpTo: stepIdReached >= 4 && stepIdReached < 6, diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/genericUpdate.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/genericUpdate.jsx index 76126fbdf3..a719722ef6 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/genericUpdate.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/genericUpdate.jsx @@ -1,21 +1,29 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - Card, - CardBody, - CardTitle, - Pagination, - SimpleList, - SimpleListItem, - Spinner, - Text, - TextContent, - TextVariants, - Wizard, + Alert, + Card, + CardBody, + CardTitle, + Pagination, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextVariants } from '@patternfly/react-core'; import { - Table, TableHeader, TableBody, TableVariant, headerCol + Wizard +} from '@patternfly/react-core/deprecated'; +import { + Table, + Thead, + Tr, + Th, + Tbody, + Td, + headerCol } from '@patternfly/react-table'; import EditableTable from '../../lib/editableTable.jsx'; import { @@ -519,15 +527,43 @@ class GenericUpdate extends React.Component { onPerPageSelect={this.handlePerPageSelect} isCompact /> - <Table - cells={columnsAttrs} - rows={pagedRowsAttrs} - onSelect={this.handleSelect} - variant={TableVariant.compact} - aria-label="Pagination Attributes" + {/* Updated Table structure for Patternfly 5 */} + <Table + aria-label="Attributes Table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Select Attributes" /> + {columnsAttrs.map((column, columnIndex) => ( + <Th key={columnIndex}> + {typeof column === 'object' ? column.title : column} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {pagedRowsAttrs.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: this.handleSelect, + isSelected: row.selected, + isDisabled: row.disableSelection + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td + key={`${rowIndex}_${cellIndex}`} + dataLabel={columnsAttrs[cellIndex]?.title || columnsAttrs[cellIndex]} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> </div> ); diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/groupTable.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/groupTable.jsx index 2356acf972..66a80484b6 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/groupTable.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/groupTable.jsx @@ -9,11 +9,11 @@ import { } from '@patternfly/react-core'; import { Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + Thead, + Tbody, + Tr, + Th, + Td } from '@patternfly/react-table'; const _ = cockpit.gettext; @@ -29,20 +29,24 @@ class GroupTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Member DN"), transforms: [sortable] }, + { title: _("Member DN"), sort: 'asc' }, ], + pagedRows: [], }; this.selectedCount = 0; this.handleSetPage = (_event, pageNumber) => { this.setState({ - page: pageNumber + page: pageNumber, + pagedRows: this.getRowsToShow(pageNumber, this.state.perPage) }); }; this.handlePerPageSelect = (_event, perPage) => { this.setState({ - perPage + perPage, + page: 1, + pagedRows: this.getRowsToShow(1, perPage) }); }; @@ -69,16 +73,22 @@ class GroupTable extends React.Component { } this.setState({ rows, - columns + columns, + pagedRows: this.getRowsToShow(1, this.state.perPage) }); } + getRowsToShow = (page, perPage) => { + const start = (page - 1) * perPage; + const end = page * perPage; + return this.state.rows.slice(start, end); + }; + handleSearchChange(event, value) { let rows = []; const val = value.toLowerCase(); for (const row of this.props.rows) { if (val !== "" && val !== "*" && row.indexOf(val) === -1) { - // Not a match, skip it continue; } rows.push({ @@ -93,16 +103,16 @@ class GroupTable extends React.Component { rows, value, page: 1, + pagedRows: this.getRowsToShow(1, this.state.perPage) }); } - handleSort(_event, index, direction) { + handleSort(_event, columnIndex, sortDirection) { const rows = []; const sortedAttrs = [...this.props.rows]; - // Sort the referrals and build the new rows sortedAttrs.sort(); - if (direction !== SortByDirection.asc) { + if (sortDirection !== 'asc') { sortedAttrs.reverse(); } for (const attrRow of sortedAttrs) { @@ -111,41 +121,48 @@ class GroupTable extends React.Component { this.setState({ sortBy: { - index, - direction + index: columnIndex, + direction: sortDirection }, rows, page: 1, + pagedRows: this.getRowsToShow(1, this.state.perPage) }); } - actions() { + getActionsForRow = (rowData) => { + // Return empty array if it's the "No Members" row + if (rowData.cells.length === 1 && rowData.cells[0] === _("No Members")) { + return []; + } + + // Only return actions for valid rows + if (!rowData.cells) { + return []; + } + return [ { title: _("View Entry"), - onClick: (event, rowId, rowData, extra) => - this.props.viewEntry(rowData.cells[0]) + onClick: () => this.props.viewEntry(rowData.cells[0]) }, { title: _("Edit Entry"), - onClick: (event, rowId, rowData, extra) => - this.props.editEntry(rowData.cells[0]) + onClick: () => this.props.editEntry(rowData.cells[0]) }, { isSeparator: true }, { title: _("Remove Entry"), - onClick: (event, rowId, rowData, extra) => - this.props.removeMember(rowData.cells[0]) + onClick: () => this.props.removeMember(rowData.cells[0]) } ]; - } + }; render() { - const { columns, rows, perPage, page, sortBy } = this.state; + const { columns, pagedRows, perPage, page, sortBy } = this.state; const extraPrimaryProps = {}; - const canSelectAll = false; let deleteBtnName = _("Remove Selected Members"); if (this.props.saving) { deleteBtnName = _("Updating group ..."); @@ -161,31 +178,45 @@ class GroupTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table - className="ds-margin-top" - canSelectAll={canSelectAll} - onSelect={(_event, isSelecting, rowIndex) => { - if (rowIndex !== -1) { - this.props.onSelectMember(this.state.rows[rowIndex].cells[0], isSelecting); - } - }} - aria-label="group table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={rows.length > 0 ? this.actions() : null} - > - <TableHeader /> - <TableBody /> + <Table aria-label="group table" variant="compact"> + <Thead> + <Tr> + <Th + sort={{ + sortBy, + onSort: this.handleSort, + columnIndex: 0 + }} + > + {columns[0].title} + </Th> + </Tr> + </Thead> + <Tbody> + {pagedRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: (_event, isSelecting) => { + this.props.onSelectMember(row.cells[0], isSelecting); + }, + isSelected: row.selected, + disable: row.disableSelection + }} + actions={this.getActionsForRow(row)} + > + {row.cells[0]} + </Td> + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -194,14 +225,11 @@ class GroupTable extends React.Component { variant="primary" onClick={this.props.handleShowConfirmBulkDelete} isLoading={this.props.saving} - spinnerAriaValueText={this.state.saving ? _("Updating group ...") : undefined} {...extraPrimaryProps} > {deleteBtnName} </Button> - <Divider - className="ds-margin-top-lg" - /> + <Divider className="ds-margin-top-lg" /> </div> ); } diff --git a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/renameEntry.jsx b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/renameEntry.jsx index b7c1ca52bf..a3c4c618a2 100644 --- a/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/renameEntry.jsx +++ b/src/cockpit/389-console/src/lib/ldap_editor/wizards/operations/renameEntry.jsx @@ -1,27 +1,29 @@ import cockpit from "cockpit"; import React from 'react'; import { - Alert, - Card, - CardBody, - CardTitle, - Checkbox, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Label, - LabelGroup, - SimpleList, - SimpleListItem, - Spinner, - Text, - TextContent, - TextInput, - TextVariants, - ValidatedOptions, - Wizard + Alert, + Card, + CardBody, + CardTitle, + Checkbox, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Label, + LabelGroup, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextInput, + TextVariants, + ValidatedOptions } from '@patternfly/react-core'; +import { + Wizard +} from '@patternfly/react-core/deprecated'; import LdapNavigator from '../../lib/ldapNavigator.jsx'; import { getBaseLevelEntryAttributes, @@ -116,7 +118,7 @@ class RenameEntry extends React.Component { }); }; - this.handleDelRdnChange = (checked) => { + this.handleDelRdnChange = (_event, checked) => { this.setState({ deleteOldRdn: checked }); @@ -220,7 +222,7 @@ class RenameEntry extends React.Component { <FormSelect id="naming-attr" value={newRdnAttr} - onChange={(str, e) => { + onChange={(e, str) => { this.handleRdnAttrChange(str); }} aria-label="FormSelect Input" @@ -242,7 +244,7 @@ class RenameEntry extends React.Component { id="naming-val" aria-describedby="horizontal-form-name-helper" name="rdnVal" - onChange={(str, e) => { + onChange={(e, str) => { this.handleRdnValChange(str); }} validated={newRdnVal === "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -253,7 +255,7 @@ class RenameEntry extends React.Component { className="ds-margin-top-lg" id="deleteoldrdn" isChecked={this.state.deleteOldRdn} - onChange={this.handleDelRdnChange} + onChange={(event, isChecked) => this.handleDelRdnChange(event, isChecked)} label={_("Delete the old RDN attribute from the entry")} /> </div> diff --git a/src/cockpit/389-console/src/lib/monitor/accesslog.jsx b/src/cockpit/389-console/src/lib/monitor/accesslog.jsx index fe6d1be321..1de45738d3 100644 --- a/src/cockpit/389-console/src/lib/monitor/accesslog.jsx +++ b/src/cockpit/389-console/src/lib/monitor/accesslog.jsx @@ -102,7 +102,7 @@ export class AccessLogMonitor extends React.Component { if (this.state.accessReloading) { spinner = ( <div> - <Spinner isSVG size="sm" /> + <Spinner size="sm" /> {_("Reloading access log...")} </div> ); @@ -134,7 +134,7 @@ export class AccessLogMonitor extends React.Component { <FormSelect id="accessLines" value={this.state.accessLines} - onChange={(value, event) => { + onChange={(event, value) => { this.handleAccessChange(event); }} aria-label="FormSelect Input" @@ -157,7 +157,7 @@ export class AccessLogMonitor extends React.Component { <Checkbox id="accessRefreshing" isChecked={this.state.accessRefreshing} - onChange={(checked, e) => { this.accessRefreshCont(e) }} + onChange={(e, checked) => { this.accessRefreshCont(e) }} label={_("Continuously Refresh")} /> </div> diff --git a/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx b/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx index adf0a1093c..7558440774 100644 --- a/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx +++ b/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx @@ -129,7 +129,7 @@ export class AuditFailLogMonitor extends React.Component { <FormSelect id="auditfailLines" value={this.state.auditfailLines} - onChange={(value, event) => { + onChange={(event, value) => { this.handleAuditFailChange(event); }} aria-label="FormSelect Input" @@ -152,7 +152,7 @@ export class AuditFailLogMonitor extends React.Component { <Checkbox id="auditfailRefreshing" isChecked={this.state.auditfailRefreshing} - onChange={(checked, e) => { this.auditFailRefreshCont(e) }} + onChange={(e, checked) => { this.auditFailRefreshCont(e) }} label={_("Continuously Refresh")} /> </div> diff --git a/src/cockpit/389-console/src/lib/monitor/auditlog.jsx b/src/cockpit/389-console/src/lib/monitor/auditlog.jsx index 4da3feaf9a..aa53eee345 100644 --- a/src/cockpit/389-console/src/lib/monitor/auditlog.jsx +++ b/src/cockpit/389-console/src/lib/monitor/auditlog.jsx @@ -127,7 +127,7 @@ export class AuditLogMonitor extends React.Component { <FormSelect id="accessLines" value={this.state.auditLines} - onChange={(value, event) => { + onChange={(event, value) => { this.handleAuditChange(event); }} aria-label="FormSelect Input" @@ -150,7 +150,7 @@ export class AuditLogMonitor extends React.Component { <Checkbox id="auditRefreshing" isChecked={this.state.auditRefreshing} - onChange={(checked, e) => { this.auditRefreshCont(e) }} + onChange={(e, checked) => { this.auditRefreshCont(e) }} label={_("Continuously Refresh")} /> </div> diff --git a/src/cockpit/389-console/src/lib/monitor/errorlog.jsx b/src/cockpit/389-console/src/lib/monitor/errorlog.jsx index 93972e15e0..9bc2f7e0d3 100644 --- a/src/cockpit/389-console/src/lib/monitor/errorlog.jsx +++ b/src/cockpit/389-console/src/lib/monitor/errorlog.jsx @@ -188,7 +188,7 @@ export class ErrorLogMonitor extends React.Component { <FormSelect id="errorLines" value={this.state.errorLines} - onChange={(value, event) => { + onChange={(event, value) => { this.handleErrorChange(event); }} aria-label="FormSelect Input" @@ -214,7 +214,7 @@ export class ErrorLogMonitor extends React.Component { <FormSelect className="ds-left-margin" value={this.state.errorSevLevel} - onChange={(value, event) => { + onChange={(event, value) => { this.handleSevChange(event); }} aria-label="FormSelect Input" @@ -238,7 +238,7 @@ export class ErrorLogMonitor extends React.Component { <Checkbox id="errorRefreshing" isChecked={this.state.errorRefreshing} - onChange={(checked, e) => { this.errorRefreshCont(e) }} + onChange={(e, checked) => { this.errorRefreshCont(e) }} label={_("Continuously Refresh")} /> </div> diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx index 78653e22b8..0e32a21938 100644 --- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx +++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx @@ -273,7 +273,7 @@ class ConflictCompareModal extends React.Component { </p> </GridItem> <GridItem span={12}> - <TextArea id="conflictValid" resizeOrientation="vertical" className="ds-conflict" value={valid} isReadOnly /> + <TextArea id="conflictValid" resizeOrientation="vertical" className="ds-conflict" value={valid} readOnlyVariant="default" /> </GridItem> <GridItem className="ds-margin-top-lg" span={6}> <TextContent> @@ -288,7 +288,7 @@ class ConflictCompareModal extends React.Component { </p> </GridItem> <GridItem span={12}> - <TextArea id="conflictConflict" resizeOrientation="vertical" className="ds-conflict" value={conflict} isReadOnly /> + <TextArea id="conflictConflict" resizeOrientation="vertical" className="ds-conflict" value={conflict} readOnlyVariant="default" /> </GridItem> <hr /> <div className="ds-container"> @@ -432,7 +432,7 @@ class ReportCredentialsModal extends React.Component { id="credsHostname" aria-describedby="cachememsize" name="credsHostname" - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -469,7 +469,7 @@ class ReportCredentialsModal extends React.Component { id="credsBinddn" aria-describedby="cachememsize" name="credsBinddn" - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -487,7 +487,7 @@ class ReportCredentialsModal extends React.Component { aria-describedby="cachememsize" name="credsBindpw" isDisabled={pwInputInterractive} - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -501,7 +501,7 @@ class ReportCredentialsModal extends React.Component { <Checkbox isChecked={pwInputInterractive} id="pwInputInterractive" - onChange={(checked, e) => { + onChange={(e, checked) => { handleFieldChange(e); }} /> @@ -570,7 +570,7 @@ class ReportConnectionModal extends React.Component { id="connName" aria-describedby="connName" name="connName" - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -587,7 +587,7 @@ class ReportConnectionModal extends React.Component { id="connHostname" aria-describedby="connHostname" name="connHostname" - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -624,7 +624,7 @@ class ReportConnectionModal extends React.Component { id="connBindDN" aria-describedby="connBindDN" name="connBindDN" - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -642,7 +642,7 @@ class ReportConnectionModal extends React.Component { aria-describedby="connCred" name="connCred" isDisabled={pwInputInterractive} - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -656,7 +656,7 @@ class ReportConnectionModal extends React.Component { <Checkbox isChecked={pwInputInterractive} id="pwInputInterractive" - onChange={(checked, e) => { + onChange={(e, checked) => { handleFieldChange(e); }} /> @@ -724,7 +724,7 @@ class ReportAliasesModal extends React.Component { id="aliasName" aria-describedby="aliasName" name="aliasName" - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -741,7 +741,7 @@ class ReportAliasesModal extends React.Component { id="aliasHostname" aria-describedby="aliasHostname" name="aliasHostname" - onChange={(str, e) => { + onChange={(e, str) => { handleFieldChange(e); }} /> @@ -834,7 +834,7 @@ class ReportLoginModal extends React.Component { aria-describedby="loginBinddn" name="loginBinddn" isDisabled={disableBinddn} - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -851,7 +851,7 @@ class ReportLoginModal extends React.Component { id="loginBindpw" aria-describedby="loginBindpw" name="loginBindpw" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -926,7 +926,7 @@ class FullReportContent extends React.Component { title={_("Display all agreements including the disabled ones and the ones we failed to connect to")} isChecked={this.state.showDisabledAgreements} id="showDisabledAgreements" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSwitchChange(e); }} label={_("Show Disabled Agreements")} @@ -945,7 +945,7 @@ class FullReportContent extends React.Component { <GridItem span={12}> <Checkbox isChecked={this.state.oneTableReport} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleSwitchChange(e); }} id="oneTableReport" diff --git a/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx b/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx index 7ac812c032..aa52355132 100644 --- a/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx +++ b/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx @@ -12,15 +12,18 @@ import { TextVariants, } from '@patternfly/react-core'; import { - cellWidth, - expandable, - Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, - info, + expandable, + TableVariant, + sortable, + SortByDirection, + Table, + Thead, + Tr, + Th, + Tbody, + Td, + ExpandableRowContent, + ActionsColumn } from '@patternfly/react-table'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon'; import PropTypes from "prop-types"; @@ -39,11 +42,11 @@ class AbortCleanALLRUVTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Task"), transforms: [sortable], cellFormatters: [expandable] }, - { title: _("Created"), transforms: [sortable] }, - { title: _("Replica ID"), transforms: [sortable] }, - { title: _("Status"), transforms: [sortable] }, - { title: '' }, + { title: _("Task"), sortable: true }, + { title: _("Created"), sortable: true }, + { title: _("Replica ID"), sortable: true }, + { title: _("Status"), sortable: true }, + { title: _("Actions")} ], }; @@ -63,23 +66,30 @@ class AbortCleanALLRUVTable extends React.Component { this.handleSort = this.handleSort.bind(this); this.handleCollapse = this.handleCollapse.bind(this); this.getLog = this.getLog.bind(this); + this.createRows = this.createRows.bind(this); } getLog(log) { return ( <TextContent> - <Text component={TextVariants.h5}> + <Text + component={TextVariants.pre} + style={{ + whiteSpace: 'pre-wrap', + wordBreak: 'break-word' + }} + > {log} </Text> </TextContent> ); } - componentDidMount() { + createRows(tasks) { let rows = []; let columns = [...this.state.columns]; - let count = 0; - for (const task of this.props.tasks) { + + for (const task of tasks) { rows.push({ isOpen: false, cells: [ @@ -87,40 +97,45 @@ class AbortCleanALLRUVTable extends React.Component { get_date_string(task.attrs.nstaskcreated[0]), task.attrs['replica-id'][0], task.attrs.nstaskstatus[0], - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getLog(task.attrs.nstasklog[0]) }] + ], + originalData: task }); - count += 2; } + if (rows.length === 0) { rows = [{ cells: [_("No Tasks")] }]; columns = [{ title: _("Abort CleanAllRUV Tasks") }]; } - this.setState({ - rows, - columns - }); + + return { rows, columns }; } - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + componentDidMount() { + const { rows, columns } = this.createRows(this.props.tasks); + this.setState({ rows, columns }); + } + + componentDidUpdate(prevProps) { + if (prevProps.tasks !== this.props.tasks) { + const { rows, columns } = this.createRows(this.props.tasks); + this.setState({ + rows, + columns, + page: 1 + }); + } + } + + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + rows[rowIndex].isOpen = isExpanding; + this.setState({ rows }); } handleSort(_event, index, direction) { const sorted_tasks = []; const rows = []; - let count = 0; - // Convert the conns into a sortable array based on the column indexes for (const task of this.props.tasks) { sorted_tasks.push({ task, @@ -131,7 +146,6 @@ class AbortCleanALLRUVTable extends React.Component { }); } - // Sort the connections and build the new rows sorted_tasks.sort((a, b) => (a[index] > b[index]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_tasks.reverse(); @@ -145,14 +159,9 @@ class AbortCleanALLRUVTable extends React.Component { get_date_string(task.attrs.nstaskcreated[0]), task.attrs['replica-id'][0], task.attrs.nstaskstatus[0], - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getLog(task.attrs.nstasklog[0]) }] + ], + originalData: task }); - count += 2; } this.setState({ sortBy: { @@ -166,35 +175,73 @@ class AbortCleanALLRUVTable extends React.Component { render() { const { columns, rows, perPage, page, sortBy } = this.state; - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); + const hasNoTasks = rows.length === 1 && rows[0].cells.length === 1; return ( <div className="ds-margin-top-xlg"> <Table - className="ds-margin-top" aria-label="Expandable table" - cells={columns} - rows={tableRows} - onCollapse={this.handleCollapse} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {!hasNoTasks && <Th screenReaderText="Row expansion" />} + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + {!hasNoTasks && ( + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + )} + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + </Tr> + {row.isOpen && row.originalData && ( + <Tr isExpanded={true}> + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + <ExpandableRowContent> + {this.getLog(row.originalData.attrs.nstasklog[0])} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.tasks.length} widgetId="pagination-options-menu-bottom" - perPage={this.state.perPage} - page={this.state.page} - variant={PaginationVariant.bottom} + perPage={perPage} + page={page} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -214,11 +261,11 @@ class CleanALLRUVTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Task"), transforms: [sortable], cellFormatters: [expandable] }, - { title: _("Created"), transforms: [sortable] }, - { title: _("Replica ID"), transforms: [sortable] }, - { title: _("Status"), transforms: [sortable] }, - { title: '' }, + { title: _("Task"), sortable: true }, + { title: _("Created"), sortable: true }, + { title: _("Replica ID"), sortable: true }, + { title: _("Status"), sortable: true }, + { title: _("Actions")} ], }; @@ -238,23 +285,30 @@ class CleanALLRUVTable extends React.Component { this.handleSort = this.handleSort.bind(this); this.handleCollapse = this.handleCollapse.bind(this); this.getLog = this.getLog.bind(this); + this.createRows = this.createRows.bind(this); } getLog(log) { return ( <TextContent> - <Text component={TextVariants.h5}> + <Text + component={TextVariants.pre} + style={{ + whiteSpace: 'pre-wrap', + wordBreak: 'break-word' + }} + > {log} </Text> </TextContent> ); } - componentDidMount() { + createRows(tasks) { let rows = []; let columns = [...this.state.columns]; - let count = 0; - for (const task of this.props.tasks) { + + for (const task of tasks) { rows.push({ isOpen: false, cells: [ @@ -262,40 +316,45 @@ class CleanALLRUVTable extends React.Component { get_date_string(task.attrs.nstaskcreated[0]), task.attrs['replica-id'][0], task.attrs.nstaskstatus[0], - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getLog(task.attrs.nstasklog[0]) }] + ], + originalData: task }); - count += 2; } + if (rows.length === 0) { rows = [{ cells: [_("No Tasks")] }]; columns = [{ title: _("CleanAllRUV Tasks") }]; } - this.setState({ - rows, - columns - }); + + return { rows, columns }; } - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + componentDidMount() { + const { rows, columns } = this.createRows(this.props.tasks); + this.setState({ rows, columns }); + } + + componentDidUpdate(prevProps) { + if (prevProps.tasks !== this.props.tasks) { + const { rows, columns } = this.createRows(this.props.tasks); + this.setState({ + rows, + columns, + page: 1 // Reset to first page when data changes + }); + } + } + + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + rows[rowIndex].isOpen = isExpanding; + this.setState({ rows }); } handleSort(_event, index, direction) { const sorted_tasks = []; const rows = []; - let count = 0; - // Convert the conns into a sortable array based on the column indexes for (const task of this.props.tasks) { sorted_tasks.push({ task, @@ -306,7 +365,6 @@ class CleanALLRUVTable extends React.Component { }); } - // Sort the connections and build the new rows sorted_tasks.sort((a, b) => (a[index] > b[index]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_tasks.reverse(); @@ -320,14 +378,9 @@ class CleanALLRUVTable extends React.Component { get_date_string(task.attrs.nstaskcreated[0]), task.attrs['replica-id'][0], task.attrs.nstaskstatus[0], - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getLog(task.attrs.nstasklog[0]) }] + ], + originalData: task }); - count += 2; } this.setState({ sortBy: { @@ -341,35 +394,73 @@ class CleanALLRUVTable extends React.Component { render() { const { columns, rows, perPage, page, sortBy } = this.state; - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); + const hasNoTasks = rows.length === 1 && rows[0].cells.length === 1; return ( <div className="ds-margin-top-xlg"> <Table - className="ds-margin-top" aria-label="Expandable table" - cells={columns} - rows={tableRows} - onCollapse={this.handleCollapse} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {!hasNoTasks && <Th screenReaderText="Row expansion" />} + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + {!hasNoTasks && ( + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + )} + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + </Tr> + {row.isOpen && row.originalData && ( + <Tr isExpanded={true}> + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + <ExpandableRowContent> + {this.getLog(row.originalData.attrs.nstasklog[0])} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.tasks.length} widgetId="pagination-options-menu-bottom" - perPage={this.state.perPage} - page={this.state.page} - variant={PaginationVariant.bottom} + perPage={perPage} + page={page} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -389,10 +480,10 @@ class WinsyncAgmtTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Agreement"), transforms: [sortable], cellFormatters: [expandable] }, - { title: _("Replica"), transforms: [sortable] }, - { title: _("Enabled"), transforms: [sortable] }, - { title: '' }, + { title: _("Agreement"), sortable: true }, + { title: _("Replica"), sortable: true }, + { title: _("Enabled"), sortable: true }, + { title: _("Poke"), sortable: false }, ], }; @@ -416,26 +507,28 @@ class WinsyncAgmtTable extends React.Component { getExpandedRow(agmt) { return ( - <Grid className="ds-indent"> - <GridItem span={3}>{_("Session In Progress:")}</GridItem> - <GridItem span={9}><b>{ agmt['update-in-progress'][0] }</b></GridItem> - <GridItem span={3}>{_("Changes Sent:")}</GridItem> - <GridItem span={9}><b>{ agmt['number-changes-sent'][0] }</b></GridItem> - <hr /> - <GridItem span={3}>{_("Last Init Started:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-init-start'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Init Ended:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-init-end'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Init Status:")}</GridItem> - <GridItem span={9}><b>{ agmt['last-init-status'][0] }</b></GridItem> - <hr /> - <GridItem span={3}>{_("Last Updated Started:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-update-start'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Update Ended:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-update-end'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Update Status:")}</GridItem> - <GridItem span={9}><b>{ agmt['last-update-status'][0] }</b></GridItem> - </Grid> + <ExpandableRowContent> + <Grid className="ds-indent"> + <GridItem span={3}>{_("Session In Progress:")}</GridItem> + <GridItem span={9}><b>{ agmt['update-in-progress'][0] }</b></GridItem> + <GridItem span={3}>{_("Changes Sent:")}</GridItem> + <GridItem span={9}><b>{ agmt['number-changes-sent'][0] }</b></GridItem> + <hr /> + <GridItem span={3}>{_("Last Init Started:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-init-start'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Init Ended:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-init-end'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Init Status:")}</GridItem> + <GridItem span={9}><b>{ agmt['last-init-status'][0] }</b></GridItem> + <hr /> + <GridItem span={3}>{_("Last Updated Started:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-update-start'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Update Ended:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-update-end'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Update Status:")}</GridItem> + <GridItem span={9}><b>{ agmt['last-update-status'][0] }</b></GridItem> + </Grid> + </ExpandableRowContent> ); } @@ -446,7 +539,7 @@ class WinsyncAgmtTable extends React.Component { variant="primary" onClick={this.props.handlePokeAgmt} title={_("Awaken the winsync replication agreement")} - isSmall + size="sm" > {_("Poke")} </Button> @@ -457,6 +550,7 @@ class WinsyncAgmtTable extends React.Component { let rows = []; let columns = [...this.state.columns]; let count = 0; + for (const agmt of this.props.agmts) { rows.push({ isOpen: false, @@ -465,40 +559,33 @@ class WinsyncAgmtTable extends React.Component { agmt.replica[0], agmt['replica-enabled'][0], { title: this.getWakeupButton(agmt['agmt-name'][0]) } - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(agmt) }] + ], + originalData: agmt }); - count += 2; + count += 1; } + if (rows.length === 0) { rows = [{ cells: [_("No Agreements")] }]; columns = [{ title: _("Winsync Agreements") }]; } + this.setState({ rows, columns }); } - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + rows[rowIndex].isOpen = isExpanding; + this.setState({ rows }); } handleSort(_event, index, direction) { const sorted_agmts = []; const rows = []; - let count = 0; - // Convert the conns into a sortable array based on the column indexes for (const agmt of this.props.agmts) { sorted_agmts.push({ agmt, @@ -508,11 +595,11 @@ class WinsyncAgmtTable extends React.Component { }); } - // Sort the connections and build the new rows sorted_agmts.sort((a, b) => (a[index] > b[index]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_agmts.reverse(); } + for (let agmt of sorted_agmts) { agmt = agmt.agmt; rows.push({ @@ -522,15 +609,11 @@ class WinsyncAgmtTable extends React.Component { agmt.replica[0], agmt['replica-enabled'][0], { title: this.getWakeupButton(agmt['agmt-name'][0]) } - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(agmt) }] + ], + originalData: agmt }); - count += 2; } + this.setState({ sortBy: { index, @@ -543,41 +626,71 @@ class WinsyncAgmtTable extends React.Component { render() { const { columns, rows, perPage, page, sortBy } = this.state; - - // We are using an expandable list, so every row has a child row with an - // index that points back to the parent. So when we splice the rows for - // pagination we have to treat each connection as two rows, and we need - // to rewrite the child's parent index to point to the correct location - // in the new spliced array - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); + const hasNoAgreements = rows.length === 1 && rows[0].cells.length === 1; return ( <div className="ds-margin-top-xlg"> <Table - className="ds-margin-top" - aria-label="Expandable table" - cells={columns} - rows={tableRows} - onCollapse={this.handleCollapse} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + aria-label="Winsync agreements table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {!hasNoAgreements && <Th screenReaderText="Row expansion" />} + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + {!hasNoAgreements && ( + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + )} + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell.title || cell}</Td> + ))} + </Tr> + {row.isOpen && row.originalData && ( + <Tr isExpanded={true}> + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + {this.getExpandedRow(row.originalData)} + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.agmts.length} widgetId="pagination-options-menu-bottom" - perPage={this.state.perPage} - page={this.state.page} - variant={PaginationVariant.bottom} + perPage={perPage} + page={page} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -597,10 +710,10 @@ class AgmtTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Agreement"), transforms: [sortable], cellFormatters: [expandable] }, - { title: _("Replica"), transforms: [sortable] }, - { title: _("Enabled"), transforms: [sortable] }, - { title: '' }, + { title: _("Agreement"), sortable: true }, + { title: _("Replica"), sortable: true }, + { title: _("Enabled"), sortable: true }, + { title: '', sortable: false, screenReaderText: _("Poke the agreement") }, ], }; @@ -624,30 +737,32 @@ class AgmtTable extends React.Component { getExpandedRow(agmt) { return ( - <Grid className="ds-indent"> - <GridItem span={3}>{_("Session In Progress:")}</GridItem> - <GridItem span={9}><b>{ agmt['update-in-progress'][0] }</b></GridItem> - <GridItem span={3}>{_("Changes Sent:")}</GridItem> - <GridItem span={9}><b>{ agmt['number-changes-sent'][0] }</b></GridItem> - <GridItem span={3}>{_("Changes Skipped:")}</GridItem> - <GridItem span={9}><b>{ agmt['number-changes-skipped'][0] }</b></GridItem> - <GridItem span={3}>{_("Reap Active:")}</GridItem> - <GridItem span={9}><b>{ agmt['reap-active'][0] }</b></GridItem> - <hr /> - <GridItem span={3}>{_("Last Init Started:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-init-start'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Init Ended:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-init-end'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Init Status:")}</GridItem> - <GridItem span={9}><b>{ agmt['last-init-status'][0] }</b></GridItem> - <hr /> - <GridItem span={3}>{_("Last Updated Started:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-update-start'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Update Ended:")}</GridItem> - <GridItem span={9}><b>{ get_date_string(agmt['last-update-end'][0]) }</b></GridItem> - <GridItem span={3}>{_("Last Update Status:")}</GridItem> - <GridItem span={9}><b>{ agmt['last-update-status'][0] }</b></GridItem> - </Grid> + <ExpandableRowContent> + <Grid className="ds-indent"> + <GridItem span={3}>{_("Session In Progress:")}</GridItem> + <GridItem span={9}><b>{ agmt['update-in-progress'][0] }</b></GridItem> + <GridItem span={3}>{_("Changes Sent:")}</GridItem> + <GridItem span={9}><b>{ agmt['number-changes-sent'][0] }</b></GridItem> + <GridItem span={3}>{_("Changes Skipped:")}</GridItem> + <GridItem span={9}><b>{ agmt['number-changes-skipped'][0] }</b></GridItem> + <GridItem span={3}>{_("Reap Active:")}</GridItem> + <GridItem span={9}><b>{ agmt['reap-active'][0] }</b></GridItem> + <hr /> + <GridItem span={3}>{_("Last Init Started:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-init-start'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Init Ended:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-init-end'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Init Status:")}</GridItem> + <GridItem span={9}><b>{ agmt['last-init-status'][0] }</b></GridItem> + <hr /> + <GridItem span={3}>{_("Last Updated Started:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-update-start'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Update Ended:")}</GridItem> + <GridItem span={9}><b>{ get_date_string(agmt['last-update-end'][0]) }</b></GridItem> + <GridItem span={3}>{_("Last Update Status:")}</GridItem> + <GridItem span={9}><b>{ agmt['last-update-status'][0] }</b></GridItem> + </Grid> + </ExpandableRowContent> ); } @@ -658,7 +773,7 @@ class AgmtTable extends React.Component { variant="primary" onClick={this.props.handlePokeAgmt} title={_("Awaken the replication agreement")} - isSmall + size="sm" > {_("Poke")} </Button> @@ -669,6 +784,7 @@ class AgmtTable extends React.Component { let rows = []; let columns = [...this.state.columns]; let count = 0; + for (const agmt of this.props.agmts) { rows.push({ isOpen: false, @@ -677,40 +793,33 @@ class AgmtTable extends React.Component { agmt.replica[0], agmt['replica-enabled'][0], { title: this.getWakeupButton(agmt['agmt-name'][0]) } - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(agmt) }] + ], + originalData: agmt }); - count += 2; + count += 1; } + if (rows.length === 0) { rows = [{ cells: [_("No Agreements")] }]; columns = [{ title: _("Replication Agreements") }]; } + this.setState({ rows, columns }); } - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + rows[rowIndex].isOpen = isExpanding; + this.setState({ rows }); } handleSort(_event, index, direction) { const sorted_agmts = []; const rows = []; - let count = 0; - // Convert the conns into a sortable array based on the column indexes for (const agmt of this.props.agmts) { sorted_agmts.push({ agmt, @@ -720,11 +829,11 @@ class AgmtTable extends React.Component { }); } - // Sort the connections and build the new rows sorted_agmts.sort((a, b) => (a[index] > b[index]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_agmts.reverse(); } + for (let agmt of sorted_agmts) { agmt = agmt.agmt; rows.push({ @@ -734,15 +843,11 @@ class AgmtTable extends React.Component { agmt.replica[0], agmt['replica-enabled'][0], { title: this.getWakeupButton(agmt['agmt-name'][0]) } - ] - }); - rows.push({ - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(agmt) }] + ], + originalData: agmt }); - count += 2; } + this.setState({ sortBy: { index, @@ -754,37 +859,73 @@ class AgmtTable extends React.Component { } render() { - // This is an expandable list const { columns, rows, perPage, page, sortBy } = this.state; - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); + const hasNoAgreements = rows.length === 1 && rows[0].cells.length === 1; return ( <div className="ds-margin-top-xlg"> <Table - className="ds-margin-top" - aria-label="Expandable table" - cells={columns} - rows={tableRows} - onCollapse={this.handleCollapse} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + aria-label="Agreements table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {!hasNoAgreements && <Th screenReaderText="Row expansion" />} + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + screenReaderText={column.screenReaderText} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + {!hasNoAgreements && ( + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + )} + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell.title || cell}</Td> + ))} + </Tr> + {row.isOpen && row.originalData && ( + <Tr isExpanded={true}> + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + {this.getExpandedRow(row.originalData)} + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.agmts.length} widgetId="pagination-options-menu-bottom" - perPage={this.state.perPage} - page={this.state.page} - variant={PaginationVariant.bottom} + perPage={perPage} + page={page} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -806,20 +947,17 @@ class ConnectionTable extends React.Component { columns: [ { title: _("Connection Opened"), - cellFormatters: [expandable], - transforms: [sortable] + sortable: true }, - { title: _("IP Address"), transforms: [sortable] }, - { title: _("Conn ID"), transforms: [sortable] }, - { title: _("Bind DN"), transforms: [sortable] }, + { title: _("IP Address"), sortable: true }, + { title: _("Conn ID"), sortable: true }, + { title: _("Bind DN"), sortable: true }, { title: _("Max Threads"), - transforms: [ - info({ - tooltip: _("If connection is currently at \"Max Threads\" then it will block new operations") - }), - sortable - ] + sortable: true, + info: { + tooltip: _("If connection is currently at \"Max Threads\" then it will block new operations") + } }, ], }; @@ -922,7 +1060,7 @@ class ConnectionTable extends React.Component { handleCollapse(event, rowKey, isOpen) { const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set + const index = (perPage * (page - 1) * 2) + rowKey; rows[index].isOpen = isOpen; this.setState({ rows @@ -1040,7 +1178,6 @@ class ConnectionTable extends React.Component { const startIdx = ((perPage * page) - perPage) * 2; const tableRows = origRows.splice(startIdx, perPage * 2); for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array tableRows[idx].parent = count; } @@ -1059,24 +1196,76 @@ class ConnectionTable extends React.Component { onClear={(evt) => this.handleSearchChange(evt, '')} /> <Table - className="ds-margin-top" aria-label="Expandable table" - cells={columns} - rows={tableRows} - onCollapse={this.handleCollapse} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: columnIndex + 1 + } : undefined} + info={column.info} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => { + if (row.parent !== undefined) { + // This is an expanded row + return ( + <Tr + key={rowIndex} + isExpanded={tableRows[row.parent].isOpen} + > + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + <ExpandableRowContent> + {/* Render the expanded content directly */} + {row.cells[0].title} + </ExpandableRowContent> + </Td> + </Tr> + ); + } + // This is a regular row + return ( + <Tr key={rowIndex}> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {/* Ensure we're rendering a string or valid React element */} + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ); + })} + </Tbody> </Table> <Pagination itemCount={this.props.conns.length} widgetId="pagination-options-menu-bottom" - perPage={this.state.perPage} - page={this.state.page} - variant={PaginationVariant.bottom} + perPage={perPage} + page={page} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1095,11 +1284,10 @@ class GlueTable extends React.Component { value: '', sortBy: {}, rows: [], - dropdownIsOpen: false, columns: [ - { title: _("Glue Entry"), transforms: [sortable, cellWidth(12)] }, - { title: _("Description"), transforms: [sortable] }, - { title: _("Created"), transforms: [sortable] }, + { title: _("Glue Entry"), sortable: true }, + { title: _("Description"), sortable: true }, + { title: _("Created"), sortable: true }, ], }; @@ -1121,54 +1309,27 @@ class GlueTable extends React.Component { componentDidMount() { const rows = []; - const columns = this.state.columns; for (const glue of this.props.glues) { - rows.push({ - cells: [ - glue.dn, glue.attrs.nsds5replconflict[0], get_date_string(glue.attrs.createtimestamp[0]) - ] - }); + rows.push([ + glue.dn, + glue.attrs.nsds5replconflict[0], + get_date_string(glue.attrs.createtimestamp[0]) + ]); } - this.setState({ - rows, - columns - }); - } - - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sorted_glues = []; - const rows = []; - - // Convert the conns into a sortable array - for (const glue of this.props.glues) { - sorted_glues.push({ - 1: glue.dn, - 2: glue.attrs.nsds5replconflict[0], - 3: get_date_string(glue.attrs.createtimestamp[0]), - }); - } + const sortedGlues = [...this.state.rows]; + + sortedGlues.sort((a, b) => { + const aValue = a[index]; + const bValue = b[index]; + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + }); - // Sort the connections and build the new rows - sorted_glues.sort((a, b) => (a[index] > b[index]) ? 1 : -1); if (direction !== SortByDirection.asc) { - sorted_glues.reverse(); - } - for (const glue of sorted_glues) { - rows.push({ - isOpen: false, - cells: [ - glue['1'], glue['2'], glue['3'] - ] - }); + sortedGlues.reverse(); } this.setState({ @@ -1176,35 +1337,36 @@ class GlueTable extends React.Component { index, direction }, - rows, + rows: sortedGlues, page: 1, }); } - actions() { + getActions(rowData) { return [ { title: _("Convert Glue Entry"), - onClick: (event, rowId, rowData, extra) => - this.props.convertGlue(rowData.cells[0]) + onClick: () => this.props.convertGlue(rowData[0]) }, { title: _("Delete Glue Entry"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteGlue(rowData.cells[0]) + onClick: () => this.props.deleteGlue(rowData[0]) } ]; } render() { - const { perPage, page, sortBy } = this.state; - let rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy - let columns = this.state.columns; - let has_rows = true; - if (rows.length === 0) { - has_rows = false; - rows = [{ cells: [_("No Glue Entries")] }]; - columns = [{ title: _("Replication Conflict Glue Entries") }]; + const { columns, rows, perPage, page, sortBy } = this.state; + const hasRows = this.props.glues.length > 0; + + // Calculate pagination + const startIdx = (perPage * page) - perPage; + let tableRows = [...rows].splice(startIdx, perPage); + let displayColumns = [...columns]; + + if (!hasRows) { + tableRows = [[_("No Glue Entries")]]; + displayColumns = [{ title: _("Replication Conflict Glue Entries") }]; } return ( @@ -1212,24 +1374,48 @@ class GlueTable extends React.Component { <Table className="ds-margin-top" aria-label="glue table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {displayColumns.map((column, idx) => ( + <Th + key={idx} + sort={hasRows && column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.glues.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1249,10 +1435,10 @@ class ConflictTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Conflict DN"), transforms: [sortable] }, - { title: _("Description"), transforms: [sortable] }, - { title: _("Created"), transforms: [sortable] }, - { title: '' } + { title: _("Conflict DN"), sortable: true }, + { title: _("Description"), sortable: true }, + { title: _("Created"), sortable: true }, + { title: '', sortable: false, screenReaderText: _("Resolve the conflict") }, ], }; @@ -1277,7 +1463,7 @@ class ConflictTable extends React.Component { <Button id={name} variant="primary" - isSmall + size="sm" onClick={() => { this.props.resolveConflict(name); }} @@ -1291,16 +1477,15 @@ class ConflictTable extends React.Component { let rows = []; let columns = this.state.columns; for (const conflict of this.props.conflicts) { - rows.push({ - isOpen: false, - cells: [ - conflict.dn, conflict.attrs.nsds5replconflict[0], get_date_string(conflict.attrs.createtimestamp[0]), - { title: this.getResolveButton(conflict.dn) } - ] - }); + rows.push([ + conflict.dn, + conflict.attrs.nsds5replconflict[0], + get_date_string(conflict.attrs.createtimestamp[0]), + this.getResolveButton(conflict.dn) + ]); } if (rows.length === 0) { - rows = [{ cells: [_("No Conflict Entries")] }]; + rows = [[_("No Conflict Entries")]]; columns = [{ title: _("Replication Conflict Entries") }]; } this.setState({ @@ -1310,31 +1495,19 @@ class ConflictTable extends React.Component { } handleSort(_event, index, direction) { - const sorted_conflicts = []; - const rows = []; - - // Convert the conns into a sortable array - for (const conflict of this.props.conflicts) { - sorted_conflicts.push({ - 1: conflict.dn, - 2: conflict.attrs.nsds5replconflict[0], - 3: get_date_string(conflict.attrs.createtimestamp[0]), - }); - } + const sortedConflicts = [...this.state.rows]; + + sortedConflicts.sort((a, b) => { + const aValue = a[index]; + const bValue = b[index]; + if (typeof aValue === 'string') { + return aValue.localeCompare(bValue); + } + return 0; + }); - // Sort the connections and build the new rows - sorted_conflicts.sort((a, b) => (a[index] > b[index]) ? 1 : -1); if (direction !== SortByDirection.asc) { - sorted_conflicts.reverse(); - } - for (const conflict of sorted_conflicts) { - rows.push({ - isOpen: false, - cells: [ - conflict['1'], conflict['2'], conflict['3'], - { title: this.getResolveButton(conflict['1']) } - ] - }); + sortedConflicts.reverse(); } this.setState({ @@ -1342,34 +1515,60 @@ class ConflictTable extends React.Component { index, direction }, - rows, + rows: sortedConflicts, page: 1, }); } render() { const { columns, rows, perPage, page, sortBy } = this.state; + + // Calculate pagination + const startIdx = (perPage * page) - perPage; + const tableRows = [...rows].splice(startIdx, perPage); return ( <div className="ds-margin-top-lg"> <Table className="ds-margin-top" aria-label="conflict table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + screenReaderText={column.screenReaderText} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> <Pagination - itemCount={this.props.conflicts.length} + itemCount={rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1385,20 +1584,23 @@ class DiskTable extends React.Component { this.state = { sortBy: {}, columns: [ - { title: _("Disk Partition"), transforms: [sortable] }, - { title: _("Disk Size"), transforms: [sortable] }, - { title: _("Used Space"), transforms: [sortable] }, - { title: _("Available Space"), transforms: [sortable] }, + { title: _("Disk Partition"), sortable: true }, + { title: _("Disk Size"), sortable: true }, + { title: _("Used Space"), sortable: true }, + { title: _("Available Space"), sortable: true }, ], }; this.handleSort = this.handleSort.bind(this); } - handleSort(_event, index, direction) { - const sortedRows = this.props.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + handleSort(_event, columnIndex, direction) { + const sortedRows = [...this.props.rows].sort((a, b) => ( + a[columnIndex] < b[columnIndex] ? -1 : a[columnIndex] > b[columnIndex] ? 1 : 0 + )); + this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows: direction === SortByDirection.asc ? sortedRows : sortedRows.reverse() @@ -1410,9 +1612,32 @@ class DiskTable extends React.Component { return ( <div className="ds-margin-top-xlg"> - <Table aria-label="Sortable Table" sortBy={sortBy} onSort={this.handleSort} cells={columns} rows={this.props.rows}> - <TableHeader /> - <TableBody /> + <Table aria-label="Sortable Table"> + <Thead> + <Tr> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {this.props.rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + </Tr> + ))} + </Tbody> </Table> </div> ); @@ -1424,25 +1649,22 @@ class ReportAliasesTable extends React.Component { super(props); this.state = { sortBy: {}, - dropdownIsOpen: false, columns: [ - { title: _("Alias"), transforms: [sortable] }, - { title: _("Connection Data"), transforms: [sortable] }, + { title: _("Alias"), sortable: true }, + { title: _("Connection Data"), sortable: true }, ], }; } - actions() { + getActions(rowData) { return [ { title: _("Edit Alias"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0], rowData[1]) + onClick: () => this.props.editConfig(rowData[0], rowData[1]) }, { title: _("Delete Alias"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) + onClick: () => this.props.deleteConfig(rowData[0]) } ]; } @@ -1450,30 +1672,61 @@ class ReportAliasesTable extends React.Component { render() { let columns = this.state.columns; let rows = JSON.parse(JSON.stringify(this.props.rows)); // Deep copy - let has_rows = true; - if (rows.length === 0) { - has_rows = false; - rows = [{ cells: [_("No Aliases")] }]; + const hasRows = rows.length > 0; + + if (!hasRows) { + rows = [[_("No Aliases")]]; columns = [{ title: _("Instance Aliases") }]; } return ( <div className="ds-margin-top-xlg"> <TextContent> - <Text className="ds-center ds-margin-bottom" component={TextVariants.h4}> + <Text className="ds-center ds-margin-bottom" component="h4"> {_("Replica Naming Aliases")} </Text> </TextContent> <Table - variant={TableVariant.compact} aria-label="Sortable Table" - sortBy={this.props.sortBy} onSort={this.props.handleSort} cells={columns} - rows={rows} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" + aria-label="Sortable Table" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={hasRows && column.sortable ? { + sortBy: this.props.sortBy, + onSort: this.props.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + : + <Td>{row.cells[0]}</Td> + } + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> </div> ); @@ -1485,61 +1738,59 @@ class ReportCredentialsTable extends React.Component { super(props); this.state = { - dropdownIsOpen: false, columns: [ - { title: _("Connection Data"), transforms: [sortable] }, // connData - { title: _("Bind DN"), transforms: [sortable] }, // credsBinddn - { title: _("Password"), transforms: [sortable] }, // credsBindpw + { title: _("Connection Data"), sortable: true }, + { title: _("Bind DN"), sortable: true }, + { title: _("Password"), sortable: true }, ], }; } - actions() { + getActions(rowData) { return [ { title: _("Edit Connection"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData.cells[0], rowData.cells[1], rowData.credsBindpw, rowData.pwInteractive) + onClick: () => this.props.editConfig(rowData[0], rowData[1], rowData.credsBindpw, rowData.pwInteractive) }, { title: _("Delete Connection"), - onClick: (event, rowId, rowData, extra) => this.props.deleteConfig(rowData.cells[0]) + onClick: () => this.props.deleteConfig(rowData[0]) } ]; } render() { - let columns = this.state.columns; - let rows = []; - let has_rows = true; - if (this.props.rows.length === 0) { - has_rows = false; - rows = [{ cells: [_("No Credentials")] }]; - columns = [{ title: _("Credentials Table") }]; + const { columns } = this.state; + let tableRows = []; + let displayColumns = [...columns]; + const hasRows = this.props.rows.length > 0; + + if (!hasRows) { + tableRows = [[_("No Credentials")]]; + displayColumns = [{ title: _("Credentials Table") }]; } else { - for (let row of this.props.rows) { - row = JSON.parse(JSON.stringify(row)); // Deep copy - const pwInteractive = row.pwInputInterractive; + tableRows = this.props.rows.map(row => { + const rowCopy = JSON.parse(JSON.stringify(row)); // Deep copy + const pwInteractive = rowCopy.pwInputInterractive; let pwField = <i>{_("Interactive Input is set")}</i>; + if (!pwInteractive) { - if (row.credsBindpw === "") { + if (rowCopy.credsBindpw === "") { pwField = <i>{_("Both Password or Interactive Input flag are not set")}</i>; } else { pwField = "********"; } } - rows.push( - { - cells: [ - row.connData, - row.credsBinddn, - { title: pwField }, - ], - credsBindpw: row.credsBindpw, - pwInteractive, - } - ); - } + + const cells = [ + rowCopy.connData, + rowCopy.credsBinddn, + pwField + ]; + cells.credsBindpw = rowCopy.credsBindpw; + cells.pwInteractive = pwInteractive; + return cells; + }); } return ( @@ -1550,15 +1801,46 @@ class ReportCredentialsTable extends React.Component { </Text> </TextContent> <Table - variant={TableVariant.compact} aria-label="Cred Table" - sortBy={this.props.sortBy} onSort={this.props.handleSort} cells={columns} - rows={rows} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + aria-label="Cred Table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {displayColumns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={hasRows && column.sortable ? { + sortBy: this.props.sortBy, + onSort: this.props.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + : + <Td>{row}</Td> + } + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActions(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> </div> ); @@ -1574,10 +1856,13 @@ class ReportSingleTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Supplier"), transforms: [sortable], cellFormatters: [expandable] }, - { title: _("Agreement"), transforms: [sortable] }, - { title: _("Status"), transforms: [sortable] }, - { title: _("Lag"), transforms: [sortable] }, + { + title: _("Supplier"), + sortable: true + }, + { title: _("Agreement"), sortable: true }, + { title: _("Status"), sortable: true }, + { title: _("Lag"), sortable: true }, ], }; @@ -1703,7 +1988,6 @@ class ReportSingleTable extends React.Component { handleCollapse(event, rowKey, isOpen) { const { rows } = this.state; - // const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set rows[rowKey].isOpen = isOpen; this.setState({ rows @@ -1771,22 +2055,70 @@ class ReportSingleTable extends React.Component { } render() { - // This is an expandable list const { columns, rows, sortBy } = this.state; return ( <div className="ds-margin-top-xlg"> <Table - className="ds-margin-top" aria-label="Expandable table" - cells={columns} - rows={rows} - onCollapse={this.handleCollapse} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: columnIndex + 1 + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {rows.map((row, rowIndex) => { + if (row.parent !== undefined) { + // Expanded row + return ( + <Tr + key={rowIndex} + isExpanded={rows[row.parent].isOpen} + > + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + <ExpandableRowContent> + {row.cells[0].title} + </ExpandableRowContent> + </Td> + </Tr> + ); + } + // Regular row + return ( + <Tr key={rowIndex}> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ); + })} + </Tbody> </Table> </div> ); @@ -1800,10 +2132,10 @@ class ReportConsumersTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Agreement Name"), transforms: [sortable], cellFormatters: [expandable] }, - { title: _("Enabled"), transforms: [sortable] }, - { title: _("Status"), transforms: [sortable] }, - { title: _("Lag"), transforms: [sortable] }, + { title: _("Agreement Name"), sortable: true }, + { title: _("Enabled"), sortable: true }, + { title: _("Status"), sortable: true }, + { title: _("Lag"), sortable: true }, ], }; @@ -1926,7 +2258,6 @@ class ReportConsumersTable extends React.Component { handleCollapse(event, rowKey, isOpen) { const { rows } = this.state; - // const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set rows[rowKey].isOpen = isOpen; this.setState({ rows @@ -1993,22 +2324,69 @@ class ReportConsumersTable extends React.Component { } render() { - // This is an expandable list const { columns, rows, sortBy } = this.state; return ( <div className="ds-margin-top"> <Table className="ds-margin-top" aria-label="Expandable consumer table" - cells={columns} - rows={rows} - onCollapse={this.handleCollapse} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: columnIndex + 1 + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {rows.map((row, rowIndex) => { + if (row.parent !== undefined) { + return ( + <Tr + key={rowIndex} + isExpanded={rows[row.parent].isOpen} + > + <Td /> + <Td + colSpan={columns.length + 1} + noPadding + > + <ExpandableRowContent> + {row.cells[0].title} + </ExpandableRowContent> + </Td> + </Tr> + ); + } + return ( + <Tr key={rowIndex}> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {typeof cell === 'object' ? cell.title : cell} + </Td> + ))} + </Tr> + ); + })} + </Tbody> </Table> </div> ); @@ -2022,11 +2400,11 @@ class ReplDSRCTable extends React.Component { this.state = { sortBy: {}, columns: [ - { title: _("Name"), transforms: [sortable] }, - { title: _("Connection Data"), transforms: [sortable] }, - { title: _("Bind DN"), transforms: [sortable] }, - { title: _("Password"), transforms: [sortable] }, - { title: '' }, + { title: _("Name"), sortable: true }, + { title: _("Connection Data"), sortable: true }, + { title: _("Bind DN"), sortable: true }, + { title: _("Password"), sortable: true }, + { title: '', sortable: false, screenReaderText: _("Delete Button") } ], rows: [], }; @@ -2041,18 +2419,20 @@ class ReplDSRCTable extends React.Component { let cred = conn[4]; if (conn[4] === "*") { const desc = <i>Prompt</i>; - cred = { title: desc }; + cred = desc; } else if (!conn[4].startsWith("[")) { cred = "**********"; } - rows.push({ - cells: [ - conn[0], conn[1] + ":" + conn[2], conn[3], cred, { props: { textCenter: true }, title: this.props.getDeleteButton(conn[0]) } - ] - }); + rows.push([ + conn[0], + `${conn[1]}:${conn[2]}`, + conn[3], + cred, + <div className="pf-v5-u-text-align-center">{this.props.getDeleteButton(conn[0])}</div> + ]); } if (this.props.rows.length === 0) { - rows = [{ cells: [_("There is no saved replication monitor connections")] }]; + rows = [[_("There is no saved replication monitor connections")]]; columns = [{ title: _("Replication Connections") }]; } this.setState({ @@ -2061,12 +2441,14 @@ class ReplDSRCTable extends React.Component { }); } - handleSort(_event, index, direction) { - const rows = [...this.state.rows]; - const sortedRows = rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + handleSort(_event, columnIndex, direction) { + const sortedRows = [...this.state.rows].sort((a, b) => ( + a[columnIndex] < b[columnIndex] ? -1 : a[columnIndex] > b[columnIndex] ? 1 : 0 + )); + this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows: direction === SortByDirection.asc ? sortedRows : sortedRows.reverse() @@ -2078,16 +2460,36 @@ class ReplDSRCTable extends React.Component { return ( <div className="ds-margin-top-xlg"> - <Table + <Table aria-label="Sortable DSRC Table" - sortBy={sortBy} - onSort={this.handleSort} - cells={columns} - rows={rows} - variant={TableVariant.compact} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + screenReaderText={column.screenReaderText} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + </Tr> + ))} + </Tbody> </Table> </div> ); @@ -2101,9 +2503,9 @@ class ReplDSRCAliasTable extends React.Component { this.state = { sortBy: {}, columns: [ - { title: _("Alias"), transforms: [sortable] }, - { title: _("Connection Data"), transforms: [sortable] }, - { title: '' } + { title: _("Alias"), sortable: true }, + { title: _("Connection Data"), sortable: true }, + { title: '', sortable: false, screenReaderText: _("Delete Button") } ], rows: [], }; @@ -2115,14 +2517,14 @@ class ReplDSRCAliasTable extends React.Component { let rows = []; for (const alias of this.props.rows) { - rows.push({ - cells: [ - alias[0], alias[1] + ":" + alias[2], { props: { textCenter: true }, title: this.props.getDeleteButton(alias[0]) } - ] - }); + rows.push([ + alias[0], + alias[1] + ":" + alias[2], + <div className="pf-v5-u-text-align-center">{this.props.getDeleteButton(alias[0])}</div> + ]); } if (this.props.rows.length === 0) { - rows = [{ cells: [_("There are no saved replication monitor aliases")] }]; + rows = [[_("There are no saved replication monitor aliases")]]; columns = [{ title: _("Replication Monitoring Aliases") }]; } this.setState({ @@ -2131,12 +2533,14 @@ class ReplDSRCAliasTable extends React.Component { }); } - handleSort(_event, index, direction) { - const rows = [...this.state.rows]; - const sortedRows = rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + handleSort(_event, columnIndex, direction) { + const sortedRows = [...this.state.rows].sort((a, b) => ( + a[columnIndex] < b[columnIndex] ? -1 : a[columnIndex] > b[columnIndex] ? 1 : 0 + )); + this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows: direction === SortByDirection.asc ? sortedRows : sortedRows.reverse() @@ -2148,16 +2552,36 @@ class ReplDSRCAliasTable extends React.Component { return ( <div className="ds-margin-top-xlg"> - <Table + <Table aria-label="Sortable DSRC Table" - sortBy={sortBy} - onSort={this.handleSort} - cells={columns} - rows={rows} - variant={TableVariant.compact} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + screenReaderText={column.screenReaderText} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + </Tr> + ))} + </Tbody> </Table> </div> ); diff --git a/src/cockpit/389-console/src/lib/monitor/replMonConflict.jsx b/src/cockpit/389-console/src/lib/monitor/replMonConflict.jsx index eae90169d7..760ecb9ad7 100644 --- a/src/cockpit/389-console/src/lib/monitor/replMonConflict.jsx +++ b/src/cockpit/389-console/src/lib/monitor/replMonConflict.jsx @@ -387,8 +387,8 @@ export class ReplMonConflict extends React.Component { } render () { - const conflictEntries = this.props.data.conflicts; - const glueEntries = this.props.data.glues; + const conflictEntries = this.props.data?.conflicts || []; + const glueEntries = this.props.data?.glues || []; return ( <div> diff --git a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx index 442585ec61..656393a742 100644 --- a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx +++ b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx @@ -106,7 +106,7 @@ export class ReplMonitor extends React.Component { aliasSortBy: {}, }; - this.handleToggle = (isExpanded) => { + this.handleToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -1381,7 +1381,7 @@ export class ReplMonitor extends React.Component { <Tab eventKey={1} id="prepare-new-report" title={<TabTitleText>{_("Prepare New Report")}</TabTitleText>}> <ExpandableSection toggleText={this.state.isExpanded ? _("Hide Help") : _("Show Help")} - onToggle={this.handleToggle} + onToggle={(event, isExpanded) => this.handleToggle(event, isExpanded)} isExpanded={this.state.isExpanded} className="ds-margin-top-lg ds-left-margin" > diff --git a/src/cockpit/389-console/src/lib/monitor/securitylog.jsx b/src/cockpit/389-console/src/lib/monitor/securitylog.jsx index d0ed59302d..08ca6e3566 100644 --- a/src/cockpit/389-console/src/lib/monitor/securitylog.jsx +++ b/src/cockpit/389-console/src/lib/monitor/securitylog.jsx @@ -102,7 +102,7 @@ export class SecurityLogMonitor extends React.Component { if (this.state.securityReloading) { spinner = ( <div> - <Spinner isSVG size="sm" /> + <Spinner size="sm" /> {_("Reloading security log...")} </div> ); @@ -134,7 +134,7 @@ export class SecurityLogMonitor extends React.Component { <FormSelect id="securityLines" value={this.state.securityLines} - onChange={(value, event) => { + onChange={(event, value) => { this.handleSecurityChange(event); }} aria-label="FormSelect Input" @@ -157,7 +157,7 @@ export class SecurityLogMonitor extends React.Component { <Checkbox id="securityRefreshing" isChecked={this.state.securityRefreshing} - onChange={(checked, e) => { this.securityRefreshCont(e) }} + onChange={(e, checked) => { this.securityRefreshCont(e) }} label={_("Continuously Refresh")} /> </div> diff --git a/src/cockpit/389-console/src/lib/notifications.jsx b/src/cockpit/389-console/src/lib/notifications.jsx index 089feb2236..cb675dec22 100644 --- a/src/cockpit/389-console/src/lib/notifications.jsx +++ b/src/cockpit/389-console/src/lib/notifications.jsx @@ -84,7 +84,7 @@ export class DoubleConfirmModal extends React.Component { <Checkbox id="modalChecked" isChecked={checked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={<><b>{_("Yes")}</b>{_(", I am sure.")}</>} diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx index 79e6626fc8..f7a08945df 100644 --- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx +++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx @@ -1,20 +1,22 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Form, - FormHelperText, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - TextInput, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormHelperText, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import { log_cmd, valid_dn } from "../tools.jsx"; @@ -116,7 +118,7 @@ class AccountPolicy extends React.Component { }, () => { this.validateConfig() }); } }; - this.handleRecordLoginToggle = isRecordLoginOpen => { + this.handleRecordLoginToggle = (_event, isRecordLoginOpen) => { this.setState({ isRecordLoginOpen }); @@ -139,7 +141,7 @@ class AccountPolicy extends React.Component { }, () => { this.validateConfig() }); } }; - this.handleSpecificAttrToggle = isSpecificAttrOpen => { + this.handleSpecificAttrToggle = (_event, isSpecificAttrOpen) => { this.setState({ isSpecificAttrOpen }); @@ -162,7 +164,7 @@ class AccountPolicy extends React.Component { }, () => { this.validateConfig() }); } }; - this.handleStateAttrToggle = isStateAttrOpen => { + this.handleStateAttrToggle = (_event, isStateAttrOpen) => { this.setState({ isStateAttrOpen }); @@ -185,7 +187,7 @@ class AccountPolicy extends React.Component { }, () => { this.validateConfig() }); } }; - this.handleAlternativeStateToggle = isAltStateAttrOpen => { + this.handleAlternativeStateToggle = (_event, isAltStateAttrOpen) => { this.setState({ isAltStateAttrOpen }); @@ -208,7 +210,7 @@ class AccountPolicy extends React.Component { }, () => { this.validateConfig() }); } }; - this.handleLimitAttrToggle = isLimitAttrOpen => { + this.handleLimitAttrToggle = (_event, isLimitAttrOpen) => { this.setState({ isLimitAttrOpen }); @@ -613,7 +615,7 @@ class AccountPolicy extends React.Component { } } - handleCheckboxChange(checked, e) { + handleCheckboxChange(e, checked) { this.setState({ [e.target.id]: checked }, () => { this.validateConfig() }); @@ -859,11 +861,11 @@ class AccountPolicy extends React.Component { id="configDN" aria-describedby="horizontal-form-name-helper" name="configDN" - onChange={(str, e) => { this.handleModalChange(e) }} + onChange={(e, str) => { this.handleModalChange(e) }} validated={errorModal.configDN ? ValidatedOptions.error : ValidatedOptions.default} isDisabled /> - <FormHelperText isError isHidden={!errorModal.configDN}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -876,7 +878,7 @@ class AccountPolicy extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel={_("Type an attribute name")} - onToggle={this.handleRecordLoginToggle} + onToggle={(event, isOpen) => this.handleRecordLoginToggle(event, isOpen)} onSelect={this.handleRecordLoginSelect} onClear={this.handleRecordLoginClear} selections={alwaysRecordLoginAttr} @@ -899,7 +901,7 @@ class AccountPolicy extends React.Component { className="ds-left-margin" isChecked={alwaysRecordLogin} title={_("Sets that every entry records its last login time (alwaysRecordLogin)")} - onChange={this.handleCheckboxChange} + onChange={(e, checked) => this.handleCheckboxChange(e, checked)} label={_("Always Record Login")} /> </GridItem> @@ -912,7 +914,7 @@ class AccountPolicy extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel={_("Type an attribute name")} - onToggle={this.handleSpecificAttrToggle} + onToggle={(event, isOpen) => this.handleSpecificAttrToggle(event, isOpen)} onSelect={this.handleSpecificAttrSelect} onClear={this.handleSpecificAttrClear} selections={specAttrName} @@ -938,7 +940,7 @@ class AccountPolicy extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel={_("Type an attribute name")} - onToggle={this.handleLimitAttrToggle} + onToggle={(event, isOpen) => this.handleLimitAttrToggle(event, isOpen)} onSelect={this.handleLimitAttrSelect} onClear={this.handleLimitAttrClear} selections={limitAttrName} @@ -964,7 +966,7 @@ class AccountPolicy extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel={_("Type an attribute name")} - onToggle={this.handleStateAttrToggle} + onToggle={(event, isOpen) => this.handleStateAttrToggle(event, isOpen)} onSelect={this.handleStateAttrSelect} onClear={this.handleStateAttrClear} selections={stateAttrName} @@ -991,7 +993,7 @@ class AccountPolicy extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel={_("Type an attribute name")} - onToggle={this.handleAlternativeStateToggle} + onToggle={(event, isOpen) => this.handleAlternativeStateToggle(event, isOpen)} onSelect={this.handleAlternativeStateSelect} onClear={this.handleAlternativeStateClear} selections={altStateAttrName} @@ -1019,7 +1021,7 @@ class AccountPolicy extends React.Component { id="checkAllStateAttrs" className="ds-left-margin" isChecked={checkAllStateAttrs} - onChange={this.handleCheckboxChange} + onChange={(e, checked) => this.handleCheckboxChange(e, checked)} label={_("Check Both - State and Alternative State Attributes")} /> </GridItem> @@ -1050,10 +1052,10 @@ class AccountPolicy extends React.Component { id="configArea" aria-describedby="horizontal-form-name-helper" name="configArea" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={error.configArea ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.configArea}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> diff --git a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx index 0e998a3ede..8ac1d10e8a 100644 --- a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx +++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx @@ -1,22 +1,24 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - TextInput, - Switch, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + Switch, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import { AttrUniqConfigTable } from "./pluginTables.jsx"; import { DoubleConfirmModal } from "../notifications.jsx"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; @@ -105,7 +107,7 @@ class AttributeUniqueness extends React.Component { ); } }; - this.handleAttributeNameToggle = isAttributeNameOpen => { + this.handleAttributeNameToggle = (_event, isAttributeNameOpen) => { this.setState({ isAttributeNameOpen }, () => { this.validateConfig() }); @@ -135,7 +137,7 @@ class AttributeUniqueness extends React.Component { ); } }; - this.handleSubtreesToggle = isSubtreesOpen => { + this.handleSubtreesToggle = (_event, isSubtreesOpen) => { this.setState({ isSubtreesOpen }, () => { this.validateConfig() }); @@ -653,7 +655,7 @@ class AttributeUniqueness extends React.Component { aria-describedby="horizontal-form-name-helper" name="configName" isDisabled={!newEntry} - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={this.state.error.configName || this.state.configName === "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -668,7 +670,7 @@ class AttributeUniqueness extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleAttributeNameToggle} + onToggle={(event, isOpen) => this.handleAttributeNameToggle(event, isOpen)} onSelect={this.handleAttributeNameSelect} onClear={this.handleAttributeNameClear} selections={attrNames} @@ -695,7 +697,7 @@ class AttributeUniqueness extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a subtree DN" - onToggle={this.handleSubtreesToggle} + onToggle={(event, isOpen) => this.handleSubtreesToggle(event, isOpen)} onSelect={this.handleSubtreesSelect} onClear={this.handleSubtreesClear} selections={subtrees} @@ -724,7 +726,7 @@ class AttributeUniqueness extends React.Component { <FormSelect id="topEntryOc" value={topEntryOc} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" @@ -741,7 +743,7 @@ class AttributeUniqueness extends React.Component { className="ds-left-margin" isChecked={acrossAllSubtrees} title={_("If enabled (on), the plug-in checks that the attribute is unique across all subtrees set. If you set the attribute to off, uniqueness is only enforced within the subtree of the updated entry (uniqueness-across-all-subtrees)")} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} label={_("Across All Subtrees")} /> </GridItem> @@ -754,7 +756,7 @@ class AttributeUniqueness extends React.Component { <FormSelect id="subtreeEnriesOc" value={subtreeEnriesOc} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" @@ -776,7 +778,7 @@ class AttributeUniqueness extends React.Component { label={_("Configuration is enabled")} labelOff={_("Configuration is disabled")} isChecked={configEnabled} - onChange={this.handleSwitchChange} + onChange={(_event, value) => this.handleSwitchChange(value)} /> </GridItem> </Grid> diff --git a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx index 0818b33421..09367dffeb 100644 --- a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx +++ b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx @@ -1,23 +1,25 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectOption, - SelectVariant, - TextInput, - Text, - TextContent, - TextVariants, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + Text, + TextContent, + TextVariants, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import { ArrowRightIcon, } from '@patternfly/react-icons'; @@ -124,7 +126,7 @@ class AutoMembership extends React.Component { }); } }; - this.handleRegexExcludeToggle = isRegexExcludeOpen => { + this.handleRegexExcludeToggle = (_event, isRegexExcludeOpen) => { this.setState({ isRegexExcludeOpen }); @@ -162,7 +164,7 @@ class AutoMembership extends React.Component { }); } }; - this.handleRegexIncludeToggle = isRegexIncludeOpen => { + this.handleRegexIncludeToggle = (_event, isRegexIncludeOpen) => { this.setState({ isRegexIncludeOpen }); @@ -1105,7 +1107,7 @@ class AutoMembership extends React.Component { id="definitionName" aria-describedby="horizontal-form-name-helper" name="definitionName" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} isDisabled={!newDefinitionEntry} @@ -1124,7 +1126,7 @@ class AutoMembership extends React.Component { id="scope" aria-describedby="horizontal-form-name-helper" name="scope" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={this.state.error.scope || scope === "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -1142,7 +1144,7 @@ class AutoMembership extends React.Component { id="filter" aria-describedby="horizontal-form-name-helper" name="filter" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={this.state.error.filter || filter === "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -1157,7 +1159,7 @@ class AutoMembership extends React.Component { <FormSelect id="groupingAttrMember" value={groupingAttrMember} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" @@ -1174,7 +1176,7 @@ class AutoMembership extends React.Component { <FormSelect id="groupingAttrEntry" value={groupingAttrEntry} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" @@ -1196,7 +1198,7 @@ class AutoMembership extends React.Component { id="defaultGroup" aria-describedby="horizontal-form-name-helper" name="defaultGroup" - onChange={(str, e) => { + onChange={(e, str) => { this.handleFieldChange(e); }} validated={this.state.error.defaultGroup ? ValidatedOptions.error : ValidatedOptions.default} @@ -1265,7 +1267,7 @@ class AutoMembership extends React.Component { id="regexName" aria-describedby="horizontal-form-name-helper" name="regexName" - onChange={(str, e) => { + onChange={(e, str) => { this.handleRegexChange(e); }} isDisabled={!newRegexEntry} @@ -1281,7 +1283,7 @@ class AutoMembership extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a regex" - onToggle={this.handleRegexExcludeToggle} + onToggle={(event, isOpen) => this.handleRegexExcludeToggle(event, isOpen)} onSelect={this.handleRegexExcludeSelect} onClear={this.handleClearRegexExcludeSelection} selections={regexExclusive} @@ -1308,7 +1310,7 @@ class AutoMembership extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a regex" - onToggle={this.handleRegexIncludeToggle} + onToggle={(event, isOpen) => this.handleRegexIncludeToggle(event, isOpen)} onSelect={this.handleRegexIncludeSelect} onClear={this.handleClearRegexIncludeSelection} selections={regexInclusive} @@ -1338,7 +1340,7 @@ class AutoMembership extends React.Component { id="regexTargetGroup" aria-describedby="horizontal-form-name-helper" name="regexTargetGroup" - onChange={(str, e) => { + onChange={(e, str) => { this.handleRegexChange(e); }} validated={this.state.errorRegex.regexTargetGroup ? ValidatedOptions.error : ValidatedOptions.default} diff --git a/src/cockpit/389-console/src/lib/plugins/dna.jsx b/src/cockpit/389-console/src/lib/plugins/dna.jsx index 02d594b156..f4e2abeb44 100644 --- a/src/cockpit/389-console/src/lib/plugins/dna.jsx +++ b/src/cockpit/389-console/src/lib/plugins/dna.jsx @@ -1,29 +1,31 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - NumberInput, - Select, - SelectOption, - SelectVariant, - Spinner, - Tab, - Tabs, - TabTitleText, - TextInput, - Text, - TextContent, - TextVariants, - Tooltip, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + NumberInput, + Spinner, + Tab, + Tabs, + TabTitleText, + TextInput, + Text, + TextContent, + TextVariants, + Tooltip, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import { DNATable, DNASharedTable } from "./pluginTables.jsx"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import PropTypes from "prop-types"; @@ -88,7 +90,7 @@ class DNAPlugin extends React.Component { saveBtnDisabled: true, }; - this.handleToggle = isOpen => { + this.handleToggle = (_event, isOpen) => { this.setState({ isOpen }); @@ -220,7 +222,7 @@ class DNAPlugin extends React.Component { }); } - handleFieldChange(str, e) { + handleFieldChange(e, str) { this.setState({ [e.target.id]: e.target.value, }, () => { this.validateConfig() }); @@ -1076,7 +1078,7 @@ class DNAPlugin extends React.Component { id="configName" aria-describedby="configName" name="configName" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} isDisabled={!newEntry} validated={error.configName ? ValidatedOptions.error : ValidatedOptions.default} /> @@ -1090,7 +1092,7 @@ class DNAPlugin extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleToggle} + onToggle={(event, isOpen) => this.handleToggle(event, isOpen)} onSelect={this.handleSelect} onClear={this.handleClearSelection} selections={selected} @@ -1119,7 +1121,7 @@ class DNAPlugin extends React.Component { id="filter" aria-describedby="filter" name="filter" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} validated={error.filter ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -1135,7 +1137,7 @@ class DNAPlugin extends React.Component { id="scope" aria-describedby="scope" name="scope" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} validated={error.scope ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -1191,7 +1193,7 @@ class DNAPlugin extends React.Component { id="magicRegen" aria-describedby="magicRegen" name="magicRegen" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} validated={error.magicRegen ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -1209,7 +1211,7 @@ class DNAPlugin extends React.Component { aria-describedby={content.name} name={content.name} key={content.name} - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} validated={error[content.id] ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -1305,7 +1307,7 @@ class DNAPlugin extends React.Component { id="sharedConfigEntry" aria-describedby="sharedConfigEntry" name="sharedConfigEntry" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} validated={error.sharedConfigEntry ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -1386,7 +1388,7 @@ class DNAPlugin extends React.Component { <FormSelect id="sharedRemoteBindMethod" value={sharedRemoteBindMethod} - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} aria-label="FormSelect Input" validated={sharedResult.validatedBindMethod} > @@ -1403,7 +1405,7 @@ class DNAPlugin extends React.Component { <FormSelect id="sharedRemoteConnProtocol" value={sharedRemoteConnProtocol} - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(e, str)} aria-label="FormSelect Input" validated={sharedResult.validatedConnProtocol} > diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx index c7c6e10379..5a6bfb5f77 100644 --- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx +++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx @@ -1,18 +1,20 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Form, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - TextInput, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Form, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import { LinkedAttributesTable } from "./pluginTables.jsx"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import PropTypes from "prop-types"; @@ -72,7 +74,7 @@ class LinkedAttributes extends React.Component { ); } }; - this.handleLinkTypeToggle = isLinkTypeOpen => { + this.handleLinkTypeToggle = (_event, isLinkTypeOpen) => { this.setState({ isLinkTypeOpen }); @@ -102,7 +104,7 @@ class LinkedAttributes extends React.Component { ); } }; - this.handleManagedTypeToggle = isManagedTypeOpen => { + this.handleManagedTypeToggle = (_event, isManagedTypeOpen) => { this.setState({ isManagedTypeOpen }); @@ -475,7 +477,7 @@ class LinkedAttributes extends React.Component { id="configName" aria-describedby="horizontal-form-name-helper" name="configName" - onChange={(str, e) => { + onChange={(e, str) => { this.onFieldChange(e); }} validated={error.configName ? ValidatedOptions.error : ValidatedOptions.default} @@ -491,7 +493,7 @@ class LinkedAttributes extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type an attribute name" - onToggle={this.handleLinkTypeToggle} + onToggle={(event, isOpen) => this.handleLinkTypeToggle(event, isOpen)} onSelect={this.handleLinkTypeSelect} onClear={this.handleLinkTypeClear} selections={linkType} @@ -517,7 +519,7 @@ class LinkedAttributes extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type an attribute name" - onToggle={this.handleManagedTypeToggle} + onToggle={(event, isOpen) => this.handleManagedTypeToggle(event, isOpen)} onSelect={this.handleManagedTypeSelect} onClear={this.handleManagedTypeClear} selections={managedType} @@ -546,7 +548,7 @@ class LinkedAttributes extends React.Component { id="linkScope" aria-describedby="horizontal-form-name-helper" name="linkScope" - onChange={(str, e) => { + onChange={(e, str) => { this.onFieldChange(e); }} validated={error.linkScope ? ValidatedOptions.error : ValidatedOptions.default} diff --git a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx index 2a7632354b..a62c2de1c0 100644 --- a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx +++ b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx @@ -1,29 +1,37 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Form, - FormGroup, - FormSelect, - FormSelectOption, - Modal, - ModalVariant, - Select, - SelectOption, - SelectVariant, - SimpleList, - SimpleListItem, - Spinner, - Tab, - Tabs, - TabTitleText, - Tooltip, - TextInput, - Text, - TextContent, - TextVariants, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Form, + FormGroup, + FormSelect, + FormSelectOption, + FormHelperText, + HelperText, + HelperTextItem, + Modal, + ModalVariant, + SimpleList, + SimpleListItem, + Spinner, + Tab, + Tabs, + TabTitleText, + Tooltip, + TextInput, + Text, + TextContent, + TextVariants, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; +import { + ExclamationCircleIcon, +} from '@patternfly/react-icons'; import { ManagedDefinitionTable, ManagedTemplateTable } from "./pluginTables.jsx"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; @@ -108,7 +116,7 @@ class ManagedEntries extends React.Component { isRDNOpen: false }); }; - this.handleRDNToggle = isRDNOpen => { + this.handleRDNToggle = (_event, isRDNOpen) => { this.setState({ isRDNOpen }); @@ -128,7 +136,7 @@ class ManagedEntries extends React.Component { }); }; - this.handleStaticToggle = isStaticOpen => { + this.handleStaticToggle = (_event, isStaticOpen) => { this.setState({ isStaticOpen }); @@ -155,7 +163,7 @@ class ManagedEntries extends React.Component { }); }; - this.handleMappedToggle = isMappedOpen => { + this.handleMappedToggle = (_event, isMappedOpen) => { this.setState({ isMappedOpen }); @@ -976,6 +984,11 @@ class ManagedEntries extends React.Component { templateAddMappedShow, } = this.state; + const isOriginScopeValidated = originScope !== "" && !valid_dn(originScope); + const isManagedBaseValidated = managedBase !== "" && !valid_dn(managedBase); + const isConfigAreaValidated = configArea !== "" && !valid_dn(configArea); + const isTemplateDNValidated = templateDN !== "" && !valid_dn(templateDN); + const specificPluginCMD = [ "dsconf", "-j", @@ -1027,7 +1040,7 @@ class ManagedEntries extends React.Component { id="configName" aria-describedby="configName" name="configName" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(str, e)} isDisabled={!newDefEntry} isRequired /> @@ -1035,12 +1048,6 @@ class ManagedEntries extends React.Component { <FormGroup label={_("Subtree Scope")} fieldId="originScope" - helperTextInvalid={_("A valid DN must be provided")} - validated={ - originScope !== "" && !valid_dn(originScope) - ? ValidatedOptions.error - : ValidatedOptions.default - } title={_("Sets the search base DN to use to find candidate entries (originScope)")} isRequired > @@ -1050,14 +1057,20 @@ class ManagedEntries extends React.Component { id="originScope" aria-describedby="originScope" name="originScope" - onChange={this.handleFieldChange} - validated={ - originScope !== "" && !valid_dn(originScope) + onChange={(e, str) => this.handleFieldChange(str, e)} + validated={isOriginScopeValidated ? ValidatedOptions.error : ValidatedOptions.default } isRequired /> + <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={isOriginScopeValidated ? 'error' : 'default'}> + {isOriginScopeValidated ? 'A valid DN must be provided' : 'Enter DN'} + </HelperTextItem> + </HelperText> + </FormHelperText> </FormGroup> <FormGroup label={_("Filter")} @@ -1071,19 +1084,13 @@ class ManagedEntries extends React.Component { id="originFilter" aria-describedby="originFilter" name="originFilter" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(str, e)} isRequired /> </FormGroup> <FormGroup label={_("Managed Base")} fieldId="managedBase" - helperTextInvalid={_("A valid DN must be provided")} - validated={ - managedBase !== "" && !valid_dn(managedBase) - ? ValidatedOptions.error - : ValidatedOptions.default - } title={_("Sets the subtree where the managed entries are created (managedBase)")} isRequired > @@ -1093,14 +1100,20 @@ class ManagedEntries extends React.Component { id="managedBase" aria-describedby="managedBase" name="managedBase" - onChange={this.handleFieldChange} - validated={ - managedBase !== "" && !valid_dn(managedBase) + onChange={(e, str) => this.handleFieldChange(str, e)} + validated={isManagedBaseValidated ? ValidatedOptions.error : ValidatedOptions.default } isRequired /> + <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={isManagedBaseValidated ? 'error' : 'default'}> + {isManagedBaseValidated ? 'A valid DN must be provided' : 'Enter DN'} + </HelperTextItem> + </HelperText> + </FormHelperText> </FormGroup> <FormGroup label={_("Template")} @@ -1111,7 +1124,7 @@ class ManagedEntries extends React.Component { <FormSelect id="managedTemplate" value={managedTemplate} - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(str, e)} aria-label="FormSelect Input" > {templateOptions.map((option, index) => ( @@ -1146,12 +1159,6 @@ class ManagedEntries extends React.Component { <FormGroup label={_("Template DN")} fieldId="templateDN" - helperTextInvalid={_("The template DN must be set to a valid DN")} - validated={ - templateDN !== "" && !valid_dn(templateDN) - ? ValidatedOptions.error - : ValidatedOptions.default - } title={_("DN of the template entry")} isRequired > @@ -1161,14 +1168,20 @@ class ManagedEntries extends React.Component { id="templateDN" aria-describedby="templateDN" name="templateDN" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(str, e)} isDisabled={!newTemplateEntry} - validated={ - templateDN !== "" && !valid_dn(templateDN) + validated={isTemplateDNValidated ? ValidatedOptions.error : ValidatedOptions.default } /> + <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={isTemplateDNValidated ? 'error' : 'default'}> + {isTemplateDNValidated ? 'The template DN must be set to a valid DN' : 'Enter DN'} + </HelperTextItem> + </HelperText> + </FormHelperText> </FormGroup> <FormGroup label={_("RDN Attribute")} @@ -1179,7 +1192,7 @@ class ManagedEntries extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleRDNToggle} + onToggle={(event, isOpen) => this.handleRDNToggle(event, isOpen)} onSelect={this.handleRDNSelect} onClear={this.handleClearRDNSelection} selections={templateRDNAttr} @@ -1313,7 +1326,7 @@ class ManagedEntries extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleStaticToggle} + onToggle={(event, isOpen) => this.handleStaticToggle(event, isOpen)} onSelect={this.handleStaticSelect} onClear={this.handleClearStaticSelection} selections={this.state.staticAttr} @@ -1341,7 +1354,7 @@ class ManagedEntries extends React.Component { id="staticValue" aria-describedby="staticValue" name="staticValue" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(str, e)} isRequired /> </FormGroup> @@ -1378,7 +1391,7 @@ class ManagedEntries extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleMappedToggle} + onToggle={(event, isOpen) => this.handleMappedToggle(event, isOpen)} onSelect={this.handleMappedSelect} onClear={this.handleClearMappedSelection} selections={this.state.mappedAttr} @@ -1406,7 +1419,7 @@ class ManagedEntries extends React.Component { id="mappedValue" aria-describedby="mappedValue" name="mappedValue" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(str, e)} /> </FormGroup> <hr /> @@ -1509,12 +1522,6 @@ class ManagedEntries extends React.Component { <FormGroup label={_("Shared Config Area")} fieldId="configArea" - helperTextInvalid={_("The DN for the shared conifig area is invalid")} - validated={ - configArea !== "" && !valid_dn(configArea) - ? ValidatedOptions.error - : ValidatedOptions.default - } > <TextInput value={configArea} @@ -1522,8 +1529,18 @@ class ManagedEntries extends React.Component { id="configArea" aria-describedby="configArea" name="configArea" - onChange={this.handleFieldChange} + onChange={(e, str) => this.handleFieldChange(str, e)} + validated={isConfigAreaValidated + ? ValidatedOptions.error + : ValidatedOptions.default} /> + <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={isManagedBaseValidated ? 'error' : 'default'}> + {isConfigAreaValidated ? 'The DN for the shared conifig area is invalid' : 'Enter DN'} + </HelperTextItem> + </HelperText> + </FormHelperText> </FormGroup> </Form> </div> diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx index c39bb31490..afddf103c8 100644 --- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx +++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx @@ -1,25 +1,27 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Form, - FormHelperText, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - TextInput, - Text, - TextContent, - TextVariants, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormHelperText, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + Text, + TextContent, + TextVariants, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import { DoubleConfirmModal } from "../notifications.jsx"; @@ -126,7 +128,7 @@ class MemberOf extends React.Component { }, () => { this.validateModal() }); } }; - this.handleConfigAttrToggle = isConfigAttrOpen => { + this.handleConfigAttrToggle = (_event, isConfigAttrOpen) => { this.setState({ isConfigAttrOpen }); @@ -156,7 +158,7 @@ class MemberOf extends React.Component { ); } }; - this.handleConfigGroupAttrToggle = isConfigGroupAttrOpen => { + this.handleConfigGroupAttrToggle = (_event, isConfigGroupAttrOpen) => { this.setState({ isConfigGroupAttrOpen }); @@ -179,7 +181,7 @@ class MemberOf extends React.Component { }, () => { this.validateModal() }); } }; - this.handleMemberOfAttrToggle = isMemberOfAttrOpen => { + this.handleMemberOfAttrToggle = (_event, isMemberOfAttrOpen) => { this.setState({ isMemberOfAttrOpen }); @@ -209,7 +211,7 @@ class MemberOf extends React.Component { ); } }; - this.handleMemberOfGroupAttrToggle = isMemberOfGroupAttrOpen => { + this.handleMemberOfGroupAttrToggle = (_event, isMemberOfGroupAttrOpen) => { this.setState({ isMemberOfGroupAttrOpen }); @@ -239,7 +241,7 @@ class MemberOf extends React.Component { ); } }; - this.handleSubtreeScopeToggle = isSubtreeScopeOpen => { + this.handleSubtreeScopeToggle = (_event, isSubtreeScopeOpen) => { this.setState({ isSubtreeScopeOpen }, () => { this.validateConfig() }); @@ -277,7 +279,7 @@ class MemberOf extends React.Component { ); } }; - this.handleExcludeScopeToggle = isExcludeScopeOpen => { + this.handleExcludeScopeToggle = (_event, isExcludeScopeOpen) => { this.setState({ isExcludeScopeOpen }, () => { this.validateConfig() }); @@ -316,7 +318,7 @@ class MemberOf extends React.Component { ); } }; - this.handleConfigScopeToggle = isConfigSubtreeScopeOpen => { + this.handleConfigScopeToggle = (_event, isConfigSubtreeScopeOpen) => { this.setState({ isConfigSubtreeScopeOpen }, () => { this.validateModal() }); @@ -354,7 +356,7 @@ class MemberOf extends React.Component { ); } }; - this.handleConfigExcludeScopeToggle = isConfigExcludeScopeOpen => { + this.handleConfigExcludeScopeToggle = (_event, isConfigExcludeScopeOpen) => { this.setState({ isConfigExcludeScopeOpen }, () => { this.validateModal() }); @@ -1235,10 +1237,10 @@ class MemberOf extends React.Component { id="fixupDN" aria-describedby="horizontal-form-name-helper" name="fixupDN" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={!valid_dn(fixupDN) ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={valid_dn(fixupDN)}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -1254,7 +1256,7 @@ class MemberOf extends React.Component { id="fixupFilter" aria-describedby="horizontal-form-name-helper" name="fixupFilter" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} /> </GridItem> </Grid> @@ -1282,11 +1284,11 @@ class MemberOf extends React.Component { id="configDN" aria-describedby="horizontal-form-name-helper" name="configDN" - onChange={(str, e) => { this.handleModalChange(e) }} + onChange={(e, str) => { this.handleModalChange(e) }} validated={errorModal.configDN ? ValidatedOptions.error : ValidatedOptions.default} isDisabled={newEntry} /> - <FormHelperText isError isHidden={!errorModal.configDN}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -1299,7 +1301,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type a member attribute" - onToggle={this.handleConfigAttrToggle} + onToggle={(event, isOpen) => this.handleConfigAttrToggle(event, isOpen)} onSelect={this.handleConfigAttrSelect} onClear={this.handleConfigAttrClear} selections={configAttr} @@ -1326,7 +1328,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a member group attribute" - onToggle={this.handleConfigGroupAttrToggle} + onToggle={(event, isOpen) => this.handleConfigGroupAttrToggle(event, isOpen)} onSelect={this.handleConfigGroupAttrSelect} onClear={this.handleConfigGroupAttrClear} selections={configGroupAttr} @@ -1353,7 +1355,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a subtree DN" - onToggle={this.handleConfigScopeToggle} + onToggle={(event, isOpen) => this.handleConfigScopeToggle(event, isOpen)} onSelect={this.handleConfigScopeSelect} onClear={this.handleConfigScopeClear} selections={configEntryScope} @@ -1372,7 +1374,7 @@ class MemberOf extends React.Component { /> ))} </Select> - <FormHelperText isError isHidden={!errorModal.configEntryScope}> + <FormHelperText > {_("Values must be valid DN's")} </FormHelperText> </GridItem> @@ -1380,7 +1382,7 @@ class MemberOf extends React.Component { <Checkbox id="configAllBackends" isChecked={configAllBackends} - onChange={(checked, e) => { this.handleModalChange(e) }} + onChange={(e, checked) => { this.handleModalChange(e) }} title={_("Specifies whether to search the local suffix for user entries on all available suffixes (memberOfAllBackends)")} label={_("All Backends")} /> @@ -1394,7 +1396,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a subtree DN" - onToggle={this.handleConfigExcludeScopeToggle} + onToggle={(event, isOpen) => this.handleConfigExcludeScopeToggle(event, isOpen)} onSelect={this.handleConfigExcludeScopeSelect} onClear={this.handleConfigExcludeScopeClear} selections={configEntryScopeExcludeSubtree} @@ -1413,7 +1415,7 @@ class MemberOf extends React.Component { /> ))} </Select> - <FormHelperText isError isHidden={!errorModal.configEntryScopeExcludeSubtree}> + <FormHelperText > {_("Values must be valid DN's")} </FormHelperText> </GridItem> @@ -1421,7 +1423,7 @@ class MemberOf extends React.Component { <Checkbox id="configSkipNested" isChecked={configSkipNested} - onChange={(checked, e) => { this.handleModalChange(e) }} + onChange={(e, checked) => { this.handleModalChange(e) }} title={_("Specifies wherher to skip nested groups or not (memberOfSkipNested)")} label={_("Skip Nested")} /> @@ -1435,7 +1437,7 @@ class MemberOf extends React.Component { <FormSelect id="configAutoAddOC" value={configAutoAddOC} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" @@ -1469,7 +1471,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type a member attribute" - onToggle={this.handleMemberOfAttrToggle} + onToggle={(event, isOpen) => this.handleMemberOfAttrToggle(event, isOpen)} onSelect={this.handleMemberOfAttrSelect} onClear={this.handleMemberOfAttrClear} selections={memberOfAttr} @@ -1496,7 +1498,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a member group attribute" - onToggle={this.handleMemberOfGroupAttrToggle} + onToggle={(event, isOpen) => this.handleMemberOfGroupAttrToggle(event, isOpen)} onSelect={this.handleMemberOfGroupAttrSelect} onClear={this.handleMemberOfGroupAttrClear} selections={memberOfGroupAttr} @@ -1523,7 +1525,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a subtree DN" - onToggle={this.handleSubtreeScopeToggle} + onToggle={(event, isOpen) => this.handleSubtreeScopeToggle(event, isOpen)} onSelect={this.handleSubtreeScopeSelect} onClear={this.handleSubtreeScopeClear} selections={memberOfEntryScope} @@ -1542,7 +1544,7 @@ class MemberOf extends React.Component { /> ))} </Select> - <FormHelperText isError isHidden={!error.memberOfEntryScope}> + <FormHelperText > {_("A subtree is required, and values must be valid DN's")} </FormHelperText> </GridItem> @@ -1550,7 +1552,7 @@ class MemberOf extends React.Component { <Checkbox id="memberOfAllBackends" isChecked={memberOfAllBackends} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} title={_("Specifies whether to search the local suffix for user entries on all available suffixes (memberOfAllBackends)")} label={_("All Backends")} /> @@ -1564,7 +1566,7 @@ class MemberOf extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a subtree DN" - onToggle={this.handleExcludeScopeToggle} + onToggle={(event, isOpen) => this.handleExcludeScopeToggle(event, isOpen)} onSelect={this.handleExcludeScopeSelect} onClear={this.handleExcludeScopeClear} selections={memberOfEntryScopeExcludeSubtree} @@ -1583,7 +1585,7 @@ class MemberOf extends React.Component { /> ))} </Select> - <FormHelperText isError isHidden={!error.memberOfEntryScopeExcludeSubtree}> + <FormHelperText > {_("Values must be valid DN's")} </FormHelperText> </GridItem> @@ -1591,7 +1593,7 @@ class MemberOf extends React.Component { <Checkbox id="memberOfSkipNested" isChecked={memberOfSkipNested} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} title={_("Specifies wherher to skip nested groups or not (memberOfSkipNested)")} label={_("Skip Nested")} /> @@ -1608,10 +1610,10 @@ class MemberOf extends React.Component { id="memberOfConfigEntry" aria-describedby="horizontal-form-name-helper" name="memberOfConfigEntry" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={error.memberOfConfigEntry ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.memberOfConfigEntry}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -1633,7 +1635,7 @@ class MemberOf extends React.Component { <FormSelect id="memberOfAutoAddOC" value={memberOfAutoAddOC} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx b/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx index 3ba2ee4138..9908694a97 100644 --- a/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx +++ b/src/cockpit/389-console/src/lib/plugins/pamPassThru.jsx @@ -1,21 +1,23 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectOption, - SelectVariant, - TextInput, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import { PassthroughAuthConfigsTable } from "./pluginTables.jsx"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import PropTypes from "prop-types"; @@ -66,7 +68,7 @@ class PAMPassthroughAuthentication extends React.Component { pamConfigEntryModalShow: false, }; - this.handleExcludeToggle = isExcludeOpen => { + this.handleExcludeToggle = (_event, isExcludeOpen) => { this.setState({ isExcludeOpen }); @@ -103,7 +105,7 @@ class PAMPassthroughAuthentication extends React.Component { } }; - this.handleIncludeToggle = isIncludeOpen => { + this.handleIncludeToggle = (_event, isIncludeOpen) => { this.setState({ isIncludeOpen }); @@ -686,7 +688,7 @@ class PAMPassthroughAuthentication extends React.Component { id="pamConfigName" aria-describedby="horizontal-form-name-helper" name="pamConfigName" - onChange={(str, e) => { + onChange={(e, str) => { this.handlePAMChange(e); }} isDisabled={!newPAMConfigEntry} @@ -708,7 +710,7 @@ class PAMPassthroughAuthentication extends React.Component { isCreatable onCreateOption={this.handleCreateExcludeOption} typeAheadAriaLabel="Add a suffix" - onToggle={this.handleExcludeToggle} + onToggle={(event, isOpen) => this.handleExcludeToggle(event, isOpen)} onSelect={this.handleExcludeSelect} onClear={this.handleClearExcludeSelection} selections={pamExcludeSuffix} @@ -739,7 +741,7 @@ class PAMPassthroughAuthentication extends React.Component { isCreatable onCreateOption={this.handleCreateIncludeOption} typeAheadAriaLabel="Add an include suffix" - onToggle={this.handleIncludeToggle} + onToggle={(event, isOpen) => this.handleIncludeToggle(event, isOpen)} onSelect={this.handleIncludeSelect} onClear={this.handleClearIncludeSelection} selections={pamIncludeSuffix} @@ -764,7 +766,7 @@ class PAMPassthroughAuthentication extends React.Component { <FormSelect id="pamIDAttr" value={pamIDAttr} - onChange={(value, event) => { + onChange={(event, value) => { this.handlePAMChange(event); }} aria-label="FormSelect Input" @@ -782,7 +784,7 @@ class PAMPassthroughAuthentication extends React.Component { <FormSelect id="pamMissingSuffix" value={pamMissingSuffix} - onChange={(value, event) => { + onChange={(event, value) => { this.handlePAMChange(event); }} aria-label="FormSelect Input" @@ -804,7 +806,7 @@ class PAMPassthroughAuthentication extends React.Component { id="pamFilter" aria-describedby="horizontal-form-name-helper" name="pamFilter" - onChange={(str, e) => { + onChange={(e, str) => { this.handlePAMChange(e); }} validated={error.pamFilter ? ValidatedOptions.error : ValidatedOptions.default} @@ -819,7 +821,7 @@ class PAMPassthroughAuthentication extends React.Component { <FormSelect id="pamIDMapMethod" value={pamIDMapMethod} - onChange={(value, event) => { + onChange={(event, value) => { this.handlePAMChange(event); }} aria-label="FormSelect Input" @@ -838,7 +840,7 @@ class PAMPassthroughAuthentication extends React.Component { <FormSelect id="pamService" value={pamService} - onChange={(value, event) => { + onChange={(event, value) => { this.handlePAMChange(event); }} aria-label="FormSelect Input" @@ -856,7 +858,7 @@ class PAMPassthroughAuthentication extends React.Component { <Checkbox id="pamFallback" isChecked={pamFallback} - onChange={(checked, e) => { this.handlePAMChange(e) }} + onChange={(e, checked) => { this.handlePAMChange(e) }} title={_("Sets whether to fallback to regular LDAP authentication if PAM authentication fails (pamFallback)")} /> </GridItem> @@ -869,7 +871,7 @@ class PAMPassthroughAuthentication extends React.Component { <Checkbox id="pamSecure" isChecked={pamSecure} - onChange={(checked, e) => { this.handlePAMChange(e) }} + onChange={(e, checked) => { this.handlePAMChange(e) }} title={_("Requires secure TLS connection for PAM authentication (pamSecure)")} /> </GridItem> diff --git a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx index 5aaa32b2d4..530039c1e5 100644 --- a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx +++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx @@ -500,7 +500,7 @@ class PassthroughAuthentication extends React.Component { <FormSelect id="urlConnType" value={urlConnType} - onChange={(value, event) => { + onChange={(event, value) => { this.handlePassthruChange(event); }} aria-label="FormSelect Input" @@ -522,7 +522,7 @@ class PassthroughAuthentication extends React.Component { id={content.id} aria-describedby="horizontal-form-name-helper" name={content.name} - onChange={(str, e) => { + onChange={(e, str) => { this.handlePassthruChange(e); }} validated={error[content.id] ? ValidatedOptions.error : ValidatedOptions.default} @@ -564,7 +564,7 @@ class PassthroughAuthentication extends React.Component { <FormSelect id="urlLDVer" value={urlLDVer} - onChange={(value, event) => { + onChange={(event, value) => { this.handlePassthruChange(event); }} aria-label="FormSelect Input" @@ -579,7 +579,7 @@ class PassthroughAuthentication extends React.Component { <Checkbox id="urlStartTLS" isChecked={urlStartTLS} - onChange={(checked, e) => { this.handlePassthruChange(e) }} + onChange={(e, checked) => { this.handlePassthruChange(e) }} title={_("A flag of whether to use Start TLS for the connection to the authenticating directory. Start TLS establishes a secure connection over the standard port, so it is useful for connecting using LDAP instead of LDAPS. The TLS server and CA certificates need to be available on both of the servers. To use Start TLS, the LDAP URL must use ldap:, not ldaps:.")} label={_("Enable StartTLS")} /> diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx index 2598923289..77efdf3377 100644 --- a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx +++ b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx @@ -40,7 +40,7 @@ class PluginBasicConfig extends React.Component { isExpanded: false, }; - this.handleToggle = (isExpanded) => { + this.handleToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -223,7 +223,7 @@ class PluginBasicConfig extends React.Component { label={enabled} labelOff={disabled} isChecked={this.state.currentPluginEnabled} - onChange={this.handleSwitchChange} + onChange={(event, str) => this.handleSwitchChange(event, str)} isDisabled={disableSwitch} /> </GridItem> @@ -238,7 +238,7 @@ class PluginBasicConfig extends React.Component { <ExpandableSection className="ds-margin-top-lg" toggleText={this.state.isExpanded ? _("Hide Plugin Details") : _("Show Plugin Details")} - onToggle={this.handleToggle} + onToggle={(event, isOpen) => this.handleToggle(event, isOpen)} isExpanded={this.state.isExpanded} > <Grid className="ds-margin-left"> diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx index add2da5efd..f81ef7ef2d 100644 --- a/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx +++ b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx @@ -4,19 +4,19 @@ import { Grid, GridItem, Pagination, - PaginationVariant, SearchInput, Switch, } from "@patternfly/react-core"; import { - // cellWidth, - expandable, Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + Thead, + Tr, + Th, + Tbody, + Td, + ExpandableRowContent, + ActionsColumn, + SortByDirection } from '@patternfly/react-table'; import PropTypes from "prop-types"; @@ -32,15 +32,10 @@ class PluginTable extends React.Component { value: '', sortBy: {}, rows: [], - dropdownIsOpen: false, columns: [ - { - title: _("Plugin Name"), - transforms: [sortable], - cellFormatters: [expandable] - }, - { title: _("Plugin Type"), transforms: [sortable] }, - { title: _("Enabled"), transforms: [sortable] }, + { title: _("Plugin Name"), sortable: true }, + { title: _("Plugin Type"), sortable: true }, + { title: _("Enabled"), sortable: true }, ], }; @@ -62,50 +57,24 @@ class PluginTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + const index = (this.state.perPage * (this.state.page - 1)) + rowIndex; + rows[index].isOpen = isExpanding; + this.setState({ rows }); } - handleSort(_event, index, direction) { - const sorted_rows = []; - const rows = []; - let count = 0; - - // Convert the rows pairings into a sortable array based on the column indexes - for (let idx = 0; idx < this.state.rows.length; idx += 2) { - sorted_rows.push({ - expandedRow: this.state.rows[idx + 1], - 1: this.state.rows[idx].cells[0], - 2: this.state.rows[idx].cells[1], - 3: this.state.rows[idx].cells[2], - }); - } - - // Sort the rows and build the new rows - sorted_rows.sort((a, b) => (a[index] > b[index]) ? 1 : -1); + handleSort(_event, columnIndex, direction) { + const rows = [...this.state.rows]; + + rows.sort((a, b) => (a.cells[columnIndex].content > b.cells[columnIndex].content) ? 1 : -1); if (direction !== SortByDirection.asc) { - sorted_rows.reverse(); - } - for (const srow of sorted_rows) { - rows.push({ - isOpen: false, - cells: [ - srow[1], srow[2], srow[3] - ], - }); - srow.expandedRow.parent = count; // reset parent idx - rows.push(srow.expandedRow); - count += 2; + rows.reverse(); } this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows, @@ -115,7 +84,6 @@ class PluginTable extends React.Component { handleSearchChange(event, value) { const rows = []; - let count = 0; for (const row of this.props.rows) { const val = value.toLowerCase(); @@ -127,22 +95,15 @@ class PluginTable extends React.Component { continue; } - rows.push( - { - isOpen: false, - cells: [ - row.cn[0], - row["nsslapd-pluginType"][0], - row["nsslapd-pluginEnabled"][0] - ], - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] - }, - ); - count += 2; + rows.push({ + isOpen: false, + cells: [ + { content: row.cn[0] }, + { content: row["nsslapd-pluginType"][0] }, + { content: row["nsslapd-pluginEnabled"][0] } + ], + originalData: row + }); } this.setState({ @@ -205,25 +166,18 @@ class PluginTable extends React.Component { componentDidMount() { const rows = []; - let count = 0; for (const row of this.props.rows) { - rows.push( - { - isOpen: false, - cells: [ - row.cn[0], - row["nsslapd-pluginType"][0], - row["nsslapd-pluginEnabled"][0] - ], - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] - }, - ); - count += 2; + // Create row with properly formatted cells + rows.push({ + isOpen: false, + cells: [ + { content: row.cn[0] }, + { content: row["nsslapd-pluginType"][0] }, + { content: row["nsslapd-pluginEnabled"][0] } + ], + originalData: row + }); } this.setState({ rows, @@ -232,14 +186,8 @@ class PluginTable extends React.Component { render() { const { perPage, page, sortBy, rows, columns } = this.state; - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); return ( <div className={this.state.toggleSpinning ? "ds-disabled" : ""}> @@ -250,24 +198,62 @@ class PluginTable extends React.Component { onClear={(evt) => this.handleSearchChange(evt, '')} /> <Table - className="ds-margin-top" aria-label="all plugins table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - onCollapse={this.handleCollapse} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {cell.content} + </Td> + ))} + </Tr> + {row.isOpen && ( + <Tr isExpanded={true}> + <Td colSpan={columns.length + 1}> + <ExpandableRowContent> + {this.getExpandedRow(row.originalData)} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination - itemCount={this.state.rows.length / 2} + itemCount={rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -295,9 +281,9 @@ class AttrUniqConfigTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Config Name"), transforms: [sortable] }, - { title: _("Attribute"), transforms: [sortable] }, - { title: _("Enabled"), transforms: [sortable] } + { title: _("Config Name"), sortable: true }, + { title: _("Attribute"), sortable: true }, + { title: _("Enabled"), sortable: true } ], }; @@ -317,37 +303,34 @@ class AttrUniqConfigTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } - - componentDidMount () { - // Copy the rows so we can handle sorting and searching - const rows = []; - for (const row of this.props.rows) { - rows.push([row.cn[0], row['uniqueness-attribute-name'].join(", "), row["nsslapd-pluginenabled"][0]]); + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) } - this.setState({ - rows - }); + ]; + + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.cn[0], + row['uniqueness-attribute-name'].join(", "), + row["nsslapd-pluginenabled"][0] + ]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -358,23 +341,22 @@ class AttrUniqConfigTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - rows.push([row.cn[0], row['uniqueness-attribute-name'].join(", "), row["nsslapd-pluginenabled"][0]]); - } + rows = this.props.rows.map(row => [ + row.cn[0], + row['uniqueness-attribute-name'].join(", "), + row["nsslapd-pluginenabled"][0] + ]); + } else { + rows = this.state.rows.filter(row => + row[0].toLowerCase().includes(val) || + row[1].toLowerCase().includes(val) + ); } + this.setState({ rows, value, @@ -383,10 +365,11 @@ class AttrUniqConfigTable extends React.Component { } render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Attribute Uniqueness Configurations") }]; @@ -395,6 +378,7 @@ class AttrUniqConfigTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -403,27 +387,57 @@ class AttrUniqConfigTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" - aria-label="glue table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + aria-label="attribute uniqueness config table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -453,10 +467,10 @@ class LinkedAttributesTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Config Name"), transforms: [sortable] }, - { title: _("Link Type"), transforms: [sortable] }, - { title: _("Managed Type"), transforms: [sortable] }, - { title: _("Link Scope"), transforms: [sortable] } + { title: _("Config Name"), sortable: true }, + { title: _("Link Type"), sortable: true }, + { title: _("Managed Type"), sortable: true }, + { title: _("Link Scope"), sortable: true } ], }; @@ -476,41 +490,35 @@ class LinkedAttributesTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } - - componentDidMount () { - // Copy the rows so we can handle sorting and searching - const rows = []; - for (const row of this.props.rows) { - const configName = row.cn === undefined ? "" : row.cn[0]; - const linkType = row.linktype === undefined ? "" : row.linktype[0]; - const managedType = row.managedtype === undefined ? "" : row.managedtype[0]; - const linkScope = row.linkscope === undefined ? "" : row.linkscope[0]; - rows.push([configName, linkType, managedType, linkScope]); + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) } - this.setState({ - rows - }); + ]; + + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.cn?.[0] || "", + row.linktype?.[0] || "", + row.managedtype?.[0] || "", + row.linkscope?.[0] || "" + ]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -521,29 +529,22 @@ class LinkedAttributesTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1 && - row[3].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2], row[3]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - const configName = row.cn === undefined ? "" : row.cn[0]; - const linkType = row.linktype === undefined ? "" : row.linktype[0]; - const managedType = row.managedtype === undefined ? "" : row.managedtype[0]; - const linkScope = row.linkscope === undefined ? "" : row.linkscope[0]; - rows.push([configName, linkType, managedType, linkScope]); - } + rows = this.props.rows.map(row => [ + row.cn?.[0] || "", + row.linktype?.[0] || "", + row.managedtype?.[0] || "", + row.linkscope?.[0] || "" + ]); + } else { + rows = this.state.rows.filter(row => + row.some(cell => cell.toLowerCase().includes(val)) + ); } + this.setState({ rows, value, @@ -552,10 +553,11 @@ class LinkedAttributesTable extends React.Component { } render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Linked Attributes Configurations") }]; @@ -564,6 +566,7 @@ class LinkedAttributesTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -572,27 +575,57 @@ class LinkedAttributesTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" - aria-label="linked table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + aria-label="linked attributes table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -621,10 +654,10 @@ class DNATable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Config Name"), transforms: [sortable] }, - { title: _("Scope"), transforms: [sortable] }, - { title: _("Filter"), transforms: [sortable] }, - { title: _("Next Value"), transforms: [sortable] } + { title: _("Config Name"), sortable: true }, + { title: _("Scope"), sortable: true }, + { title: _("Filter"), sortable: true }, + { title: _("Next Value"), sortable: true } ], }; @@ -644,23 +677,35 @@ class DNATable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - // Copy the rows so we can handle sorting and searching - const rows = []; - for (const row of this.props.rows) { - const configName = row.cn === undefined ? "" : row.cn[0]; - const nextValue = row.dnanextvalue === undefined ? "" : row.dnanextvalue[0]; - const filter = row.dnafilter === undefined ? "" : row.dnafilter[0]; - const scope = row.dnascope === undefined ? "" : row.dnascope[0]; - rows.push([configName, scope, filter, nextValue]); + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.cn?.[0] || "", + row.dnascope?.[0] || "", + row.dnafilter?.[0] || "", + row.dnanextvalue?.[0] || "" + ]); + this.setState({ rows }); + } + + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) } - this.setState({ - rows - }); - } + ]; handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -671,29 +716,22 @@ class DNATable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1 && - row[3].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2], row[3]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - const configName = row.cn === undefined ? "" : row.cn[0]; - const nextValue = row.dnanextvalue === undefined ? "" : row.dnanextvalue[0]; - const filter = row.dnafilter === undefined ? "" : row.dnafilter[0]; - const scope = row.scope === undefined ? "" : row.scope[0]; - rows.push([configName, scope, filter, nextValue]); - } + rows = this.props.rows.map(row => [ + row.cn?.[0] || "", + row.dnascope?.[0] || "", + row.dnafilter?.[0] || "", + row.dnanextvalue?.[0] || "" + ]); + } else { + rows = this.state.rows.filter(row => + row.some(cell => cell.toLowerCase().includes(val)) + ); } + this.setState({ rows, value, @@ -701,29 +739,12 @@ class DNATable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } - render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("DNA Configurations") }]; @@ -741,27 +762,57 @@ class DNATable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="dna table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -790,9 +841,9 @@ class DNASharedTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Hostname"), transforms: [sortable] }, - { title: _("Port"), transforms: [sortable] }, - { title: _("Remaining Values"), transforms: [sortable] }, + { title: _("Hostname"), sortable: true }, + { title: _("Port"), sortable: true }, + { title: _("Remaining Values"), sortable: true }, ], }; @@ -812,22 +863,20 @@ class DNASharedTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - const rows = []; - for (const row of this.props.rows) { - rows.push([ - row.dnahostname[0], - row.dnaportnum[0], - row.dnaremainingvalues[0] - ]); - } - this.setState({ - rows - }); + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.dnahostname[0], + row.dnaportnum[0], + row.dnaremainingvalues[0] + ]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -838,28 +887,21 @@ class DNASharedTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - rows.push([ - row.dnahostname[0], - row.dnaportnum[0], - row.dnaremainingvalues[0] - ]); - } + rows = this.props.rows.map(row => [ + row.dnahostname[0], + row.dnaportnum[0], + row.dnaremainingvalues[0] + ]); + } else { + rows = this.state.rows.filter(row => + row.some(cell => cell.toLowerCase().includes(val)) + ); } + this.setState({ rows, value, @@ -867,29 +909,26 @@ class DNASharedTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0] + ":" + rowData[1]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0] + ":" + rowData[1]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(`${rowData[0]}:${rowData[1]}`) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(`${rowData[0]}:${rowData[1]}`) + } + ]; render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("DNA Shared Configurations") }]; @@ -907,27 +946,57 @@ class DNASharedTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="dna shared table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -956,10 +1025,10 @@ class AutoMembershipDefinitionTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Definition Name"), transforms: [sortable] }, - { title: _("Default Group"), transforms: [sortable] }, - { title: _("Scope"), transforms: [sortable] }, - { title: _("Filter"), transforms: [sortable] }, + { title: _("Definition Name"), sortable: true }, + { title: _("Default Group"), sortable: true }, + { title: _("Scope"), sortable: true }, + { title: _("Filter"), sortable: true }, ], }; @@ -979,23 +1048,20 @@ class AutoMembershipDefinitionTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - const rows = []; - for (const row of this.props.rows) { - rows.push([ - row.cn[0], - "automemberdefaultgroup" in row ? row.automemberdefaultgroup[0] : "", - row.automemberscope[0], - row.automemberfilter[0], - ]); - } - this.setState({ - rows - }); + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.cn[0], + "automemberdefaultgroup" in row ? row.automemberdefaultgroup[0] : "", + row.automemberscope[0], + row.automemberfilter[0], + ]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); this.setState({ sortBy: { index, @@ -1006,30 +1072,22 @@ class AutoMembershipDefinitionTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1 && - row[3].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2], row[3]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - rows.push([ - row.cn[0], - row.automemberdefaultgroup[0], - row.automemberscope[0], - row.automemberfilter[0], - ]); - } + rows = this.props.rows.map(row => [ + row.cn[0], + "automemberdefaultgroup" in row ? row.automemberdefaultgroup[0] : "", + row.automemberscope[0], + row.automemberfilter[0], + ]); + } else { + rows = this.state.rows.filter(row => + row.some(cell => cell.toLowerCase().includes(val)) + ); } + this.setState({ rows, value, @@ -1037,29 +1095,26 @@ class AutoMembershipDefinitionTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) + } + ]; render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Automembership Definitions") }]; @@ -1068,6 +1123,7 @@ class AutoMembershipDefinitionTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -1077,27 +1133,57 @@ class AutoMembershipDefinitionTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="automember def table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1126,10 +1212,10 @@ class AutoMembershipRegexTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Config Name"), transforms: [sortable] }, - { title: _("Exclusive Regex"), transforms: [sortable] }, - { title: _("Inclusive Regex"), transforms: [sortable] }, - { title: _("Target Group"), transforms: [sortable] }, + { title: _("Config Name"), sortable: true }, + { title: _("Exclusive Regex"), sortable: true }, + { title: _("Inclusive Regex"), sortable: true }, + { title: _("Target Group"), sortable: true }, ], }; @@ -1149,21 +1235,21 @@ class AutoMembershipRegexTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - const rows = []; - for (const row of this.props.rows) { - const includeReg = row.automemberinclusiveregex === undefined ? "" : row.automemberinclusiveregex.join(", "); - const excludeReg = row.automemberexclusiveregex === undefined ? "" : row.automemberexclusiveregex.join(", "); - const targetGrp = row.automembertargetgroup === undefined ? "" : row.automembertargetgroup[0]; - rows.push([row.cn[0], excludeReg, includeReg, targetGrp]); - } - this.setState({ - rows - }); + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.cn[0], + row.automemberexclusiveregex?.join(", ") || "", + row.automemberinclusiveregex?.join(", ") || "", + row.automembertargetgroup?.[0] || "" + ]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -1173,34 +1259,37 @@ class AutoMembershipRegexTable extends React.Component { }); } + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) + } + ]; + handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1 && - row[3].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2], row[3]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - const includeReg = row.automemberinclusiveregex === undefined ? "" : row.automemberinclusiveregex[0]; - const excludeReg = row.automemberexclusiveregex === undefined ? "" : row.automemberexclusiveregex[0]; - const targetGrp = row.automembertargetgroup === undefined ? "" : row.automembertargetgroup[0]; - rows.push([ - row.cn[0], - excludeReg.join(", "), - includeReg.join(", "), - targetGrp, - ]); - } + rows = this.props.rows.map(row => [ + row.cn[0], + row.automemberexclusiveregex?.join(", ") || "", + row.automemberinclusiveregex?.join(", ") || "", + row.automembertargetgroup?.[0] || "" + ]); + } else { + rows = this.state.rows.filter(row => + row.some(cell => cell.toLowerCase().includes(val)) + ); } + this.setState({ rows, value, @@ -1208,29 +1297,12 @@ class AutoMembershipRegexTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } - render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Automembership Regular Expressions") }]; @@ -1239,6 +1311,7 @@ class AutoMembershipRegexTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -1248,27 +1321,57 @@ class AutoMembershipRegexTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="automember regex table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1277,6 +1380,7 @@ class AutoMembershipRegexTable extends React.Component { } } + AutoMembershipRegexTable.propTypes = { rows: PropTypes.array, editConfig: PropTypes.func, @@ -1297,10 +1401,10 @@ class ManagedDefinitionTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Config Name"), transforms: [sortable] }, - { title: _("Scope"), transforms: [sortable] }, - { title: _("Filter"), transforms: [sortable] }, - { title: _("Managed Base"), transforms: [sortable] }, + { title: _("Config Name"), sortable: true }, + { title: _("Scope"), sortable: true }, + { title: _("Filter"), sortable: true }, + { title: _("Managed Base"), sortable: true }, ], }; @@ -1320,21 +1424,21 @@ class ManagedDefinitionTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - const rows = []; - for (const row of this.props.rows) { - const managedBase = row.managedbase === undefined ? "" : row.managedbase[0]; - const scope = row.originscope === undefined ? "" : row.originscope[0]; - const filter = row.originfilter === undefined ? "" : row.originfilter[0]; - rows.push([row.cn[0], scope, filter, managedBase]); - } - this.setState({ - rows - }); + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.cn[0], + row.originscope?.[0] || "", + row.originfilter?.[0] || "", + row.managedbase?.[0] || "" + ]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -1345,28 +1449,22 @@ class ManagedDefinitionTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1 && - row[3].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2], row[3]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - const managedBase = row.managedbase === undefined ? "" : row.managedbase[0]; - const scope = row.originscope === undefined ? "" : row.originscope[0]; - const filter = row.originfilter === undefined ? "" : row.originfilter[0]; - rows.push([row.cn[0], scope, filter, managedBase]); - } + rows = this.props.rows.map(row => [ + row.cn[0], + row.originscope?.[0] || "", + row.originfilter?.[0] || "", + row.managedbase?.[0] || "" + ]); + } else { + rows = this.state.rows.filter(row => + row.some(cell => cell.toLowerCase().includes(val)) + ); } + this.setState({ rows, value, @@ -1374,29 +1472,26 @@ class ManagedDefinitionTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) + } + ]; render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Managed Entry Definitions") }]; @@ -1405,6 +1500,7 @@ class ManagedDefinitionTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -1414,27 +1510,57 @@ class ManagedDefinitionTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="managed def table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1463,7 +1589,7 @@ class ManagedTemplateTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Template DN"), transforms: [sortable] }, + { title: _("Template DN"), sortable: true }, ], }; @@ -1483,18 +1609,15 @@ class ManagedTemplateTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - const rows = []; - for (const row of this.props.rows) { - rows.push([row.entrydn[0]]); - } - this.setState({ - rows - }); + componentDidMount() { + const rows = this.props.rows.map(row => [row.entrydn[0]]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); this.setState({ sortBy: { index, @@ -1505,21 +1628,17 @@ class ManagedTemplateTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && row[0].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - rows.push([row.entrydn[0]]); - } + rows = this.props.rows.map(row => [row.entrydn[0]]); + } else { + rows = this.state.rows.filter(row => + row[0].toLowerCase().includes(val) + ); } + this.setState({ rows, value, @@ -1527,29 +1646,26 @@ class ManagedTemplateTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) + } + ]; render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Managed Entry Templates") }]; @@ -1558,6 +1674,7 @@ class ManagedTemplateTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -1567,27 +1684,57 @@ class ManagedTemplateTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="managed template table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1616,7 +1763,7 @@ class PassthroughAuthURLsTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("URL"), transforms: [sortable] }, + { title: _("URL"), sortable: true }, ], }; @@ -1636,18 +1783,16 @@ class PassthroughAuthURLsTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - const rows = []; - for (const row of this.props.rows) { - rows.push([row.url]); - } - this.setState({ - rows - }); + componentDidMount() { + const rows = this.props.rows.map(row => [row.url]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -1658,21 +1803,17 @@ class PassthroughAuthURLsTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && row[0].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - rows.push([row.url]); - } + rows = this.props.rows.map(row => [row.url]); + } else { + rows = this.state.rows.filter(row => + row[0].toLowerCase().includes(val) + ); } + this.setState({ rows, value, @@ -1680,29 +1821,26 @@ class PassthroughAuthURLsTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit URL"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete URL"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit URL"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete URL"), + onClick: () => this.props.deleteConfig(rowData[0]) + } + ]; render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("Pass-Through Authentication URLs") }]; @@ -1711,6 +1849,7 @@ class PassthroughAuthURLsTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -1720,27 +1859,57 @@ class PassthroughAuthURLsTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="passthru url table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -1769,10 +1938,10 @@ class PassthroughAuthConfigsTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Config Name"), transforms: [sortable] }, - { title: _("Attribute"), transforms: [sortable] }, - { title: _("Map Method"), transforms: [sortable] }, - { title: _("Filter"), transforms: [sortable] }, + { title: _("Config Name"), sortable: true }, + { title: _("Attribute"), sortable: true }, + { title: _("Map Method"), sortable: true }, + { title: _("Filter"), sortable: true }, ], }; @@ -1792,21 +1961,21 @@ class PassthroughAuthConfigsTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - componentDidMount () { - const rows = []; - for (const row of this.props.rows) { - const attr = row.pamidattr === undefined ? "" : row.pamidattr[0]; - const mapMethod = row.pamidmapmethod === undefined ? "" : row.pamidmapmethod[0]; - const filter = row.pamfilter === undefined ? "" : row.pamfilter[0]; - rows.push([row.cn[0], attr, mapMethod, filter]); - } - this.setState({ - rows - }); + componentDidMount() { + const rows = this.props.rows.map(row => [ + row.cn[0], + row.pamidattr?.[0] || "", + row.pamidmapmethod?.[0] || "", + row.pamfilter?.[0] || "" + ]); + this.setState({ rows }); } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + this.setState({ sortBy: { index, @@ -1817,28 +1986,22 @@ class PassthroughAuthConfigsTable extends React.Component { } handleSearchChange(event, value) { - const rows = []; + let rows = []; const val = value.toLowerCase(); - for (const row of this.state.rows) { - if (val !== "" && - row[0].indexOf(val) === -1 && - row[1].indexOf(val) === -1 && - row[2].indexOf(val) === -1 && - row[3].indexOf(val) === -1) { - // Not a match, skip it - continue; - } - rows.push([row[0], row[1], row[2], row[3]]); - } + if (val === "") { - // reset rows - for (const row of this.props.rows) { - const attr = row.pamidattr === undefined ? "" : row.pamidattr[0]; - const mapMethod = row.pamidmapmethod === undefined ? "" : row.pamidmapmethod[0]; - const filter = row.pamfilter === undefined ? "" : row.pamfilter[0]; - rows.push([row.cn[0], attr, mapMethod, filter]); - } + rows = this.props.rows.map(row => [ + row.cn[0], + row.pamidattr?.[0] || "", + row.pamidmapmethod?.[0] || "", + row.pamfilter?.[0] || "" + ]); + } else { + rows = this.state.rows.filter(row => + row.some(cell => cell.toLowerCase().includes(val)) + ); } + this.setState({ rows, value, @@ -1846,29 +2009,26 @@ class PassthroughAuthConfigsTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Config"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Config"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Config"), + onClick: () => this.props.editConfig(rowData[0]) + }, + { + isSeparator: true + }, + { + title: _("Delete Config"), + onClick: () => this.props.deleteConfig(rowData[0]) + } + ]; render() { - const rows = JSON.parse(JSON.stringify(this.state.rows)); // Deep copy + const rows = JSON.parse(JSON.stringify(this.state.rows)); let columns = this.state.columns; let has_rows = true; let tableRows; + if (rows.length === 0) { has_rows = false; columns = [{ title: _("PAM Configurations") }]; @@ -1877,6 +2037,7 @@ class PassthroughAuthConfigsTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div> <SearchInput @@ -1886,27 +2047,57 @@ class PassthroughAuthConfigsTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table + <Table className="ds-margin-top" aria-label="pass config table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> diff --git a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx index f754985d85..23748c3410 100644 --- a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx +++ b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx @@ -1,20 +1,22 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Form, - FormHelperText, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - TextInput, - NumberInput, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Form, + FormHelperText, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + NumberInput, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import { listsEqual, log_cmd, valid_dn, file_is_path } from "../tools.jsx"; @@ -108,7 +110,7 @@ class ReferentialIntegrity extends React.Component { ); } }; - this.handleConfigMembershipAttrToggle = isConfigMembershipAttrOpen => { + this.handleConfigMembershipAttrToggle = (_event, isConfigMembershipAttrOpen) => { this.setState({ isConfigMembershipAttrOpen }); @@ -139,7 +141,7 @@ class ReferentialIntegrity extends React.Component { ); } }; - this.handleMembershipAttrToggle = isMembershipAttrOpen => { + this.handleMembershipAttrToggle = (_event, isMembershipAttrOpen) => { this.setState({ isMembershipAttrOpen }); @@ -865,11 +867,11 @@ class ReferentialIntegrity extends React.Component { id="configDN" aria-describedby="horizontal-form-name-helper" name="configDN" - onChange={(str, e) => { this.handleModalChange(e) }} + onChange={(e, str) => { this.handleModalChange(e) }} validated={errorModal.configDN ? ValidatedOptions.error : ValidatedOptions.default} isDisabled={!newEntry} /> - <FormHelperText isError isHidden={!errorModal.configDN}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -882,7 +884,7 @@ class ReferentialIntegrity extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleConfigMembershipAttrToggle} + onToggle={(event, isOpen) => this.handleConfigMembershipAttrToggle(event, isOpen)} onSelect={this.handleConfigMembershipAttrSelect} onClear={this.handleConfigMembershipAttrClear} selections={configMembershipAttr} @@ -899,7 +901,7 @@ class ReferentialIntegrity extends React.Component { /> ))} </Select> - <FormHelperText isError isHidden={!errorModal.configMembershipAttr}> + <FormHelperText > {_("At least one attribute must be specified")} </FormHelperText> </GridItem> @@ -915,11 +917,11 @@ class ReferentialIntegrity extends React.Component { id="configEntryScope" aria-describedby="horizontal-form-name-helper" name="configEntryScope" - onChange={(str, e) => { this.handleModalChange(e) }} + onChange={(e, str) => { this.handleModalChange(e) }} validated={errorModal.configEntryScope ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> - <FormHelperText isError isHidden={!errorModal.configEntryScope}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </Grid> @@ -934,10 +936,10 @@ class ReferentialIntegrity extends React.Component { id="configExcludeEntryScope" aria-describedby="horizontal-form-name-helper" name="configExcludeEntryScope" - onChange={(str, e) => { this.handleModalChange(e) }} + onChange={(e, str) => { this.handleModalChange(e) }} validated={errorModal.configExcludeEntryScope ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errorModal.configExcludeEntryScope}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -953,10 +955,10 @@ class ReferentialIntegrity extends React.Component { id="configContainerScope" aria-describedby="horizontal-form-name-helper" name="configContainerScope" - onChange={(str, e) => { this.handleModalChange(e) }} + onChange={(e, str) => { this.handleModalChange(e) }} validated={errorModal.configContainerScope ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errorModal.configContainerScope}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -976,10 +978,10 @@ class ReferentialIntegrity extends React.Component { id="configLogFile" aria-describedby="horizontal-form-name-helper" name="configLogFile" - onChange={(str, e) => { this.handleModalChange(e) }} + onChange={(e, str) => { this.handleModalChange(e) }} validated={errorModal.configLogFile ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!errorModal.configLogFile}> + <FormHelperText > {_("Invalid log file name")} </FormHelperText> </GridItem> @@ -1029,7 +1031,7 @@ class ReferentialIntegrity extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleMembershipAttrToggle} + onToggle={(event, isOpen) => this.handleMembershipAttrToggle(event, isOpen)} onSelect={this.handleMembershipAttrSelect} onClear={this.handleMembershipAttrClear} selections={membershipAttr} @@ -1046,7 +1048,7 @@ class ReferentialIntegrity extends React.Component { /> ))} </Select> - <FormHelperText isError isHidden={!error.membershipAttr}> + <FormHelperText > {_("At least one attribute needs to be specified")} </FormHelperText> </GridItem> @@ -1062,10 +1064,10 @@ class ReferentialIntegrity extends React.Component { id="entryScope" aria-describedby="horizontal-form-name-helper" name="entryScope" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={error.entryScope ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.entryScope}> + <FormHelperText > {_("The value must be a valid DN")} </FormHelperText> </GridItem> @@ -1081,10 +1083,10 @@ class ReferentialIntegrity extends React.Component { id="excludeEntryScope" aria-describedby="horizontal-form-name-helper" name="excludeEntryScope" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={error.excludeEntryScope ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.excludeEntryScope}> + <FormHelperText > {_("The value must be a valid DN")} </FormHelperText> </GridItem> @@ -1100,10 +1102,10 @@ class ReferentialIntegrity extends React.Component { id="containerScope" aria-describedby="horizontal-form-name-helper" name="containerScope" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={error.containerScope ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.containerScope}> + <FormHelperText > {_("The value must be a valid DN")} </FormHelperText> </GridItem> @@ -1122,10 +1124,10 @@ class ReferentialIntegrity extends React.Component { id="logFile" aria-describedby="horizontal-form-name-helper" name="logFile" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={error.logFile ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.logFile}> + <FormHelperText > {_("Invalid log nameN")} </FormHelperText> </GridItem> @@ -1162,10 +1164,10 @@ class ReferentialIntegrity extends React.Component { id="referintConfigEntry" aria-describedby="horizontal-form-name-helper" name="referintConfigEntry" - onChange={(str, e) => { this.handleFieldChange(e) }} + onChange={(e, str) => { this.handleFieldChange(e) }} validated={error.referintConfigEntry ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.referintConfigEntry}> + <FormHelperText > {_("The value must be a valid DN")} </FormHelperText> </GridItem> diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx index ace36f3e32..18d7f3ca7b 100644 --- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx +++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx @@ -1,20 +1,22 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Form, - FormHelperText, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Select, - SelectVariant, - SelectOption, - NumberInput, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormHelperText, + FormSelect, + FormSelectOption, + Grid, + GridItem, + NumberInput, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import { log_cmd, valid_dn, listsEqual } from "../tools.jsx"; @@ -115,7 +117,7 @@ class RetroChangelog extends React.Component { ); } }; - this.handleExcludeAttrToggle = isExcludeAttrOpen => { + this.handleExcludeAttrToggle = (_event, isExcludeAttrOpen) => { this.setState({ isExcludeAttrOpen }); @@ -144,7 +146,7 @@ class RetroChangelog extends React.Component { ); } }; - this.handleExcludeSuffixToggle = isExcludeSuffixOpen => { + this.handleExcludeSuffixToggle = (_event, isExcludeSuffixOpen) => { this.setState({ isExcludeSuffixOpen }, () => { this.validate() }); @@ -382,7 +384,7 @@ class RetroChangelog extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a suffix" - onToggle={this.handleExcludeSuffixToggle} + onToggle={(event, isOpen) => this.handleExcludeSuffixToggle(event, isOpen)} onSelect={this.handleExcludeSuffixSelect} onClear={this.handleExcludeSuffixClear} selections={excludeSuffix} @@ -401,7 +403,7 @@ class RetroChangelog extends React.Component { /> ))} </Select> - <FormHelperText isError isHidden={!error.excludeSuffix}> + <FormHelperText > {_("Values must be valid DN !")} </FormHelperText> </GridItem> @@ -409,7 +411,7 @@ class RetroChangelog extends React.Component { <Checkbox id="isReplicated" isChecked={isReplicated} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} title={_("Sets a flag to indicate on a change in the changelog whether the change is newly made on that server or whether it was replicated over from another server (isReplicated)")} label={_("Is Replicated")} /> @@ -423,7 +425,7 @@ class RetroChangelog extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleExcludeAttrToggle} + onToggle={(event, isOpen) => this.handleExcludeAttrToggle(event, isOpen)} onSelect={this.handleExcludeAttrSelect} onClear={this.handleExcludeAttrClear} selections={excludeAttrs} @@ -466,7 +468,7 @@ class RetroChangelog extends React.Component { className="ds-margin-left" id="maxAgeUnit" value={maxAgeUnit} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx index 398ae42697..d7220d8158 100644 --- a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx +++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx @@ -1,17 +1,19 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Form, - FormHelperText, - Grid, - GridItem, - Select, - SelectVariant, - SelectOption, - TimePicker, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormHelperText, + Grid, + GridItem, + TimePicker +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import { log_cmd, listsEqual } from "../tools.jsx"; @@ -96,7 +98,7 @@ class RootDNAccessControl extends React.Component { ); } }; - this.handleAllowHostToggle = isAllowHostOpen => { + this.handleAllowHostToggle = (_event, isAllowHostOpen) => { this.setState({ isAllowHostOpen }); @@ -134,7 +136,7 @@ class RootDNAccessControl extends React.Component { ); } }; - this.handleDenyHostToggle = isDenyHostOpen => { + this.handleDenyHostToggle = (_event, isDenyHostOpen) => { this.setState({ isDenyHostOpen }); @@ -172,7 +174,7 @@ class RootDNAccessControl extends React.Component { ); } }; - this.handleAllowIPToggle = isAllowIPOpen => { + this.handleAllowIPToggle = (_event, isAllowIPOpen) => { this.setState({ isAllowIPOpen }); @@ -210,7 +212,7 @@ class RootDNAccessControl extends React.Component { ); } }; - this.handleDenyIPToggle = isDenyIPOpen => { + this.handleDenyIPToggle = (_event, isDenyIPOpen) => { this.setState({ isDenyIPOpen }); @@ -520,7 +522,7 @@ class RootDNAccessControl extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a hostname " - onToggle={this.handleAllowHostToggle} + onToggle={(event, isOpen) => this.handleAllowHostToggle(event, isOpen)} onSelect={this.handleAllowHostSelect} onClear={this.handleAllowHostClear} selections={allowHost} @@ -548,7 +550,7 @@ class RootDNAccessControl extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type a hostname " - onToggle={this.handleDenyHostToggle} + onToggle={(event, isOpen) => this.handleDenyHostToggle(event, isOpen)} onSelect={this.handleDenyHostSelect} onClear={this.handleDenyHostClear} selections={denyHost} @@ -576,7 +578,7 @@ class RootDNAccessControl extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an IP address" - onToggle={this.handleAllowIPToggle} + onToggle={(event, isOpen) => this.handleDenyHostToggle(event, isOpen)} onSelect={this.handleAllowIPSelect} onClear={this.handleAllowIPClear} selections={allowIP} @@ -604,7 +606,7 @@ class RootDNAccessControl extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an IP address" - onToggle={this.handleDenyIPToggle} + onToggle={(event, isOpen) => this.handleDenyIPToggle(event, isOpen)} onSelect={this.handleDenyIPSelect} onClear={this.handleDenyIPClear} selections={denyIP} @@ -658,7 +660,7 @@ class RootDNAccessControl extends React.Component { <GridItem span={3}> <Checkbox id="allowMon" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} name={name} @@ -669,7 +671,7 @@ class RootDNAccessControl extends React.Component { <GridItem span={3}> <Checkbox id="allowFri" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} name={name} @@ -682,7 +684,7 @@ class RootDNAccessControl extends React.Component { <GridItem span={3}> <Checkbox id="allowTue" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} name={name} @@ -693,7 +695,7 @@ class RootDNAccessControl extends React.Component { <GridItem span={3}> <Checkbox id="allowSat" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} name={name} @@ -706,7 +708,7 @@ class RootDNAccessControl extends React.Component { <GridItem span={3}> <Checkbox id="allowWed" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} name={name} @@ -717,7 +719,7 @@ class RootDNAccessControl extends React.Component { <GridItem span={3}> <Checkbox id="allowSun" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} name={name} @@ -730,7 +732,7 @@ class RootDNAccessControl extends React.Component { <GridItem span={3}> <Checkbox id="allowThu" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleFieldChange(e); }} name={name} @@ -739,7 +741,7 @@ class RootDNAccessControl extends React.Component { /> </GridItem> </Grid> - <FormHelperText isError isHidden={!error.daysAllowed}> + <FormHelperText > {_("You must set at least one day")} </FormHelperText> </GridItem> diff --git a/src/cockpit/389-console/src/lib/plugins/usn.jsx b/src/cockpit/389-console/src/lib/plugins/usn.jsx index 2c58693ee6..ae4fea15f1 100644 --- a/src/cockpit/389-console/src/lib/plugins/usn.jsx +++ b/src/cockpit/389-console/src/lib/plugins/usn.jsx @@ -275,7 +275,7 @@ class USNPlugin extends React.Component { <FormSelect id="configAutoAddOC" value={cleanupSuffix} - onChange={(value, event) => { + onChange={(event, value) => { this.handleFieldChange(event); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/plugins/winsync.jsx b/src/cockpit/389-console/src/lib/plugins/winsync.jsx index 45348b461e..a5beca17f7 100644 --- a/src/cockpit/389-console/src/lib/plugins/winsync.jsx +++ b/src/cockpit/389-console/src/lib/plugins/winsync.jsx @@ -339,12 +339,12 @@ class WinSync extends React.Component { id="fixupDN" aria-describedby="horizontal-form-name-helper" name="fixupDN" - onChange={(str, e) => { + onChange={(e, str) => { this.handleModalChange(e); }} validated={error.fixupDN ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.fixupDN}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -360,12 +360,12 @@ class WinSync extends React.Component { id="fixupFilter" aria-describedby="horizontal-form-name-helper" name="fixupFilter" - onChange={(str, e) => { + onChange={(e, str) => { this.handleModalChange(e); }} validated={error.fixupFilter ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.fixupDN}> + <FormHelperText > {_("Enter an LDAP search filter")} </FormHelperText> </GridItem> @@ -389,7 +389,7 @@ class WinSync extends React.Component { <Checkbox id="posixWinsyncCreateMemberOfTask" isChecked={posixWinsyncCreateMemberOfTask} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} label={_("Create MemberOf Task")} /> </GridItem> @@ -399,7 +399,7 @@ class WinSync extends React.Component { <Checkbox id="posixWinsyncLowerCaseUID" isChecked={posixWinsyncLowerCaseUID} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} label={_("Lower Case UID")} /> </GridItem> @@ -409,7 +409,7 @@ class WinSync extends React.Component { <Checkbox id="posixWinsyncMapMemberUID" isChecked={posixWinsyncMapMemberUID} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} label={_("Map Member UID")} /> </GridItem> @@ -419,7 +419,7 @@ class WinSync extends React.Component { <Checkbox id="posixWinsyncMapNestedGrouping" isChecked={posixWinsyncMapNestedGrouping} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} label={_("Map Nested Grouping")} /> </GridItem> @@ -429,7 +429,7 @@ class WinSync extends React.Component { <Checkbox id="posixWinsyncMsSFUSchema" isChecked={posixWinsyncMsSFUSchema} - onChange={(checked, e) => { this.handleFieldChange(e) }} + onChange={(e, checked) => { this.handleFieldChange(e) }} label={_("Microsoft System Services for Unix 3.0 (msSFU30) schema")} /> </GridItem> diff --git a/src/cockpit/389-console/src/lib/replication/replAgmts.jsx b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx index 4ee18c4932..de3bf137bf 100644 --- a/src/cockpit/389-console/src/lib/replication/replAgmts.jsx +++ b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx @@ -84,7 +84,7 @@ export class ReplAgmts extends React.Component { }; // Create - Exclude Attributes - this.handleExcludeAttrsCreateToggle = isExcludeAttrsCreateOpen => { + this.handleExcludeAttrsCreateToggle = (_event, isExcludeAttrsCreateOpen) => { this.setState({ isExcludeAttrsCreateOpen }); @@ -97,7 +97,7 @@ export class ReplAgmts extends React.Component { }; // Create - Exclude Init Attributes - this.handleExcludeAttrsInitCreateToggle = isExcludeInitAttrsCreateOpen => { + this.handleExcludeAttrsInitCreateToggle = (_event, isExcludeInitAttrsCreateOpen) => { this.setState({ isExcludeInitAttrsCreateOpen }); @@ -110,7 +110,7 @@ export class ReplAgmts extends React.Component { }; // Create - Skip Attributes - this.onStripAttrsCreateToggle = isStripAttrsCreateOpen => { + this.onStripAttrsCreateToggle = (_event, isStripAttrsCreateOpen) => { this.setState({ isStripAttrsCreateOpen }); @@ -123,7 +123,7 @@ export class ReplAgmts extends React.Component { }; // Edit - Exclude Attributes - this.handleExcludeAttrsEditToggle = isExcludeAttrsEditOpen => { + this.handleExcludeAttrsEditToggle = (_event, isExcludeAttrsEditOpen) => { this.setState({ isExcludeAttrsEditOpen }); @@ -136,7 +136,7 @@ export class ReplAgmts extends React.Component { }; // Edit - Exclude Init Attributes - this.handleExcludeAttrsInitEditToggle = isExcludeInitAttrsEditOpen => { + this.handleExcludeAttrsInitEditToggle = (_event, isExcludeInitAttrsEditOpen) => { this.setState({ isExcludeInitAttrsEditOpen }); @@ -149,7 +149,7 @@ export class ReplAgmts extends React.Component { }; // Edit - Skip Attributes - this.onStripAttrsEditToggle = isStripAttrsEditOpen => { + this.onStripAttrsEditToggle = (_event, isStripAttrsEditOpen) => { this.setState({ isStripAttrsEditOpen }); @@ -823,7 +823,7 @@ export class ReplAgmts extends React.Component { this.handleTASelectChange(e); } - onSelectToggle = (isExpanded, toggleId) => { + onSelectToggle = (_event, isExpanded, toggleId) => { this.setState({ [toggleId]: isExpanded }); diff --git a/src/cockpit/389-console/src/lib/replication/replChangelog.jsx b/src/cockpit/389-console/src/lib/replication/replChangelog.jsx index 8bb6fd8f44..7416334a00 100644 --- a/src/cockpit/389-console/src/lib/replication/replChangelog.jsx +++ b/src/cockpit/389-console/src/lib/replication/replChangelog.jsx @@ -287,7 +287,7 @@ export class Changelog extends React.Component { className="ds-margin-left" id="clMaxAgeUnit" value={this.state.clMaxAgeUnit} - onChange={this.handleChange} + onChange={(e, str) => this.handleChange(str, e)} aria-label="FormSelect Input" isDisabled={this.state.clMaxAge < 1} > @@ -342,7 +342,7 @@ export class Changelog extends React.Component { <Checkbox id="clEncrypt" isChecked={this.state.clEncrypt} - onChange={this.handleChange} + onChange={(e, str) => this.handleChange(str, e)} /> </GridItem> </Grid> diff --git a/src/cockpit/389-console/src/lib/replication/replConfig.jsx b/src/cockpit/389-console/src/lib/replication/replConfig.jsx index 6c9b8ca699..9412c5985f 100644 --- a/src/cockpit/389-console/src/lib/replication/replConfig.jsx +++ b/src/cockpit/389-console/src/lib/replication/replConfig.jsx @@ -65,7 +65,7 @@ export class ReplConfig extends React.Component { _nsds5replicakeepaliveupdateinterval: Number(this.props.data.nsds5replicakeepaliveupdateinterval) === 0 ? 3600 : Number(this.props.data.nsds5replicakeepaliveupdateinterval), }; - this.handleToggle = (isExpanded) => { + this.handleToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -604,7 +604,7 @@ export class ReplConfig extends React.Component { </Grid> <ExpandableSection toggleText={this.state.isExpanded ? _("Hide Advanced Settings") : _("Show Advanced Settings")} - onToggle={this.handleToggle} + onToggle={(event, isExpanded) => this.handleToggle(event, isExpanded)} isExpanded={this.state.isExpanded} > <div className="ds-margin-top ds-margin-left ds-margin-bottom-md"> @@ -622,7 +622,7 @@ export class ReplConfig extends React.Component { id="nsds5replicabinddngroup" aria-describedby="horizontal-form-name-helper" name="nsds5replicabinddngroup" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} validated={this.state.errObj.nsds5replicabinddngroup && this.state.nsds5replicabinddngroup !== "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -824,7 +824,7 @@ export class ReplConfig extends React.Component { <Checkbox id="nsds5replicaprecisetombstonepurging" isChecked={this.state.nsds5replicaprecisetombstonepurging} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> diff --git a/src/cockpit/389-console/src/lib/replication/replModals.jsx b/src/cockpit/389-console/src/lib/replication/replModals.jsx index 9827ff5ff0..f046382702 100644 --- a/src/cockpit/389-console/src/lib/replication/replModals.jsx +++ b/src/cockpit/389-console/src/lib/replication/replModals.jsx @@ -1,33 +1,35 @@ import React from "react"; import cockpit from "cockpit"; import { - Button, - Checkbox, - Form, - FormHelperText, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - NumberInput, - Radio, - Select, - SelectVariant, - SelectOption, - Spinner, - Tab, - Tabs, - TabTitleIcon, - TabTitleText, - TextInput, - Text, - TextContent, - TextVariants, - TimePicker, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormHelperText, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + NumberInput, + Radio, + Spinner, + Tab, + Tabs, + TabTitleIcon, + TabTitleText, + TextInput, + Text, + TextContent, + TextVariants, + TimePicker, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon'; @@ -168,7 +170,7 @@ export class WinsyncAgmtModal extends React.Component { <FormSelect value={agmtInit} id="agmtInit" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -193,7 +195,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncMon" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -205,7 +207,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncFri" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -219,7 +221,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncTue" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -231,7 +233,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncSat" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -245,7 +247,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncWed" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -257,7 +259,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncSun" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -271,7 +273,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncThu" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -297,7 +299,7 @@ export class WinsyncAgmtModal extends React.Component { direction="up" is24Hour /> - <FormHelperText isError isHidden={!error.agmtStartTime}> + <FormHelperText > {_("Start time must be before the End time")} </FormHelperText> </GridItem> @@ -317,7 +319,7 @@ export class WinsyncAgmtModal extends React.Component { direction="up" is24Hour /> - <FormHelperText isError isHidden={!error.agmtEndTime}> + <FormHelperText > {_("End time must be after the Start time")} </FormHelperText> </GridItem> @@ -371,7 +373,7 @@ export class WinsyncAgmtModal extends React.Component { id="agmtName" aria-describedby="horizontal-form-name-helper" name="agmtName" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} isDisabled={this.props.edit} @@ -390,7 +392,7 @@ export class WinsyncAgmtModal extends React.Component { id="agmtHost" aria-describedby="horizontal-form-name-helper" name="agmtHost" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtHost ? ValidatedOptions.error : ValidatedOptions.default} @@ -408,7 +410,7 @@ export class WinsyncAgmtModal extends React.Component { id="agmtPort" aria-describedby="horizontal-form-name-helper" name="agmtPort" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtPort ? ValidatedOptions.error : ValidatedOptions.default} @@ -426,12 +428,12 @@ export class WinsyncAgmtModal extends React.Component { id="agmtBindDN" aria-describedby="horizontal-form-name-helper" name="agmtBindDN" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBindDN ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBindDN || agmtBindDN === ""}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -447,12 +449,12 @@ export class WinsyncAgmtModal extends React.Component { id="agmtBindPW" aria-describedby="horizontal-form-name-helper" name="agmtBindPW" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBindPW ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBindPW || agmtBindPW === "" || agmtBindPWConfirm === ""}> + <FormHelperText > {_("Passwords must match")} </FormHelperText> </GridItem> @@ -468,12 +470,12 @@ export class WinsyncAgmtModal extends React.Component { id="agmtBindPWConfirm" aria-describedby="horizontal-form-name-helper" name="agmtBindPWConfirm" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBindPWConfirm ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBindPWConfirm || agmtBindPWConfirm === ""}> + <FormHelperText > {_("Passwords must match")} </FormHelperText> </GridItem> @@ -492,7 +494,7 @@ export class WinsyncAgmtModal extends React.Component { id="agmtWinDomain" aria-describedby="horizontal-form-name-helper" name="agmtWinDomain" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtWinDomain ? ValidatedOptions.error : ValidatedOptions.default} @@ -510,13 +512,13 @@ export class WinsyncAgmtModal extends React.Component { id="agmtWinSubtree" aria-describedby="horizontal-form-name-helper" name="agmtWinSubtree" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} placeholder={_("e.g. cn=Users,dc=domain,dc=com")} validated={error.agmtWinSubtree ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtWinSubtree || agmtWinSubtree === ""}> + <FormHelperText > Value must be a valid DN </FormHelperText> </GridItem> @@ -532,13 +534,13 @@ export class WinsyncAgmtModal extends React.Component { id="agmtDSSubtree" aria-describedby="horizontal-form-name-helper" name="agmtDSSubtree" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} placeholder={_("e.g. ou=People,dc=domain,dc=com")} validated={error.agmtDSSubtree ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtDSSubtree || agmtDSSubtree === ""}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -553,7 +555,7 @@ export class WinsyncAgmtModal extends React.Component { <FormSelect value={agmtProtocol} id="agmtProtocol" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -571,7 +573,7 @@ export class WinsyncAgmtModal extends React.Component { <FormSelect value={agmtOneWaySync} id="agmtOneWaySync" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -593,7 +595,7 @@ export class WinsyncAgmtModal extends React.Component { id="agmtSyncInterval" aria-describedby="horizontal-form-name-helper" name="agmtSyncInterval" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtSyncInterval ? ValidatedOptions.error : ValidatedOptions.default} @@ -630,7 +632,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem> <Checkbox id="agmtSyncGroups" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -643,7 +645,7 @@ export class WinsyncAgmtModal extends React.Component { <GridItem> <Checkbox id="agmtSyncUsers" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -667,7 +669,7 @@ export class WinsyncAgmtModal extends React.Component { <Checkbox id="agmtSync" isChecked={agmtSync} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -834,7 +836,7 @@ export class ReplAgmtModal extends React.Component { <FormSelect value={agmtInit} id="agmtInit" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -860,12 +862,12 @@ export class ReplAgmtModal extends React.Component { id="agmtBootstrapBindDN" aria-describedby="horizontal-form-name-helper" name="agmtBootstrapBindDN" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBootstrapBindDN ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBootstrapBindDN || agmtBootstrapBindDN === ""}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -881,12 +883,12 @@ export class ReplAgmtModal extends React.Component { id="agmtBootstrapBindPW" aria-describedby="horizontal-form-name-helper" name="agmtBootstrapBindPW" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBootstrapBindPW ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBootstrapBindPW || agmtBootstrapBindPW === "" || error.agmtBootstrapBindPWConfirm === ""}> + <FormHelperText > {_("Password must match")} </FormHelperText> </GridItem> @@ -902,12 +904,12 @@ export class ReplAgmtModal extends React.Component { id="agmtBootstrapBindPWConfirm" aria-describedby="horizontal-form-name-helper" name="agmtBootstrapBindPWConfirm" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBootstrapBindPWConfirm ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBootstrapBindPWConfirm || agmtBootstrapBindPWConfirm === ""}> + <FormHelperText > {_("Passwords must match")} </FormHelperText> </GridItem> @@ -920,7 +922,7 @@ export class ReplAgmtModal extends React.Component { <FormSelect value={agmtBootstrapProtocol} id="agmtBootstrapProtocol" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -940,7 +942,7 @@ export class ReplAgmtModal extends React.Component { <FormSelect value={agmtBootstrapBindMethod} id="agmtBootstrapBindMethod" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -967,7 +969,7 @@ export class ReplAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncMon" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -979,7 +981,7 @@ export class ReplAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncFri" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -993,7 +995,7 @@ export class ReplAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncTue" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -1005,7 +1007,7 @@ export class ReplAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncSat" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -1019,7 +1021,7 @@ export class ReplAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncWed" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -1031,7 +1033,7 @@ export class ReplAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncSun" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -1045,7 +1047,7 @@ export class ReplAgmtModal extends React.Component { <GridItem span={3}> <Checkbox id="agmtSyncThu" - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -1070,7 +1072,7 @@ export class ReplAgmtModal extends React.Component { stepMinutes={5} is24Hour /> - <FormHelperText isError isHidden={!error.agmtStartTime}> + <FormHelperText > {_("Start time must be before the End time")} </FormHelperText> </GridItem> @@ -1089,7 +1091,7 @@ export class ReplAgmtModal extends React.Component { stepMinutes={5} is24Hour /> - <FormHelperText isError isHidden={!error.agmtEndTime}> + <FormHelperText > {_("End time must be after the Start time")} </FormHelperText> </GridItem> @@ -1145,13 +1147,13 @@ export class ReplAgmtModal extends React.Component { id="agmtName" aria-describedby="horizontal-form-name-helper" name="agmtName" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} isDisabled={this.props.edit} validated={error.agmtName ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtName || agmtName === ""}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -1167,12 +1169,12 @@ export class ReplAgmtModal extends React.Component { id="agmtHost" aria-describedby="horizontal-form-name-helper" name="agmtHost" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtHost ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtHost || agmtHost === ""}> + <FormHelperText > {_("Required field")} </FormHelperText> </GridItem> @@ -1188,12 +1190,12 @@ export class ReplAgmtModal extends React.Component { id="agmtPort" aria-describedby="horizontal-form-name-helper" name="agmtPort" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtPort ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtPort}> + <FormHelperText > {_("Port must be between 1 and 65535")} </FormHelperText> </GridItem> @@ -1209,12 +1211,12 @@ export class ReplAgmtModal extends React.Component { id="agmtBindDN" aria-describedby="horizontal-form-name-helper" name="agmtBindDN" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBindDN ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBindDN}> + <FormHelperText > {_("Value must be a valid DN")} </FormHelperText> </GridItem> @@ -1230,12 +1232,12 @@ export class ReplAgmtModal extends React.Component { id="agmtBindPW" aria-describedby="horizontal-form-name-helper" name="agmtBindPW" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBindPW ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBindPW || error.agmtBindPW === "" || error.agmtBindPWConfirm === ""}> + <FormHelperText > {_("Passwords must match")} </FormHelperText> </GridItem> @@ -1251,12 +1253,12 @@ export class ReplAgmtModal extends React.Component { id="agmtBindPWConfirm" aria-describedby="horizontal-form-name-helper" name="agmtBindPWConfirm" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.agmtBindPWConfirm ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!error.agmtBindPWConfirm || agmtBindPWConfirm === ""}> + <FormHelperText > {_("Passwords must match")} </FormHelperText> </GridItem> @@ -1269,7 +1271,7 @@ export class ReplAgmtModal extends React.Component { <FormSelect value={agmtProtocol} id="agmtProtocol" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -1289,7 +1291,7 @@ export class ReplAgmtModal extends React.Component { <FormSelect value={agmtBindMethod} id="agmtBindMethod" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -1388,7 +1390,7 @@ export class ReplAgmtModal extends React.Component { <Checkbox id="agmtBootstrap" isChecked={agmtBootstrap} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -1412,7 +1414,7 @@ export class ReplAgmtModal extends React.Component { <Checkbox id="agmtSync" isChecked={agmtSync} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} name={name} @@ -1549,7 +1551,7 @@ export class ChangeReplRoleModal extends React.Component { <FormSelect value={newRole} id="newRole" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -1566,7 +1568,7 @@ export class ChangeReplRoleModal extends React.Component { <Checkbox id="modalChecked" isChecked={checked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={<><b>{_("Yes")}</b>{_(", I am sure.")}</>} @@ -1640,7 +1642,7 @@ export class AddManagerModal extends React.Component { id="manager" aria-describedby="horizontal-form-name-helper" name="manager" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.manager ? ValidatedOptions.error : ValidatedOptions.default} @@ -1658,7 +1660,7 @@ export class AddManagerModal extends React.Component { id="manager_passwd" aria-describedby="horizontal-form-name-helper" name="manager_passwd" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.manager_passwd ? ValidatedOptions.error : ValidatedOptions.default} @@ -1676,7 +1678,7 @@ export class AddManagerModal extends React.Component { id="manager_passwd_confirm" aria-describedby="horizontal-form-name-helper" name="manager_passwd_confirm" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.manager_passwd_confirm ? ValidatedOptions.error : ValidatedOptions.default} @@ -1778,7 +1780,7 @@ export class EnableReplModal extends React.Component { <FormSelect id="enableRole" value={enableRole} - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -1807,7 +1809,7 @@ export class EnableReplModal extends React.Component { id="enableBindDN" aria-describedby="horizontal-form-name-helper" name="enableBindDN" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.enableBindDN ? ValidatedOptions.error : ValidatedOptions.default} @@ -1825,7 +1827,7 @@ export class EnableReplModal extends React.Component { id="enableBindPW" aria-describedby="horizontal-form-name-helper" name="enableBindPW" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.enableBindPW ? ValidatedOptions.error : ValidatedOptions.default} @@ -1843,7 +1845,7 @@ export class EnableReplModal extends React.Component { id="enableBindPWConfirm" aria-describedby="horizontal-form-name-helper" name="enableBindPWConfirm" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.enableBindPWConfirm ? ValidatedOptions.error : ValidatedOptions.default} @@ -1861,7 +1863,7 @@ export class EnableReplModal extends React.Component { id="enableBindGroupDN" aria-describedby="horizontal-form-name-helper" name="enableBindGroupDN" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.enableBindGroupDN ? ValidatedOptions.error : ValidatedOptions.default} @@ -1938,7 +1940,7 @@ export class ExportCLModal extends React.Component { id="ldifFile" aria-describedby="horizontal-form-name-helper" name="ldifFile" - onChange={(str, e) => { + onChange={(e, str) => { handleLDIFChange(e); }} /> @@ -1949,7 +1951,7 @@ export class ExportCLModal extends React.Component { id="decodeCL" isChecked={decodeCL} isDisabled={exportCSN} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Decode base64 changes")} @@ -1960,7 +1962,7 @@ export class ExportCLModal extends React.Component { id="exportCSN" isChecked={exportCSN} isDisabled={decodeCL} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} label={_("Only Export CSN's")} diff --git a/src/cockpit/389-console/src/lib/replication/replTables.jsx b/src/cockpit/389-console/src/lib/replication/replTables.jsx index fe65e742b3..94375c5850 100644 --- a/src/cockpit/389-console/src/lib/replication/replTables.jsx +++ b/src/cockpit/389-console/src/lib/replication/replTables.jsx @@ -3,17 +3,18 @@ import React from "react"; import { Button, Pagination, - PaginationVariant, SearchInput, Spinner, } from '@patternfly/react-core'; import { - Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + Table, + Thead, + Tr, + Th, + Tbody, + Td, + ActionsColumn, + SortByDirection } from '@patternfly/react-table'; import { TrashAltIcon } from '@patternfly/react-icons/dist/js/icons/trash-alt-icon'; import PropTypes from "prop-types"; @@ -30,11 +31,11 @@ class ReplAgmtTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Name"), transforms: [sortable] }, - { title: _("Host"), transforms: [sortable] }, - { title: _("Port"), transforms: [sortable] }, - { title: _("State"), transforms: [sortable] }, - { title: _("Last Init Status"), transforms: [sortable] }, + { title: _("Name"), sortable: true }, + { title: _("Host"), sortable: true }, + { title: _("Port"), sortable: true }, + { title: _("State"), sortable: true }, + { title: _("Last Init Status"), sortable: true }, ], }; @@ -52,77 +53,59 @@ class ReplAgmtTable extends React.Component { } componentDidMount() { - // Deep copy the rows so we can handle sorting and searching this.setState({ page: this.props.page }); } - actions() { - return [ - { - title: _("Edit Agreement"), - onClick: (event, rowId, rowData, extra) => - this.props.edit(rowData.cells[0]) - }, - { - title: _("Initialize Agreement"), - onClick: (event, rowId, rowData, extra) => - this.props.init(rowData.cells[0]) - }, - { - title: _("Poke Agreement"), - onClick: (event, rowId, rowData, extra) => - this.props.poke(rowData.cells[0]) - }, - { - title: _("Disable/Enable Agreement"), - onClick: (event, rowId, rowData, extra) => - this.props.enable(rowData.cells[0], rowData.cells[3]) - }, - { - isSeparator: true - }, - { - title: _("Delete Agreement"), - onClick: (event, rowId, rowData, extra) => - this.props.delete(rowData.cells[0], rowData.cells[3]) - }, - - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Agreement"), + onClick: () => this.props.edit(rowData[0]) + }, + { + title: _("Initialize Agreement"), + onClick: () => this.props.init(rowData[0]) + }, + { + title: _("Poke Agreement"), + onClick: () => this.props.poke(rowData[0]) + }, + { + title: _("Disable/Enable Agreement"), + onClick: () => this.props.enable(rowData[0], rowData[3]) + }, + { + isSeparator: true + }, + { + title: _("Delete Agreement"), + onClick: () => this.props.delete(rowData[0], rowData[3]) + }, + ]; convertStatus(msg) { if (msg === "Initialized") { - return ( - <i>{_("Initialized")}</i> - ); + return <i>{_("Initialized")}</i>; } else if (msg === "Not Initialized") { - return ( - <i>{_("Not Initialized")}</i> - ); + return <i>{_("Not Initialized")}</i>; } else if (msg === "Initializing") { return ( <div> <i>{_("Initializing")}</i> <Spinner size="sm" /> </div> ); - } else { - return ( - <i>{msg}</i> - ); } + return <i>{msg}</i>; } render() { - // let rows = this.state.rows; const rows = []; let columns = this.state.columns; let has_rows = true; let tableRows; const rows_copy = JSON.parse(JSON.stringify(this.props.rows)); - // Refine rows to handle JSX objects for (const row of rows_copy) { - rows.push({ cells: [row[0], row[1], row[2], row[3], { title: this.convertStatus(row[5]) }] }); + rows.push([row[0], row[1], row[2], row[3], this.convertStatus(row[5])]); } if (rows.length === 0) { @@ -133,6 +116,7 @@ class ReplAgmtTable extends React.Component { const startIdx = (this.state.perPage * this.state.page) - this.state.perPage; tableRows = rows.splice(startIdx, this.state.perPage); } + return ( <div className="ds-margin-top-xlg"> <SearchInput @@ -142,27 +126,57 @@ class ReplAgmtTable extends React.Component { onChange={this.props.handleSearch} onClear={(evt) => this.props.search(evt, '')} /> - <Table + <Table className="ds-margin-top-lg" aria-label="agmt table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={this.props.sortBy} - onSort={this.props.handleSort} - actions={has_rows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.props.sortBy, + onSort: this.props.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {has_rows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {has_rows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> <Pagination itemCount={this.props.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -178,7 +192,16 @@ class ManagerTable extends React.Component { this.state = { sortBy: {}, rows: [], - columns: ['', ''], + columns: [ + { + title: 'Manager Name', + sortable: true + }, + { + title: 'Actions', + screenReaderText: 'Manager actions' + } + ], }; this.handleSort = this.handleSort.bind(this); @@ -188,42 +211,36 @@ class ManagerTable extends React.Component { componentDidMount() { let rows = []; let columns = this.state.columns; + for (const managerRow of this.props.rows) { - rows.push({ - cells: [managerRow, { props: { textCenter: true }, title: this.getDeleteButton(managerRow) }] - }); + rows.push([ + managerRow, + this.getDeleteButton(managerRow) + ]); } + if (rows.length === 0) { - rows = [{ cells: [_("No Replication Managers")] }]; + rows = [[_("No Replication Managers")]]; columns = [{ title: '' }]; } + this.setState({ rows, columns }); } - handleSort(_event, index, direction) { - const rows = []; - const sortedManagers = [...this.state.rows]; - - // Sort the managers and build the new rows - sortedManagers.sort(); - if (direction !== SortByDirection.asc) { - sortedManagers.reverse(); - } - - for (const managerRow of sortedManagers) { - rows.push({ cells: [managerRow.cells[0], { props: { textCenter: true }, title: this.getDeleteButton(managerRow.cells[0]) }] }); - } + handleSort(_event, columnIndex, sortDirection) { + const sortedRows = [...this.state.rows].sort((a, b) => + (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0) + ); this.setState({ sortBy: { - index, - direction + index: columnIndex, + direction: sortDirection }, - rows, - page: 1, + rows: sortDirection === 'asc' ? sortedRows : sortedRows.reverse() }); } @@ -245,14 +262,39 @@ class ManagerTable extends React.Component { return ( <Table aria-label="manager table" - cells={this.state.columns} - rows={this.state.rows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {this.state.columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + screenReaderText={column.screenReaderText} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {this.state.rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.map((cell, cellIndex) => ( + <Td + key={cellIndex} + textCenter={cellIndex === 1} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> ); } @@ -266,10 +308,10 @@ class RUVTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Replica ID"), transforms: [sortable] }, - { title: _("Replica LDAP URL"), transforms: [sortable] }, - { title: _("Max CSN"), transforms: [sortable] }, - { title: '' }, + { title: _("Replica ID"), sortable: true }, + { title: _("Replica LDAP URL"), sortable: true }, + { title: _("Max CSN"), sortable: true }, + { title: '', sortable: false, screenReaderText: _("Clean RUV") } ], }; @@ -281,9 +323,12 @@ class RUVTable extends React.Component { let rows = []; let columns = this.state.columns; for (const row of this.props.rows) { - rows.push({ - cells: [row.rid, row.url, row.maxcsn, { props: { textCenter: true }, title: this.getCleanButton(row.rid) }] - }); + rows.push([ + row.rid, + row.url, + row.maxcsn, + this.getCleanButton(row.rid) + ]); } if (rows.length === 0) { rows = [{ cells: [_("No RUV's")] }]; @@ -296,7 +341,9 @@ class RUVTable extends React.Component { } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); this.setState({ sortBy: { index, @@ -321,19 +368,47 @@ class RUVTable extends React.Component { } render() { + const tableRows = this.state.rows.map((row, rowIndex) => ({ + cells: Array.isArray(row) ? row : row.cells + })); + return ( <div className="ds-margin-top"> <Table - className="ds-margin-top" aria-label="ruv table" - cells={this.state.columns} - rows={this.state.rows} - variant={TableVariant.compact} - sortBy={this.state.sortBy} - onSort={this.handleSort} + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {this.state.columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + screenReaderText={column.screenReaderText} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {row.cells.map((cell, cellIndex) => ( + <Td + key={cellIndex} + textCenter={cellIndex === row.cells.length - 1} + > + {cell} + </Td> + ))} + </Tr> + ))} + </Tbody> </Table> </div> ); @@ -349,9 +424,9 @@ class ReplicaLDIFTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("LDIF File"), transforms: [sortable] }, - { title: _("Creation Date"), transforms: [sortable] }, - { title: _("File Size"), transforms: [sortable] }, + { title: _("LDIF File"), sortable: true }, + { title: _("Creation Date"), sortable: true }, + { title: _("File Size"), sortable: true }, ], }; @@ -374,14 +449,10 @@ class ReplicaLDIFTable extends React.Component { let rows = []; let columns = this.state.columns; for (const ldifRow of this.props.rows) { - rows.push({ - cells: [ - ldifRow[0], ldifRow[1], ldifRow[2] - ] - }); + rows.push(ldifRow); } if (rows.length === 0) { - rows = [{ cells: [_("No LDIF files")] }]; + rows = [[_("No LDIF files")]]; columns = [{ title: _("LDIF File") }]; } this.setState({ @@ -391,29 +462,16 @@ class ReplicaLDIFTable extends React.Component { } handleSort(_event, index, direction) { - const rows = []; - const sortedLDIF = [...this.props.rows]; - - // Sort the referrals and build the new rows - sortedLDIF.sort(); - if (direction !== SortByDirection.asc) { - sortedLDIF.reverse(); - } - for (const ldifRow of sortedLDIF) { - rows.push({ - cells: - [ - ldifRow[0], ldifRow[1], ldifRow[2] - ] - }); - } + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); this.setState({ sortBy: { index, direction }, - rows, + rows: direction === SortByDirection.asc ? sortedRows : sortedRows.reverse(), page: 1, }); } @@ -424,16 +482,39 @@ class ReplicaLDIFTable extends React.Component { return ( <Table aria-label="ldif table" - cells={columns} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + </Tr> + ))} + </Tbody> </Table> ); } diff --git a/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx b/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx index 3e9c678b70..5ed5d0d440 100644 --- a/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx +++ b/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx @@ -75,7 +75,7 @@ export class WinsyncAgmts extends React.Component { }; // Create - Exclude Attributes - this.handleExcludeAttrCreateToggle = isExcludeAttrCreateOpen => { + this.handleExcludeAttrCreateToggle = (_event, isExcludeAttrCreateOpen) => { this.setState({ isExcludeAttrCreateOpen }); @@ -88,7 +88,7 @@ export class WinsyncAgmts extends React.Component { }; // Edit - Exclude Attributes - this.handleExcludeAttrEditToggle = isExcludeAttrEditOpen => { + this.handleExcludeAttrEditToggle = (_event, isExcludeAttrEditOpen) => { this.setState({ isExcludeAttrEditOpen }); diff --git a/src/cockpit/389-console/src/lib/schema/schemaModals.jsx b/src/cockpit/389-console/src/lib/schema/schemaModals.jsx index e020be3e95..2893f266eb 100644 --- a/src/cockpit/389-console/src/lib/schema/schemaModals.jsx +++ b/src/cockpit/389-console/src/lib/schema/schemaModals.jsx @@ -1,21 +1,23 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - Modal, - ModalVariant, - Select, - SelectVariant, - SelectOption, - TextInput, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + Modal, + ModalVariant, + TextInput, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import PropTypes from "prop-types"; const _ = cockpit.gettext; @@ -116,7 +118,7 @@ class ObjectClassModal extends React.Component { id="ocName" aria-describedby="horizontal-form-name-helper" name="ocName" - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} validated={error.ocName ? ValidatedOptions.error : ValidatedOptions.default} isDisabled={!newOcEntry} /> @@ -133,7 +135,7 @@ class ObjectClassModal extends React.Component { id="ocDesc" aria-describedby="horizontal-form-name-helper" name="ocDesc" - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} validated={error.ocDesc ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -150,7 +152,7 @@ class ObjectClassModal extends React.Component { aria-describedby="horizontal-form-name-helper" name="ocOID" isDisabled={ocModalViewOnly} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} /> </GridItem> </Grid> @@ -163,7 +165,7 @@ class ObjectClassModal extends React.Component { id="ocParent" value={ocParent} isDisabled={ocModalViewOnly} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} aria-label="FormSelect Input" > {objectclasses.map((obj, index) => ( @@ -181,7 +183,7 @@ class ObjectClassModal extends React.Component { id="ocKind" value={ocKind} isDisabled={ocModalViewOnly} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key={0} value="STRUCTURAL" label="STRUCTURAL" /> @@ -409,7 +411,7 @@ class AttributeTypeModal extends React.Component { aria-describedby="horizontal-form-name-helper" name="atName" isDisabled={!newAtEntry} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} validated={error.atName ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -426,7 +428,7 @@ class AttributeTypeModal extends React.Component { aria-describedby="horizontal-form-name-helper" name="atDesc" isDisabled={atModalViewOnly} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} validated={error.atDesc ? ValidatedOptions.error : ValidatedOptions.default} /> </GridItem> @@ -443,7 +445,7 @@ class AttributeTypeModal extends React.Component { aria-describedby="horizontal-form-name-helper" name="atOID" isDisabled={atModalViewOnly} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} /> </GridItem> </Grid> @@ -482,7 +484,7 @@ class AttributeTypeModal extends React.Component { id="atSyntax" value={atSyntax} isDisabled={atModalViewOnly} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} aria-label="FormSelect Input" > {syntaxes.map((syntax, index) => ( @@ -500,7 +502,7 @@ class AttributeTypeModal extends React.Component { id="atUsage" value={atUsage} isDisabled={atModalViewOnly} - onChange={(str, e) => { handleFieldChange(e) }} + onChange={(e, str) => { handleFieldChange(e) }} aria-label="FormSelect Input" > <FormSelectOption key={0} value="userApplications" label="userApplications" /> @@ -519,7 +521,7 @@ class AttributeTypeModal extends React.Component { id="atMultivalued" isChecked={atMultivalued} title={_("If attribute can have a multiple values")} - onChange={(checked, e) => { + onChange={(e, checked) => { handleFieldChange(e); }} isDisabled={atModalViewOnly} @@ -535,7 +537,7 @@ class AttributeTypeModal extends React.Component { id="atNoUserMod" isChecked={atNoUserMod} title={_("If attribute is not modifiable by a client application")} - onChange={(checked, e) => { + onChange={(e, checked) => { handleFieldChange(e); }} isDisabled={atModalViewOnly} diff --git a/src/cockpit/389-console/src/lib/schema/schemaTables.jsx b/src/cockpit/389-console/src/lib/schema/schemaTables.jsx index c5f5242f7e..f52b90bd70 100644 --- a/src/cockpit/389-console/src/lib/schema/schemaTables.jsx +++ b/src/cockpit/389-console/src/lib/schema/schemaTables.jsx @@ -4,7 +4,6 @@ import { Grid, GridItem, Pagination, - PaginationVariant, SearchInput, Spinner, Text, @@ -12,14 +11,15 @@ import { TextVariants, } from '@patternfly/react-core'; import { - // cellWidth, - expandable, - Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + Table, + Thead, + Tr, + Th, + Tbody, + Td, + ExpandableRowContent, + ActionsColumn, + SortByDirection } from '@patternfly/react-table'; import PropTypes from "prop-types"; @@ -35,14 +35,15 @@ class ObjectClassesTable extends React.Component { value: '', sortBy: {}, rows: [], - noRows: true, columns: [ { title: _("Objectclass Name"), - transforms: [sortable], - cellFormatters: [expandable] + sortable: true + }, + { + title: _("OID"), + sortable: true }, - { title: _("OID"), transforms: [sortable] }, ], }; @@ -64,45 +65,63 @@ class ObjectClassesTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - handleSort(_event, index, direction) { - const sorted_rows = []; + handleSort(_event, columnIndex, direction) { + const rows = [...this.state.rows]; + + rows.sort((a, b) => (a.cells[columnIndex].content > b.cells[columnIndex].content) ? 1 : -1); + if (direction !== SortByDirection.asc) { + rows.reverse(); + } + + this.setState({ + sortBy: { + index: columnIndex, + direction + }, + rows, + page: 1, + }); + } + + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + const index = (this.state.perPage * (this.state.page - 1)) + rowIndex; + rows[index].isOpen = isExpanding; + this.setState({ rows }); + } + + handleSearchChange(event, value) { const rows = []; - let count = 0; + const val = value.toLowerCase(); - // Convert the rows pairings into a sortable array based on the column indexes - for (let idx = 0; idx < this.state.rows.length; idx += 2) { - sorted_rows.push({ - expandedRow: this.state.rows[idx + 1], - 1: this.state.rows[idx].cells[0], - 2: this.state.rows[idx].cells[1], - not_user_defined: this.state.rows[idx].disableActions - }); - } + for (const row of this.props.rows) { + // Check for matches of all the parts + if (val !== "" && + row.name[0].toLowerCase().indexOf(val) === -1 && + row.oid[0].toLowerCase().indexOf(val) === -1) { + continue; + } + + let user_defined = false; + if (row.x_origin.length > 0 && + row.x_origin.indexOf("user defined") !== -1) { + user_defined = true; + } - // Sort the rows and build the new rows - sorted_rows.sort((a, b) => (a[index] > b[index]) ? 1 : -1); - if (direction !== SortByDirection.asc) { - sorted_rows.reverse(); - } - for (const srow of sorted_rows) { rows.push({ isOpen: false, cells: [ - srow[1], srow[2] + { content: row.name[0] }, + { content: row.oid[0] } ], - disableActions: srow.not_user_defined + disableActions: !user_defined, + originalData: row }); - srow.expandedRow.parent = count; // reset parent idx - rows.push(srow.expandedRow); - count += 2; } this.setState({ - sortBy: { - index, - direction - }, rows, + value, page: 1, }); } @@ -131,118 +150,49 @@ class ObjectClassesTable extends React.Component { } componentDidMount() { - let rows = []; - let columns = this.state.columns; - let count = 0; - let noRows = false; - - for (const row of this.props.rows) { - let user_defined = false; - if (row.x_origin.length > 0 && - row.x_origin.indexOf("user defined") !== -1) { - user_defined = true; - } - rows.push( - { - isOpen: false, - cells: [row.name[0], row.oid[0]], - disableActions: !user_defined, - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] - }, - ); - count += 2; - } - if (rows.length === 0) { - noRows = true; - rows = [{ cells: [_("No Objectclasses")] }]; - columns = [{ title: _("Objectclasses") }]; - } - this.setState({ - rows, - columns, - noRows, - }); - } - - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); - } - - handleSearchChange(event, value) { const rows = []; - let count = 0; for (const row of this.props.rows) { let user_defined = false; - const val = value.toLowerCase(); - - // Check for matches of all the parts - if (val !== "" && row.name[0].toLowerCase().indexOf(val) === -1 && - row.oid[0].toLowerCase().indexOf(val) === -1) { - // Not a match - continue; - } - if (row.x_origin.length > 0 && row.x_origin.indexOf("user defined") !== -1) { user_defined = true; } - rows.push( - { - isOpen: false, - cells: [row.name[0], row.oid[0]], - disableActions: !user_defined - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] - }, - ); - count += 2; + + rows.push({ + isOpen: false, + cells: [ + { content: row.name[0] }, + { content: row.oid[0] } + ], + disableActions: !user_defined, + originalData: row + }); } this.setState({ rows, - value, - page: 1, }); } - actions() { - return [ - { - title: _("Edit Objectclass"), - onClick: (event, rowId, rowData, extra) => - this.props.editModalHandler(rowData.cells[0]) - }, - { - title: _("Delete Objectclass"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteHandler(rowData.cells[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Objectclass"), + onClick: () => this.props.editModalHandler(rowData.cells[0].content) + }, + { + isSeparator: true + }, + { + title: _("Delete Objectclass"), + onClick: () => this.props.deleteHandler(rowData.cells[0].content) + } + ]; render() { - const { perPage, page, sortBy, rows, noRows, columns } = this.state; - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const { perPage, page, sortBy, rows, columns } = this.state; + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); let content = ( <div className="ds-center ds-margin-top-xlg"> @@ -268,28 +218,70 @@ class ObjectClassesTable extends React.Component { /> </GridItem> </Grid> - <Table - className="ds-margin-top" - aria-label="oc table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - onCollapse={this.handleCollapse} - actions={noRows ? null : this.actions()} - dropdownPosition="right" - dropdownDirection="bottom" + <Table + aria-label="objectclasses table" + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + <Th screenReaderText="Actions" /> + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {cell.content} + </Td> + ))} + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + isDisabled={row.disableActions} + /> + </Td> + </Tr> + {row.isOpen && ( + <Tr isExpanded={true}> + <Td colSpan={columns.length + 2}> + <ExpandableRowContent> + {this.getExpandedRow(row.originalData)} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination - itemCount={this.state.rows.length / 2} + itemCount={rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -329,11 +321,10 @@ class AttributesTable extends React.Component { columns: [ { title: _("Attribute Name"), - transforms: [sortable], - cellFormatters: [expandable] + sortable: true }, - { title: _("OID"), transforms: [sortable] }, - { title: _("Syntax"), transforms: [sortable] }, + { title: _("OID"), sortable: true }, + { title: _("Syntax"), sortable: true }, ], }; @@ -355,24 +346,24 @@ class AttributesTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - handleSort(_event, index, direction) { + handleSort(_event, columnIndex, direction) { const sorted_rows = []; const rows = []; let count = 0; - // Convert the rows pairings into a sortable array based on the column indexes + // Convert the rows pairings into a sortable array for (let idx = 0; idx < this.state.rows.length; idx += 2) { sorted_rows.push({ expandedRow: this.state.rows[idx + 1], - 1: this.state.rows[idx].cells[0], - 2: this.state.rows[idx].cells[1], - 3: this.state.rows[idx].cells[2], + 1: this.state.rows[idx].cells[0].content, + 2: this.state.rows[idx].cells[1].content, + 3: this.state.rows[idx].cells[2].content, not_user_defined: this.state.rows[idx].disableActions }); } - // Sort the rows and build the new rows - sorted_rows.sort((a, b) => (a[index] > b[index]) ? 1 : -1); + // Sort and rebuild rows + sorted_rows.sort((a, b) => (a[columnIndex + 1] > b[columnIndex + 1]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_rows.reverse(); } @@ -380,18 +371,20 @@ class AttributesTable extends React.Component { rows.push({ isOpen: false, cells: [ - srow[1], srow[2], srow[2] + { content: srow[1] }, + { content: srow[2] }, + { content: srow[3] } ], disableActions: srow.not_user_defined }); - srow.expandedRow.parent = count; // reset parent idx + srow.expandedRow.parent = count; rows.push(srow.expandedRow); count += 2; } this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows, @@ -432,34 +425,28 @@ class AttributesTable extends React.Component { componentDidMount() { let rows = []; let columns = this.state.columns; - let count = 0; let noRows = false; for (const row of this.props.rows) { - let user_defined = false; - if (row.x_origin.length > 0 && - row.x_origin.indexOf("user defined") !== -1) { - user_defined = true; - } - rows.push( - { - isOpen: false, - cells: [row.name[0], row.oid[0], row.syntax[0]], - disableActions: !user_defined, - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] - }, - ); - count += 2; + let user_defined = row.x_origin.includes("user defined"); + rows.push({ + isOpen: false, + cells: [ + { content: row.name[0] }, + { content: row.oid[0] }, + { content: row.syntax[0] } + ], + disableActions: !user_defined, + originalData: row + }); } + if (rows.length === 0) { noRows = true; - rows = [{ cells: [_("No Attributes")] }]; + rows = [{ cells: [{ content: _("No Attributes") }] }]; columns = [{ title: _("Attributes") }]; } + this.setState({ rows, columns, @@ -518,31 +505,21 @@ class AttributesTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Attribute"), - onClick: (event, rowId, rowData, extra) => - this.props.editModalHandler(rowData.cells[0]) - }, - { - title: _("Delete Attribute"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteHandler(rowData.cells[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Attribute"), + onClick: () => this.props.editModalHandler(rowData.cells[0].content) + }, + { + title: _("Delete Attribute"), + onClick: () => this.props.deleteHandler(rowData.cells[0].content) + } + ]; render() { const { perPage, page, sortBy, rows, noRows, columns } = this.state; - const origRows = [...rows]; - const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); let content = ( <div className="ds-center ds-margin-top-xlg"> @@ -568,28 +545,70 @@ class AttributesTable extends React.Component { /> </GridItem> </Grid> - <Table - className="ds-margin-top" - aria-label="attr table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - onCollapse={this.handleCollapse} - actions={noRows ? null : this.actions()} - dropdownPosition="right" - dropdownDirection="bottom" + <Table + aria-label="attributes table" + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + <Th screenReaderText="Actions" /> + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {cell.content} + </Td> + ))} + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + isDisabled={row.disableActions} + /> + </Td> + </Tr> + {row.isOpen && ( + <Tr isExpanded={true}> + <Td colSpan={columns.length + 2}> + <ExpandableRowContent> + {this.getExpandedRow(row.originalData)} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination - itemCount={this.state.rows.length / 2} + itemCount={rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> @@ -629,13 +648,9 @@ class MatchingRulesTable extends React.Component { sortBy: {}, rows: [], columns: [ - { - title: _("Matching Rule"), - transforms: [sortable], - cellFormatters: [expandable] - }, - { title: _("OID"), transforms: [sortable] }, - { title: _("Syntax"), transforms: [sortable] }, + { title: _("Matching Rule"), sortable: true }, + { title: _("OID"), sortable: true }, + { title: _("Syntax"), sortable: true }, ], }; @@ -648,7 +663,7 @@ class MatchingRulesTable extends React.Component { this.handlePerPageSelect = (_event, perPage) => { this.setState({ perPage, - page: 1 // reset page back to 1 + page: 1 }); }; @@ -657,41 +672,45 @@ class MatchingRulesTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - handleSort(_event, index, direction) { + handleSort(_event, columnIndex, direction) { const sorted_rows = []; const rows = []; let count = 0; - // Convert the rows pairings into a sortable array based on the column indexes + // Convert the rows pairings into a sortable array for (let idx = 0; idx < this.state.rows.length; idx += 2) { sorted_rows.push({ expandedRow: this.state.rows[idx + 1], - 1: this.state.rows[idx].cells[0], - 2: this.state.rows[idx].cells[1], - 3: this.state.rows[idx].cells[2], + 1: this.state.rows[idx].cells[0].content, + 2: this.state.rows[idx].cells[1].content, + 3: this.state.rows[idx].cells[2].content, }); } - // Sort the rows and build the new rows - sorted_rows.sort((a, b) => (a[index] > b[index]) ? 1 : -1); + sorted_rows.sort((a, b) => (a[columnIndex + 1] > b[columnIndex + 1]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_rows.reverse(); } + for (const srow of sorted_rows) { rows.push({ isOpen: false, cells: [ - srow[1], srow[2], srow[2] + { content: srow[1] }, + { content: srow[2] }, + { content: srow[3] } ], }); - srow.expandedRow.parent = count; // reset parent idx - rows.push(srow.expandedRow); + rows.push({ + ...srow.expandedRow, + parent: count + }); count += 2; } this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows, @@ -713,42 +732,42 @@ class MatchingRulesTable extends React.Component { ); } + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + const index = (this.state.perPage * (this.state.page - 1) * 2) + rowIndex; + rows[index].isOpen = isExpanding; + this.setState({ rows }); + } + componentDidMount() { let rows = []; - let columns = this.state.columns; let count = 0; for (const row of this.props.rows) { rows.push( { isOpen: false, - cells: [{ title: row.name[0] }, row.oid[0], row.syntax[0]], + cells: [ + { content: row.name[0] }, + { content: row.oid[0] }, + { content: row.syntax[0] } + ], }, { parent: count, fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] + cells: [{ content: this.getExpandedRow(row) }] }, ); count += 2; } if (rows.length === 0) { - rows = [{ cells: ['No Matching Rules'] }]; - columns = [{ title: 'Matching Rules' }]; + rows = [{ cells: [{ content: 'No Matching Rules' }] }]; + this.setState({ + columns: [{ title: 'Matching Rules' }] + }); } - this.setState({ - rows, - columns - }); - } - - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + this.setState({ rows }); } handleSearchChange(event, value) { @@ -759,24 +778,28 @@ class MatchingRulesTable extends React.Component { const val = value.toLowerCase(); let name = ""; // Check for matches of all the parts - if (row.names.length > 0) { + if (row.names && row.names.length > 0) { name = row.name[0]; } - if (val !== "" && name.indexOf(val) === -1 && - row.oid[0].indexOf(val) === -1 && - row.syntax[0].indexOf(val) === -1) { + if (name.toLowerCase().indexOf(val) === -1 && + row.oid[0].toLowerCase().indexOf(val) === -1 && + row.syntax[0].toLowerCase().indexOf(val) === -1) { // Not a match continue; } rows.push( { isOpen: false, - cells: [{ title: name === "" ? <i><{_("No Name")}></i> : name }, row.oid[0], row.syntax[0]], + cells: [ + { content: name || <i><{_("No Name")}></i> }, + { content: row.oid[0] }, + { content: row.syntax[0] } + ], }, { parent: count, fullWidth: true, - cells: [{ title: this.getExpandedRow(row) }] + cells: [{ content: this.getExpandedRow(row) }] }, ); count += 2; @@ -791,14 +814,12 @@ class MatchingRulesTable extends React.Component { render() { const { perPage, page, sortBy, rows, columns } = this.state; - const origRows = [...rows]; const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); + const tableRows = rows.slice(startIdx, startIdx + (perPage * 2)); - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + // Filter out the expanded rows for the main table display + const displayRows = tableRows.filter((row, index) => index % 2 === 0); + const expandedContent = tableRows.filter((row, index) => index % 2 === 1); return ( <div> @@ -814,23 +835,62 @@ class MatchingRulesTable extends React.Component { </Grid> <Table className="ds-margin-top" - aria-label="attr table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - onCollapse={this.handleCollapse} + aria-label="matching rules table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + </Tr> + </Thead> + <Tbody> + {displayRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex * 2, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {cell.content} + </Td> + ))} + </Tr> + {row.isOpen && ( + <Tr isExpanded={true}> + <Td colSpan={columns.length + 1}> + <ExpandableRowContent> + {expandedContent[rowIndex].cells[0].content} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> <Pagination - itemCount={this.state.rows.length / 2} + itemCount={rows.length / 2} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} /> diff --git a/src/cockpit/389-console/src/lib/security/ciphers.jsx b/src/cockpit/389-console/src/lib/security/ciphers.jsx index 8f13701d07..f8c4ed0754 100644 --- a/src/cockpit/389-console/src/lib/security/ciphers.jsx +++ b/src/cockpit/389-console/src/lib/security/ciphers.jsx @@ -1,23 +1,25 @@ import React from "react"; import cockpit from "cockpit"; import { - Button, - Form, - FormSelect, - FormSelectOption, - FormHelperText, - Grid, - GridItem, - Select, - SelectVariant, - SelectOption, - SimpleList, - SimpleListItem, - Spinner, - Text, - TextContent, - TextVariants, -} from "@patternfly/react-core"; + Button, + Form, + FormSelect, + FormSelectOption, + FormHelperText, + Grid, + GridItem, + SimpleList, + SimpleListItem, + Spinner, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import { log_cmd } from "../../lib/tools.jsx"; import PropTypes from "prop-types"; @@ -40,7 +42,7 @@ export class Ciphers extends React.Component { }; // Allow Ciphers - this.handleAllowCipherToggle = isAllowCipherOpen => { + this.handleAllowCipherToggle = (_event, isAllowCipherOpen) => { this.setState({ isAllowCipherOpen }); @@ -52,7 +54,7 @@ export class Ciphers extends React.Component { }); }; - this.handleDenyCipherToggle = isDenyCipherOpen => { + this.handleDenyCipherToggle = (_event, isDenyCipherOpen) => { this.setState({ isDenyCipherOpen }); @@ -408,14 +410,14 @@ export class Ciphers extends React.Component { <FormSelect id="cipherPref" value={this.state.cipherPref} - onChange={this.handlePrefChange} + onChange={(_event, val) => this.handlePrefChange(val)} aria-label="pref select" > <FormSelectOption key="1" value="default" label={_("Default Ciphers")} /> <FormSelectOption key="2" value="+all" label={_("All Ciphers")} /> <FormSelectOption key="3" value="-all" label={_("No Ciphers")} /> </FormSelect> - <FormHelperText isHidden={this.state.cipherPref !== "default"}> + <FormHelperText > {_("Default cipher suite is chosen. It enables the default ciphers advertised by NSS except weak ciphers.")}{(this.state.allowCiphers.length !== 0 || this.state.denyCiphers.length !== 0) ? _(" Any data in the 'Allow Specific Ciphers' and 'Deny Specific Ciphers' fields will be cleaned after the restart.") : ""} </FormHelperText> </GridItem> @@ -429,7 +431,7 @@ export class Ciphers extends React.Component { variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel={_("Type a cipher")} isDisabled={this.state.cipherPref === "default"} - onToggle={this.handleAllowCipherToggle} + onToggle={(event, isOpen) => this.handleAllowCipherToggle(event, isOpen)} onSelect={this.handleAllowCipherChange} onClear={this.handleAllowCipherClear} selections={this.state.allowCiphers} @@ -457,7 +459,7 @@ export class Ciphers extends React.Component { variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel={_("Type a cipher")} isDisabled={this.state.cipherPref === "default"} - onToggle={this.handleDenyCipherToggle} + onToggle={(event, isOpen) => this.handleDenyCipherToggle(event, isOpen)} onSelect={this.handleDenyCipherChange} onClear={this.handleDenyCipherClear} selections={this.state.denyCiphers} diff --git a/src/cockpit/389-console/src/lib/security/securityModals.jsx b/src/cockpit/389-console/src/lib/security/securityModals.jsx index 51f06f893e..fcb9184c63 100644 --- a/src/cockpit/389-console/src/lib/security/securityModals.jsx +++ b/src/cockpit/389-console/src/lib/security/securityModals.jsx @@ -1,31 +1,34 @@ import cockpit from "cockpit"; import React from "react"; import { - Button, - Checkbox, - ClipboardCopy, ClipboardCopyVariant, - Divider, - FileUpload, - Form, - FormSelect, - FormSelectOption, - Grid, - GridItem, - HelperText, - HelperTextItem, - Modal, - ModalVariant, - Radio, - Select, - SelectOption, - SelectVariant, - Text, - TextContent, - TextVariants, - TextInput, - Tooltip, - ValidatedOptions, -} from "@patternfly/react-core"; + Button, + Checkbox, + ClipboardCopy, + ClipboardCopyVariant, + Divider, + FileUpload, + Form, + FormSelect, + FormSelectOption, + Grid, + GridItem, + HelperText, + HelperTextItem, + Modal, + ModalVariant, + Radio, + Text, + TextContent, + TextVariants, + TextInput, + Tooltip, + ValidatedOptions +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons/dist/js/icons/outlined-question-circle-icon'; import PropTypes from "prop-types"; import { bad_file_name, validHostname } from "../tools.jsx"; @@ -97,7 +100,7 @@ export class ExportCertModal extends React.Component { id="exportFileName" aria-describedby="horizontal-form-name-helper" name="exportFileName" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={fileName === "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -110,7 +113,7 @@ export class ExportCertModal extends React.Component { <Checkbox label={_("Export Certificate In Binary/DER Format")} isChecked={binaryFormat} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} id="exportDERFormat" @@ -239,7 +242,7 @@ export class SecurityAddCertModal extends React.Component { id="certName" aria-describedby="horizontal-form-name-helper" name="certName" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={certName === "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -337,7 +340,7 @@ export class SecurityAddCertModal extends React.Component { id="certFile" aria-describedby="horizontal-form-name-helper" name="certFile" - onChange={(value, e) => { + onChange={(e, value) => { handleChange(e); }} validated={certRadioFile && certFile === "" ? ValidatedOptions.error : ValidatedOptions.default} @@ -425,7 +428,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrName" aria-describedby="horizontal-form-name-helper" name="csrName" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.csrName || bad_file_name(csrName) ? ValidatedOptions.error : ValidatedOptions.default} @@ -477,7 +480,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrSubjectCommonName" aria-describedby="horizontal-form-name-helper" name="csrSubjectCommonName" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} validated={error.csrSubjectCommonName ? ValidatedOptions.error : ValidatedOptions.default} @@ -495,7 +498,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrSubjectOrg" aria-describedby="horizontal-form-name-helper" name="csrSubjectOrg" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -512,7 +515,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrSubjectOrgUnit" aria-describedby="horizontal-form-name-helper" name="csrSubjectOrgUnit" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -529,7 +532,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrSubjectLocality" aria-describedby="horizontal-form-name-helper" name="csrSubjectLocality" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -546,7 +549,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrSubjectState" aria-describedby="horizontal-form-name-helper" name="csrSubjectState" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -563,7 +566,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrSubjectCountry" aria-describedby="horizontal-form-name-helper" name="csrSubjectCountry" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -580,7 +583,7 @@ export class SecurityAddCSRModal extends React.Component { id="csrSubjectEmail" aria-describedby="horizontal-form-name-helper" name="csrSubjectEmail" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} /> @@ -701,7 +704,7 @@ export class SecurityEnableModal extends React.Component { <FormSelect value={primaryName} id="certNameSelect" - onChange={(str, e) => { + onChange={(e, str) => { handleChange(e); }} aria-label="FormSelect Input" @@ -864,7 +867,7 @@ export class EditCertModal extends React.Component { <Checkbox id="CflagSSL" isChecked={CSSLChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -873,7 +876,7 @@ export class EditCertModal extends React.Component { <Checkbox id="CflagEmail" isChecked={CEmailChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -882,7 +885,7 @@ export class EditCertModal extends React.Component { <Checkbox id="CflagOS" isChecked={COSChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -895,7 +898,7 @@ export class EditCertModal extends React.Component { <Checkbox id="TflagSSL" isChecked={TSSLChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -904,7 +907,7 @@ export class EditCertModal extends React.Component { <Checkbox id="TflagEmail" isChecked={TEmailChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -913,7 +916,7 @@ export class EditCertModal extends React.Component { <Checkbox id="TflagOS" isChecked={TOSChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -926,7 +929,7 @@ export class EditCertModal extends React.Component { <Checkbox id="cflagSSL" isChecked={cSSLChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -935,7 +938,7 @@ export class EditCertModal extends React.Component { <Checkbox id="cflagEmail" isChecked={cEmailChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -944,7 +947,7 @@ export class EditCertModal extends React.Component { <Checkbox id="cflagOS" isChecked={cOSChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -957,7 +960,7 @@ export class EditCertModal extends React.Component { <Checkbox id="PflagSSL" isChecked={PSSLChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -966,7 +969,7 @@ export class EditCertModal extends React.Component { <Checkbox id="PflagEmail" isChecked={PEmailChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -975,7 +978,7 @@ export class EditCertModal extends React.Component { <Checkbox id="PflagOS" isChecked={POSChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -988,7 +991,7 @@ export class EditCertModal extends React.Component { <Checkbox id="pflagSSL" isChecked={pSSLChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -997,7 +1000,7 @@ export class EditCertModal extends React.Component { <Checkbox id="pflagEmail" isChecked={pEmailChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> @@ -1006,7 +1009,7 @@ export class EditCertModal extends React.Component { <Checkbox id="pflagOS" isChecked={pOSChecked} - onChange={(checked, e) => { + onChange={(e, checked) => { handleChange(e); }} /> diff --git a/src/cockpit/389-console/src/lib/security/securityTables.jsx b/src/cockpit/389-console/src/lib/security/securityTables.jsx index 27e6a78f45..fce4cb04e6 100644 --- a/src/cockpit/389-console/src/lib/security/securityTables.jsx +++ b/src/cockpit/389-console/src/lib/security/securityTables.jsx @@ -9,13 +9,15 @@ import { Tooltip, } from '@patternfly/react-core'; import { - expandable, Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + SortByDirection, + Thead, + Tr, + Th, + Tbody, + Td, + ActionsColumn, + ExpandableRowContent } from '@patternfly/react-table'; import PropTypes from "prop-types"; @@ -33,9 +35,9 @@ class KeyTable extends React.Component { rows: [], hasRows: false, columns: [ - { title: _("Cipher"), transforms: [sortable] }, - { title: _("Key Identifier"), transforms: [sortable] }, - { title: _("State"), transforms: [sortable] }, + { title: _("Cipher"), sortable: true }, + { title: _("Key Identifier"), sortable: true }, + { title: _("State"), sortable: true }, ], }; @@ -50,6 +52,8 @@ class KeyTable extends React.Component { perPage }); }; + + this.handleSort = this.handleSort.bind(this); } componentDidMount() { @@ -58,12 +62,11 @@ class KeyTable extends React.Component { let hasRows = true; for (const ServerKey of this.props.ServerKeys) { - rows.push( - { - isOpen: false, - cells: [ServerKey.attrs.cipher, ServerKey.attrs.key_id, ServerKey.attrs.state], - }, - ); + rows.push([ + ServerKey.attrs.cipher, + ServerKey.attrs.key_id, + ServerKey.attrs.state + ]); } if (rows.length === 0) { @@ -79,63 +82,107 @@ class KeyTable extends React.Component { }); } - actions() { - return [ - { - title: _("Delete Key"), - onClick: (event, rowId, rowData, extra) => { - if (rowData.cells[1]) { - this.props.delKey(rowData.cells[1]); - } + getActionsForRow = (rowData) => [ + { + title: _("Delete Key"), + onClick: () => { + if (rowData[1]) { + this.props.delKey(rowData[1]); } } - ]; + } + ]; + + handleSort(_event, index, direction) { + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); + + this.setState({ + sortBy: { + index, + direction + }, + rows: direction === SortByDirection.asc ? sortedRows : sortedRows.reverse() + }); } render() { const { perPage, page, sortBy, rows, columns, hasRows } = this.state; + const startIdx = (perPage * page) - perPage; + const tableRows = rows.slice(startIdx, startIdx + perPage); return ( <div className="ds-margin-top-lg"> <Tooltip - content={ - <div> - <p> - {_("An orphan key is a private key in the NSS DB for which there is NO cert with the corresponding public key. An orphan key is created during CSR creation, when the certificate associated with a CSR has been imported into the NSS DB its orphan state will be removed.")} - <br /><br /> - {_("Make sure an orphan key is not associated with a submitted CSR before you delete it.")} - </p> - </div> - } + content={ + <div> + <p> + {_("An orphan key is a private key in the NSS DB for which there is NO cert with the corresponding public key. An orphan key is created during CSR creation, when the certificate associated with a CSR has been imported into the NSS DB its orphan state will be removed.")} + <br /><br /> + {_("Make sure an orphan key is not associated with a submitted CSR before you delete it.")} + </p> + </div> + } > <a className="ds-font-size-sm">{_("What is an orphan key?")}</a> </Tooltip> - <Table + <Table className="ds-margin-top" aria-label="orph key table" - cells={columns} - key={rows} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={hasRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> {hasRows && <Pagination - itemCount={this.state.rows.length} + itemCount={rows.length} widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} - />} + /> + } </div> ); } @@ -152,10 +199,10 @@ class CSRTable extends React.Component { sortBy: {}, rows: [], columns: [ - { title: _("Name"), transforms: [sortable] }, - { title: _("Subject DN"), transforms: [sortable] }, - { title: _("Subject Alternative Names"), transforms: [sortable] }, - { title: _("Modification Date"), transforms: [sortable] }, + { title: _("Name"), sortable: true }, + { title: _("Subject DN"), sortable: true }, + { title: _("Subject Alternative Names"), sortable: true }, + { title: _("Modification Date"), sortable: true }, ], }; @@ -177,7 +224,9 @@ class CSRTable extends React.Component { } handleSort(_event, index, direction) { - const sortedRows = this.state.rows.sort((a, b) => (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)); + const sortedRows = [...this.state.rows].sort((a, b) => + (a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0) + ); this.setState({ sortBy: { index, @@ -187,27 +236,37 @@ class CSRTable extends React.Component { }); } + getActionsForRow = (rowData) => [ + { + title: _("Delete CSR"), + onClick: () => this.props.delCSR(rowData[0]) + }, + { + title: _("View CSR"), + onClick: () => this.props.viewCSR(rowData[0]) + } + ]; + componentDidMount() { let rows = []; let columns = this.state.columns; let hasRows = true; for (const ServerCSR of this.props.ServerCSRs) { - rows.push( - { - isOpen: false, - cells: [ - ServerCSR.attrs.name, ServerCSR.attrs.subject, - ServerCSR.attrs.subject_alt_names.join(", "), ServerCSR.attrs.modified - ], - }, - ); + rows.push([ + ServerCSR.attrs.name, + ServerCSR.attrs.subject, + ServerCSR.attrs.subject_alt_names.join(", "), + ServerCSR.attrs.modified + ]); } + if (rows.length === 0) { rows = [{ cells: [_("No Certificate Signing Requests")] }]; columns = [{ title: _("Certificate Signing Requests") }]; hasRows = false; } + this.setState({ rows, columns, @@ -221,27 +280,19 @@ class CSRTable extends React.Component { for (const cert of this.props.ServerCSRs) { const val = value.toLowerCase(); - // Check for matches of all the parts if (val !== "" && cert.attrs.name.toLowerCase().indexOf(val) === -1 && cert.attrs.subject.toLowerCase().indexOf(val) === -1 && - cert.attrs.subject_alt_names.join().toLowerCase() - .indexOf(val) === -1 && + cert.attrs.subject_alt_names.join().toLowerCase().indexOf(val) === -1 && cert.attrs.modified.toLowerCase().indexOf(val) === -1) { - // Not a match continue; } - rows.push( - { - isOpen: false, - cells: [ - cert.attrs.name, cert.attrs.subject, - cert.attrs.subject_alt_names.join(", "), - cert.attrs.modified - ], - - }, - ); + rows.push([ + cert.attrs.name, + cert.attrs.subject, + cert.attrs.subject_alt_names.join(", "), + cert.attrs.modified + ]); } this.setState({ @@ -251,29 +302,9 @@ class CSRTable extends React.Component { }); } - actions() { - return [ - { - title: _("Delete CSR"), - onClick: (event, rowId, rowData, extra) => { - if (rowData.cells.length > 1) { - this.props.delCSR(rowData.cells[0]); - } - } - }, - { - title: _("View CSR"), - onClick: (event, rowId, rowData, extra) => { - if (rowData.cells.length > 1) { - this.props.viewCSR(rowData.cells[0]); - } - } - } - ]; - } - render() { const { perPage, page, sortBy, rows, columns, hasRows } = this.state; + const tableRows = rows.slice((page - 1) * perPage, page * perPage); return ( <div className="ds-margin-top-lg"> @@ -283,22 +314,52 @@ class CSRTable extends React.Component { value={this.state.value} onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} - />} - <Table + /> + } + <Table className="ds-margin-top" aria-label="csr table" - cells={columns} - key={rows} - rows={rows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - actions={hasRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + {Array.isArray(row) ? ( + row.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + ) : ( + row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + )) + )} + {hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + ))} + </Tbody> </Table> {hasRows && <Pagination @@ -306,10 +367,11 @@ class CSRTable extends React.Component { widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} - />} + /> + } </div> ); } @@ -330,11 +392,10 @@ class CertTable extends React.Component { columns: [ { title: _("Nickname"), - transforms: [sortable], - cellFormatters: [expandable] + sortable: true }, - { title: _("Subject DN"), transforms: [sortable] }, - { title: _("Expiration Date"), transforms: [sortable] }, + { title: _("Subject DN"), sortable: true }, + { title: _("Expiration Date"), sortable: true }, ], }; @@ -356,45 +417,47 @@ class CertTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } - handleSort(_event, index, direction) { + handleSort(_event, columnIndex, direction) { const sorted_rows = []; const rows = []; let count = 0; - // Convert the rows pairings into a sortable array based on the column indexes + // Convert the rows pairings into a sortable array for (let idx = 0; idx < this.state.rows.length; idx += 2) { sorted_rows.push({ expandedRow: this.state.rows[idx + 1], - 1: this.state.rows[idx].cells[0], - 2: this.state.rows[idx].cells[1], - 3: this.state.rows[idx].cells[2], + 1: this.state.rows[idx].cells[0].content, + 2: this.state.rows[idx].cells[1].content, + 3: this.state.rows[idx].cells[2].content, issuer: this.state.rows[idx].issuer, flags: this.state.rows[idx].flags }); } - // Sort the rows and build the new rows - sorted_rows.sort((a, b) => (a[index] > b[index]) ? 1 : -1); + sorted_rows.sort((a, b) => (a[columnIndex + 1] > b[columnIndex + 1]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_rows.reverse(); } + for (const srow of sorted_rows) { rows.push({ isOpen: false, cells: [ - srow[1], srow[2], srow[3] + { content: srow[1] }, + { content: srow[2] }, + { content: srow[3] } ], issuer: srow.issuer, flags: srow.flags, }); - srow.expandedRow.parent = count; // reset parent idx + srow.expandedRow.parent = count; rows.push(srow.expandedRow); count += 2; } this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows, @@ -409,7 +472,6 @@ class CertTable extends React.Component { <GridItem span={9}><b>{issuer}</b></GridItem> <GridItem span={3}>{_("Trust Flags:")}</GridItem> <GridItem span={9}><b>{flags}</b></GridItem> - </Grid> ); } @@ -421,24 +483,21 @@ class CertTable extends React.Component { let hasRows = true; for (const cert of this.props.certs) { - rows.push( - { - isOpen: false, - cells: [cert.attrs.nickname, cert.attrs.subject, cert.attrs.expires], - issuer: cert.attrs.issuer, - flags: cert.attrs.flags, - - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(cert.attrs.issuer, cert.attrs.flags) }] - }, - ); - count += 2; + rows.push({ + isOpen: false, + cells: [ + { content: cert.attrs.nickname }, + { content: cert.attrs.subject }, + { content: cert.attrs.expires } + ], + issuer: cert.attrs.issuer, + flags: cert.attrs.flags, + originalData: cert.attrs // Store the original data for expansion + }); + count += 1; } if (rows.length === 0) { - rows = [{ cells: [_("No Certificates")] }]; + rows = [{ cells: [{ content: _("No Certificates") }] }]; columns = [{ title: _("Certificates") }]; hasRows = false; } @@ -449,13 +508,11 @@ class CertTable extends React.Component { }); } - handleCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + const index = (this.state.perPage * (this.state.page - 1) * 2) + rowIndex; + rows[index].isOpen = isExpanding; + this.setState({ rows }); } handleSearchChange(event, value) { @@ -498,39 +555,28 @@ class CertTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Trust Flags"), - onClick: (event, rowId, rowData, extra) => - this.props.editCert(rowData.cells[0], rowData.flags) - }, - { - title: _("Export Certificate"), - onClick: (event, rowId, rowData, extra) => - this.props.exportCert(rowData.cells[0]) - }, - { - isSeparator: true - }, - { - title: _("Delete Certificate"), - onClick: (event, rowId, rowData, extra) => - this.props.delCert(rowData.cells[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Trust Flags"), + onClick: () => this.props.editCert(rowData.cells[0].content, rowData.flags) + }, + { + title: _("Export Certificate"), + onClick: () => this.props.exportCert(rowData.cells[0].content) + }, + { + isSeparator: true + }, + { + title: _("Delete Certificate"), + onClick: () => this.props.delCert(rowData.cells[0].content) + } + ]; render() { const { perPage, page, sortBy, rows, columns, hasRows } = this.state; - const origRows = [...rows]; const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const tableRows = rows.slice(startIdx, startIdx + (perPage * 2)); return ( <div className="ds-margin-top-lg"> @@ -541,22 +587,62 @@ class CertTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} />} - <Table - className="ds-margin-top" + <Table aria-label="cert table" - cells={columns} - key={tableRows} - rows={tableRows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - onCollapse={this.handleCollapse} - actions={hasRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + <Th screenReaderText="Actions" /> + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {cell.content} + </Td> + ))} + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + </Tr> + {row.isOpen && ( + <Tr isExpanded={true}> + <Td colSpan={columns.length + 2}> + <ExpandableRowContent> + {this.getExpandedRow(row.issuer, row.flags)} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> {hasRows && <Pagination @@ -564,7 +650,7 @@ class CertTable extends React.Component { widgetId="pagination-options-menu-bottom" perPage={perPage} page={page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} />} @@ -584,13 +670,12 @@ class CRLTable extends React.Component { value: '', sortBy: {}, rows: [], - dropdownIsOpen: false, hasRows: false, columns: [ - { title: _("Issued By"), transforms: [sortable] }, - { title: _("Effective Date"), transforms: [sortable] }, - { title: _("Next Update"), transforms: [sortable] }, - { title: _("Type"), transforms: [sortable] }, + { title: _("Issued By"), sortable: true }, + { title: _("Effective Date"), sortable: true }, + { title: _("Next Update"), sortable: true }, + { title: _("Type"), sortable: true }, ], }; @@ -610,37 +695,48 @@ class CRLTable extends React.Component { this.handleSearchChange = this.handleSearchChange.bind(this); } + getActionsForRow = (rowData) => [ + { + title: _("View CRL"), + onClick: () => this.props.editConfig(rowData.cells[0], rowData.cells[1], rowData.credsBindpw, rowData.pwInteractive) + }, + { + title: _("Delete CRL"), + onClick: () => this.props.deleteConfig(rowData.cells[0]) + } + ]; + handleSort(_event, index, direction) { const sorted_rows = []; const rows = []; let count = 0; - // Convert the rows pairings into a sortable array based on the column indexes + // Convert the rows pairings into a sortable array for (let idx = 0; idx < this.state.rows.length; idx += 2) { sorted_rows.push({ 1: this.state.rows[idx].cells[0], 2: this.state.rows[idx].cells[1], 3: this.state.rows[idx].cells[2], issuer: this.state.rows[idx].issuer, - flags: this.state.rows[idx].flags + flags: this.state.rows[idx].flags, + expandedRow: this.state.rows[idx + 1] }); } - // Sort the rows and build the new rows + // Sort and rebuild rows sorted_rows.sort((a, b) => (a[index] > b[index]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_rows.reverse(); } + for (const srow of sorted_rows) { rows.push({ isOpen: false, - cells: [ - srow[1], srow[2], srow[3] - ], + cells: [srow[1], srow[2], srow[3]], issuer: srow.issuer, flags: srow.flags, }); - srow.expandedRow.parent = count; // reset parent idx + srow.expandedRow.parent = count; rows.push(srow.expandedRow); count += 2; } @@ -696,22 +792,12 @@ class CRLTable extends React.Component { }); } - actions() { - return [ - { - title: _("View CRL"), - onClick: (event, rowId, rowData, extra) => - this.props.editConfig(rowData.cells[0], rowData.cells[1], rowData.credsBindpw, rowData.pwInteractive) - }, - { - title: _("Delete CRL"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteConfig(rowData.cells[0]) - } - ]; - } - render() { + const tableRows = this.state.rows.slice( + (this.state.page - 1) * this.state.perPage, + this.state.page * this.state.perPage + ); + return ( <div className="ds-margin-top"> <SearchInput @@ -720,27 +806,66 @@ class CRLTable extends React.Component { onChange={this.handleSearchChange} onClear={(evt) => this.handleSearchChange(evt, '')} /> - <Table - variant={TableVariant.compact} aria-label="Cred Table" - sortBy={this.sortBy} onSort={this.handleSort} cells={this.state.columns} - rows={this.state.rows} - actions={this.state.hasRows ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + <Table + aria-label="CRL Table" + variant="compact" > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + {this.state.columns.map((column, idx) => ( + <Th + key={idx} + sort={column.sortable ? { + sortBy: this.state.sortBy, + onSort: this.handleSort, + columnIndex: idx + } : undefined} + > + {column.title} + </Th> + ))} + {this.state.hasRows && <Th screenReaderText="Actions" />} + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}>{cell}</Td> + ))} + {this.state.hasRows && ( + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + )} + </Tr> + {row.isOpen && ( + <Tr isExpanded> + <Td colSpan={this.state.columns.length + 1}> + <ExpandableRowContent> + {this.getExpandedRow(row.issuer, row.flags)} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> - {this.state.hasRows && + {this.state.hasRows && ( <Pagination itemCount={this.state.rows.length} widgetId="pagination-options-menu-bottom" perPage={this.state.perPage} page={this.state.page} - variant={PaginationVariant.bottom} + variant="bottom" onSetPage={this.handleSetPage} onPerPageSelect={this.handlePerPageSelect} - />} + /> + )} </div> ); } diff --git a/src/cockpit/389-console/src/lib/server/accessLog.jsx b/src/cockpit/389-console/src/lib/server/accessLog.jsx index 1f68ad21e4..416fd8d9f9 100644 --- a/src/cockpit/389-console/src/lib/server/accessLog.jsx +++ b/src/cockpit/389-console/src/lib/server/accessLog.jsx @@ -25,9 +25,11 @@ import { } from "@patternfly/react-core"; import { Table, - TableHeader, - TableBody, - TableVariant + Thead, + Tr, + Th, + Tbody, + Td, } from '@patternfly/react-table'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { @@ -107,7 +109,7 @@ export class ServerAccessLog extends React.Component { }); }; - this.handleOnToggle = isExpanded => { + this.handleOnToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -550,7 +552,7 @@ export class ServerAccessLog extends React.Component { className="ds-margin-top-xlg" id="nsslapd-accesslog-logging-enabled" isChecked={this.state['nsslapd-accesslog-logging-enabled']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Enable access logging (nsslapd-accesslog-logging-enabled).")} @@ -568,7 +570,7 @@ export class ServerAccessLog extends React.Component { id="nsslapd-accesslog" aria-describedby="horizontal-form-name-helper" name="nsslapd-accesslog" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "settings"); }} /> @@ -578,7 +580,7 @@ export class ServerAccessLog extends React.Component { className="ds-left-margin-md ds-margin-top-lg" id="nsslapd-accesslog-logbuffering" isChecked={this.state['nsslapd-accesslog-logbuffering']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Disable access log buffering for faster troubleshooting, but this will impact server performance (nsslapd-accesslog-logbuffering).")} @@ -591,17 +593,30 @@ export class ServerAccessLog extends React.Component { onToggle={this.handleOnToggle} isExpanded={this.state.isExpanded} > - <Table - className="ds-left-margin" - onSelect={this.handleOnSelect} - canSelectAll={this.state.canSelectAll} - variant={TableVariant.compact} - aria-label="Selectable Table" - cells={this.state.columns} - rows={this.state.rows} - > - <TableHeader /> - <TableBody /> + <Table aria-label="Selectable Table"> + <Thead> + <Tr> + <Th screenReaderText="Checkboxes" /> + <Th>{this.state.columns[0].title}</Th> + </Tr> + </Thead> + <Tbody> + {this.state.rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: (_event, isSelecting) => + this.handleOnSelect(_event, isSelecting, rowIndex), + isSelected: row.selected + }} + /> + <Td> + {row.cells[0].title} + </Td> + </Tr> + ))} + </Tbody> </Table> </ExpandableSection> @@ -689,7 +704,7 @@ export class ServerAccessLog extends React.Component { <FormSelect id="nsslapd-accesslog-logrotationtimeunit" value={this.state['nsslapd-accesslog-logrotationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rotation"); }} aria-label="FormSelect Input" @@ -724,7 +739,7 @@ export class ServerAccessLog extends React.Component { <Switch id="nsslapd-accesslog-compress" isChecked={this.state['nsslapd-accesslog-compress']} - onChange={this.handleSwitchChange} + onChange={(_event, value) => this.handleSwitchChange(value)} aria-label="nsslapd-accesslog-compress" /> </GridItem> @@ -818,7 +833,7 @@ export class ServerAccessLog extends React.Component { <FormSelect id="nsslapd-accesslog-logexpirationtimeunit" value={this.state['nsslapd-accesslog-logexpirationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "exp"); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/server/auditLog.jsx b/src/cockpit/389-console/src/lib/server/auditLog.jsx index 52eb4fb614..f290689302 100644 --- a/src/cockpit/389-console/src/lib/server/auditLog.jsx +++ b/src/cockpit/389-console/src/lib/server/auditLog.jsx @@ -2,29 +2,31 @@ import cockpit from "cockpit"; import React from "react"; import { listsEqual, log_cmd } from "../tools.jsx"; import { - Button, - Checkbox, - Form, - FormGroup, - FormSelect, - FormSelectOption, - Grid, - GridItem, - NumberInput, - Select, - SelectVariant, - SelectOption, - Spinner, - Switch, - Tab, - Tabs, - TabTitleText, - TextInput, - Text, - TextContent, - TextVariants, - TimePicker, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + FormGroup, + FormSelect, + FormSelectOption, + Grid, + GridItem, + NumberInput, + Spinner, + Switch, + Tab, + Tabs, + TabTitleText, + TextInput, + Text, + TextContent, + TextVariants, + TimePicker +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSyncAlt @@ -110,7 +112,7 @@ export class ServerAuditLog extends React.Component { ); } }; - this.handleOnDisplayAttrToggle = isDisplayAttrOpen => { + this.handleOnDisplayAttrToggle = (_event, isDisplayAttrOpen) => { this.setState({ isDisplayAttrOpen }); @@ -533,7 +535,7 @@ export class ServerAuditLog extends React.Component { className="ds-margin-top-xlg" id="nsslapd-auditlog-logging-enabled" isChecked={this.state['nsslapd-auditlog-logging-enabled']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Enable audit logging (nsslapd-auditlog-logging-enabled).")} @@ -551,7 +553,7 @@ export class ServerAuditLog extends React.Component { id="nsslapd-auditlog" aria-describedby="horizontal-form-name-helper" name="nsslapd-auditlog" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "settings"); }} /> @@ -567,7 +569,7 @@ export class ServerAuditLog extends React.Component { id="nsslapd-auditlog-time-format" aria-describedby="horizontal-form-name-helper" name="nsslapd-auditlog-time-format" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "settings"); }} /> @@ -580,7 +582,7 @@ export class ServerAuditLog extends React.Component { <FormSelect id="nsslapd-auditlog-log-format" value={this.state['nsslapd-auditlog-log-format']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "settings"); }} aria-label="FormSelect Input" @@ -601,7 +603,7 @@ export class ServerAuditLog extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type an attribute" - onToggle={this.handleOnDisplayAttrToggle} + onToggle={(event, isOpen) => this.handleOnDisplayAttrToggle(event, isOpen)} onSelect={this.handleOnDisplayAttrSelect} onClear={this.handleOnDisplayAttrClear} selections={this.state.displayAttrs} @@ -622,7 +624,7 @@ export class ServerAuditLog extends React.Component { className="ds-lower-field-md" id="displayAllAttrs" isChecked={this.state.displayAllAttrs} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Display all attributes from the entry in the audit log (nsslapd-auditlog-display-attrs).")} @@ -634,7 +636,7 @@ export class ServerAuditLog extends React.Component { className="ds-left-margin-md ds-margin-top-lg" id="nsslapd-auditlog-logbuffering" isChecked={this.state['nsslapd-auditlog-logbuffering']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("This applies to both the audit & auditfail logs. Disable audit log buffering for faster troubleshooting, but this will impact server performance (nsslapd-auditlog-logbuffering).")} @@ -724,7 +726,7 @@ export class ServerAuditLog extends React.Component { <FormSelect id="nsslapd-auditlog-logrotationtimeunit" value={this.state['nsslapd-auditlog-logrotationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rotation"); }} aria-label="FormSelect Input" @@ -759,7 +761,7 @@ export class ServerAuditLog extends React.Component { <Switch id="nsslapd-auditlog-compress" isChecked={this.state['nsslapd-auditlog-compress']} - onChange={this.handleSwitchChange} + onChange={(_event, value) => this.handleSwitchChange(value)} aria-label="nsslapd-auditlog-compress" />` </GridItem> @@ -853,7 +855,7 @@ export class ServerAuditLog extends React.Component { <FormSelect id="nsslapd-auditlog-logexpirationtimeunit" value={this.state['nsslapd-auditlog-logexpirationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "exp"); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/server/auditfailLog.jsx b/src/cockpit/389-console/src/lib/server/auditfailLog.jsx index f682e98b16..5b29e54db7 100644 --- a/src/cockpit/389-console/src/lib/server/auditfailLog.jsx +++ b/src/cockpit/389-console/src/lib/server/auditfailLog.jsx @@ -426,7 +426,7 @@ export class ServerAuditFailLog extends React.Component { className="ds-margin-top-xlg" id="nsslapd-auditfaillog-logging-enabled" isChecked={this.state['nsslapd-auditfaillog-logging-enabled']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Enable audit fail logging (nsslapd-auditfaillog-logging-enabled).")} @@ -443,7 +443,7 @@ export class ServerAuditFailLog extends React.Component { id="nsslapd-auditfaillog" aria-describedby="horizontal-form-name-helper" name="nsslapd-auditfaillog" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "settings"); }} /> @@ -533,7 +533,7 @@ export class ServerAuditFailLog extends React.Component { <FormSelect id="nsslapd-auditfaillog-logrotationtimeunit" value={this.state['nsslapd-auditfaillog-logrotationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rotation"); }} aria-label="FormSelect Input" @@ -568,7 +568,7 @@ export class ServerAuditFailLog extends React.Component { <Switch id="nsslapd-auditfaillog-compress" isChecked={this.state['nsslapd-auditfaillog-compress']} - onChange={this.handleSwitchChange} + onChange={(_event, value) => this.handleSwitchChange(value)} aria-label="nsslapd-auditfaillog-compress" /> </GridItem> @@ -662,7 +662,7 @@ export class ServerAuditFailLog extends React.Component { <FormSelect id="nsslapd-auditfaillog-logexpirationtimeunit" value={this.state['nsslapd-auditfaillog-logexpirationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "exp"); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/server/errorLog.jsx b/src/cockpit/389-console/src/lib/server/errorLog.jsx index 2281d00315..4e5216e332 100644 --- a/src/cockpit/389-console/src/lib/server/errorLog.jsx +++ b/src/cockpit/389-console/src/lib/server/errorLog.jsx @@ -25,9 +25,11 @@ import { } from "@patternfly/react-core"; import { Table, - TableHeader, - TableBody, - TableVariant + Thead, + Tr, + Th, + Tbody, + Td } from '@patternfly/react-table'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { @@ -125,7 +127,7 @@ export class ServerErrorLog extends React.Component { ], }; - this.handleOnToggle = isExpanded => { + this.handleOnToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -500,14 +502,14 @@ export class ServerErrorLog extends React.Component { }, this.props.enableTree); } - handleOnSelect(event, isSelected, rowId) { + handleOnSelect = (_event, isSelected, rowIndex) => { let disableSaveBtn = true; const rows = [...this.state.rows]; + + // Update the selected row + rows[rowIndex].selected = isSelected; - // Update the row - rows[rowId].selected = isSelected; - - // Handle "save button" state, first check the other config settings + // Check other config settings for (const config_attr of settings_attrs) { if (this.state['_' + config_attr] !== this.state[config_attr]) { disableSaveBtn = false; @@ -515,14 +517,12 @@ export class ServerErrorLog extends React.Component { } } - // Handle the table contents + // Check table contents for (const row of rows) { for (const orig_row of this.state._rows) { - if (orig_row.level === row.level) { - if (orig_row.selected !== row.selected) { - disableSaveBtn = false; - break; - } + if (orig_row.level === row.level && orig_row.selected !== row.selected) { + disableSaveBtn = false; + break; } } } @@ -566,7 +566,7 @@ export class ServerErrorLog extends React.Component { className="ds-margin-top-xlg" id="nsslapd-errorlog-logging-enabled" isChecked={this.state['nsslapd-errorlog-logging-enabled']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Enable Error logging (nsslapd-errorlog-logging-enabled).")} @@ -584,7 +584,7 @@ export class ServerErrorLog extends React.Component { id="nsslapd-errorlog" aria-describedby="horizontal-form-name-helper" name="nsslapd-errorlog" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "settings"); }} /> @@ -594,20 +594,36 @@ export class ServerErrorLog extends React.Component { <ExpandableSection className="ds-left-margin-md ds-margin-top-lg ds-font-size-md" toggleText={this.state.isExpanded ? _("Hide Verbose Logging Levels") : _("Show Verbose Logging Levels")} - onToggle={this.handleOnToggle} + onToggle={(event, isExpanded) => this.handleOnToggle(event, isExpanded)} isExpanded={this.state.isExpanded} > <Table + aria-label="Selectable Error Log Levels" className="ds-left-margin" - onSelect={this.handleOnSelect} - canSelectAll={this.state.canSelectAll} - variant={TableVariant.compact} - aria-label="Selectable Table" - cells={this.state.columns} - rows={this.state.rows} > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Checkboxes" /> + <Th>{_("Logging Level")}</Th> + </Tr> + </Thead> + <Tbody> + {this.state.rows.map((row, rowIndex) => ( + <Tr key={rowIndex}> + <Td + select={{ + rowIndex, + onSelect: (_event, isSelecting) => + this.handleOnSelect(_event, isSelecting, rowIndex), + isSelected: row.selected + }} + /> + <Td> + {row.cells[0].title} + </Td> + </Tr> + ))} + </Tbody> </Table> </ExpandableSection> @@ -695,7 +711,7 @@ export class ServerErrorLog extends React.Component { <FormSelect id="nsslapd-errorlog-logrotationtimeunit" value={this.state['nsslapd-errorlog-logrotationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rotation"); }} aria-label="FormSelect Input" @@ -730,7 +746,7 @@ export class ServerErrorLog extends React.Component { <Switch id="nsslapd-errorlog-compress" isChecked={this.state['nsslapd-errorlog-compress']} - onChange={this.handleSwitchChange} + onChange={(_event, value) => this.handleSwitchChange(value)} aria-label="nsslapd-errorlog-compress" /> </GridItem> @@ -824,7 +840,7 @@ export class ServerErrorLog extends React.Component { <FormSelect id="nsslapd-errorlog-logexpirationtimeunit" value={this.state['nsslapd-errorlog-logexpirationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "exp"); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/server/ldapi.jsx b/src/cockpit/389-console/src/lib/server/ldapi.jsx index e719967a40..8781818f7c 100644 --- a/src/cockpit/389-console/src/lib/server/ldapi.jsx +++ b/src/cockpit/389-console/src/lib/server/ldapi.jsx @@ -2,20 +2,22 @@ import cockpit from "cockpit"; import React from "react"; import { log_cmd } from "../tools.jsx"; import { - Button, - Checkbox, - Form, - Grid, - GridItem, - Select, - SelectOption, - SelectVariant, - Spinner, - TextInput, - Text, - TextContent, - TextVariants, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + Grid, + GridItem, + Spinner, + TextInput, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSyncAlt @@ -46,7 +48,7 @@ export class ServerLDAPI extends React.Component { isGIDOpen: false, }; - this.handleUIDToggle = isUIDOpen => { + this.handleUIDToggle = (_event, isUIDOpen) => { this.setState({ isUIDOpen }); @@ -59,7 +61,7 @@ export class ServerLDAPI extends React.Component { }, () => { this.validateSaveBtn() }); }; - this.handleGIDToggle = isGIDOpen => { + this.handleGIDToggle = (_event, isGIDOpen) => { this.setState({ isGIDOpen }); @@ -272,7 +274,7 @@ export class ServerLDAPI extends React.Component { <Select variant={SelectVariant.single} aria-label="Select UID Input" - onToggle={this.handleUIDToggle} + onToggle={(event, isOpen) => this.handleUIDToggle(event, isOpen)} onSelect={this.handleUIDSelect} selections={this.state['nsslapd-ldapiuidnumbertype']} isOpen={this.state.isUIDOpen} @@ -293,7 +295,7 @@ export class ServerLDAPI extends React.Component { <Select variant={SelectVariant.single} aria-label="Select GID Input" - onToggle={this.handleGIDToggle} + onToggle={(event, isOpen) => this.handleGIDToggle(event, isOpen)} onSelect={this.handleGIDSelect} selections={this.state['nsslapd-ldapigidnumbertype']} isOpen={this.state.isGIDOpen} @@ -316,7 +318,7 @@ export class ServerLDAPI extends React.Component { type="text" id="nsslapd-ldapientrysearchbase" aria-describedby="horizontal-form-name-helper" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -365,7 +367,7 @@ export class ServerLDAPI extends React.Component { <Checkbox id="nsslapd-ldapimaptoentries" isChecked={this.state['nsslapd-ldapimaptoentries']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} aria-label="uncontrolled checkbox example" diff --git a/src/cockpit/389-console/src/lib/server/sasl.jsx b/src/cockpit/389-console/src/lib/server/sasl.jsx index 2f160f44b0..a4478498a8 100644 --- a/src/cockpit/389-console/src/lib/server/sasl.jsx +++ b/src/cockpit/389-console/src/lib/server/sasl.jsx @@ -3,20 +3,22 @@ import React from "react"; import { DoubleConfirmModal } from "../notifications.jsx"; import { log_cmd, listsEqual } from "../tools.jsx"; import { - Button, - Checkbox, - Form, - Grid, - GridItem, - Select, - SelectVariant, - SelectOption, - Spinner, - TextInput, - Text, - TextContent, - TextVariants, -} from "@patternfly/react-core"; + Button, + Checkbox, + Form, + Grid, + GridItem, + Spinner, + TextInput, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; +import { + Select, + SelectVariant, + SelectOption +} from '@patternfly/react-core/deprecated'; import { SASLTable } from "./serverTables.jsx"; import { SASLMappingModal } from "./serverModals.jsx"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -62,7 +64,7 @@ export class ServerSASL extends React.Component { isAllowedMechOpen: false, }; // Allowed SASL Mechanisms - this.handleOnAllowedMechToggle = isAllowedMechOpen => { + this.handleOnAllowedMechToggle = (_event, isAllowedMechOpen) => { this.setState({ isAllowedMechOpen, }); @@ -649,7 +651,7 @@ export class ServerSASL extends React.Component { id="maxBufSize" aria-describedby="horizontal-form-name-helper" name="maxBufSize" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -665,7 +667,7 @@ export class ServerSASL extends React.Component { <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Type SASL mechanism to allow" - onToggle={this.handleOnAllowedMechToggle} + onToggle={(event, isOpen) => this.handleOnAllowedMechToggle(event, isOpen)} onSelect={this.handleOnSelect} onClear={this.handleOnAllowedMechClear} selections={this.state.allowedMechs} @@ -689,7 +691,7 @@ export class ServerSASL extends React.Component { <Checkbox isChecked={this.state.mappingFallback} id="mappingFallback" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} label={_("Allow SASL Mapping Fallback")} diff --git a/src/cockpit/389-console/src/lib/server/securityLog.jsx b/src/cockpit/389-console/src/lib/server/securityLog.jsx index 8157dedc06..6cf7e8c0f9 100644 --- a/src/cockpit/389-console/src/lib/server/securityLog.jsx +++ b/src/cockpit/389-console/src/lib/server/securityLog.jsx @@ -23,12 +23,6 @@ import { TextVariants, TimePicker, } from "@patternfly/react-core"; -import { - Table, - TableHeader, - TableBody, - TableVariant -} from '@patternfly/react-table'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSyncAlt @@ -40,7 +34,6 @@ const _ = cockpit.gettext; const settings_attrs = [ 'nsslapd-securitylog', - 'nsslapd-securitylog-level', 'nsslapd-securitylog-logbuffering', 'nsslapd-securitylog-logging-enabled', ]; @@ -83,16 +76,6 @@ export class ServerSecurityLog extends React.Component { saveRotationDisabled: true, saveExpDisabled: true, attrs: this.props.attrs, - canSelectAll: false, - isExpanded: false, - rows: [ - { cells: ['Default Logging'], level: 256, selected: true }, - { cells: ['Internal Operations'], level: 4, selected: false }, - { cells: ['Entry Security and Referrals'], level: 512, selected: false } - ], - columns: [ - { title: _("Logging Level") }, - ], }; // Toggle currently active tab @@ -102,19 +85,12 @@ export class ServerSecurityLog extends React.Component { }); }; - this.handleOnToggle = isExpanded => { - this.setState({ - isExpanded - }); - }; - this.handleChange = this.handleChange.bind(this); this.handleSwitchChange = this.handleSwitchChange.bind(this); this.handleTimeChange = this.handleTimeChange.bind(this); this.loadConfig = this.loadConfig.bind(this); this.refreshConfig = this.refreshConfig.bind(this); this.saveConfig = this.saveConfig.bind(this); - this.handleOnSelect = this.handleOnSelect.bind(this); this.onMinusConfig = (id, nav_tab) => { this.setState({ [id]: Number(this.state[id]) - 1 @@ -155,17 +131,6 @@ export class ServerSecurityLog extends React.Component { if (nav_tab === "settings") { config_attrs = settings_attrs; disableBtnName = "saveSettingsDisabled"; - // Handle the table contents check now - for (const row of this.state.rows) { - for (const orig_row of this.state._rows) { - if (orig_row.cells[0] === row.cells[0]) { - if (orig_row.selected !== row.selected) { - disableSaveBtn = false; - break; - } - } - } - } } else if (nav_tab === "rotation") { disableBtnName = "saveRotationDisabled"; config_attrs = rotation_attrs; @@ -245,7 +210,6 @@ export class ServerSecurityLog extends React.Component { } saveConfig(nav_tab) { - let new_level = 0; this.setState({ loading: true }); @@ -278,18 +242,6 @@ export class ServerSecurityLog extends React.Component { } } - for (const row of this.state.rows) { - if (row.selected) { - new_level += row.level; - } - } - if (new_level.toString() !== this.state['_nsslapd-securitylog-level']) { - if (new_level === 0) { - new_level = 256; // default - } - cmd.push("nsslapd-securitylog-level" + "=" + new_level.toString()); - } - if (cmd.length === 5) { // Nothing to save, just return return; @@ -334,8 +286,6 @@ export class ServerSecurityLog extends React.Component { let enabled = false; let buffering = false; let compress = false; - const level_val = parseInt(attrs['nsslapd-securitylog-level'][0]); - const rows = [...this.state.rows]; if (attrs['nsslapd-securitylog-logging-enabled'][0] === "on") { enabled = true; @@ -346,13 +296,6 @@ export class ServerSecurityLog extends React.Component { if (attrs['nsslapd-securitylog-compress'][0] === "on") { compress = true; } - for (const row in rows) { - if (rows[row].level & level_val) { - rows[row].selected = true; - } else { - rows[row].selected = false; - } - } this.setState({ loading: false, @@ -361,7 +304,6 @@ export class ServerSecurityLog extends React.Component { saveRotationDisabled: true, saveExpDisabled: true, 'nsslapd-securitylog': attrs['nsslapd-securitylog'][0], - 'nsslapd-securitylog-level': attrs['nsslapd-securitylog-level'][0], 'nsslapd-securitylog-logbuffering': buffering, 'nsslapd-securitylog-logexpirationtime': attrs['nsslapd-securitylog-logexpirationtime'][0], 'nsslapd-securitylog-logexpirationtimeunit': attrs['nsslapd-securitylog-logexpirationtimeunit'][0], @@ -376,11 +318,8 @@ export class ServerSecurityLog extends React.Component { 'nsslapd-securitylog-maxlogsize': attrs['nsslapd-securitylog-maxlogsize'][0], 'nsslapd-securitylog-maxlogsperdir': attrs['nsslapd-securitylog-maxlogsperdir'][0], 'nsslapd-securitylog-compress': compress, - rows, // Record original values - _rows: JSON.parse(JSON.stringify(rows)), '_nsslapd-securitylog': attrs['nsslapd-securitylog'][0], - '_nsslapd-securitylog-level': attrs['nsslapd-securitylog-level'][0], '_nsslapd-securitylog-logbuffering': buffering, '_nsslapd-securitylog-logexpirationtime': attrs['nsslapd-securitylog-logexpirationtime'][0], '_nsslapd-securitylog-logexpirationtimeunit': attrs['nsslapd-securitylog-logexpirationtimeunit'][0], @@ -415,8 +354,6 @@ export class ServerSecurityLog extends React.Component { let enabled = false; let buffering = false; let compress = false; - const level_val = parseInt(attrs['nsslapd-securitylog-level'][0]); - const rows = [...this.state.rows]; if (attrs['nsslapd-securitylog-logging-enabled'][0] === "on") { enabled = true; @@ -427,13 +364,6 @@ export class ServerSecurityLog extends React.Component { if (attrs['nsslapd-securitylog-compress'][0] === "on") { compress = true; } - for (const row in rows) { - if (rows[row].level & level_val) { - rows[row].selected = true; - } else { - rows[row].selected = false; - } - } this.setState({ loading: false, @@ -442,7 +372,6 @@ export class ServerSecurityLog extends React.Component { saveRotationDisabled: true, saveExpDisabled: true, 'nsslapd-securitylog': attrs['nsslapd-securitylog'][0], - 'nsslapd-securitylog-level': attrs['nsslapd-securitylog-level'][0], 'nsslapd-securitylog-logbuffering': buffering, 'nsslapd-securitylog-logexpirationtime': attrs['nsslapd-securitylog-logexpirationtime'][0], 'nsslapd-securitylog-logexpirationtimeunit': attrs['nsslapd-securitylog-logexpirationtimeunit'][0], @@ -457,11 +386,8 @@ export class ServerSecurityLog extends React.Component { 'nsslapd-securitylog-maxlogsize': attrs['nsslapd-securitylog-maxlogsize'][0], 'nsslapd-securitylog-maxlogsperdir': attrs['nsslapd-securitylog-maxlogsperdir'][0], 'nsslapd-securitylog-compress': compress, - rows, // Record original values - _rows: JSON.parse(JSON.stringify(rows)), '_nsslapd-securitylog': attrs['nsslapd-securitylog'][0], - '_nsslapd-securitylog-level': attrs['nsslapd-securitylog-level'][0], '_nsslapd-securitylog-logbuffering': buffering, '_nsslapd-securitylog-logexpirationtime': attrs['nsslapd-securitylog-logexpirationtime'][0], '_nsslapd-securitylog-logexpirationtimeunit': attrs['nsslapd-securitylog-logexpirationtimeunit'][0], @@ -479,39 +405,6 @@ export class ServerSecurityLog extends React.Component { }, this.props.enableTree); } - handleOnSelect(event, isSelected, rowId) { - let disableSaveBtn = true; - const rows = JSON.parse(JSON.stringify(this.state.rows)); - - // Update the row - rows[rowId].selected = isSelected; - - // Handle "save button" state, first check the other config settings - for (const config_attr of settings_attrs) { - if (this.state['_' + config_attr] !== this.state[config_attr]) { - disableSaveBtn = false; - break; - } - } - - // Handle the table contents - for (const row of rows) { - for (const orig_row of this.state._rows) { - if (orig_row.cells[0] === row.cells[0]) { - if (orig_row.selected !== row.selected) { - disableSaveBtn = false; - break; - } - } - } - } - - this.setState({ - rows, - saveSettingsDisabled: disableSaveBtn, - }); - } - render() { let saveSettingsName = _("Save Log Settings"); let saveRotationName = _("Save Rotation Settings"); @@ -545,7 +438,7 @@ export class ServerSecurityLog extends React.Component { className="ds-margin-top-xlg" id="nsslapd-securitylog-logging-enabled" isChecked={this.state['nsslapd-securitylog-logging-enabled']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Enable security logging (nsslapd-securitylog-logging-enabled).")} @@ -563,7 +456,7 @@ export class ServerSecurityLog extends React.Component { id="nsslapd-securitylog" aria-describedby="horizontal-form-name-helper" name="nsslapd-securitylog" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "settings"); }} /> @@ -573,33 +466,13 @@ export class ServerSecurityLog extends React.Component { className="ds-left-margin-md ds-margin-top-lg" id="nsslapd-securitylog-logbuffering" isChecked={this.state['nsslapd-securitylog-logbuffering']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e, "settings"); }} title={_("Disable security log buffering for faster troubleshooting, but this will impact server performance (nsslapd-securitylog-logbuffering).")} label={_("Security Log Buffering Enabled")} /> - <ExpandableSection - className="ds-hidden ds-left-margin-md ds-margin-top-lg ds-font-size-md" - toggleText={this.state.isExpanded ? _("Hide Logging Levels") : _("Show Logging Levels")} - onToggle={this.handleOnToggle} - isExpanded={this.state.isExpanded} - > - <Table - className="ds-left-margin" - onSelect={this.handleOnSelect} - canSelectAll={this.state.canSelectAll} - variant={TableVariant.compact} - aria-label="Selectable Table" - cells={this.state.columns} - rows={this.state.rows} - > - <TableHeader /> - <TableBody /> - </Table> - </ExpandableSection> - <Button key="save settings" isDisabled={this.state.saveSettingsDisabled || this.state.loading} @@ -684,7 +557,7 @@ export class ServerSecurityLog extends React.Component { <FormSelect id="nsslapd-securitylog-logrotationtimeunit" value={this.state['nsslapd-securitylog-logrotationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rotation"); }} aria-label="FormSelect Input" @@ -719,7 +592,7 @@ export class ServerSecurityLog extends React.Component { <Switch id="nsslapd-securitylog-compress" isChecked={this.state['nsslapd-securitylog-compress']} - onChange={this.handleSwitchChange} + onChange={(_event, value) => this.handleSwitchChange(value)} /> </GridItem> </Grid> @@ -812,7 +685,7 @@ export class ServerSecurityLog extends React.Component { <FormSelect id="nsslapd-securitylog-logexpirationtimeunit" value={this.state['nsslapd-securitylog-logexpirationtimeunit']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "exp"); }} aria-label="FormSelect Input" diff --git a/src/cockpit/389-console/src/lib/server/serverModals.jsx b/src/cockpit/389-console/src/lib/server/serverModals.jsx index 990598ffd1..3374d947eb 100644 --- a/src/cockpit/389-console/src/lib/server/serverModals.jsx +++ b/src/cockpit/389-console/src/lib/server/serverModals.jsx @@ -70,14 +70,14 @@ export class SASLMappingModal extends React.Component { id="saslMapName" aria-describedby="horizontal-form-name-helper" name="saslMapName" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} validated={this.props.error.saslMapName ? ValidatedOptions.error : ValidatedOptions.default} isRequired isDisabled={this.props.type === "Edit"} /> - <FormHelperText isError isHidden={!this.props.error.saslMapName}> + <FormHelperText > {_("You must provide a name for this mapping")} </FormHelperText> </GridItem> @@ -95,13 +95,13 @@ export class SASLMappingModal extends React.Component { id="saslMapRegex" aria-describedby="horizontal-form-name-helper" name="saslMapRegex" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isRequired validated={this.props.error.saslMapRegex ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!this.props.error.saslMapRegex}> + <FormHelperText > {_("You must provide a valid regular expression")} </FormHelperText> </GridItem> @@ -119,7 +119,7 @@ export class SASLMappingModal extends React.Component { id="saslTestText" aria-describedby="horizontal-form-name-helper" name="saslTestText" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} placeholder={_("Enter text to test regex")} @@ -149,13 +149,13 @@ export class SASLMappingModal extends React.Component { id="saslBase" aria-describedby="horizontal-form-name-helper" name="saslBase" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isRequired validated={this.props.error.saslBase ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!this.props.error.saslBase}> + <FormHelperText > {_("You must provide a search base")} </FormHelperText> </GridItem> @@ -173,13 +173,13 @@ export class SASLMappingModal extends React.Component { id="saslFilter" aria-describedby="horizontal-form-name-helper" name="saslFilter" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} isRequired validated={this.props.error.saslFilter ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!this.props.error.saslFilter}> + <FormHelperText > {_("You must provide an LDAP search filter")} </FormHelperText> </GridItem> @@ -197,12 +197,12 @@ export class SASLMappingModal extends React.Component { id="saslPriority" aria-describedby="horizontal-form-name-helper" name="saslPriority" - onChange={(str, e) => { + onChange={(e, str) => { this.props.handleChange(e); }} validated={this.props.error.saslPriority ? ValidatedOptions.error : ValidatedOptions.default} /> - <FormHelperText isError isHidden={!this.props.error.saslPriority}> + <FormHelperText > {_("Priority must be between 1 and 100")} </FormHelperText> </GridItem> diff --git a/src/cockpit/389-console/src/lib/server/serverTables.jsx b/src/cockpit/389-console/src/lib/server/serverTables.jsx index ee0bd4235a..d1d458c29e 100644 --- a/src/cockpit/389-console/src/lib/server/serverTables.jsx +++ b/src/cockpit/389-console/src/lib/server/serverTables.jsx @@ -8,13 +8,15 @@ import { SearchInput, } from '@patternfly/react-core'; import { - expandable, Table, - TableHeader, - TableBody, - TableVariant, - sortable, - SortByDirection, + Thead, + Tr, + Th, + Tbody, + Td, + ExpandableRowContent, + ActionsColumn, + SortByDirection } from '@patternfly/react-table'; import PropTypes from "prop-types"; @@ -31,24 +33,24 @@ export class SASLTable extends React.Component { sortBy: {}, rows: [], dropdownIsOpen: false, + hasRows: true, columns: [ { title: _("Mapping Name"), - transforms: [sortable], - cellFormatters: [expandable] + sortable: true }, - { title: _("Search Base"), transforms: [sortable] }, - { title: _("Priority"), transforms: [sortable] }, + { title: _("Search Base"), sortable: true }, + { title: _("Priority"), sortable: true }, ], }; - this.handleOnSetPage = (_event, pageNumber) => { + this.handleSetPage = (_event, pageNumber) => { this.setState({ page: pageNumber }); }; - this.handleOnPerPageSelect = (_event, perPage) => { + this.handlePerPageSelect = (_event, perPage) => { this.setState({ perPage, page: 1 @@ -56,49 +58,50 @@ export class SASLTable extends React.Component { }; this.handleSort = this.handleSort.bind(this); - this.handleOnCollapse = this.handleOnCollapse.bind(this); - this.handleOnSearchChange = this.handleOnSearchChange.bind(this); + this.handleCollapse = this.handleCollapse.bind(this); + this.handleSearchChange = this.handleSearchChange.bind(this); } - handleSort(_event, index, direction) { + handleSort(_event, columnIndex, direction) { const sorted_rows = []; const rows = []; let count = 0; - // Convert the rows pairings into a sortable array based on the column indexes for (let idx = 0; idx < this.state.rows.length; idx += 2) { sorted_rows.push({ expandedRow: this.state.rows[idx + 1], - 1: this.state.rows[idx].cells[0], - 2: this.state.rows[idx].cells[1], - 3: this.state.rows[idx].cells[2], + 1: this.state.rows[idx].cells[0].content, + 2: this.state.rows[idx].cells[1].content, + 3: this.state.rows[idx].cells[2].content, regex: this.state.rows[idx].regex, filter: this.state.rows[idx].filter }); } - // Sort the rows and build the new rows - sorted_rows.sort((a, b) => (a[index] > b[index]) ? 1 : -1); + sorted_rows.sort((a, b) => (a[columnIndex + 1] > b[columnIndex + 1]) ? 1 : -1); if (direction !== SortByDirection.asc) { sorted_rows.reverse(); } + for (const srow of sorted_rows) { rows.push({ isOpen: false, cells: [ - srow[1], srow[2], srow[3] + { content: srow[1] }, + { content: srow[2] }, + { content: srow[3] } ], regex: srow.regex, filter: srow.filter, }); - srow.expandedRow.parent = count; // reset parent idx + srow.expandedRow.parent = count; rows.push(srow.expandedRow); count += 2; } this.setState({ sortBy: { - index, + index: columnIndex, direction }, rows, @@ -113,7 +116,6 @@ export class SASLTable extends React.Component { <GridItem span={9}><b>{regex}</b></GridItem> <GridItem span={3}>{_("Search Filter:")}</GridItem> <GridItem span={9}><b>{filter}</b></GridItem> - </Grid> ); } @@ -121,73 +123,65 @@ export class SASLTable extends React.Component { componentDidMount() { let rows = []; let columns = this.state.columns; + let hasRows = true; let count = 0; - for (const row of this.props.rows) { - rows.push( - { - isOpen: false, - cells: [row.cn[0], row.nssaslmapbasedntemplate[0], row.nssaslmappriority[0]], - regex: row.nssaslmapregexstring[0], - filter: row.nssaslmapfiltertemplate[0], - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row.nssaslmapregexstring[0], row.nssaslmapfiltertemplate[0]) }] - }, - ); - count += 2; + for (const row of this.props.rows) { + rows.push({ + isOpen: false, + cells: [ + { content: row.cn[0] }, + { content: row.nssaslmapbasedntemplate[0] }, + { content: row.nssaslmappriority[0] } + ], + regex: row.nssaslmapregexstring[0], + filter: row.nssaslmapfiltertemplate[0], + }); + count += 1; } if (rows.length === 0) { - rows = [{ cells: [_("No SASL Mappings")] }]; + rows = [{ cells: [{ content: _("No SASL Mappings") }] }]; columns = [{ title: _("SASL Mappings") }]; + hasRows = false; } this.setState({ rows, - columns + columns, + hasRows }); } - handleOnCollapse(event, rowKey, isOpen) { - const { rows, perPage, page } = this.state; - const index = (perPage * (page - 1) * 2) + rowKey; // Adjust for page set - rows[index].isOpen = isOpen; - this.setState({ - rows - }); + handleCollapse(_event, rowIndex, isExpanding) { + const rows = [...this.state.rows]; + const index = (this.state.perPage * (this.state.page - 1) * 2) + rowIndex; + rows[index].isOpen = isExpanding; + this.setState({ rows }); } - handleOnSearchChange(value, event) { + handleSearchChange(event, value) { const rows = []; let count = 0; for (const row of this.props.rows) { const val = value.toLowerCase(); - // Check for matches of all the parts if (val !== "" && row.cn[0].toLowerCase().indexOf(val) === -1 && row.nssaslmapbasedntemplate[0].toLowerCase().indexOf(val) === -1 && row.nssaslmappriority[0].toLowerCase().indexOf(val) === -1) { - // Not a match continue; } - rows.push( - { - isOpen: false, - cells: [row.cn[0], row.nssaslmapbasedntemplate[0], row.nssaslmappriority[0]], - regex: row.nssaslmapregexstring[0], - filter: row.nssaslmapfiltertemplate[0], - - }, - { - parent: count, - fullWidth: true, - cells: [{ title: this.getExpandedRow(row.nssaslmapregexstring[0], row.nssaslmapfiltertemplate[0]) }] - }, - ); - count += 2; + rows.push({ + isOpen: false, + cells: [ + { content: row.cn[0] }, + { content: row.nssaslmapbasedntemplate[0] }, + { content: row.nssaslmappriority[0] } + ], + regex: row.nssaslmapregexstring[0], + filter: row.nssaslmapfiltertemplate[0], + }); + count += 1; } this.setState({ @@ -197,71 +191,104 @@ export class SASLTable extends React.Component { }); } - actions() { - return [ - { - title: _("Edit Mapping"), - onClick: (event, rowId, rowData, extra) => - this.props.editMapping( - rowData.cells[0], - rowData.regex, - rowData.cells[1], - rowData.filter, - rowData.cells[2], - ) - }, - { - title: _("Delete Mapping"), - onClick: (event, rowId, rowData, extra) => - this.props.deleteMapping(rowData.cells[0]) - } - ]; - } + getActionsForRow = (rowData) => [ + { + title: _("Edit Mapping"), + onClick: () => this.props.editMapping( + rowData.cells[0].content, + rowData.regex, + rowData.cells[1].content, + rowData.filter, + rowData.cells[2].content, + ) + }, + { + title: _("Delete Mapping"), + onClick: () => this.props.deleteMapping(rowData.cells[0].content) + } + ]; render() { - const { perPage, page, sortBy, rows, columns } = this.state; - const origRows = [...rows]; + const { perPage, page, sortBy, rows, columns, hasRows } = this.state; const startIdx = ((perPage * page) - perPage) * 2; - const tableRows = origRows.splice(startIdx, perPage * 2); - - for (let idx = 1, count = 0; idx < tableRows.length; idx += 2, count += 2) { - // Rewrite parent index to match new spliced array - tableRows[idx].parent = count; - } + const tableRows = rows.slice(startIdx, startIdx + (perPage * 2)); return ( <div className="ds-margin-top-lg"> - <SearchInput - placeholder={_("Search Mappings")} - value={this.state.value} - onChange={this.handleOnSearchChange} - onClear={(evt) => this.handleOnSearchChange('', evt)} - /> - <Table - className="ds-margin-top" + {hasRows && + <SearchInput + placeholder={_("Search Mappings")} + value={this.state.value} + onChange={this.handleSearchChange} + onClear={(evt) => this.handleSearchChange(evt, '')} + />} + <Table aria-label="sasl table" - cells={columns} - rows={tableRows} - variant={TableVariant.compact} - sortBy={sortBy} - onSort={this.handleSort} - onCollapse={this.handleOnCollapse} - actions={tableRows.length > 0 ? this.actions() : null} - dropdownPosition="right" - dropdownDirection="bottom" + variant='compact' > - <TableHeader /> - <TableBody /> + <Thead> + <Tr> + <Th screenReaderText="Row expansion" /> + {columns.map((column, columnIndex) => ( + <Th + key={columnIndex} + sort={column.sortable ? { + sortBy, + onSort: this.handleSort, + columnIndex + } : undefined} + > + {column.title} + </Th> + ))} + <Th screenReaderText="Actions" /> + </Tr> + </Thead> + <Tbody> + {tableRows.map((row, rowIndex) => ( + <React.Fragment key={rowIndex}> + <Tr> + <Td + expand={{ + rowIndex, + isExpanded: row.isOpen, + onToggle: () => this.handleCollapse(null, rowIndex, !row.isOpen) + }} + /> + {row.cells.map((cell, cellIndex) => ( + <Td key={cellIndex}> + {cell.content} + </Td> + ))} + <Td isActionCell> + <ActionsColumn + items={this.getActionsForRow(row)} + /> + </Td> + </Tr> + {row.isOpen && ( + <Tr isExpanded={true}> + <Td colSpan={columns.length + 2}> + <ExpandableRowContent> + {this.getExpandedRow(row.regex, row.filter)} + </ExpandableRowContent> + </Td> + </Tr> + )} + </React.Fragment> + ))} + </Tbody> </Table> - <Pagination - itemCount={this.state.rows.length / 2} - widgetId="pagination-options-menu-bottom" - perPage={perPage} - page={page} - variant={PaginationVariant.bottom} - onSetPage={this.handleOnSetPage} - onPerPageSelect={this.handleOnPerPageSelect} - /> + {hasRows && + <Pagination + itemCount={this.state.rows.length / 2} + widgetId="pagination-options-menu-bottom" + perPage={perPage} + page={page} + variant="bottom" + onSetPage={this.handleSetPage} + onPerPageSelect={this.handlePerPageSelect} + />} </div> ); } diff --git a/src/cockpit/389-console/src/lib/server/settings.jsx b/src/cockpit/389-console/src/lib/server/settings.jsx index 0f690ce161..0e0c2fd213 100644 --- a/src/cockpit/389-console/src/lib/server/settings.jsx +++ b/src/cockpit/389-console/src/lib/server/settings.jsx @@ -2,34 +2,33 @@ import cockpit from "cockpit"; import React from "react"; import { log_cmd, valid_dn, isValidIpAddress } from "../tools.jsx"; import { - Button, - Checkbox, - Form, - FormHelperText, - FormSelect, - FormSelectOption, - Grid, - GridItem, - HelperText, - HelperTextItem, - Select, - SelectOption, - SelectVariant, - Spinner, - Tab, - Tabs, - TabTitleText, - NumberInput, - TextInput, - Text, - TextContent, - TextVariants, - ValidatedOptions, -} from "@patternfly/react-core"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + Button, + Checkbox, + Form, + FormHelperText, + FormSelect, + FormSelectOption, + Grid, + GridItem, + HelperText, + HelperTextItem, + Spinner, + Tab, + Tabs, + TabTitleText, + NumberInput, + TextInput, + Text, + TextContent, + TextVariants, + ValidatedOptions +} from '@patternfly/react-core'; import { - faSyncAlt -} from '@fortawesome/free-solid-svg-icons'; + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; +import { SyncAltIcon } from '@patternfly/react-icons'; import '@fortawesome/fontawesome-svg-core/styles.css'; import PropTypes from "prop-types"; @@ -109,7 +108,7 @@ export class ServerSettings extends React.Component { invalidIP: false, }; - this.handleOnHaproxyIPsToggle = isHaproxyIPsOpen => { + this.handleOnHaproxyIPsToggle = (_event, isHaproxyIPsOpen) => { this.setState({ isHaproxyIPsOpen, invalidIP: false, @@ -1044,8 +1043,8 @@ export class ServerSettings extends React.Component { plusBtnAriaLabel="plus" widthChars={8} /> - <FormHelperText isError isHidden={!this.state.errObjDiskMon['nsslapd-disk-monitoring-threshold']}> - _("Value must be greater than or equal to 4096") + <FormHelperText > + {_("Value must be greater than or equal to 4096")} </FormHelperText> </GridItem> </Grid> @@ -1079,7 +1078,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-disk-monitoring-logging-critical" isChecked={this.state['nsslapd-disk-monitoring-logging-critical']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "diskmon"); }} label={_("Preserve Logs Even If Disk Space Gets Low")} @@ -1107,13 +1106,13 @@ export class ServerSettings extends React.Component { <TextContent> <Text component={TextVariants.h3}> {_("Server Settings")} - <FontAwesomeIcon - size="lg" - className="ds-left-margin ds-refresh" - icon={faSyncAlt} - title={_("Refresh configuration settings")} + <Button + variant="plain" + aria-label={_("Refresh configuration settings")} onClick={this.handleReloadConfig} - /> + > + <SyncAltIcon size="lg" /> + </Button> </Text> </TextContent> </GridItem> @@ -1153,7 +1152,7 @@ export class ServerSettings extends React.Component { id="nsslapd-localhost" aria-describedby="horizontal-form-name-helper" name="server-hostname" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "config"); }} validated={this.state.errObjConfig['nsslapd-localhost'] ? ValidatedOptions.error : ValidatedOptions.default} @@ -1219,7 +1218,7 @@ export class ServerSettings extends React.Component { id="nsslapd-listenhost" aria-describedby="horizontal-form-name-helper" name="server-listenhost" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "config"); }} validated={this.state.errObjConfig['nsslapd-listenhost'] ? ValidatedOptions.error : ValidatedOptions.default} @@ -1239,13 +1238,13 @@ export class ServerSettings extends React.Component { id="nsslapd-bakdir" aria-describedby="horizontal-form-name-helper" name="server-bakdir" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "config"); }} validated={this.state.errObjConfig['nsslapd-bakdir'] ? ValidatedOptions.error : ValidatedOptions.default} /> {this.state.errObjConfig['nsslapd-bakdir'] && - <FormHelperText isError isHidden={!this.state.errObjConfig['nsslapd-bakdir']}> + <FormHelperText > Invalid path </FormHelperText>} </GridItem> @@ -1263,13 +1262,13 @@ export class ServerSettings extends React.Component { id="nsslapd-ldifdir" aria-describedby="horizontal-form-name-helper" name="server-ldifdir" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "config"); }} validated={this.state.errObjConfig['nsslapd-ldifdir'] ? ValidatedOptions.error : ValidatedOptions.default} /> {this.state.errObjConfig['nsslapd-ldifdir'] && - <FormHelperText isError isHidden={!this.state.errObjConfig['nsslapd-ldifdir']}> + <FormHelperText > Invalid path </FormHelperText>} </GridItem> @@ -1287,13 +1286,13 @@ export class ServerSettings extends React.Component { id="nsslapd-schemadir" aria-describedby="horizontal-form-name-helper" name="server-schemadir" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "config"); }} validated={this.state.errObjConfig['nsslapd-schemadir'] ? ValidatedOptions.error : ValidatedOptions.default} /> {this.state.errObjConfig['nsslapd-schemadir'] && - <FormHelperText isError isHidden={!this.state.errObjConfig['nsslapd-schemadir']}> + <FormHelperText > Invalid path </FormHelperText>} </GridItem> @@ -1311,13 +1310,13 @@ export class ServerSettings extends React.Component { id="nsslapd-certdir" aria-describedby="horizontal-form-name-helper" name="server-certdir" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "config"); }} validated={this.state.errObjConfig['nsslapd-certdir'] ? ValidatedOptions.error : ValidatedOptions.default} /> {this.state.errObjConfig['nsslapd-certdir'] && - <FormHelperText isError isHidden={!this.state.errObjConfig['nsslapd-certdir']}> + <FormHelperText > Invalid path </FormHelperText>} </GridItem> @@ -1368,7 +1367,7 @@ export class ServerSettings extends React.Component { id="nsslapd-rootpw" aria-describedby="horizontal-form-name-helper" name="nsslapd-rootpw" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rootdn"); }} validated={this.state.errObjRootDN['nsslapd-rootpw'] ? ValidatedOptions.error : ValidatedOptions.default} @@ -1388,7 +1387,7 @@ export class ServerSettings extends React.Component { id="confirmRootpw" aria-describedby="horizontal-form-name-helper" name="confirmRootpw" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rootdn"); }} validated={this.state.errObjRootDN.confirmRootpw ? ValidatedOptions.error : ValidatedOptions.default} @@ -1405,7 +1404,7 @@ export class ServerSettings extends React.Component { <FormSelect id="nsslapd-rootpwstoragescheme" value={this.state['nsslapd-rootpwstoragescheme']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "rootdn"); }} aria-label="FormSelect Input" @@ -1434,7 +1433,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-disk-monitoring" isChecked={this.state['nsslapd-disk-monitoring']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "diskmon"); }} label={_("Enable Disk Space Monitoring")} @@ -1460,7 +1459,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-schemacheck" isChecked={this.state['nsslapd-schemacheck']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("Enable schema checking (nsslapd-schemacheck).")} @@ -1472,7 +1471,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-syntaxcheck" isChecked={this.state['nsslapd-syntaxcheck']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("Enable attribute syntax checking (nsslapd-syntaxcheck).")} @@ -1485,7 +1484,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-plugin-logging" isChecked={this.state['nsslapd-plugin-logging']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("Enable plugins to log access and audit events. (nsslapd-plugin-logging).")} @@ -1496,7 +1495,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-syntaxlogging" isChecked={this.state['nsslapd-syntaxlogging']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("Enable syntax logging (nsslapd-syntaxlogging).")} @@ -1509,7 +1508,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-plugin-binddn-tracking" isChecked={this.state['nsslapd-plugin-binddn-tracking']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} label={_("Enable Plugin Bind DN Tracking")} @@ -1520,7 +1519,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-attribute-name-exceptions" isChecked={this.state['nsslapd-attribute-name-exceptions']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("Allows non-standard characters in attribute names to be used for backwards compatibility with older servers (nsslapd-attribute-name-exceptions).")} @@ -1533,7 +1532,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-dn-validate-strict" isChecked={this.state['nsslapd-dn-validate-strict']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} label={_("Strict DN Syntax Validation")} @@ -1544,7 +1543,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-entryusn-global" isChecked={this.state['nsslapd-entryusn-global']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("For USN plugin - maintain unique USNs across all back end databases (nsslapd-entryusn-global).")} @@ -1557,7 +1556,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-ignore-time-skew" isChecked={this.state['nsslapd-ignore-time-skew']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("Ignore replication time skew when acquiring a replica to start a replciation session (nsslapd-ignore-time-skew).")} @@ -1568,7 +1567,7 @@ export class ServerSettings extends React.Component { <Checkbox id="nsslapd-readonly" isChecked={this.state['nsslapd-readonly']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} title={_("Make entire server read-only (nsslapd-readonly)")} @@ -1587,7 +1586,7 @@ export class ServerSettings extends React.Component { <FormSelect id="nsslapd-allow-anonymous-access" value={this.state['nsslapd-allow-anonymous-access']} - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} aria-label="FormSelect Input" @@ -1616,7 +1615,7 @@ export class ServerSettings extends React.Component { id="nsslapd-anonlimitsdn" aria-describedby="horizontal-form-name-helper" name="nsslapd-anonlimitsdn" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e, "adv"); }} validated={this.state.errObjAdv.anonLimitsDN ? ValidatedOptions.error : ValidatedOptions.default} @@ -1634,7 +1633,7 @@ export class ServerSettings extends React.Component { variant={SelectVariant.typeaheadMulti} id="nsslpad-haproxy-trusted-ip" typeAheadAriaLabel="Type trusted HAProxy server IP address" - onToggle={this.handleOnHaproxyIPsToggle} + onToggle={(event, isOpen) => this.handleOnHaproxyIPsToggle(event, isOpen)} onSelect={(e, selection) => { this.handleOnHaproxyIPsSelect(e, selection, "adv"); }} diff --git a/src/cockpit/389-console/src/lib/server/tuning.jsx b/src/cockpit/389-console/src/lib/server/tuning.jsx index de5abea8fb..5a4eccf84b 100644 --- a/src/cockpit/389-console/src/lib/server/tuning.jsx +++ b/src/cockpit/389-console/src/lib/server/tuning.jsx @@ -15,10 +15,7 @@ import { TextVariants, } from "@patternfly/react-core"; import PropTypes from "prop-types"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faSyncAlt -} from '@fortawesome/free-solid-svg-icons'; +import { SyncAltIcon } from '@patternfly/react-icons'; import '@fortawesome/fontawesome-svg-core/styles.css'; const tuning_attrs = [ @@ -56,7 +53,7 @@ export class ServerTuning extends React.Component { isExpanded: false, }; - this.handleOnToggle = (isExpanded) => { + this.handleOnToggle = (_event, isExpanded) => { this.setState({ isExpanded }); @@ -279,15 +276,15 @@ export class ServerTuning extends React.Component { <TextContent> <Text component={TextVariants.h3}> {_("Tuning & Limits")} - <FontAwesomeIcon - size="lg" - className="ds-left-margin ds-refresh" - icon={faSyncAlt} - title={_("Refresh settings")} + <Button + variant="plain" + aria-label={_("Refresh settings")} onClick={() => { this.loadConfig(1); }} - /> + > + <SyncAltIcon size="lg" /> + </Button> </Text> </TextContent> </GridItem> @@ -430,7 +427,7 @@ export class ServerTuning extends React.Component { <ExpandableSection className="ds-margin-top-xlg" toggleText={this.state.isExpanded ? _("Hide Advanced Settings") : _("Show Advanced Settings")} - onToggle={this.handleOnToggle} + onToggle={(event, isExpanded) => this.handleOnToggle(event, isExpanded)} isExpanded={this.state.isExpanded} > <div className="ds-margin-top ds-indent"> @@ -553,7 +550,7 @@ export class ServerTuning extends React.Component { <Checkbox id="nsslapd-connection-nocanon" isChecked={this.state['nsslapd-connection-nocanon']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} label={_("Disable Reverse DNS Lookups")} @@ -567,7 +564,7 @@ export class ServerTuning extends React.Component { <Checkbox id="nsslapd-enable-turbo-mode" isChecked={this.state['nsslapd-enable-turbo-mode']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} label={_("Enable Connection Turbo Mode")} @@ -581,7 +578,7 @@ export class ServerTuning extends React.Component { <Checkbox id="nsslapd-ignore-virtual-attrs" isChecked={this.state['nsslapd-ignore-virtual-attrs']} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} label={_("Disable Virtual Attribute Lookups")} @@ -595,7 +592,7 @@ export class ServerTuning extends React.Component { <Checkbox isChecked={this.state['nsslapd-ndn-cache-enabled']} id="nsslapd-ndn-cache-enabled" - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} label={_("Enable Normalized DN Cache")} diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx index 54a1e7bfbd..c6d72b7d44 100644 --- a/src/cockpit/389-console/src/monitor.jsx +++ b/src/cockpit/389-console/src/monitor.jsx @@ -1061,7 +1061,7 @@ export class Monitor extends React.Component { addNotification={this.props.addNotification} reloadAgmts={this.reloadReplAgmts} enableTree={this.enableTree} - handelReload={this.onHandleLoadMonitorReplication} + handleReload={this.onHandleLoadMonitorReplication} key={this.state.node_name} /> </div> diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx index 572f3075a6..4124a77ef6 100644 --- a/src/cockpit/389-console/src/plugins.jsx +++ b/src/cockpit/389-console/src/plugins.jsx @@ -91,9 +91,9 @@ export class Plugins extends React.Component { currentPluginPrecedence: "" }; - this.handleSelect = result => { + this.handleSelect = (_event, item) => { this.setState({ - activePlugin: result.itemId + activePlugin: item.itemId }); }; @@ -677,7 +677,7 @@ export class Plugins extends React.Component { <div hidden={this.state.firstLoad} className={this.state.loading ? "ds-disabled" : ""}> <Grid className="ds-margin-top-xlg" hasGutter> <GridItem span={3} className="ds-vert-scroll"> - <Nav key={this.state.pluginTableKey} theme="light" onSelect={this.handleSelect}> + <Nav key={this.state.pluginTableKey} theme="light" onSelect={(event, item) => this.handleSelect(event, item)}> <NavList> {Object.entries(selectPlugins).map(([id, item]) => ( <NavItem key={item.name} itemId={item.name} isActive={this.state.activePlugin === item.name}> diff --git a/src/cockpit/389-console/src/schema.jsx b/src/cockpit/389-console/src/schema.jsx index 896fd52d26..e39f9fef22 100644 --- a/src/cockpit/389-console/src/schema.jsx +++ b/src/cockpit/389-console/src/schema.jsx @@ -108,7 +108,7 @@ export class Schema extends React.Component { }; // Substring Matching Rule - this.handleSubstringMRToggle = isSubstringMROpen => { + this.handleSubstringMRToggle = (_event, isSubstringMROpen) => { this.setState({ isSubstringMROpen }); @@ -136,7 +136,7 @@ export class Schema extends React.Component { }; // Order Matching Rule - this.handleOrderMRToggle = isOrderMROpen => { + this.handleOrderMRToggle = (_event, isOrderMROpen) => { this.setState({ isOrderMROpen }); @@ -164,7 +164,7 @@ export class Schema extends React.Component { }; // Equaliry Matching Rule - this.handleEqualityMRToggle = isEqualityMROpen => { + this.handleEqualityMRToggle = (_event, isEqualityMROpen) => { this.setState({ isEqualityMROpen }); @@ -192,7 +192,7 @@ export class Schema extends React.Component { }; // Alias Name - this.handleAliasNameToggle = isAliasNameOpen => { + this.handleAliasNameToggle = (_event, isAliasNameOpen) => { this.setState({ isAliasNameOpen }); @@ -222,7 +222,7 @@ export class Schema extends React.Component { ); } }; - this.handleAliasNameCreateOption = newValue => { + this.handleAliasNameCreateOption = (_event, newValue) => { if (!this.state.atAliasOptions.includes(newValue)) { this.setState({ atAliasOptions: [...this.state.atAliasOptions, { value: newValue }], @@ -232,7 +232,7 @@ export class Schema extends React.Component { }; // Parent Attribute - this.handleParentAttrToggle = isParentAttrOpen => { + this.handleParentAttrToggle = (_event, isParentAttrOpen) => { this.setState({ isParentAttrOpen }); @@ -262,7 +262,7 @@ export class Schema extends React.Component { }; // Required Attributes - this.handleRequiredAttrsToggle = isRequiredAttrsOpen => { + this.handleRequiredAttrsToggle = (_event, isRequiredAttrsOpen) => { this.setState({ isRequiredAttrsOpen }); @@ -301,7 +301,7 @@ export class Schema extends React.Component { }; // Allowed Attributes - this.handleAllowedAttrsToggle = isAllowedAttrsOpen => { + this.handleAllowedAttrsToggle = (_event, isAllowedAttrsOpen) => { this.setState({ isAllowedAttrsOpen }); @@ -1402,7 +1402,7 @@ export class Schema extends React.Component { id="ocUserDefined" isChecked={this.state.ocUserDefined} title={_("Show only the objectclasses that are defined by a user and have the X-ORIGIN set to 'user defined'")} - onChange={(checked, e) => { + onChange={(e, checked) => { this.onFieldChange(e); }} label={_("Only Show Non-standard/Custom Schema")} @@ -1463,7 +1463,7 @@ export class Schema extends React.Component { id="atUserDefined" isChecked={this.state.atUserDefined} title={_("Show only the attributes that are defined by a user, and have the X-ORIGIN set to 'user defined'")} - onChange={(checked, e) => { + onChange={(e, checked) => { this.onFieldChange(e); }} label={_("Only Show Non-standard/Custom Schema")} diff --git a/src/cockpit/389-console/src/security.jsx b/src/cockpit/389-console/src/security.jsx index 79c366444b..cb622d9276 100644 --- a/src/cockpit/389-console/src/security.jsx +++ b/src/cockpit/389-console/src/security.jsx @@ -6,29 +6,28 @@ import { CertificateManagement } from "./lib/security/certificateManagement.jsx" import { SecurityEnableModal } from "./lib/security/securityModals.jsx"; import { Ciphers } from "./lib/security/ciphers.jsx"; import { - Button, - Checkbox, - Form, - Grid, - GridItem, - Select, - SelectOption, - SelectVariant, - Spinner, - Switch, - Tab, - Tabs, - TabTitleText, - TextInput, - Text, - TextContent, - TextVariants, -} from "@patternfly/react-core"; -import PropTypes from "prop-types"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + Button, + Checkbox, + Form, + Grid, + GridItem, + Spinner, + Switch, + Tab, + Tabs, + TabTitleText, + TextInput, + Text, + TextContent, + TextVariants +} from '@patternfly/react-core'; import { - faSyncAlt -} from '@fortawesome/free-solid-svg-icons'; + Select, + SelectOption, + SelectVariant +} from '@patternfly/react-core/deprecated'; +import PropTypes from "prop-types"; +import { SyncAltIcon } from '@patternfly/react-icons'; import '@fortawesome/fontawesome-svg-core/styles.css'; const _ = cockpit.gettext; @@ -121,10 +120,10 @@ export class Security extends React.Component { disableSaveBtn, }); }; - this.handleServerCertToggle = isServerCertOpen => { - this.setState({ - isServerCertOpen - }); + this.handleServerCertToggle = (_event, isServerCertOpen) => { + this.setState({ + isServerCertOpen + }); }; this.handleServerCertClear = () => { this.setState({ @@ -151,7 +150,7 @@ export class Security extends React.Component { return false; }; - this.handleMinSSLToggle = isMinSSLOpen => { + this.handleMinSSLToggle = (_event, isMinSSLOpen) => { this.setState({ isMinSSLOpen }); @@ -170,7 +169,7 @@ export class Security extends React.Component { }); }; - this.handleMaxSSLToggle = isMaxSSLOpen => { + this.handleMaxSSLToggle = (_event, isMaxSSLOpen) => { this.setState({ isMaxSSLOpen }); @@ -189,7 +188,7 @@ export class Security extends React.Component { }); }; - this.handleClientAuthToggle = isClientAuthOpen => { + this.handleClientAuthToggle = (_event, isClientAuthOpen) => { this.setState({ isClientAuthOpen }); @@ -209,7 +208,7 @@ export class Security extends React.Component { }); }; - this.handleValidateCertToggle = isValidateCertOpen => { + this.handleValidateCertToggle = (_event, isValidateCertOpen) => { this.setState({ isValidateCertOpen }); @@ -976,7 +975,7 @@ export class Security extends React.Component { }); } - onSelectToggle = (isExpanded, toggleId) => { + onSelectToggle = (_event, isExpanded, toggleId) => { this.setState({ [toggleId]: isExpanded }); @@ -1015,7 +1014,7 @@ export class Security extends React.Component { <Select variant={SelectVariant.typeahead} typeAheadAriaLabel="Type a server certificate nickname" - onToggle={this.handleServerCertToggle} + onToggle={(event, isOpen) => this.handleServerCertToggle(event, isOpen)} onSelect={this.handleServerCertSelect} onClear={this.handleServerCertClear} selections={serverCert} @@ -1043,7 +1042,7 @@ export class Security extends React.Component { <Select variant={SelectVariant.single} aria-label="Select Input" - onToggle={this.handleMinSSLToggle} + onToggle={(event, isOpen) => this.handleMinSSLToggle(event, isOpen)} onSelect={this.handleMinSSLSelect} selections={this.state.sslVersionMin} isOpen={this.state.isMinSSLOpen} @@ -1067,7 +1066,7 @@ export class Security extends React.Component { <Select variant={SelectVariant.single} aria-label="Select Input" - onToggle={this.handleMaxSSLToggle} + onToggle={(event, isOpen) => this.handleMaxSSLToggle(event, isOpen)} onSelect={this.handleMaxSSLSelect} selections={this.state.sslVersionMax} isOpen={this.state.isMaxSSLOpen} @@ -1091,7 +1090,7 @@ export class Security extends React.Component { <Select variant={SelectVariant.single} aria-label="Select Input" - onToggle={this.handleClientAuthToggle} + onToggle={(event, isOpen) => this.handleClientAuthToggle(event, isOpen)} onSelect={this.handleClientAuthSelect} selections={this.state.clientAuth} isOpen={this.state.isClientAuthOpen} @@ -1113,7 +1112,7 @@ export class Security extends React.Component { <Select variant={SelectVariant.single} aria-label="Select Input" - onToggle={this.handleValidateCertToggle} + onToggle={(event, isOpen) => this.handleValidateCertToggle(event, isOpen)} onSelect={this.handleValidateCertSelect} selections={this.state.validateCert} isOpen={this.state.isValidateCertOpen} @@ -1138,7 +1137,7 @@ export class Security extends React.Component { id="secureListenhost" aria-describedby="horizontal-form-name-helper" name="server-hostname" - onChange={(str, e) => { + onChange={(e, str) => { this.handleChange(e); }} /> @@ -1151,7 +1150,7 @@ export class Security extends React.Component { <Checkbox id="requireSecureBinds" isChecked={this.state.requireSecureBinds} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} label={_("Require Secure Connections")} @@ -1165,7 +1164,7 @@ export class Security extends React.Component { <Checkbox id="checkHostname" isChecked={this.state.checkHostname} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} label={_("Verify Certificate Subject Hostname")} @@ -1179,7 +1178,7 @@ export class Security extends React.Component { <Checkbox id="allowWeakCipher" isChecked={this.state.allowWeakCipher} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} title={_("Allow weak ciphers (allowWeakCipher).")} @@ -1194,7 +1193,7 @@ export class Security extends React.Component { <Checkbox id="nstlsallowclientrenegotiation" isChecked={this.state.nstlsallowclientrenegotiation} - onChange={(checked, e) => { + onChange={(e, checked) => { this.handleChange(e); }} title={_("Allow client-initiated renegotiation (nsTLSAllowClientRenegotiation).")} @@ -1227,13 +1226,13 @@ export class Security extends React.Component { <TextContent> <Text component={TextVariants.h3}> {_("Security Settings")} - <FontAwesomeIcon - size="lg" - className="ds-left-margin ds-refresh" - icon={faSyncAlt} - title={_("Refresh settings")} + <Button + variant="plain" + aria-label={_("Refresh settings")} onClick={this.handleReloadConfig} - /> + > + <SyncAltIcon size="lg" /> + </Button> </Text> </TextContent> </GridItem> @@ -1248,7 +1247,7 @@ export class Security extends React.Component { label={_("Security Enabled")} labelOff="Security Disabled" isChecked={this.state.securityEnabled} - onChange={this.handleSwitchChange} + onChange={(_event, value) => this.handleSwitchChange(value)} /> </GridItem> <hr /> diff --git a/src/cockpit/389-console/src/server.jsx b/src/cockpit/389-console/src/server.jsx index f9c9c25862..e824771ee8 100644 --- a/src/cockpit/389-console/src/server.jsx +++ b/src/cockpit/389-console/src/server.jsx @@ -19,16 +19,13 @@ import { TextContent, TextVariants, } from "@patternfly/react-core"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faBook, -} from '@fortawesome/free-solid-svg-icons'; import { CatalogIcon, CogIcon, KeyIcon, TachometerAltIcon, LockIcon, + BookIcon, RouteIcon } from '@patternfly/react-icons'; @@ -194,27 +191,27 @@ export class Server extends React.Component { children: [ { name: _("Access Log"), - icon: <FontAwesomeIcon size="sm" icon={faBook} />, + icon: <BookIcon size="sm" />, id: "access-log-config", }, { name: _("Audit Log"), - icon: <FontAwesomeIcon size="sm" icon={faBook} />, + icon: <BookIcon size="sm" />, id: "audit-log-config", }, { name: _("Audit Failure Log"), - icon: <FontAwesomeIcon size="sm" icon={faBook} />, + icon: <BookIcon size="sm" />, id: "auditfail-log-config", }, { name: _("Errors Log"), - icon: <FontAwesomeIcon size="sm" icon={faBook} />, + icon: <BookIcon size="sm" />, id: "error-log-config", }, { name: _("Security Log"), - icon: <FontAwesomeIcon size="sm" icon={faBook} />, + icon: <BookIcon size="sm" />, id: "security-log-config", } ],