From 68b6319d28239e7a9fa45c58ad2d8e5cd818f616 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Apr 15 2019 16:32:43 +0000 Subject: Issue 50041 - Add the rest UI Plugin tabs - Part 1 Description: Add UI plugin tabs for accountPolicy, attributeUniqueness, linkedAttributes, referentialIntegrity, retroChangelog, rootDNAccessControl and winsync. Reorder the tabs to make the usage more intuitive. Fix Attribute Uniqueness logging level issue. Move pluginTable.jsx content to pluginTables.jsx. Fix a small 'help' typo in dbtasks.py. https://pagure.io/389-ds-base/issue/50041 Reviewed by: mreynolds (Thanks!) --- diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx index fae8652..8468877 100644 --- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx +++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx @@ -1,24 +1,688 @@ +import cockpit from "cockpit"; import React from "react"; -import { noop } from "patternfly-react"; +import { + Icon, + Modal, + Button, + Row, + Col, + Form, + noop, + FormGroup, + FormControl, + Checkbox, + ControlLabel +} from "patternfly-react"; +import { Typeahead } from "react-bootstrap-typeahead"; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import { log_cmd } from "../tools.jsx"; import "../../css/ds.css"; +// Use default aacount policy name + class AccountPolicy extends React.Component { + componentWillMount(prevProps) { + this.updateFields(); + } + + componentDidUpdate(prevProps) { + if (this.props.rows !== prevProps.rows) { + this.updateFields(); + } + } + + constructor(props) { + super(props); + + this.getAttributes = this.getAttributes.bind(this); + this.updateFields = this.updateFields.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + this.handleCheckboxChange = this.handleCheckboxChange.bind(this); + this.openModal = this.openModal.bind(this); + this.closeModal = this.closeModal.bind(this); + this.addConfig = this.addConfig.bind(this); + this.editConfig = this.editConfig.bind(this); + this.deleteConfig = this.deleteConfig.bind(this); + this.cmdOperation = this.cmdOperation.bind(this); + + this.state = { + attributes: [], + configArea: "", + configDN: "", + altStateAttrName: [], + alwaysRecordLogin: false, + alwaysRecordLoginAttr: [], + limitAttrName: [], + specAttrName: [], + stateAttrName: [], + configEntryModalShow: false, + fixupModalShow: false, + newEntry: false + }; + } + + openModal() { + this.getAttributes(); + if (!this.state.configArea) { + this.setState({ + configEntryModalShow: true, + newEntry: true, + configDN: "", + altStateAttrName: [], + alwaysRecordLogin: false, + alwaysRecordLoginAttr: [], + limitAttrName: [], + specAttrName: [], + stateAttrName: [] + }); + } else { + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + + this.props.serverId + + ".socket", + "plugin", + "account-policy", + "config-entry", + "show", + this.state.configArea + ]; + + this.props.toggleLoadingHandler(); + log_cmd( + "openModal", + "Fetch the Account Policy Plugin config entry", + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + let configEntry = JSON.parse(content).attrs; + this.setState({ + configEntryModalShow: true, + newEntry: false, + configDN: this.state.configArea, + altStateAttrName: + configEntry["altstateattrname"] === undefined + ? [] + : [{id: configEntry["altstateattrname"][0], + label: configEntry["altstateattrname"][0]}], + alwaysRecordLogin: !( + configEntry["alwaysrecordlogin"] === undefined || + configEntry["alwaysrecordlogin"][0] == "no" + ), + alwaysRecordLoginAttr: + configEntry["alwaysrecordloginattr"] === undefined + ? [] + : [{id: configEntry["alwaysrecordloginattr"][0], + label: configEntry["alwaysrecordloginattr"][0]}], + limitAttrName: + configEntry["limitattrname"] === undefined + ? [] + : [{id: configEntry["limitattrname"][0], + label: configEntry["limitattrname"][0]}], + specAttrName: + configEntry["specattrname"] === undefined + ? [] + : [{id: configEntry["specattrname"][0], + label: configEntry["specattrname"][0]}], + stateAttrName: + configEntry["stateattrname"] === undefined + ? [] + : [{id: configEntry["stateattrname"][0], + label: configEntry["stateattrname"][0]}], + }); + this.props.toggleLoadingHandler(); + }) + .fail(_ => { + this.setState({ + configEntryModalShow: true, + newEntry: true, + configDN: this.state.configArea, + altStateAttrName: [], + alwaysRecordLogin: false, + alwaysRecordLoginAttr: [], + limitAttrName: [], + specAttrName: [], + stateAttrName: [] + }); + this.props.toggleLoadingHandler(); + }); + } + } + + closeModal() { + this.setState({ configEntryModalShow: false }); + } + + cmdOperation(action) { + const { + configDN, + altStateAttrName, + alwaysRecordLogin, + alwaysRecordLoginAttr, + limitAttrName, + specAttrName, + stateAttrName + } = this.state; + + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "account-policy", + "config-entry", + action, + configDN, + "--always-record-login", + alwaysRecordLogin ? "yes" : "no", + ]; + + cmd = [...cmd, "--alt-state-attr"]; + if (altStateAttrName.length != 0) { + cmd = [...cmd, altStateAttrName[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + cmd = [...cmd, "--always-record-login-attr"]; + if (alwaysRecordLoginAttr.length != 0) { + cmd = [...cmd, alwaysRecordLoginAttr[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + cmd = [...cmd, "--limit-attr"]; + if (limitAttrName.length != 0) { + cmd = [...cmd, limitAttrName[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + cmd = [...cmd, "--spec-attr"]; + if (specAttrName.length != 0) { + cmd = [...cmd, specAttrName[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + cmd = [...cmd, "--state-attr"]; + if (stateAttrName.length != 0) { + cmd = [...cmd, stateAttrName[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + this.props.toggleLoadingHandler(); + log_cmd( + "accountPolicyOperation", + `Do the ${action} operation on the Account Policy Plugin`, + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + console.info("accountPolicyOperation", "Result", content); + this.props.addNotification( + "success", + `Config entry ${configDN} was successfully ${action}ed` + ); + this.props.pluginListHandler(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + this.props.addNotification( + "error", + `Error during the config entry ${action} operation - ${err}` + ); + this.props.pluginListHandler(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }); + } + + deleteConfig() { + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "account-policy", + "config-entry", + "delete", + this.state.configDN + ]; + + this.props.toggleLoadingHandler(); + log_cmd( + "deleteConfig", + "Delete the Account Policy Plugin config entry", + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + console.info("deleteConfig", "Result", content); + this.props.addNotification( + "success", + `Config entry ${ + this.state.configDN + } was successfully deleted` + ); + this.props.pluginListHandler(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + this.props.addNotification( + "error", + `Error during the config entry removal operation - ${err}` + ); + this.props.pluginListHandler(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }); + } + + addConfig() { + this.cmdOperation("add"); + } + + editConfig() { + this.cmdOperation("set"); + } + + handleCheckboxChange(e) { + this.setState({ + [e.target.id]: e.target.checked + }); + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + updateFields() { + if (this.props.rows.length > 0) { + const pluginRow = this.props.rows.find( + row => row.cn[0] === "Account Policy Plugin" + ); + + this.setState({ + configArea: + pluginRow["nsslapd_pluginconfigarea"] === undefined + ? "" + : pluginRow["nsslapd_pluginconfigarea"][0] + }); + } + } + + getAttributes() { + const attr_cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "schema", + "attributetypes", + "list" + ]; + log_cmd("getAttributes", "Get attrs", attr_cmd); + cockpit + .spawn(attr_cmd, { superuser: true, err: "message" }) + .done(content => { + const attrContent = JSON.parse(content); + let attrs = []; + for (let content of attrContent["items"]) { + attrs.push({ + id: content.name, + label: content.name + }); + } + this.setState({ + attributes: attrs + }); + }) + .fail(err => { + this.props.addNotification( + "error", + `Failed to get attributes - ${err}` + ); + }); + } + render() { + const { + attributes, + configArea, + configDN, + altStateAttrName, + alwaysRecordLogin, + alwaysRecordLoginAttr, + limitAttrName, + specAttrName, + stateAttrName, + newEntry, + configEntryModalShow + } = this.state; + + let specificPluginCMD = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "account-policy", + "set", + "--config-entry", + configArea || "delete" + ]; + return (
+ +
+ + + + Manage Account Policy Plugin Shared Config Entry + + + + + +
+ + + + Config DN + + + + + + +
+ +
+ + +
+ + + Always Record Login Attribute + + + { + this.setState({ + alwaysRecordLoginAttr: value + }); + }} + selected={alwaysRecordLoginAttr} + options={attributes} + newSelectionPrefix="Add a managed attribute: " + placeholder="Type an attribute..." + /> + + + + Always Record Login + + + +
+ +
+ + +
+ + + Specific Attribute + + + { + this.setState({ + specAttrName: value + }); + }} + selected={specAttrName} + options={attributes} + newSelectionPrefix="Add a managed attribute: " + placeholder="Type an attribute..." + /> + + + + + + State Attribute + + + + { + this.setState({ + stateAttrName: value + }); + }} + selected={stateAttrName} + options={attributes} + newSelectionPrefix="Add a managed attribute: " + placeholder="Type an attribute..." + /> + + +
+ +
+ + +
+ + + Alternative State Attribute + + + { + this.setState({ + altStateAttrName: value + }); + }} + selected={altStateAttrName} + options={attributes} + newSelectionPrefix="Add a managed attribute: " + placeholder="Type an attribute..." + /> + + + + + + Limit Attribute + + + + { + this.setState({ + limitAttrName: value + }); + }} + selected={limitAttrName} + options={attributes} + newSelectionPrefix="Add a managed attribute: " + placeholder="Type an attribute..." + /> + + +
+ +
+
+ + + + + + +
+
+ > + + +
+ + + Shared Config Entry + + + + + + + + +
+ +
+
); } diff --git a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx index 0521a89..24c307d 100644 --- a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx +++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx @@ -1,14 +1,698 @@ +import cockpit from "cockpit"; import React from "react"; -import { noop } from "patternfly-react"; -import PropTypes from "prop-types"; +import { + Icon, + Modal, + Button, + Row, + Col, + Form, + Switch, + noop, + FormGroup, + FormControl, + Checkbox, + ControlLabel +} from "patternfly-react"; +import { Typeahead } from "react-bootstrap-typeahead"; +import { AttrUniqConfigTable } from "./pluginTables.jsx"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import PropTypes from "prop-types"; +import { log_cmd } from "../tools.jsx"; import "../../css/ds.css"; class AttributeUniqueness extends React.Component { + componentWillMount() { + this.loadConfigs(); + } + + constructor(props) { + super(props); + this.state = { + configRows: [], + attributes: [], + objectClasses: [], + + configName: "", + configEnabled: false, + attrNames: [], + subtrees: [], + acrossAllSubtrees: false, + topEntryOc: [], + subtreeEnriesOc: [], + + newEntry: false, + showConfigModal: false, + showConfirmDeleteConfig: false + }; + + this.handleSwitchChange = this.handleSwitchChange.bind(this); + this.handleCheckboxChange = this.handleCheckboxChange.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + this.loadConfigs = this.loadConfigs.bind(this); + this.showEditConfigModal = this.showEditConfigModal.bind(this); + this.showAddConfigModal = this.showAddConfigModal.bind(this); + this.getAttributes = this.getAttributes.bind(this); + this.getObjectClasses = this.getObjectClasses.bind(this); + this.closeModal = this.closeModal.bind(this); + this.openModal = this.openModal.bind(this); + this.cmdOperation = this.cmdOperation.bind(this); + this.deleteConfig = this.deleteConfig.bind(this); + this.addConfig = this.addConfig.bind(this); + this.editConfig = this.editConfig.bind(this); + } + + handleSwitchChange(value) { + this.setState({ + configEnabled: !value + }); + } + + handleCheckboxChange(e) { + this.setState({ + [e.target.id]: e.target.checked + }); + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + loadConfigs() { + // Get all the attributes and matching rules now + const cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "attr-uniq", + "list" + ]; + this.props.toggleLoadingHandler(); + log_cmd("loadConfigs", "Get Attribute Uniqueness Plugin configs", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + let myObject = JSON.parse(content); + this.setState({ + configRows: myObject.items.map( + item => JSON.parse(item).attrs + ) + }); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + if (err != 0) { + console.log("loadConfigs failed", err); + } + this.props.toggleLoadingHandler(); + }); + } + + showEditConfigModal(rowData) { + this.openModal(rowData.cn[0]); + } + + showAddConfigModal(rowData) { + this.openModal(); + } + + openModal(name) { + this.getAttributes(); + this.getObjectClasses(); + if (!name) { + this.setState({ + configEntryModalShow: true, + newEntry: true, + configName: "", + attrNames: [], + subtrees: [], + acrossAllSubtrees: false, + topEntryOc: [], + subtreeEnriesOc: [] + }); + } else { + let configAttrNamesList = []; + let configSubtreesList = []; + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + + this.props.serverId + + ".socket", + "plugin", + "attr-uniq", + "show", + name + ]; + + this.props.toggleLoadingHandler(); + log_cmd( + "openModal", + "Fetch the Attribute Uniqueness Plugin config entry", + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + let configEntry = JSON.parse(content).attrs; + this.setState({ + configEntryModalShow: true, + newEntry: false, + configName: + configEntry["cn"] === undefined + ? "" + : configEntry["cn"][0], + configEnabled: !( + configEntry["nsslapd-pluginenabled"] === + undefined || + configEntry["nsslapd-pluginenabled"][0] == "off" + ), + acrossAllSubtrees: !( + configEntry["uniqueness-across-all-subtrees"] === + undefined || + configEntry["uniqueness-across-all-subtrees"][0] == + "off" + ), + topEntryOc: + configEntry["uniqueness-top-entry-oc"] === undefined + ? [] + : [{id: configEntry["uniqueness-top-entry-oc"][0], + label: configEntry["uniqueness-top-entry-oc"][0]}], + subtreeEnriesOc: + configEntry["uniqueness-subtree-entries-oc"] === + undefined + ? [] + : [{id: configEntry["uniqueness-subtree-entries-oc"][0], + label: configEntry["uniqueness-subtree-entries-oc"][0]}] + }); + + if ( + configEntry["uniqueness-attribute-name"] === undefined + ) { + this.setState({ attrNames: [] }); + } else { + for (let value of configEntry["uniqueness-attribute-name"]) { + configAttrNamesList = [ + ...configAttrNamesList, + { id: value, label: value } + ]; + } + this.setState({ attrNames: configAttrNamesList }); + } + if (configEntry["uniqueness-subtrees"] === undefined) { + this.setState({ subtrees: [] }); + } else { + for (let value of configEntry["uniqueness-subtrees"]) { + configSubtreesList = [ + ...configSubtreesList, + { id: value, label: value } + ]; + } + this.setState({ subtrees: configSubtreesList }); + } + this.props.toggleLoadingHandler(); + }) + .fail(_ => { + this.setState({ + configEntryModalShow: true, + newEntry: true, + configName: "", + attrNames: [], + subtrees: [], + acrossAllSubtrees: false, + topEntryOc: [], + subtreeEnriesOc: [] + }); + this.props.toggleLoadingHandler(); + }); + } + } + + closeModal() { + this.setState({ configEntryModalShow: false }); + } + + cmdOperation(action) { + const { + configName, + configEnabled, + attrNames, + subtrees, + acrossAllSubtrees, + topEntryOc, + subtreeEnriesOc + } = this.state; + + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "attr-uniq", + action, + configName, + "--enabled", + configEnabled ? "on" : "off", + "--across-all-subtrees", + acrossAllSubtrees ? "on" : "off" + ]; + + // Delete attributes if the user set an empty value to the field + if (!(action == "add" && attrNames.length == 0)) { + cmd = [...cmd, "--attr-name"]; + if (attrNames.length != 0) { + for (let value of attrNames) { + cmd = [...cmd, value.id]; + } + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + } + + if (!(action == "add" && subtrees.length == 0)) { + cmd = [...cmd, "--subtree"]; + if (subtrees.length != 0) { + for (let value of subtrees) { + cmd = [...cmd, value.id]; + } + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + } + + cmd = [...cmd, "--top-entry-oc"]; + if (topEntryOc.length != 0) { + cmd = [...cmd, topEntryOc[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + cmd = [...cmd, "--subtree-entries-oc"]; + if (subtreeEnriesOc.length != 0) { + cmd = [...cmd, subtreeEnriesOc[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + this.props.toggleLoadingHandler(); + log_cmd( + "attrUniqOperation", + `Do the ${action} operation on the Attribute Uniqueness Plugin`, + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + console.info("attrUniqOperation", "Result", content); + this.props.addNotification( + "success", + `The ${action} operation was successfully done on "${configName}" entry` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + this.props.addNotification( + "error", + `Error during the config entry ${action} operation - ${err}` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }); + } + + deleteConfig(rowData) { + let configName = rowData.cn[0]; + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "attr-uniq", + "delete", + configName + ]; + + this.props.toggleLoadingHandler(); + log_cmd( + "deleteConfig", + "Delete the Attribute Uniqueness Plugin config entry", + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + console.info("deleteConfig", "Result", content); + this.props.addNotification( + "success", + `Config entry ${configName} was successfully deleted` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + this.props.addNotification( + "error", + `Error during the config entry removal operation - ${err}` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }); + } + + addConfig() { + this.cmdOperation("add"); + } + + editConfig() { + this.cmdOperation("set"); + } + + getAttributes() { + const attr_cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "schema", + "attributetypes", + "list" + ]; + log_cmd("getAttributes", "Get attrs", attr_cmd); + cockpit + .spawn(attr_cmd, { superuser: true, err: "message" }) + .done(content => { + const attrContent = JSON.parse(content); + let attrs = []; + for (let content of attrContent["items"]) { + attrs.push({ + id: content.name, + label: content.name + }); + } + this.setState({ + attributes: attrs + }); + }) + .fail(err => { + this.props.addNotification( + "error", + `Failed to get attributes - ${err}` + ); + }); + } + + getObjectClasses() { + const oc_cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "schema", + "objectclasses", + "list" + ]; + log_cmd("getObjectClasses", "Get objectClasses", oc_cmd); + cockpit + .spawn(oc_cmd, { superuser: true, err: "message" }) + .done(content => { + const ocContent = JSON.parse(content); + let ocs = []; + for (let content of ocContent["items"]) { + ocs.push({ + id: content.name, + label: content.name + }); + } + this.setState({ + objectClasses: ocs + }); + }) + .fail(err => { + this.props.addNotification( + "error", + `Failed to get objectClasses - ${err}` + ); + }); + } + render() { + const { + configEntryModalShow, + configName, + attrNames, + subtrees, + acrossAllSubtrees, + configEnabled, + topEntryOc, + subtreeEnriesOc, + newEntry, + attributes, + objectClasses + } = this.state; + return (
+ +
+ + + + {newEntry ? "Add" : "Edit"} Attribute Uniqueness + Plugin Config Entry + + + + + +
+ + + + Config Name + + + + + + + + + Attribute Names + + + { + this.setState({ + attrNames: values + }); + }} + selected={attrNames} + newSelectionPrefix="Add an attribute: " + options={attributes} + placeholder="Type an attribute name..." + /> + + + + + Subtrees + + + { + this.setState({ + subtrees: values + }); + }} + selected={subtrees} + options={[""]} + newSelectionPrefix="Add a subtree: " + placeholder="Type a subtree DN..." + /> + + +
+ +
+ + +
+ + + Top Entry OC + + + { + this.setState({ + topEntryOc: value + }); + }} + selected={topEntryOc} + options={objectClasses} + newSelectionPrefix="Add a top entry objectClass: " + placeholder="Type an objectClass..." + /> + + + + + Subtree Entries OC + + + { + this.setState({ + subtreeEnriesOc: value + }); + }} + selected={subtreeEnriesOc} + options={objectClasses} + newSelectionPrefix="Add a subtree entries objectClass: " + placeholder="Type an objectClass..." + /> + + + + Across All Subtrees + + + + + + Enable config + + + + this.handleSwitchChange( + configEnabled + ) + } + animate={false} + /> + + +
+ +
+
+ + + + +
+
+ > + + + + + + +
); } diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx index 5216b15..a799e54 100644 --- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx +++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx @@ -1,13 +1,467 @@ +import cockpit from "cockpit"; import React from "react"; -import { noop } from "patternfly-react"; -import PropTypes from "prop-types"; +import { + Icon, + Modal, + Button, + Row, + Col, + Form, + noop, + FormGroup, + FormControl, + ControlLabel +} from "patternfly-react"; +import { Typeahead } from "react-bootstrap-typeahead"; +import { LinkedAttributesTable } from "./pluginTables.jsx"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import PropTypes from "prop-types"; +import { log_cmd } from "../tools.jsx"; import "../../css/ds.css"; class LinkedAttributes extends React.Component { + componentWillMount() { + this.loadConfigs(); + } + + constructor(props) { + super(props); + this.state = { + configRows: [], + attributes: [], + + configName: "", + linkType: [], + managedType: [], + linkScope: "", + + newEntry: false, + showConfigModal: false, + showConfirmDeleteConfig: false + }; + + this.getAttributes = this.getAttributes.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + this.loadConfigs = this.loadConfigs.bind(this); + this.showEditConfigModal = this.showEditConfigModal.bind(this); + this.showAddConfigModal = this.showAddConfigModal.bind(this); + this.closeModal = this.closeModal.bind(this); + this.openModal = this.openModal.bind(this); + this.cmdOperation = this.cmdOperation.bind(this); + this.deleteConfig = this.deleteConfig.bind(this); + this.addConfig = this.addConfig.bind(this); + this.editConfig = this.editConfig.bind(this); + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + loadConfigs() { + // Get all the attributes and matching rules now + const cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "linked-attr", + "list" + ]; + this.props.toggleLoadingHandler(); + log_cmd("loadConfigs", "Get Linked Attributes Plugin configs", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + let myObject = JSON.parse(content); + this.setState({ + configRows: myObject.items.map( + item => JSON.parse(item).attrs + ) + }); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + if (err != 0) { + console.log("loadConfigs failed", err); + } + this.props.toggleLoadingHandler(); + }); + } + + showEditConfigModal(rowData) { + this.openModal(rowData.cn[0]); + } + + showAddConfigModal(rowData) { + this.openModal(); + } + + openModal(name) { + this.getAttributes(); + if (!name) { + this.setState({ + configEntryModalShow: true, + newEntry: true, + configName: "", + linkType: [], + managedType: [], + linkScope: "" + }); + } else { + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + + this.props.serverId + + ".socket", + "plugin", + "linked-attr", + "config", + name, + "show" + ]; + + this.props.toggleLoadingHandler(); + log_cmd( + "openModal", + "Fetch the Linked Attributes Plugin config entry", + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + let configEntry = JSON.parse(content).attrs; + this.setState({ + configEntryModalShow: true, + newEntry: false, + configName: + configEntry["cn"] === undefined + ? "" + : configEntry["cn"][0], + linkType: + configEntry["linktype"] === undefined + ? [] + : [{id: configEntry["linktype"][0], + label: configEntry["linktype"][0]}], + managedType: + configEntry["managedtype"] === undefined + ? [] + : [{id: configEntry["managedtype"][0], + label: configEntry["managedtype"][0]}], + linkScope: + configEntry["linkscope"] === undefined + ? "" + : configEntry["linkscope"][0] + }); + + this.props.toggleLoadingHandler(); + }) + .fail(_ => { + this.setState({ + configEntryModalShow: true, + newEntry: true, + configName: "", + linkType: [], + managedType: [], + linkScope: "" + }); + this.props.toggleLoadingHandler(); + }); + } + } + + closeModal() { + this.setState({ configEntryModalShow: false }); + } + + cmdOperation(action) { + const { configName, linkType, managedType, linkScope } = this.state; + + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "linked-attr", + "config", + configName, + action, + "--link-scope", + linkScope || action == "add" ? linkScope : "delete" + ]; + + cmd = [...cmd, "--link-type"]; + if (linkType.length != 0) { + cmd = [...cmd, linkType[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + cmd = [...cmd, "--managed-type"]; + if (managedType.length != 0) { + cmd = [...cmd, managedType[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + + this.props.toggleLoadingHandler(); + log_cmd( + "linkedAttributesOperation", + `Do the ${action} operation on the Linked Attributes Plugin`, + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + console.info("linkedAttributesOperation", "Result", content); + this.props.addNotification( + "success", + `The ${action} operation was successfully done on "${configName}" entry` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + this.props.addNotification( + "error", + `Error during the config entry ${action} operation - ${err}` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }); + } + + deleteConfig(rowData) { + let configName = rowData.cn[0]; + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "linked-attr", + "config", + configName, + "delete" + ]; + + this.props.toggleLoadingHandler(); + log_cmd( + "deleteConfig", + "Delete the Linked Attributes Plugin config entry", + cmd + ); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .done(content => { + console.info("deleteConfig", "Result", content); + this.props.addNotification( + "success", + `Config entry ${configName} was successfully deleted` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }) + .fail(err => { + this.props.addNotification( + "error", + `Error during the config entry removal operation - ${err}` + ); + this.loadConfigs(); + this.closeModal(); + this.props.toggleLoadingHandler(); + }); + } + + addConfig() { + this.cmdOperation("add"); + } + + editConfig() { + this.cmdOperation("set"); + } + + getAttributes() { + const attr_cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "schema", + "attributetypes", + "list" + ]; + log_cmd("getAttributes", "Get attrs", attr_cmd); + cockpit + .spawn(attr_cmd, { superuser: true, err: "message" }) + .done(content => { + const attrContent = JSON.parse(content); + let attrs = []; + for (let content of attrContent["items"]) { + attrs.push({ + id: content.name, + label: content.name + }); + } + this.setState({ + attributes: attrs + }); + }) + .fail(err => { + this.props.addNotification( + "error", + `Failed to get attributes - ${err}` + ); + }); + } + render() { + const { + configEntryModalShow, + configName, + linkType, + managedType, + linkScope, + newEntry, + attributes + } = this.state; + return (
+ +
+ + + + {newEntry ? "Add" : "Edit"} Linked Attributes + Plugin Config Entry + + + + + +
+ + + + Config Name + + + + + + + + + + Link Type + + + + { + this.setState({ + linkType: value + }); + }} + selected={linkType} + options={attributes} + newSelectionPrefix="Add a managed attribute: " + placeholder="Type an attribute..." + /> + + + + + + Managed Type + + + + { + this.setState({ + managedType: value + }); + }} + selected={managedType} + options={attributes} + newSelectionPrefix="Add a dynamic attribute: " + placeholder="Type an attribute..." + /> + + + + + + Link Scope + + + + + + +
+ +
+
+ + + + +
+
+ > + + + + + + +
); } diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx index d838054..68a0862 100644 --- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx +++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx @@ -21,6 +21,7 @@ import "../../css/ds.css"; class MemberOf extends React.Component { componentWillMount(prevProps) { + this.getObjectClasses(); this.updateFields(); } @@ -33,6 +34,7 @@ class MemberOf extends React.Component { constructor(props) { super(props); + this.getObjectClasses = this.getObjectClasses.bind(this); this.updateFields = this.updateFields.bind(this); this.handleFieldChange = this.handleFieldChange.bind(this); this.handleCheckboxChange = this.handleCheckboxChange.bind(this); @@ -46,11 +48,13 @@ class MemberOf extends React.Component { this.toggleFixupModal = this.toggleFixupModal.bind(this); this.state = { + objectClasses: [], + memberOfAttr: [], memberOfGroupAttr: [], memberOfEntryScope: "", memberOfEntryScopeExcludeSubtree: "", - memberOfAutoAddOC: "", + memberOfAutoAddOC: [], memberOfAllBackends: false, memberOfSkipNested: false, memberOfConfigEntry: "", @@ -62,7 +66,7 @@ class MemberOf extends React.Component { configGroupAttr: [], configEntryScope: "", configEntryScopeExcludeSubtree: "", - configAutoAddOC: "", + configAutoAddOC: [], configAllBackends: false, configSkipNested: false, newEntry: true, @@ -87,7 +91,9 @@ class MemberOf extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + + this.props.serverId + + ".socket", "plugin", "memberof", "fixup", @@ -129,6 +135,7 @@ class MemberOf extends React.Component { } openModal() { + this.getObjectClasses(); if (!this.state.memberOfConfigEntry) { this.setState({ configEntryModalShow: true, @@ -138,7 +145,7 @@ class MemberOf extends React.Component { configGroupAttr: [], configEntryScope: "", configEntryScopeExcludeSubtree: "", - configAutoAddOC: "", + configAutoAddOC: [], configAllBackends: false, configSkipNested: false }); @@ -148,7 +155,9 @@ class MemberOf extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + + this.props.serverId + + ".socket", "plugin", "memberof", "config-entry", @@ -157,7 +166,11 @@ class MemberOf extends React.Component { ]; this.props.toggleLoadingHandler(); - log_cmd("openMemberOfModal", "Fetch the MemberOf Plugin config entry", cmd); + log_cmd( + "openMemberOfModal", + "Fetch the MemberOf Plugin config entry", + cmd + ); cockpit .spawn(cmd, { superuser: true, @@ -171,8 +184,9 @@ class MemberOf extends React.Component { configDN: this.state.memberOfConfigEntry, configAutoAddOC: configEntry["memberofautoaddoc"] === undefined - ? "" - : configEntry["memberofautoaddoc"][0], + ? [] + : [{id:configEntry["memberofautoaddoc"][0], + label: configEntry["memberofautoaddoc"][0]}], configAllBackends: !( configEntry["memberofallbackends"] === undefined || configEntry["memberofallbackends"][0] == "off" @@ -182,7 +196,8 @@ class MemberOf extends React.Component { configEntry["memberofskipnested"][0] == "off" ), configConfigEntry: - configEntry["nsslapd-pluginConfigArea"] === undefined + configEntry["nsslapd-pluginConfigArea"] === + undefined ? "" : configEntry["nsslapd-pluginConfigArea"][0], configEntryScope: @@ -190,7 +205,8 @@ class MemberOf extends React.Component { ? "" : configEntry["memberofentryscope"][0], configEntryScopeExcludeSubtree: - configEntry["memberofentryscopeexcludesubtree"] === undefined + configEntry["memberofentryscopeexcludesubtree"] === + undefined ? "" : configEntry["memberofentryscopeexcludesubtree"][0] }); @@ -214,9 +230,11 @@ class MemberOf extends React.Component { { id: value, label: value } ]; } - this.setState({ configGroupAttr: configGroupAttrObjectList }); - this.props.toggleLoadingHandler(); + this.setState({ + configGroupAttr: configGroupAttrObjectList + }); } + this.props.toggleLoadingHandler(); }) .fail(_ => { this.setState({ @@ -227,7 +245,7 @@ class MemberOf extends React.Component { configGroupAttr: [], configEntryScope: "", configEntryScopeExcludeSubtree: "", - configAutoAddOC: "", + configAutoAddOC: [], configAllBackends: false, configSkipNested: false }); @@ -261,26 +279,37 @@ class MemberOf extends React.Component { let cmd = [ "dsconf", "-j", - "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "ldapi://%2fvar%2frun%2fslapd-" + + this.props.serverId + + ".socket", "plugin", "memberof", "config-entry", action, configDN, "--scope", - configEntryScope || action == "add" ? configEntryScope : "delete", + configEntryScope || action == "add" + ? configEntryScope + : "delete", "--exclude", configEntryScopeExcludeSubtree || action == "add" ? configEntryScopeExcludeSubtree : "delete", - "--autoaddoc", - configAutoAddOC || action == "add" ? configAutoAddOC : "delete", "--allbackends", configAllBackends ? "on" : "off", "--skipnested", configSkipNested ? "on" : "off" ]; + cmd = [...cmd, "--autoaddoc"]; + if (configAutoAddOC.length != 0) { + cmd = [...cmd, configAutoAddOC[0].id]; + } else if (action == "add") { + cmd = [...cmd, ""]; + } else { + cmd = [...cmd, "delete"]; + } + // Delete attributes if the user set an empty value to the field cmd = [...cmd, "--attr"]; if (configAttr.length != 0) { @@ -296,7 +325,11 @@ class MemberOf extends React.Component { } this.props.toggleLoadingHandler(); - log_cmd("memberOfOperation", `Do the ${action} operation on the MemberOf Plugin`, cmd); + log_cmd( + "memberOfOperation", + `Do the ${action} operation on the MemberOf Plugin`, + cmd + ); cockpit .spawn(cmd, { superuser: true, @@ -347,7 +380,9 @@ class MemberOf extends React.Component { console.info("deleteConfig", "Result", content); this.props.addNotification( "success", - `Config entry ${this.state.configDN} was successfully deleted` + `Config entry ${ + this.state.configDN + } was successfully deleted` ); this.props.pluginListHandler(); this.closeModal(); @@ -389,13 +424,20 @@ class MemberOf extends React.Component { let memberOfGroupAttrObjectList = []; if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find(row => row.cn[0] === "MemberOf Plugin"); + const pluginRow = this.props.rows.find( + row => row.cn[0] === "MemberOf Plugin" + ); this.setState({ memberOfAutoAddOC: pluginRow["memberofautoaddoc"] === undefined - ? "" - : pluginRow["memberofautoaddoc"][0], + ? [] + : [ + { + id: pluginRow["memberofautoaddoc"][0], + label: pluginRow["memberofautoaddoc"][0] + } + ], memberOfAllBackends: !( pluginRow["memberofallbackends"] === undefined || pluginRow["memberofallbackends"][0] == "off" @@ -437,13 +479,49 @@ class MemberOf extends React.Component { { id: value, label: value } ]; } - this.setState({ memberOfGroupAttr: memberOfGroupAttrObjectList }); + this.setState({ + memberOfGroupAttr: memberOfGroupAttrObjectList + }); } } } + getObjectClasses() { + const oc_cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "schema", + "objectclasses", + "list" + ]; + log_cmd("getObjectClasses", "Get objectClasses", oc_cmd); + cockpit + .spawn(oc_cmd, { superuser: true, err: "message" }) + .done(content => { + const ocContent = JSON.parse(content); + let ocs = []; + for (let content of ocContent["items"]) { + ocs.push({ + id: content.name, + label: content.name + }); + } + this.setState({ + objectClasses: ocs + }); + }) + .fail(err => { + this.props.addNotification( + "error", + `Failed to get objectClasses - ${err}` + ); + }); + } + render() { const { + objectClasses, memberOfAttr, memberOfGroupAttr, memberOfEntryScope, @@ -478,8 +556,6 @@ class MemberOf extends React.Component { memberOfEntryScope || "delete", "--exclude", memberOfEntryScopeExcludeSubtree || "delete", - "--autoaddoc", - memberOfAutoAddOC || "delete", "--config-entry", memberOfConfigEntry || "delete", "--allbackends", @@ -488,6 +564,13 @@ class MemberOf extends React.Component { memberOfSkipNested ? "on" : "off" ]; + specificPluginCMD = [...specificPluginCMD, "--autoaddoc"]; + if (memberOfAutoAddOC.length != 0) { + specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].id]; + } else { + specificPluginCMD = [...specificPluginCMD, "delete"]; + } + // Delete attributes if the user set an empty value to the field specificPluginCMD = [...specificPluginCMD, "--attr"]; if (memberOfAttr.length != 0) { @@ -526,27 +609,41 @@ class MemberOf extends React.Component {
- + - Base DN + + Base DN + - + - Filter DN + + Filter DN + @@ -579,7 +676,9 @@ class MemberOf extends React.Component { > - Manage MemberOf Plugin Shared Config Entry + + Manage MemberOf Plugin Shared Config Entry + @@ -587,13 +686,17 @@ class MemberOf extends React.Component { - Config DN + + Config DN + @@ -603,7 +706,11 @@ class MemberOf extends React.Component { controlId="configAttr" disabled={false} > - + Attribute @@ -632,7 +739,11 @@ class MemberOf extends React.Component { controlId="configGroupAttr" disabled={false} > - + Group Attribute @@ -667,21 +778,31 @@ class MemberOf extends React.Component { controlId="configEntryScope" disabled={false} > - + Entry Scope All Backends @@ -692,21 +813,33 @@ class MemberOf extends React.Component { controlId="configEntryScopeExcludeSubtree" disabled={false} > - + Entry Scope Exclude Subtree Skip Nested @@ -718,15 +851,27 @@ class MemberOf extends React.Component { - + - Auto Add OC + + Auto Add OC + - { + this.setState({ + configAutoAddOC: value + }); + }} + selected={configAutoAddOC} + options={objectClasses} + newSelectionPrefix="Add a memberOf objectClass: " + placeholder="Type an objectClass..." /> @@ -749,10 +894,18 @@ class MemberOf extends React.Component { > Delete - - @@ -778,7 +931,11 @@ class MemberOf extends React.Component { controlId="memberOfAttr" disabled={false} > - + Attribute @@ -815,7 +972,11 @@ class MemberOf extends React.Component { controlId="memberOfGroupAttr" disabled={false} > - + Group Attribute @@ -862,7 +1023,11 @@ class MemberOf extends React.Component { controlId="memberOfEntryScope" disabled={false} > - + Entry Scope @@ -877,6 +1042,7 @@ class MemberOf extends React.Component { id="memberOfAllBackends" checked={memberOfAllBackends} onChange={this.handleCheckboxChange} + title="Specifies whether to search the local suffix for user entries on all available suffixes (memberOfAllBackends)" > All Backends @@ -887,13 +1053,19 @@ class MemberOf extends React.Component { controlId="memberOfEntryScopeExcludeSubtree" disabled={false} > - + Entry Scope Exclude Subtree @@ -902,6 +1074,7 @@ class MemberOf extends React.Component { id="memberOfSkipNested" checked={memberOfSkipNested} onChange={this.handleCheckboxChange} + title="Specifies wherher to skip nested groups or not (memberOfSkipNested)" > Skip Nested @@ -917,7 +1090,11 @@ class MemberOf extends React.Component { key="memberOfConfigEntry" controlId="memberOfConfigEntry" > - + Shared Config Entry @@ -943,15 +1120,28 @@ class MemberOf extends React.Component { - - + + Auto Add OC - { + this.setState({ + memberOfAutoAddOC: value + }); + }} + selected={memberOfAutoAddOC} + options={objectClasses} + newSelectionPrefix="Add a memberOf objectClass: " + placeholder="Type an objectClass..." /> diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx index ae37334..64925ce 100644 --- a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx +++ b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx @@ -64,7 +64,9 @@ class PluginBasicConfig extends React.Component { addNotification, toggleLoadingHandler } = this.props; - const new_status = this.state.currentPluginEnabled ? "disable" : "enable"; + const new_status = this.state.currentPluginEnabled + ? "disable" + : "enable"; const cmd = [ "dsconf", "-j", @@ -76,7 +78,11 @@ class PluginBasicConfig extends React.Component { toggleLoadingHandler(); this.setState({ disableSwitch: true }); - log_cmd("handleSwitchChange", "Switch plugin states from the plugin tab", cmd); + log_cmd( + "handleSwitchChange", + "Switch plugin states from the plugin tab", + cmd + ); cockpit .spawn(cmd, { superuser: true, err: "message" }) .done(content => { @@ -101,7 +107,9 @@ class PluginBasicConfig extends React.Component { updateFields() { if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find(row => row.cn[0] === this.props.cn); + const pluginRow = this.props.rows.find( + row => row.cn[0] === this.props.cn + ); this.setState({ currentPluginType: pluginRow["nsslapd-pluginType"][0], @@ -110,7 +118,8 @@ class PluginBasicConfig extends React.Component { currentPluginId: pluginRow["nsslapd-pluginId"][0], currentPluginVendor: pluginRow["nsslapd-pluginVendor"][0], currentPluginVersion: pluginRow["nsslapd-pluginVersion"][0], - currentPluginDescription: pluginRow["nsslapd-pluginDescription"][0], + currentPluginDescription: + pluginRow["nsslapd-pluginDescription"][0], currentPluginDependsOnType: pluginRow["nsslapd-plugin-depends-on-type"] === undefined ? "" @@ -126,7 +135,9 @@ class PluginBasicConfig extends React.Component { updateSwitch() { if (this.props.rows.length > 0) { - const pluginRow = this.props.rows.find(row => row.cn[0] === this.props.cn); + const pluginRow = this.props.rows.find( + row => row.cn[0] === this.props.cn + ); var pluginEnabled; if (pluginRow["nsslapd-pluginEnabled"][0] === "on") { @@ -185,24 +196,32 @@ class PluginBasicConfig extends React.Component { - - - + - Status - - this.handleSwitchChange(currentPluginEnabled)} - animate={false} - disabled={disableSwitch} - /> - - + + Status + + + this.handleSwitchChange( + currentPluginEnabled + ) + } + animate={false} + disabled={disableSwitch} + /> + + + )} {this.props.children} @@ -210,21 +229,35 @@ class PluginBasicConfig extends React.Component {
- {Object.entries(modalFieldsCol1).map(([id, value]) => ( - - - {this.props.memberOfAttr} Plugin{" "} - {id.replace("currentPlugin", "")} - - - - - - ))} + {Object.entries(modalFieldsCol1).map( + ([id, value]) => ( + + + {this.props.memberOfAttr} Plugin{" "} + {id.replace( + "currentPlugin", + "" + )} + + + + + + ) + )} @@ -252,7 +288,10 @@ class PluginBasicConfig extends React.Component { @@ -261,20 +300,35 @@ class PluginBasicConfig extends React.Component { - {Object.entries(modalFieldsCol2).map(([id, value]) => ( - - - Plugin {id.replace("currentPlugin", "")} - - - - - - ))} + {Object.entries(modalFieldsCol2).map( + ([id, value]) => ( + + + Plugin{" "} + {id.replace( + "currentPlugin", + "" + )} + + + + + + ) + )}
@@ -296,7 +350,8 @@ class PluginBasicConfig extends React.Component { description: currentPluginDescription, dependsOnType: currentPluginDependsOnType, dependsOnNamed: currentPluginDependsOnNamed, - specificPluginCMD: this.props.specificPluginCMD + specificPluginCMD: this.props + .specificPluginCMD }) } > @@ -316,6 +371,7 @@ PluginBasicConfig.propTypes = { cn: PropTypes.string, pluginName: PropTypes.string, cmdName: PropTypes.string, + removeSwitch: PropTypes.bool, specificPluginCMD: PropTypes.array, savePluginHandler: PropTypes.func, pluginListHandler: PropTypes.func, @@ -329,6 +385,7 @@ PluginBasicConfig.defaultProps = { cn: "", pluginName: "", cmdName: "", + removeSwitch: false, specificPluginCMD: [], savePluginHandler: noop, pluginListHandler: noop, diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx b/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx deleted file mode 100644 index 6c29e6b..0000000 --- a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import React from "react"; -import { - Button, - noop, - actionHeaderCellFormatter, - sortableHeaderCellFormatter, - tableCellFormatter, -} from "patternfly-react"; -import PropTypes from "prop-types"; -import { DSTable } from "../dsTable.jsx"; -import "../../css/ds.css"; - -class PluginTable extends React.Component { - constructor(props) { - super(props); - - this.state = { - searchFilterValue: "", - fieldsToSearch: ["cn", "nsslapd-pluginType"], - columns: [ - { - property: "cn", - header: { - label: "Plugin Name", - props: { - index: 0, - rowSpan: 1, - colSpan: 1, - sort: true - }, - transforms: [], - formatters: [], - customFormatters: [sortableHeaderCellFormatter] - }, - cell: { - props: { - index: 0 - }, - formatters: [tableCellFormatter] - } - }, - { - property: "nsslapd-pluginType", - header: { - label: "Plugin Type", - props: { - index: 1, - rowSpan: 1, - colSpan: 1, - sort: true - }, - transforms: [], - formatters: [], - customFormatters: [sortableHeaderCellFormatter] - }, - cell: { - props: { - index: 1 - }, - formatters: [tableCellFormatter] - } - }, - { - property: "nsslapd-pluginEnabled", - header: { - label: "Enabled", - props: { - index: 2, - rowSpan: 1, - colSpan: 1, - sort: true - }, - transforms: [], - formatters: [], - customFormatters: [sortableHeaderCellFormatter] - }, - cell: { - props: { - index: 2 - }, - formatters: [tableCellFormatter] - } - }, - { - property: "actions", - header: { - label: "Actions", - props: { - index: 3, - rowSpan: 1, - colSpan: 1 - }, - formatters: [actionHeaderCellFormatter] - }, - cell: { - props: { - index: 3 - }, - formatters: [ - (value, { rowData }) => { - return [ - - - - ]; - } - ] - } - } - ], - }; - this.getColumns = this.getColumns.bind(this); - } - - getColumns() { - return this.state.columns; - } - - render() { - return ( -
- -
- ); - } -} - -PluginTable.propTypes = { - rows: PropTypes.array, - loadModalHandler: PropTypes.func, -}; - -PluginTable.defaultProps = { - rows: [], - loadModalHandler: noop, -}; - -export default PluginTable; diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx new file mode 100644 index 0000000..a9bc3f4 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/pluginTables.jsx @@ -0,0 +1,500 @@ +import React from "react"; +import { + Button, + DropdownButton, + MenuItem, + actionHeaderCellFormatter, + sortableHeaderCellFormatter, + tableCellFormatter, + noop +} from "patternfly-react"; +import { DSTable } from "../dsTable.jsx"; +import PropTypes from "prop-types"; +import "../../css/ds.css"; + +class PluginTable extends React.Component { + constructor(props) { + super(props); + + this.state = { + searchFilterValue: "", + fieldsToSearch: ["cn", "nsslapd-pluginType"], + columns: [ + { + property: "cn", + header: { + label: "Plugin Name", + props: { + index: 0, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 0 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "nsslapd-pluginType", + header: { + label: "Plugin Type", + props: { + index: 1, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 1 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "nsslapd-pluginEnabled", + header: { + label: "Enabled", + props: { + index: 2, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 2 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "actions", + header: { + label: "Actions", + props: { + index: 3, + rowSpan: 1, + colSpan: 1 + }, + formatters: [actionHeaderCellFormatter] + }, + cell: { + props: { + index: 3 + }, + formatters: [ + (value, { rowData }) => { + return [ + + + + ]; + } + ] + } + } + ] + }; + this.getColumns = this.getColumns.bind(this); + } + + getColumns() { + return this.state.columns; + } + + render() { + return ( +
+ +
+ ); + } +} + +PluginTable.propTypes = { + rows: PropTypes.array, + loadModalHandler: PropTypes.func +}; + +PluginTable.defaultProps = { + rows: [], + loadModalHandler: noop +}; + +class AttrUniqConfigTable extends React.Component { + constructor(props) { + super(props); + + this.getColumns = this.getColumns.bind(this); + + this.state = { + searchField: "Configs", + fieldsToSearch: ["cn", "uniqueness-attribute-name"], + columns: [ + { + property: "cn", + header: { + label: "Config Name", + props: { + index: 0, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 0 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "uniqueness-attribute-name", + header: { + label: "Attribute", + props: { + index: 1, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 1 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "nsslapd-pluginenabled", + header: { + label: "Enabled", + props: { + index: 2, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 2 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "actions", + header: { + props: { + index: 3, + rowSpan: 1, + colSpan: 1 + }, + formatters: [actionHeaderCellFormatter] + }, + cell: { + props: { + index: 3 + }, + formatters: [ + (value, { rowData }) => { + return [ + + + { + this.props.editConfig( + rowData + ); + }} + > + Edit Config + + + { + this.props.deleteConfig( + rowData + ); + }} + > + Delete Config + + + + ]; + } + ] + } + } + ] + }; + } + + getColumns() { + return this.state.columns; + } + + render() { + return ( +
+ +
+ ); + } +} + +AttrUniqConfigTable.propTypes = { + rows: PropTypes.array, + editConfig: PropTypes.func, + deleteConfig: PropTypes.func +}; + +AttrUniqConfigTable.defaultProps = { + rows: [], + editConfig: noop, + deleteConfig: noop +}; + +class LinkedAttributesTable extends React.Component { + constructor(props) { + super(props); + + this.getColumns = this.getColumns.bind(this); + + this.state = { + searchField: "Configs", + fieldsToSearch: ["cn", "linkType", "managedType", "linkScope"], + columns: [ + { + property: "cn", + header: { + label: "Config Name", + props: { + index: 0, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 0 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "linktype", + header: { + label: "Link Type", + props: { + index: 1, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 1 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "managedtype", + header: { + label: "Managed Type", + props: { + index: 2, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 2 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "linkscope", + header: { + label: "Link Scope", + props: { + index: 2, + rowSpan: 1, + colSpan: 1, + sort: true + }, + transforms: [], + formatters: [], + customFormatters: [sortableHeaderCellFormatter] + }, + cell: { + props: { + index: 2 + }, + formatters: [tableCellFormatter] + } + }, + { + property: "actions", + header: { + props: { + index: 3, + rowSpan: 1, + colSpan: 1 + }, + formatters: [actionHeaderCellFormatter] + }, + cell: { + props: { + index: 3 + }, + formatters: [ + (value, { rowData }) => { + return [ + + + { + this.props.editConfig( + rowData + ); + }} + > + Edit Config + + + { + this.props.deleteConfig( + rowData + ); + }} + > + Delete Config + + + + ]; + } + ] + } + } + ] + }; + } + + getColumns() { + return this.state.columns; + } + + render() { + return ( +
+ +
+ ); + } +} + +LinkedAttributesTable.propTypes = { + rows: PropTypes.array, + editConfig: PropTypes.func, + deleteConfig: PropTypes.func +}; + +LinkedAttributesTable.defaultProps = { + rows: [], + editConfig: noop, + deleteConfig: noop +}; + +export { PluginTable, AttrUniqConfigTable, LinkedAttributesTable }; diff --git a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx index 96e8464..d7b1bc4 100644 --- a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx +++ b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx @@ -1,11 +1,166 @@ +import cockpit from "cockpit"; import React from "react"; -import { noop } from "patternfly-react"; +import { + noop, + FormGroup, + FormControl, + Row, + Col, + Form, + ControlLabel +} from "patternfly-react"; +import { Typeahead } from "react-bootstrap-typeahead"; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import { log_cmd } from "../tools.jsx"; import "../../css/ds.css"; class ReferentialIntegrity extends React.Component { + componentWillMount(prevProps) { + this.getAttributes(); + this.updateFields(); + } + + componentDidUpdate(prevProps) { + if (this.props.rows !== prevProps.rows) { + this.updateFields(); + } + } + + constructor(props) { + super(props); + + this.state = { + updateDelay: "", + membershipAttr: [], + entryScope: "", + excludeEntryScope: "", + containerScope: "", + attributes: [] + }; + + this.updateFields = this.updateFields.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + this.getAttributes = this.getAttributes.bind(this); + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + updateFields() { + let membershipAttrList = []; + + if (this.props.rows.length > 0) { + const pluginRow = this.props.rows.find( + row => row.cn[0] === "referential integrity postoperation" + ); + + this.setState({ + updateDelay: + pluginRow["referint-update-delay"] === undefined + ? "" + : pluginRow["referint-update-delay"][0], + entryScope: + pluginRow["nsslapd-pluginEntryScope"] === undefined + ? "" + : pluginRow["nsslapd-pluginEntryScope"][0], + excludeEntryScope: + pluginRow["nsslapd-pluginExcludeEntryScope"] === undefined + ? "" + : pluginRow["nsslapd-pluginExcludeEntryScope"][0], + containerScope: + pluginRow["nsslapd-pluginContainerScope"] === undefined + ? "" + : pluginRow["nsslapd-pluginContainerScope"][0] + }); + + if (pluginRow["referint-membership-attr"] === undefined) { + this.setState({ membershipAttr: [] }); + } else { + for (let value of pluginRow["referint-membership-attr"]) { + membershipAttrList = [ + ...membershipAttrList, + { id: value, label: value } + ]; + } + this.setState({ membershipAttr: membershipAttrList }); + } + } + } + + getAttributes() { + const attr_cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "schema", + "attributetypes", + "list" + ]; + log_cmd("getAttributes", "Get attrs", attr_cmd); + cockpit + .spawn(attr_cmd, { superuser: true, err: "message" }) + .done(content => { + const attrContent = JSON.parse(content); + let attrs = []; + for (let content of attrContent["items"]) { + attrs.push({ + id: content.name, + label: content.name + }); + } + this.setState({ + attributes: attrs + }); + }) + .fail(err => { + this.props.addNotification( + "error", + `Failed to get attributes - ${err}` + ); + }); + } + render() { + const { + updateDelay, + membershipAttr, + entryScope, + excludeEntryScope, + containerScope, + attributes + } = this.state; + + let specificPluginCMD = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "referential-integrity", + "set", + "--update-delay", + updateDelay || "delete", + "--entry-scope", + entryScope || "delete", + "--exclude-entry-scope", + excludeEntryScope || "delete", + "--container-scope", + containerScope || "delete" + ]; + + // Delete attributes if the user set an empty value to the field + specificPluginCMD = [...specificPluginCMD, "--membership-attr"]; + if (membershipAttr.length != 0) { + for (let value of membershipAttr) { + specificPluginCMD = [...specificPluginCMD, value.label]; + } + } else { + specificPluginCMD = [...specificPluginCMD, "delete"]; + } + return (
+ > + + +
+ + + Update Delay + + + + + + + + Membership Attribute + + + { + this.setState({ + membershipAttr: value + }); + }} + selected={membershipAttr} + options={attributes} + newSelectionPrefix="Add a membership attribute: " + placeholder="Type an attribute..." + /> + + + + + Entry Scope + + + + + + + + Exclude Entry Scope + + + + + + + + Container Scope + + + + + +
+ +
+
); } diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx index 4e3490b..560cd7f 100644 --- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx +++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx @@ -1,11 +1,160 @@ +import cockpit from "cockpit"; import React from "react"; -import { noop } from "patternfly-react"; +import { + noop, + FormGroup, + FormControl, + Row, + Col, + Form, + ControlLabel, + Checkbox +} from "patternfly-react"; import PropTypes from "prop-types"; +import { Typeahead } from "react-bootstrap-typeahead"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import { log_cmd } from "../tools.jsx"; import "../../css/ds.css"; class RetroChangelog extends React.Component { + componentWillMount(prevProps) { + this.getAttributes(); + this.updateFields(); + } + + componentDidUpdate(prevProps) { + if (this.props.rows !== prevProps.rows) { + this.updateFields(); + } + } + + constructor(props) { + super(props); + + this.state = { + isReplicated: false, + attribute: [], + directory: "", + maxAge: "", + excludeSuffix: "", + attributes: [] + }; + + this.updateFields = this.updateFields.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + this.handleCheckboxChange = this.handleCheckboxChange.bind(this); + } + + handleCheckboxChange(e) { + this.setState({ + [e.target.id]: e.target.checked + }); + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + updateFields() { + if (this.props.rows.length > 0) { + const pluginRow = this.props.rows.find( + row => row.cn[0] === "Retro Changelog Plugin" + ); + + this.setState({ + isReplicated: !( + pluginRow["isReplicated"] === undefined || + pluginRow["isReplicated"][0] == "FALSE" + ), + attribute: + pluginRow["nsslapd-attribute"] === undefined + ? [] + : [ + { + id: pluginRow["nsslapd-attribute"][0], + label: pluginRow["nsslapd-attribute"][0] + } + ], + directory: + pluginRow["nsslapd-changelogdir"] === undefined + ? "" + : pluginRow["nsslapd-changelogdir"][0], + maxAge: + pluginRow["nsslapd-changelogmaxage"] === undefined + ? "" + : pluginRow["nsslapd-changelogmaxage"][0], + excludeSuffix: + pluginRow["nsslapd-exclude-suffix"] === undefined + ? "" + : pluginRow["nsslapd-exclude-suffix"][0] + }); + } + } + + getAttributes() { + const attr_cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "schema", + "attributetypes", + "list" + ]; + log_cmd("getAttributes", "Get attrs", attr_cmd); + cockpit + .spawn(attr_cmd, { superuser: true, err: "message" }) + .done(content => { + const attrContent = JSON.parse(content); + let attrs = []; + for (let content of attrContent["items"]) { + attrs.push({ + id: content.name, + label: content.name + }); + } + this.setState({ + attributes: attrs + }); + }) + .fail(err => { + this.props.addNotification( + "error", + `Failed to get attributes - ${err}` + ); + }); + } + render() { + const { + isReplicated, + attribute, + directory, + maxAge, + excludeSuffix, + attributes + } = this.state; + + let specificPluginCMD = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "retro-changelog", + "set", + "--is-replicated", + isReplicated ? "TRUE" : "FALSE", + "--attribute", + attribute.length != 0 ? attribute[0].id : "delete", + "--directory", + directory || "delete", + "--max-age", + maxAge || "delete", + "--exclude-suffix", + excludeSuffix || "delete" + ]; + return (
+ > + + +
+ + + Attribute + + + { + this.setState({ + attribute: value + }); + }} + selected={attribute} + options={attributes} + newSelectionPrefix="Add an attribute: " + placeholder="Type an attribute..." + /> + + + + + Directory + + + + + + + + Max Age + + + + + + + + Exclude Suffix + + + + + + + Is Replicated + + + +
+ +
+
); } diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx index 3e4d820..f606a96 100644 --- a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx +++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx @@ -1,11 +1,178 @@ import React from "react"; -import { noop } from "patternfly-react"; +import { + noop, + FormGroup, + FormControl, + Row, + Col, + Form, + ControlLabel +} from "patternfly-react"; +import { Typeahead } from "react-bootstrap-typeahead"; import PropTypes from "prop-types"; import PluginBasicConfig from "./pluginBasicConfig.jsx"; import "../../css/ds.css"; class RootDNAccessControl extends React.Component { + componentWillMount(prevProps) { + this.updateFields(); + } + + componentDidUpdate(prevProps) { + if (this.props.rows !== prevProps.rows) { + this.updateFields(); + } + } + + constructor(props) { + super(props); + + this.state = { + allowHost: [], + denyHost: [], + allowIP: [], + denyIP: [], + openTime: "", + closeTime: "", + daysAllowed: "" + }; + + this.updateFields = this.updateFields.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + updateFields() { + let allowHostList = []; + let denyHostList = []; + let allowIPList = []; + let denyIPList = []; + + if (this.props.rows.length > 0) { + const pluginRow = this.props.rows.find( + row => row.cn[0] === "RootDN Access Control" + ); + this.setState({ + openTime: + pluginRow["rootdn-open-time"] === undefined + ? "" + : pluginRow["rootdn-open-time"][0], + closeTime: + pluginRow["rootdn-close-time"] === undefined + ? "" + : pluginRow["rootdn-close-time"][0], + daysAllowed: + pluginRow["rootdn-days-allowed"] === undefined + ? "" + : pluginRow["rootdn-days-allowed"][0] + }); + + if (pluginRow["rootdn-allow-host"] === undefined) { + this.setState({ allowHost: [] }); + } else { + for (let value of pluginRow["rootdn-allow-host"]) { + allowHostList = [ + ...allowHostList, + { id: value, label: value } + ]; + } + this.setState({ allowHost: allowHostList }); + } + if (pluginRow["rootdn-deny-host"] === undefined) { + this.setState({ denyHost: [] }); + } else { + for (let value of pluginRow["rootdn-deny-host"]) { + denyHostList = [ + ...denyHostList, + { id: value, label: value } + ]; + } + this.setState({ denyHost: denyHostList }); + } + if (pluginRow["rootdn-allow-ip"] === undefined) { + this.setState({ allowIP: [] }); + } else { + for (let value of pluginRow["rootdn-allow-ip"]) { + allowIPList = [...allowIPList, { id: value, label: value }]; + } + this.setState({ allowIP: allowIPList }); + } + if (pluginRow["rootdn-deny-ip"] === undefined) { + this.setState({ denyIP: [] }); + } else { + for (let value of pluginRow["rootdn-deny-ip"]) { + denyIPList = [...denyIPList, { id: value, label: value }]; + } + this.setState({ denyIP: denyIPList }); + } + } + } + render() { + const { + allowHost, + denyHost, + allowIP, + denyIP, + openTime, + closeTime, + daysAllowed + } = this.state; + + let specificPluginCMD = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "root-dn", + "set", + "--open-time", + openTime || "delete", + "--close-time", + closeTime || "delete", + "--days-allowed", + daysAllowed || "delete" + ]; + + // Delete attributes if the user set an empty value to the field + specificPluginCMD = [...specificPluginCMD, "--allow-host"]; + if (allowHost.length != 0) { + for (let value of allowHost) { + specificPluginCMD = [...specificPluginCMD, value.label]; + } + } else { + specificPluginCMD = [...specificPluginCMD, "delete"]; + } + specificPluginCMD = [...specificPluginCMD, "--deny-host"]; + if (denyHost.length != 0) { + for (let value of denyHost) { + specificPluginCMD = [...specificPluginCMD, value.label]; + } + } else { + specificPluginCMD = [...specificPluginCMD, "delete"]; + } + specificPluginCMD = [...specificPluginCMD, "--allow-ip"]; + if (allowIP.length != 0) { + for (let value of allowIP) { + specificPluginCMD = [...specificPluginCMD, value.label]; + } + } else { + specificPluginCMD = [...specificPluginCMD, "delete"]; + } + specificPluginCMD = [...specificPluginCMD, "--allow-host"]; + if (allowHost.length != 0) { + for (let value of allowHost) { + specificPluginCMD = [...specificPluginCMD, value.label]; + } + } else { + specificPluginCMD = [...specificPluginCMD, "delete"]; + } + return (
+ > + + +
+ + + Allow Host + + + { + this.setState({ + allowHost: value + }); + }} + selected={allowHost} + options={[]} + newSelectionPrefix="Add a host to allow: " + placeholder="Type a hostname (wild cards are allowed)..." + /> + + + + + Deny Host + + + { + this.setState({ + denyHost: value + }); + }} + selected={denyHost} + options={[]} + newSelectionPrefix="Add a host to deny: " + placeholder="Type a hostname (wild cards are allowed)..." + /> + + + + + Allow IP address + + + { + this.setState({ + allowIP: value + }); + }} + selected={allowIP} + options={[]} + newSelectionPrefix="Add an IP address to allow: " + placeholder="Type an IP address (wild cards are allowed)..." + /> + + + + + Deny IP address + + + { + this.setState({ + denyIP: value + }); + }} + selected={denyIP} + options={[]} + newSelectionPrefix="Add an IP address to deny: " + placeholder="Type an IP address (wild cards are allowed)..." + /> + + + + + Open Time + + + + + + + + Close Time + + + + + + + + Days Allowed + + + + + +
+ +
+
); } diff --git a/src/cockpit/389-console/src/lib/plugins/winsync.jsx b/src/cockpit/389-console/src/lib/plugins/winsync.jsx new file mode 100644 index 0000000..37b38eb --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/winsync.jsx @@ -0,0 +1,247 @@ +import React from "react"; +import { + Row, + Col, + Form, + noop, + FormGroup, + Checkbox, + ControlLabel +} from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class WinSync extends React.Component { + componentWillMount(prevProps) { + this.updateFields(); + } + + componentDidUpdate(prevProps) { + if (this.props.rows !== prevProps.rows) { + this.updateFields(); + } + } + + constructor(props) { + super(props); + + this.handleCheckboxChange = this.handleCheckboxChange.bind(this); + this.updateFields = this.updateFields.bind(this); + + this.state = { + posixWinsyncCreateMemberOfTask: false, + posixWinsyncLowerCaseUID: false, + posixWinsyncMapMemberUID: false, + posixWinsyncMapNestedGrouping: false, + posixWinsyncMsSFUSchema: false + }; + } + + handleCheckboxChange(e) { + this.setState({ + [e.target.id]: e.target.checked + }); + } + + updateFields() { + if (this.props.rows.length > 0) { + const pluginRow = this.props.rows.find( + row => row.cn[0] === "Posix Winsync API" + ); + + this.setState({ + posixWinsyncCreateMemberOfTask: !( + pluginRow["posixwinsynccreatememberoftask"] === undefined || + pluginRow["posixwinsynccreatememberoftask"][0] == "false" + ), + posixWinsyncLowerCaseUID: !( + pluginRow["posixwinsynclowercaseuid"] === undefined || + pluginRow["posixwinsynclowercaseuid"][0] == "false" + ), + posixWinsyncMapMemberUID: !( + pluginRow["posixwinsyncmapmemberuid"] === undefined || + pluginRow["posixwinsyncmapmemberuid"][0] == "false" + ), + posixWinsyncMapNestedGrouping: !( + pluginRow["posixwinsyncmapnestedgrouping"] === undefined || + pluginRow["posixwinsyncmapnestedgrouping"][0] == "false" + ), + posixWinsyncMsSFUSchema: !( + pluginRow["posixwinsyncmssfuschema"] === undefined || + pluginRow["posixwinsyncmssfuschema"][0] == "false" + ) + }); + } + } + + render() { + const { + posixWinsyncCreateMemberOfTask, + posixWinsyncLowerCaseUID, + posixWinsyncMapMemberUID, + posixWinsyncMapNestedGrouping, + posixWinsyncMsSFUSchema + } = this.state; + + let specificPluginCMD = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "posix-winsync", + "set", + "--create-memberof-task", + posixWinsyncCreateMemberOfTask ? "true" : "false", + "--lower-case-uid", + posixWinsyncLowerCaseUID ? "true" : "false", + "--map-member-uid", + posixWinsyncMapMemberUID ? "true" : "false", + "--map-nested-grouping", + posixWinsyncMapNestedGrouping ? "true" : "false", + "--ms-sfu-schema", + posixWinsyncMsSFUSchema ? "true" : "false" + ]; + return ( +
+ + + +
+ + + Create MemberOf Task + + + + + + + + Lower Case UID + + + + + + + + Map Member UID + + + + + + + + Map Nested Grouping + + + + + + + + Microsoft System Services for Unix 3.0 + (msSFU30) schema + + + + + +
+ +
+
+
+ ); + } +} + +WinSync.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +WinSync.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default WinSync; diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx index ac9f8b7..19801d0 100644 --- a/src/cockpit/389-console/src/plugins.jsx +++ b/src/cockpit/389-console/src/plugins.jsx @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; import { log_cmd } from "./lib/tools.jsx"; import { Col, Row, Tab, Nav, NavItem, Spinner } from "patternfly-react"; import PluginEditModal from "./lib/plugins/pluginModal.jsx"; -import PluginTable from "./lib/plugins/pluginTable.jsx"; +import { PluginTable } from "./lib/plugins/pluginTables.jsx"; import AccountPolicy from "./lib/plugins/accountPolicy.jsx"; import AttributeUniqueness from "./lib/plugins/attributeUniqueness.jsx"; import AutoMembership from "./lib/plugins/autoMembership.jsx"; @@ -17,6 +17,7 @@ import ReferentialIntegrity from "./lib/plugins/referentialIntegrity.jsx"; import RetroChangelog from "./lib/plugins/retroChangelog.jsx"; import RootDNAccessControl from "./lib/plugins/rootDNAccessControl.jsx"; import USN from "./lib/plugins/usn.jsx"; +import WinSync from "./lib/plugins/winsync.jsx"; import { NotificationController } from "./lib/notifications.jsx"; import "./css/ds.css"; @@ -339,10 +340,10 @@ export class Plugins extends React.Component { /> ) }, - autoMembership: { - name: "Auto Membership", + linkedAttributes: { + name: "Linked Attributes", component: ( - ) }, - linkedAttributes: { - name: "Linked Attributes", + autoMembership: { + name: "Auto Membership", component: ( - ) }, + winsync: { + name: "Posix Winsync", + component: ( + + ) + }, referentialIntegrity: { name: "Referential Integrity", component: ( diff --git a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py index e585d2a..4cdf531 100644 --- a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py +++ b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py @@ -11,7 +11,7 @@ from lib389.plugins import AccountPolicyPlugin, AccountPolicyConfigs, AccountPol from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add arg_to_attr = { - 'config_entry': 'nsslapd-pluginConfigArea' + 'config_entry': 'nsslapd_pluginconfigarea' } arg_to_attr_config = { @@ -23,6 +23,7 @@ arg_to_attr_config = { 'state_attr': 'stateattrname' } + def accountpolicy_edit(inst, basedn, log, args): log = log.getChild('accountpolicy_edit') plugin = AccountPolicyPlugin(inst) diff --git a/src/lib389/lib389/cli_conf/plugins/attruniq.py b/src/lib389/lib389/cli_conf/plugins/attruniq.py index a26154c..fee4137 100644 --- a/src/lib389/lib389/cli_conf/plugins/attruniq.py +++ b/src/lib389/lib389/cli_conf/plugins/attruniq.py @@ -14,6 +14,7 @@ from lib389.cli_conf import (add_generic_plugin_parsers, generic_object_edit, ge from lib389._constants import DN_PLUGIN arg_to_attr = { + 'enabled': 'nsslapd-pluginenabled', 'attr_name': 'uniqueness-attribute-name', 'subtree': 'uniqueness-subtrees', 'across_all_subtrees': 'uniqueness-across-all-subtrees', @@ -80,6 +81,8 @@ def attruniq_del(inst, basedn, log, args): def _add_parser_args(parser): parser.add_argument('NAME', help='Sets the name of the plug-in configuration record. (cn) You can use any string, ' 'but "attribute_name Attribute Uniqueness" is recommended.') + parser.add_argument('--enabled', choices=['on', 'off'], + help='Identifies whether or not the config is enabled.') parser.add_argument('--attr-name', nargs='+', help='Sets the name of the attribute whose values must be unique. ' 'This attribute is multi-valued. (uniqueness-attribute-name)') diff --git a/src/lib389/lib389/cli_conf/plugins/posix_winsync.py b/src/lib389/lib389/cli_conf/plugins/posix_winsync.py index 5c5d6cc..e2856af 100644 --- a/src/lib389/lib389/cli_conf/plugins/posix_winsync.py +++ b/src/lib389/lib389/cli_conf/plugins/posix_winsync.py @@ -26,7 +26,7 @@ def winsync_edit(inst, basedn, log, args): def _add_parser_args(parser): parser.add_argument('--create-memberof-task', choices=['true', 'false'], type=str.lower, - help=' sets whether to run the memberOf fix-up task immediately after a sync run in order ' + help='Sets whether to run the memberOf fix-up task immediately after a sync run in order ' 'to update group memberships for synced users (posixWinsyncCreateMemberOfTask)') parser.add_argument('--lower-case-uid', choices=['true', 'false'], type=str.lower, help='Sets whether to store (and, if necessary, convert) the UID value in the memberUID ' diff --git a/src/lib389/lib389/cli_conf/plugins/referint.py b/src/lib389/lib389/cli_conf/plugins/referint.py index 9482a14..c0481cd 100644 --- a/src/lib389/lib389/cli_conf/plugins/referint.py +++ b/src/lib389/lib389/cli_conf/plugins/referint.py @@ -36,7 +36,7 @@ def _add_parser_args(parser): parser.add_argument('--exclude-entry-scope', help='Defines the subtree in which the plug-in ignores any operations ' 'for deleting or renaming a user (nsslapd-pluginExcludeEntryScope)') - parser.add_argument('--container_scope', + parser.add_argument('--container-scope', help='Specifies which branch the plug-in searches for the groups to which the user belongs. ' 'It only updates groups that are under the specified container branch, ' 'and leaves all other groups not updated (nsslapd-pluginContainerScope)') diff --git a/src/lib389/lib389/cli_conf/plugins/retrochangelog.py b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py index e677310..2fcaf20 100644 --- a/src/lib389/lib389/cli_conf/plugins/retrochangelog.py +++ b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py @@ -25,7 +25,7 @@ def retrochangelog_edit(inst, basedn, log, args): def _add_parser_args(parser): - parser.add_argument('--is-replicated', choices=['true', 'false'], type=str.lower, + parser.add_argument('--is-replicated', choices=['TRUE', 'FALSE'], type=str.upper, help='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)') parser.add_argument('--attribute', diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py index 9477b3c..590a1ea 100644 --- a/src/lib389/lib389/cli_ctl/dbtasks.py +++ b/src/lib389/lib389/cli_ctl/dbtasks.py @@ -125,7 +125,7 @@ def create_parser(subcommands): backups_parser.add_argument('--delete', nargs=1, help="Delete backup directory") backups_parser.set_defaults(func=dbtasks_backups) - ldifs_parser = subcommands.add_parser('ldifs', help="List all the DLIF files located in the server's LDIF directory") + ldifs_parser = subcommands.add_parser('ldifs', help="List all the LDIF files located in the server's LDIF directory") ldifs_parser.add_argument('--delete', nargs=1, help="Delete LDIF file") ldifs_parser.set_defaults(func=dbtasks_ldifs) diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py index bd66fa6..e7a1f95 100644 --- a/src/lib389/lib389/plugins.py +++ b/src/lib389/lib389/plugins.py @@ -184,7 +184,7 @@ class AttributeUniquenessPlugins(DSLdapObjects): """ def __init__(self, instance, basedn="cn=plugins,cn=config"): - super(DSLdapObjects, self).__init__(instance) + super(DSLdapObjects, self).__init__(instance.verbose) self._instance = instance self._objectclasses = ['top', 'nsslapdplugin', 'extensibleObject'] self._filterattrs = ['cn', 'nsslapd-pluginPath']