From 00c3b7a94545d627bf8a334c1ddf206282078024 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Dec 18 2018 19:15:43 +0000 Subject: Issue 50041 - Add basic plugin UI/CLI wrappers Description: Add plugin UI tabs with basic data editing, enable/disable and dynamic plugin switch. Fix loading. Fix small CSS issues. React. Add customCollapse element. Make customToolbar customizable. Rework data flow in the component. CLI. Put all the plugins to 'plugin' parser. Add wrappers for all main plugins. Clean up plugin args (remove get_dn, generic enable and disable) https://pagure.io/389-ds-base/issue/50041 Reviewed by: mreynolds, mhonek (Thanks!) --- diff --git a/src/cockpit/389-console/package.json b/src/cockpit/389-console/package.json index 6f155dd..054a373 100644 --- a/src/cockpit/389-console/package.json +++ b/src/cockpit/389-console/package.json @@ -42,10 +42,13 @@ "webpack-cli": "^3.1.0" }, "dependencies": { - "@babel/polyfill": "^7.0.0", - "patternfly-react": "^2.22.1", - "node-sass": "^4.9.0", - "react": "^16.4.2", - "react-dom": "^16.4.2" + "patternfly": "3.58.0", + "patternfly-react": "2.24.5", + "react-bootstrap": "0.32.4", + "react": "16.6.1", + "react-dom": "16.6.1", + "prop-types": "15.6.2", + "table-resolver": "4.1.1", + "recompose": "0.30.0" } } diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css index e75853b..d38ff55 100644 --- a/src/cockpit/389-console/src/css/ds.css +++ b/src/cockpit/389-console/src/css/ds.css @@ -1179,14 +1179,17 @@ option { } .ds-accordion { + box-shadow: unset; + background-image: unset; margin-top: 20px; color: #228bc0 !important; - background-color: white; + background-color: transparent !important; border: 0; position: relative; overflow: hidden; width: 700px; text-align: left; + font: inherit; } .ds-accordion:after { @@ -1201,7 +1204,9 @@ option { text-align: left; } +.ds-accordion:hover, .ds-accordion:focus { + background-color: transparent !important; outline: none !important; border: 0 !important; -moz-outline: none !important; @@ -1209,6 +1214,10 @@ option { box-shadow: 0 !important; } +.ds-accordion:active { + background-color: transparent !important; +} + .ds-accordion-panel { padding-left: 25px; margin-top: 10px; @@ -1431,10 +1440,57 @@ option { margin-right: 10px; } -.content-view-pf-pagination > div > span { +.content-view-pf-pagination > div > span:last-child { margin-left: 8px; + position: relative; + top: -8px; } tbody:nth-child(even) { background: #f1f1f1; } + +.ds-toolbar { + bottom: 10px; +} + +.ds-plugin-spinner { + margin-top: 20px; + margin-bottom: 10px; + margin-left: 80px; +} + +@media screen and (max-width: 1300px) { + .ds-plugin-spinner { + margin-left: 0px; + } +} + +/* Link */ +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #228bc0; +} + +/* Nav stacked (vertical) bar */ +.nav-pills > li > a { + padding: 3px 15px; + font-size: 15px; + border-radius: 3px; +} + +.ds-plugin-tab-header { + position: relative; + top: -16px; +} + +@media screen and (max-width: 770px) { + .ds-plugin-tab-header { + margin-top: 0px; + margin-bottom: 0px; + } +} + +.control-label { + text-align: left !important; +} diff --git a/src/cockpit/389-console/src/ds.js b/src/cockpit/389-console/src/ds.js index 3fed897..b5f091a 100644 --- a/src/cockpit/389-console/src/ds.js +++ b/src/cockpit/389-console/src/ds.js @@ -223,9 +223,6 @@ function get_insts() { for (i = 0; i < insts.length; i++) { $("#select-server").append(''); $("#select-server select").val(insts[i]); - // We have to dispatch an event for the React components rerender - server_select_elem = document.getElementById('select-server'); - server_select_elem.dispatchEvent(new Event('change')); } // Handle changing instance here @@ -244,7 +241,10 @@ function get_insts() { server_id = insts[0]; server_inst = insts[0].replace("slapd-", ""); check_inst_alive(); - load_config(); + // We have to dispatch an event for the React components rerender + // It should also trigger the listener defined before + server_select_elem = document.getElementById('select-server'); + server_select_elem.dispatchEvent(new Event('change')); } }).fail(function(error){ set_no_insts(); diff --git a/src/cockpit/389-console/src/index.es6 b/src/cockpit/389-console/src/index.es6 index 057c771..bc74f51 100644 --- a/src/cockpit/389-console/src/index.es6 +++ b/src/cockpit/389-console/src/index.es6 @@ -4,12 +4,28 @@ import { Plugins } from "./plugins.jsx"; var serverIdElem; -function renderReactDOM() { - serverIdElem = document.getElementById("select-server"); - const element = ( - +function renderReactDOM(clear) { + // We should clear the React properties on the instance removal + if (clear) { + serverIdElem = ""; + } else { + serverIdElem = document + .getElementById("select-server") + .value.replace("slapd-", ""); + } + ReactDOM.render( + , + document.getElementById("plugins") ); - ReactDOM.render(element, document.getElementById("plugins")); +} + +// We have to create the wrappers because this is no simple way +// to pass arguments to the listener's callback function +function renderReactWrapper() { + renderReactDOM(false); +} +function renderClearWrapper() { + renderReactDOM(true); } document.addEventListener("DOMContentLoaded", function() { @@ -18,13 +34,16 @@ document.addEventListener("DOMContentLoaded", function() { if (serverIdElem != null && serverIdElem.value.startsWith("slapd-")) { document .getElementById("select-server") - .addEventListener("change", renderReactDOM); + .addEventListener("change", renderReactWrapper); document .getElementById("start-server-btn") - .addEventListener("click", renderReactDOM); + .addEventListener("click", renderReactWrapper); document .getElementById("restart-server-btn") - .addEventListener("click", renderReactDOM); + .addEventListener("click", renderReactWrapper); + document + .getElementById("remove-server-btn") + .addEventListener("click", renderClearWrapper); renderReactDOM(); clearInterval(init_config); } diff --git a/src/cockpit/389-console/src/index.html b/src/cockpit/389-console/src/index.html index 13c3462..6b3f213 100644 --- a/src/cockpit/389-console/src/index.html +++ b/src/cockpit/389-console/src/index.html @@ -30,8 +30,8 @@ - + diff --git a/src/cockpit/389-console/src/lib/customCollapse.jsx b/src/cockpit/389-console/src/lib/customCollapse.jsx new file mode 100644 index 0000000..71ea0f5 --- /dev/null +++ b/src/cockpit/389-console/src/lib/customCollapse.jsx @@ -0,0 +1,56 @@ +import React from "react"; +import PropTypes from "prop-types"; +import classNames from "classnames"; +import { Icon, Button } from "patternfly-react"; + +class CustomCollapse extends React.Component { + constructor(props) { + super(props); + + this.state = { + open: false + }; + } + + render() { + const { children, className, textOpened, textClosed } = this.props; + const { open } = this.state; + + return ( +
+ +
{open && children}
+
+ ); + } +} + +CustomCollapse.propTypes = { + children: PropTypes.any.isRequired, + /** Top-level custom class */ + className: PropTypes.string, + /** Text for the link in opened state */ + textOpened: PropTypes.string, + /** Text for the link in closed state */ + textClosed: PropTypes.string +}; + +CustomCollapse.defaultProps = { + className: "", + textClosed: "Show Advanced Settings", + textOpened: "Hide Advanced Settings" +}; + +export default CustomCollapse; diff --git a/src/cockpit/389-console/src/lib/customTableToolbar.jsx b/src/cockpit/389-console/src/lib/customTableToolbar.jsx index a15f018..d6b6e2e 100644 --- a/src/cockpit/389-console/src/lib/customTableToolbar.jsx +++ b/src/cockpit/389-console/src/lib/customTableToolbar.jsx @@ -1,27 +1,38 @@ import React from "react"; import PropTypes from "prop-types"; import classNames from "classnames"; -import { noop, Spinner, FormControl } from "patternfly-react"; +import { + noop, + Spinner, + Form, + ControlLabel, + FormControl +} from "patternfly-react"; class CustomTableToolbar extends React.Component { render() { const { + children, className, placeholder, modelToSearch, searchFilterValue, handleValueChange, + disableLoadingSpinner, loading } = this.props; - const classes = classNames("form-group toolbar-pf-find", className); + const classes = classNames( + "form-group toolbar-pf-find ds-toolbar", + className + ); return (
-
); } } CustomTableToolbar.propTypes = { + children: PropTypes.any, className: PropTypes.string, placeholder: PropTypes.string, modelToSearch: PropTypes.string, searchFilterValue: PropTypes.string, handleValueChange: PropTypes.func, - loading: PropTypes.bool + loading: PropTypes.bool, + disableLoadingSpinner: PropTypes.bool }; CustomTableToolbar.defaultProps = { @@ -55,7 +71,8 @@ CustomTableToolbar.defaultProps = { modelToSearch: "", searchFilterValue: "", handleValueChange: noop, - loading: false + loading: false, + disableLoadingSpinner: false }; export default CustomTableToolbar; diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx new file mode 100644 index 0000000..90ff501 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class AccountPolicy extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +AccountPolicy.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +AccountPolicy.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default AccountPolicy; diff --git a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx new file mode 100644 index 0000000..3d708de --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class AttributeUniqueness extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +AttributeUniqueness.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +AttributeUniqueness.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default AttributeUniqueness; diff --git a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx new file mode 100644 index 0000000..e3291fa --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class AutoMembership extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +AutoMembership.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +AutoMembership.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default AutoMembership; diff --git a/src/cockpit/389-console/src/lib/plugins/dna.jsx b/src/cockpit/389-console/src/lib/plugins/dna.jsx new file mode 100644 index 0000000..caf62bb --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/dna.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class DNA extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +DNA.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +DNA.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default DNA; diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx new file mode 100644 index 0000000..5982fcc --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class LinkedAttributes extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +LinkedAttributes.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +LinkedAttributes.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default LinkedAttributes; diff --git a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx new file mode 100644 index 0000000..4bd5657 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class ManagedEntries extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +ManagedEntries.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +ManagedEntries.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default ManagedEntries; diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx new file mode 100644 index 0000000..33a2091 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class MemberOf extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +MemberOf.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +MemberOf.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default MemberOf; diff --git a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx new file mode 100644 index 0000000..dfa08c7 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class PassthroughAuthentication extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +PassthroughAuthentication.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +PassthroughAuthentication.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default PassthroughAuthentication; diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx new file mode 100644 index 0000000..a61356e --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx @@ -0,0 +1,334 @@ +import cockpit from "cockpit"; +import React from "react"; +import { + Button, + Row, + Col, + Form, + Switch, + noop, + FormGroup, + FormControl, + ControlLabel +} from "patternfly-react"; +import PropTypes from "prop-types"; +import CustomCollapse from "../customCollapse.jsx"; +import { log_cmd } from "../tools.jsx"; +import "../../css/ds.css"; + +class PluginBasicConfig extends React.Component { + componentWillMount(prevProps) { + this.updateFields(); + } + + componentDidUpdate(prevProps) { + if (this.props.rows !== prevProps.rows) { + this.updateFields(); + } + } + + constructor(props) { + super(props); + + this.updateFields = this.updateFields.bind(this); + this.updateSwitch = this.updateSwitch.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + + this.state = { + disableSwitch: false, + currentPluginEnabled: true, + currentPluginType: "", + currentPluginPath: "", + currentPluginInitfunc: "", + currentPluginId: "", + currentPluginVendor: "", + currentPluginVersion: "", + currentPluginDescription: "" + }; + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + handleSwitchChange(value) { + const { + pluginName, + cmdName, + serverId, + pluginListHandler, + addNotification, + toggleLoadingHandler + } = this.props; + const new_status = this.state.currentPluginEnabled + ? "disable" + : "enable"; + const cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + serverId + ".socket", + "plugin", + cmdName, + new_status + ]; + + toggleLoadingHandler(); + this.setState({ disableSwitch: true }); + log_cmd( + "handleSwitchChange", + "Switch plugin states from the plugin tab", + cmd + ); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + console.info("savePlugin", "Result", content); + pluginListHandler(); + addNotification( + "success", + `${pluginName} plugin was successfully ${new_status}d` + ); + toggleLoadingHandler(); + }) + .fail(err => { + addNotification( + "error", + `Error during ${pluginName} plugin modification - ${err}` + ); + toggleLoadingHandler(); + this.setState({ disableSwitch: false }); + }); + } + + updateFields() { + if (this.props.rows.length > 0) { + const pluginRow = this.props.rows.find( + row => row.cn[0] === this.props.cn + ); + + this.setState({ + currentPluginType: pluginRow["nsslapd-pluginType"][0], + currentPluginPath: pluginRow["nsslapd-pluginPath"][0], + currentPluginInitfunc: pluginRow["nsslapd-pluginInitfunc"][0], + currentPluginId: pluginRow["nsslapd-pluginId"][0], + currentPluginVendor: pluginRow["nsslapd-pluginVendor"][0], + currentPluginVersion: pluginRow["nsslapd-pluginVersion"][0], + currentPluginDescription: + pluginRow["nsslapd-pluginDescription"][0] + }); + } + this.updateSwitch(); + } + + updateSwitch() { + if (this.props.rows.length > 0) { + const pluginRow = this.props.rows.find( + row => row.cn[0] === this.props.cn + ); + + var pluginEnabled; + if (pluginRow["nsslapd-pluginEnabled"][0] === "on") { + pluginEnabled = true; + } else if (pluginRow["nsslapd-pluginEnabled"][0] === "off") { + pluginEnabled = false; + } else { + console.error( + "openPluginModal failed", + "wrong nsslapd-pluginenabled attribute value", + pluginRow["nsslapd-pluginEnabled"][0] + ); + } + + this.setState({ + currentPluginEnabled: pluginEnabled, + disableSwitch: false + }); + } + } + + render() { + const { + currentPluginEnabled, + currentPluginType, + currentPluginPath, + currentPluginInitfunc, + currentPluginId, + currentPluginVendor, + currentPluginVersion, + currentPluginDescription, + disableSwitch + } = this.state; + + const modalFieldsCol1 = { + currentPluginType: this.state.currentPluginType, + currentPluginPath: this.state.currentPluginPath, + currentPluginInitfunc: this.state.currentPluginInitfunc, + currentPluginId: this.state.currentPluginId + }; + const modalFieldsCol2 = { + currentPluginVendor: this.state.currentPluginVendor, + currentPluginVersion: this.state.currentPluginVersion, + currentPluginDescription: this.state.currentPluginDescription + }; + return ( +
+
+ + +

+ + {this.props.pluginName} + +

+ + + + + Status + + + this.handleSwitchChange( + currentPluginEnabled + ) + } + animate={false} + disabled={disableSwitch} + /> + + +
+
+ {this.props.children} + + + +
+ {Object.entries(modalFieldsCol1).map( + ([id, value]) => ( + + + Plugin{" "} + {id.replace( + "currentPlugin", + "" + )} + + + + + + ) + )} +
+ + +
+ {Object.entries(modalFieldsCol2).map( + ([id, value]) => ( + + + Plugin{" "} + {id.replace( + "currentPlugin", + "" + )} + + + + + + ) + )} +
+ +
+ + + + + +
+
+ ); + } +} + +PluginBasicConfig.propTypes = { + children: PropTypes.any.isRequired, + rows: PropTypes.array, + serverId: PropTypes.string, + cn: PropTypes.string, + pluginName: PropTypes.string, + cmdName: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +PluginBasicConfig.defaultProps = { + rows: [], + serverId: "", + cn: "", + pluginName: "", + cmdName: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default PluginBasicConfig; diff --git a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx index aa8e090..2572475 100644 --- a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx +++ b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx @@ -1,4 +1,3 @@ -import cockpit from "cockpit"; import React from "react"; import { Modal, @@ -13,77 +12,9 @@ import { Col } from "patternfly-react"; import PropTypes from "prop-types"; -import { log_cmd } from "../tools.jsx"; import "../../css/ds.css"; -var cmd; - class PluginEditModal extends React.Component { - constructor(props) { - super(props); - this.savePlugin = this.savePlugin.bind(this); - } - - savePlugin() { - const { - currentPluginName, - currentPluginType, - currentPluginEnabled, - currentPluginPath, - currentPluginInitfunc, - currentPluginId, - currentPluginVendor, - currentPluginVersion, - currentPluginDescription, - pluginListHandler, - closeHandler, - addNotification - } = this.props; - cmd = [ - "dsconf", - "-j", - "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", - "plugin", - "edit", - currentPluginName, - "--enabled", - currentPluginEnabled ? "on" : "off", - "--type", - currentPluginType, - "--path", - currentPluginPath, - "--initfunc", - currentPluginInitfunc, - "--id", - currentPluginId, - "--vendor", - currentPluginVendor, - "--version", - currentPluginVersion, - "--description", - currentPluginDescription - ]; - log_cmd("savePlugin", "Edit the plugin from the modal form", cmd); - cockpit - .spawn(cmd, { superuser: true, err: "message" }) - .done(content => { - console.info("savePlugin", "Result", content); - addNotification( - "success", - `Plugin ${currentPluginName} was successfully modified` - ); - pluginListHandler(); - closeHandler(); - }) - .fail(err => { - addNotification( - "error", - `Error during plugin ${currentPluginName} modification - ${err}` - ); - closeHandler(); - }); - } - render() { const modalFields = { currentPluginType: this.props.currentPluginType, @@ -99,8 +30,16 @@ class PluginEditModal extends React.Component { closeHandler, currentPluginName, currentPluginEnabled, + currentPluginType, + currentPluginPath, + currentPluginInitfunc, + currentPluginId, + currentPluginVendor, + currentPluginVersion, + currentPluginDescription, handleChange, - handleSwitchChange + handleSwitchChange, + savePluginHandler } = this.props; return ( @@ -132,7 +71,7 @@ class PluginEditModal extends React.Component { handleSwitchChange( @@ -171,7 +110,20 @@ class PluginEditModal extends React.Component { > Cancel - @@ -193,9 +145,8 @@ PluginEditModal.propTypes = { currentPluginVendor: PropTypes.string, currentPluginVersion: PropTypes.string, currentPluginDescription: PropTypes.string, - serverId: PropTypes.string, closeHandler: PropTypes.func, - pluginListHandler: PropTypes.func, + savePluginHandler: PropTypes.func, showModal: PropTypes.bool }; @@ -211,9 +162,8 @@ PluginEditModal.defaultProps = { currentPluginVendor: "", currentPluginVersion: "", currentPluginDescription: "", - serverId: "", closeHandler: noop, - pluginListHandler: noop, + savePluginHandler: noop, showModal: false }; diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx b/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx index 0b67eeb..ca69207 100644 --- a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx +++ b/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx @@ -3,6 +3,7 @@ import { PaginationRow, paginate, Table, + Button, noop, actionHeaderCellFormatter, customHeaderFormattersDefinition, @@ -54,8 +55,6 @@ class PluginTable extends React.Component { this.customHeaderFormatters = customHeaderFormattersDefinition; this.handleSearchValueChange = this.handleSearchValueChange.bind(this); - this.hideDropdown = this.hideDropdown.bind(this); - this.toggleDropdownShown = this.toggleDropdownShown.bind(this); this.totalPages = this.totalPages.bind(this); this.onPageInput = this.onPageInput.bind(this); this.onSubmit = this.onSubmit.bind(this); @@ -69,7 +68,6 @@ class PluginTable extends React.Component { this.filteredSearchedRows = this.filteredSearchedRows.bind(this); this.state = { - dropdownShown: false, searchFilterValue: "", fieldsToSearch: ["cn", "nsslapd-pluginType"], // Sort the first column in an ascending way by default. @@ -161,8 +159,8 @@ class PluginTable extends React.Component { formatters: [ (value, { rowData }) => { return [ - - + + ]; } ] @@ -195,18 +193,6 @@ class PluginTable extends React.Component { this.setState({ searchFilterValue: event.target.value }); } - hideDropdown() { - const { onExit } = this.props; - this.setState({ dropdownShown: false }); - onExit(); - } - - toggleDropdownShown() { - this.setState(prevState => ({ - dropdownShown: !prevState.dropdownShown - })); - } - totalPages() { const { rows } = this.props; const { perPage } = this.state.pagination; @@ -309,7 +295,7 @@ class PluginTable extends React.Component { modelToSearch="Plugins" searchFilterValue={this.state.searchFilterValue} handleValueChange={this.handleSearchValueChange} - loading={this.props.loading} + disableLoadingSpinner /> ({ + role: "row" + })} /> + + + ); + } +} + +ReferentialIntegrity.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +ReferentialIntegrity.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default ReferentialIntegrity; diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx new file mode 100644 index 0000000..51d7bb4 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class RetroChangelog extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +RetroChangelog.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +RetroChangelog.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default RetroChangelog; diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx new file mode 100644 index 0000000..27c1d37 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class RootDNAccessControl extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +RootDNAccessControl.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +RootDNAccessControl.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default RootDNAccessControl; diff --git a/src/cockpit/389-console/src/lib/plugins/usn.jsx b/src/cockpit/389-console/src/lib/plugins/usn.jsx new file mode 100644 index 0000000..bb4d321 --- /dev/null +++ b/src/cockpit/389-console/src/lib/plugins/usn.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import { noop } from "patternfly-react"; +import PropTypes from "prop-types"; +import PluginBasicConfig from "./pluginBasicConfig.jsx"; +import "../../css/ds.css"; + +class USN extends React.Component { + render() { + return ( +
+ +
+ ); + } +} + +USN.propTypes = { + rows: PropTypes.array, + serverId: PropTypes.string, + savePluginHandler: PropTypes.func, + pluginListHandler: PropTypes.func, + addNotification: PropTypes.func, + toggleLoadingHandler: PropTypes.func +}; + +USN.defaultProps = { + rows: [], + serverId: "", + savePluginHandler: noop, + pluginListHandler: noop, + addNotification: noop, + toggleLoadingHandler: noop +}; + +export default USN; diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx index ffb67a5..7afb619 100644 --- a/src/cockpit/389-console/src/plugins.jsx +++ b/src/cockpit/389-console/src/plugins.jsx @@ -2,8 +2,21 @@ import cockpit from "cockpit"; import React from "react"; 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 AccountPolicy from "./lib/plugins/accountPolicy.jsx"; +import AttributeUniqueness from "./lib/plugins/attributeUniqueness.jsx"; +import AutoMembership from "./lib/plugins/autoMembership.jsx"; +import DNA from "./lib/plugins/dna.jsx"; +import LinkedAttributes from "./lib/plugins/linkedAttributes.jsx"; +import ManagedEntries from "./lib/plugins/managedEntries.jsx"; +import MemberOf from "./lib/plugins/memberOf.jsx"; +import PassthroughAuthentication from "./lib/plugins/passthroughAuthentication.jsx"; +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 NotificationController from "./lib/notifications.jsx"; import "./css/ds.css"; @@ -12,6 +25,12 @@ var cmd; export class Plugins extends React.Component { componentWillMount() { this.pluginList(); + this.setState(prevState => ({ + pluginTabs: { + ...prevState.pluginTabs, + basicConfig: true + } + })); } componentDidUpdate(prevProps) { @@ -30,13 +49,15 @@ export class Plugins extends React.Component { this.pluginList = this.pluginList.bind(this); this.removeNotification = this.removeNotification.bind(this); this.addNotification = this.addNotification.bind(this); + this.onChangeTab = this.onChangeTab.bind(this); + this.savePlugin = this.savePlugin.bind(this); + this.toggleLoading = this.toggleLoading.bind(this); this.state = { notifications: [], loading: false, showPluginModal: false, - // Sort the first column in an ascending way by default. - // rows and row selection state + currentPluginTab: "", rows: [], // Plugin attributes @@ -52,6 +73,10 @@ export class Plugins extends React.Component { }; } + onChangeTab(event) { + this.setState({ currentPluginTab: event.target.value }); + } + addNotification(type, message, timerdelay, persistent) { this.setState(prevState => ({ notifications: [ @@ -93,6 +118,12 @@ export class Plugins extends React.Component { }); } + toggleLoading() { + this.setState(prevState => ({ + loading: !prevState.loading + })); + } + openPluginModal(rowData) { var pluginEnabled; if (rowData["nsslapd-pluginEnabled"][0] === "on") { @@ -121,9 +152,6 @@ export class Plugins extends React.Component { } pluginList() { - this.setState({ - loading: true - }); cmd = [ "dsconf", "-j", @@ -131,36 +159,294 @@ export class Plugins extends React.Component { "plugin", "list" ]; + this.toggleLoading(); log_cmd("pluginList", "Get plugins for table rows", cmd); cockpit .spawn(cmd, { superuser: true, err: "message" }) .done(content => { var myObject = JSON.parse(content); this.setState({ - rows: myObject.items, - loading: false + rows: myObject.items }); + this.toggleLoading(); }) .fail(err => { if (err != 0) { console.error("pluginList failed", err); } - this.setState({ - loading: false - }); + this.toggleLoading(); + }); + } + + savePlugin(data) { + cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "plugin", + "edit", + data.name, + "--type", + data.type, + "--path", + data.path, + "--initfunc", + data.initfunc, + "--id", + data.id, + "--vendor", + data.vendor, + "--version", + data.version, + "--description", + data.description + ]; + + if ("enabled" in data) { + cmd = [...cmd, "--enabled", data.enabled ? "on" : "off"]; + } + + this.toggleLoading(); + log_cmd("savePlugin", "Edit the plugin from the modal form", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + console.info("savePlugin", "Result", content); + this.addNotification( + "success", + `Plugin ${data.name} was successfully modified` + ); + this.pluginList(); + this.closePluginModal(); + this.toggleLoading(); + }) + .fail(err => { + this.addNotification( + "error", + `Error during plugin ${data.name} modification - ${err}` + ); + this.closePluginModal(); + this.toggleLoading(); }); } render() { + const selectPlugins = { + allPlugins: { + name: "All Plugins", + component: ( + + ) + }, + accountPolicy: { + name: "Account Policy", + component: ( + + ) + }, + attributeUniqueness: { + name: "Attribute Uniqueness", + component: ( + + ) + }, + autoMembership: { + name: "Auto Membership", + component: ( + + ) + }, + dna: { + name: "DNA", + component: ( + + ) + }, + linkedAttributes: { + name: "Linked Attributes", + component: ( + + ) + }, + memberOf: { + name: "MemberOf", + component: ( + + ) + }, + managedEntries: { + name: "Managed Entries", + component: ( + + ) + }, + passthroughAuthentication: { + name: "Passthrough Authentication", + component: ( + + ) + }, + referentialIntegrity: { + name: "Referential Integrity", + component: ( + + ) + }, + retroChangelog: { + name: "Retro Changelog", + component: ( + + ) + }, + rootDnaAccessControl: { + name: "RootDN Access Control", + component: ( + + ) + }, + usn: { + name: "USN", + component: ( + + ) + } + }; return (
-

Plugins

+ + +

Plugins

+ + + + +
+
+ + + + + + + + {Object.entries(selectPlugins).map( + ([id, item]) => ( + + {item.component} + + ) + )} + + + + -
); diff --git a/src/cockpit/389-console/src/replication.js b/src/cockpit/389-console/src/replication.js index 13d0d40..62215ac 100644 --- a/src/cockpit/389-console/src/replication.js +++ b/src/cockpit/389-console/src/replication.js @@ -97,7 +97,6 @@ function clear_agmt_wizard () { $('#frac-exclude-list').find('option').remove(); $('#frac-exclude-tot-list').find('option').remove(); $('#frac-strip-list').find('option').remove(); - load_schema_objects_to_select('attributetypes', 'select-attr-list'); $("#select-attr-list").prop('selectedIndex',-1); $("#init-options").prop("selectedIndex", 0); $("#init-agmt-dropdown").show(); @@ -332,6 +331,17 @@ function get_and_set_repl_agmts () { repl_agmt_table.clear().draw(); }); } // suffix + + // Load fractional replication agreement attr list here + var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'schema', 'list']; + log_cmd('get_and_set_repl_agmts', 'Get all schema objects', cmd); + cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(schema_data) { + var schema_json = JSON.parse(schema_data); + load_schema_objects_to_select('attributetypes', 'select-attr-list', schema_json); + }).fail(function(oc_data) { + console.log("Get all schema objects failed: " + oc_data.message); + check_inst_alive(1); + }); } diff --git a/src/cockpit/389-console/src/servers.js b/src/cockpit/389-console/src/servers.js index 10c4715..3543349 100644 --- a/src/cockpit/389-console/src/servers.js +++ b/src/cockpit/389-console/src/servers.js @@ -501,6 +501,25 @@ $(document).ready( function() { }); }); + document.getElementById("remove-server-btn").addEventListener("click", function() { + popup_confirm("Are you sure you want to this remove instance: " + server_id + "", "Confirmation", function (yes) { + if (yes) { + var cmd = [DSCTL, server_id, "remove", "--do-it"]; + $("#ds-remove-inst").html(" Removing instance " + server_id + "..."); + $("#remove-instance-form").modal('toggle'); + log_cmd('#remove-server-btn (click)', 'Remove instance', cmd); + cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) { + $("#remove-instance-form").modal('toggle'); + popup_success("Instance has been deleted"); + get_insts(); + }).fail(function(data) { + $("#remove-instance-form").modal('toggle'); + popup_err("Failed to remove instance", data.message); + }); + } + }); + }); + clearInterval(init_config); } }, 250); @@ -1286,7 +1305,7 @@ $(document).ready( function() { "Backups are written to the server's backup directory (nsslapd-bakdir)"); return; } - + // First check if backup name is already used var check_cmd = [DSCTL, '-j', server_id, 'backups']; log_cmd('#restore-server-btn (click)', 'Restore server instance', check_cmd); @@ -1422,27 +1441,6 @@ $(document).ready( function() { }); }); - - // Remove instance - $("#remove-server-btn").on("click", function () { - popup_confirm("Are you sure you want to this remove instance: " + server_id + "", "Confirmation", function (yes) { - if (yes) { - var cmd = [DSCTL, server_id, "remove", "--do-it"]; - $("#ds-remove-inst").html(" Removing instance " + server_id + "..."); - $("#remove-instance-form").modal('toggle'); - log_cmd('#remove-server-btn (click)', 'Remove instance', cmd); - cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) { - $("#remove-instance-form").modal('toggle'); - popup_success("Instance has been deleted"); - get_insts(); - }).fail(function(data) { - $("#remove-instance-form").modal('toggle'); - popup_err("Failed to remove instance", data.message); - }); - } - }); - }); - // Create instance form $("#create-server-btn").on("click", function() { clear_inst_form(); diff --git a/src/lib389/cli/dsconf b/src/lib389/cli/dsconf index 33b3b5a..b22736e 100755 --- a/src/lib389/cli/dsconf +++ b/src/lib389/cli/dsconf @@ -28,12 +28,6 @@ from lib389.cli_conf import pwpolicy as cli_pwpolicy from lib389.cli_conf import backup as cli_backup from lib389.cli_conf import replication as cli_replication from lib389.cli_conf import chaining as cli_chaining -from lib389.cli_conf.plugins import memberof as cli_memberof -from lib389.cli_conf.plugins import usn as cli_usn -from lib389.cli_conf.plugins import rootdn_ac as cli_rootdn_ac -from lib389.cli_conf.plugins import whoami as cli_whoami -from lib389.cli_conf.plugins import referint as cli_referint -from lib389.cli_conf.plugins import automember as cli_automember from lib389.cli_base import disconnect_instance, connect_instance from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat from lib389.cli_base import setup_script_logger @@ -78,23 +72,17 @@ parser.add_argument('-j', '--json', subparsers = parser.add_subparsers(help="resources to act upon") -cli_automember.create_parser(subparsers) cli_backend.create_parser(subparsers) cli_backup.create_parser(subparsers) cli_chaining.create_parser(subparsers) cli_config.create_parser(subparsers) cli_directory_manager.create_parsers(subparsers) cli_health.create_parser(subparsers) -cli_memberof.create_parser(subparsers) cli_plugin.create_parser(subparsers) cli_pwpolicy.create_parser(subparsers) -cli_referint.create_parser(subparsers) cli_replication.create_parser(subparsers) -cli_rootdn_ac.create_parser(subparsers) cli_sasl.create_parser(subparsers) cli_schema.create_parser(subparsers) -cli_usn.create_parser(subparsers) -cli_whoami.create_parser(subparsers) argcomplete.autocomplete(parser) diff --git a/src/lib389/lib389/cli_conf/__init__.py b/src/lib389/lib389/cli_conf/__init__.py index d57ac33..a969724 100644 --- a/src/lib389/lib389/cli_conf/__init__.py +++ b/src/lib389/lib389/cli_conf/__init__.py @@ -1,8 +1,54 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2016 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). # See LICENSE for details. # --- END COPYRIGHT BLOCK --- +def generic_show(inst, basedn, log, args): + """Display plugin configuration.""" + plugin = args.plugin_cls(inst) + print(plugin.display()) + + +def generic_enable(inst, basedn, log, args): + plugin = args.plugin_cls(inst) + if plugin.status(): + print("Plugin '%s' already enabled" % plugin.rdn) + else: + plugin.enable() + print("Enabled plugin '%s'" % plugin.rdn) + + +def generic_disable(inst, basedn, log, args): + plugin = args.plugin_cls(inst) + if not plugin.status(): + print("Plugin '%s' already disabled " % plugin.rdn) + else: + plugin.disable() + print("Disabled plugin '%s'" % plugin.rdn) + + +def generic_status(inst, basedn, log, args): + plugin = args.plugin_cls(inst) + if plugin.status() is True: + print("Plugin '%s' is enabled" % plugin.rdn) + else: + print("Plugin '%s' is disabled" % plugin.rdn) + + +def add_generic_plugin_parsers(subparser, plugin_cls): + show_parser = subparser.add_parser('show', help='display plugin configuration') + show_parser.set_defaults(func=generic_show, plugin_cls=plugin_cls) + + enable_parser = subparser.add_parser('enable', help='enable plugin') + enable_parser.set_defaults(func=generic_enable, plugin_cls=plugin_cls) + + disable_parser = subparser.add_parser('disable', help='disable plugin') + disable_parser.set_defaults(func=generic_disable, plugin_cls=plugin_cls) + + status_parser = subparser.add_parser('status', help='display plugin status') + status_parser.set_defaults(func=generic_status, plugin_cls=plugin_cls) + + diff --git a/src/lib389/lib389/cli_conf/plugin.py b/src/lib389/lib389/cli_conf/plugin.py index 39a9fed..ea43a8d 100644 --- a/src/lib389/lib389/cli_conf/plugin.py +++ b/src/lib389/lib389/cli_conf/plugin.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2016 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -10,16 +10,22 @@ import json from lib389.plugins import Plugin, Plugins from lib389.utils import ensure_dict_str from lib389.cli_base import ( - _generic_list, _generic_get, - _generic_get_dn, - _generic_create, - _generic_delete, _get_arg, - _get_args, - _get_attributes, - _warn, - ) +) +from lib389.cli_conf.plugins import memberof as cli_memberof +from lib389.cli_conf.plugins import usn as cli_usn +from lib389.cli_conf.plugins import rootdn_ac as cli_rootdn_ac +from lib389.cli_conf.plugins import whoami as cli_whoami +from lib389.cli_conf.plugins import referint as cli_referint +from lib389.cli_conf.plugins import accountpolicy as cli_accountpolicy +from lib389.cli_conf.plugins import attruniq as cli_attruniq +from lib389.cli_conf.plugins import dna as cli_dna +from lib389.cli_conf.plugins import linkedattr as cli_linkedattr +from lib389.cli_conf.plugins import managedentries as cli_managedentries +from lib389.cli_conf.plugins import passthroughauth as cli_passthroughauth +from lib389.cli_conf.plugins import retrochangelog as cli_retrochangelog +from lib389.cli_conf.plugins import automember as cli_automember SINGULAR = Plugin MANY = Plugins @@ -44,7 +50,7 @@ def plugin_list(inst, basedn, log, args): if args and args.json: json_result['items'].append(plugin_data) else: - plugin_log.info(plugin_data) + plugin_log.info(plugin_data["cn"][0]) if args and args.json: print(json.dumps(json_result)) @@ -54,11 +60,6 @@ def plugin_get(inst, basedn, log, args): _generic_get(inst, basedn, log.getChild('plugin_get'), MANY, rdn, args) -def plugin_get_dn(inst, basedn, log, args): - dn = _get_arg(args.dn, msg="Enter dn to retrieve") - _generic_get_dn(inst, basedn, log.getChild('plugin_get_dn'), MANY, dn, args) - - def vaidate_args(plugin, attr_arg_list): """Check if the attribute needs to be changed Return mods for the replace_many() method @@ -97,100 +98,33 @@ def plugin_edit(inst, basedn, log, args): raise ValueError("Nothing to change") -# Plugin enable -def plugin_enable(inst, basedn, log, args): - dn = _get_arg(args.dn, msg="Enter plugin dn to enable") - mc = MANY(inst, basedn) - o = mc.get(dn=dn) - if o.status(): - print('Plugin already enabled') - else: - o.enable() - print('Enabled plugin') - - -# Plugin disable -def plugin_disable(inst, basedn, log, args, warn=True): - dn = _get_arg(args.dn, msg="Enter plugin dn to disable") - if warn: - _warn(dn, msg="Disabling %s %s" % (SINGULAR.__name__, dn)) - mc = MANY(inst, basedn) - o = mc.get(dn=dn) - if not o.state(): - print("Plugin already disabled") - else: - o.disable() - print('Disabled plugin') - - -# Plugin configure? -def plugin_configure(inst, basedn, log, args): - pass - - -def generic_show(inst, basedn, log, args): - """Display plugin configuration.""" - plugin = args.plugin_cls(inst) - print(plugin.display()) - - -def generic_enable(inst, basedn, log, args): - plugin = args.plugin_cls(inst) - if plugin.status(): - print("Plugin '%s' already enabled" % plugin.rdn) - else: - plugin.enable() - print("Enabled plugin '%s'" % plugin.rdn) - - -def generic_disable(inst, basedn, log, args): - plugin = args.plugin_cls(inst) - if not plugin.status(): - print("Plugin '%s' already disabled " % plugin.rdn) - else: - plugin.disable() - print("Disabled plugin '%s'" % plugin.rdn) - - -def generic_status(inst, basedn, log, args): - plugin = args.plugin_cls(inst) - if plugin.status() is True: - print("Plugin '%s' is enabled" % plugin.rdn) - else: - print("Plugin '%s' is disabled" % plugin.rdn) - - -def add_generic_plugin_parsers(subparser, plugin_cls): - show_parser = subparser.add_parser('show', help='display plugin configuration') - show_parser.set_defaults(func=generic_show, plugin_cls=plugin_cls) - - enable_parser = subparser.add_parser('enable', help='enable plugin') - enable_parser.set_defaults(func=generic_enable, plugin_cls=plugin_cls) - - disable_parser = subparser.add_parser('disable', help='disable plugin') - disable_parser.set_defaults(func=generic_disable, plugin_cls=plugin_cls) - - status_parser = subparser.add_parser('status', help='display plugin status') - status_parser.set_defaults(func=generic_status, plugin_cls=plugin_cls) - - def create_parser(subparsers): plugin_parser = subparsers.add_parser('plugin', help="Manage plugins available on the server") - subcommands = plugin_parser.add_subparsers(help="action") + subcommands = plugin_parser.add_subparsers(help="Plugins") + + cli_memberof.create_parser(subcommands) + cli_automember.create_parser(subcommands) + cli_referint.create_parser(subcommands) + cli_rootdn_ac.create_parser(subcommands) + cli_usn.create_parser(subcommands) + cli_accountpolicy.create_parser(subcommands) + cli_attruniq.create_parser(subcommands) + cli_dna.create_parser(subcommands) + cli_linkedattr.create_parser(subcommands) + cli_managedentries.create_parser(subcommands) + cli_passthroughauth.create_parser(subcommands) + cli_retrochangelog.create_parser(subcommands) + cli_whoami.create_parser(subcommands) list_parser = subcommands.add_parser('list', help="List current configured (enabled and disabled) plugins") list_parser.set_defaults(func=plugin_list) - get_parser = subcommands.add_parser('get', help='get') + get_parser = subcommands.add_parser('get', help='Get the plugin data') get_parser.set_defaults(func=plugin_get) get_parser.add_argument('selector', nargs='?', help='The plugin to search for') - get_dn_parser = subcommands.add_parser('get_dn', help='get_dn') - get_dn_parser.set_defaults(func=plugin_get_dn) - get_dn_parser.add_argument('dn', nargs='?', help='The plugin dn to get') - - edit_parser = subcommands.add_parser('edit', help='get') + edit_parser = subcommands.add_parser('edit', help='Edit the plugin') edit_parser.set_defaults(func=plugin_edit) edit_parser.add_argument('selector', nargs='?', help='The plugin to edit') edit_parser.add_argument('--type', help='The type of plugin.') @@ -202,11 +136,3 @@ def create_parser(subparsers): edit_parser.add_argument('--vendor', help='The vendor of plugin.') edit_parser.add_argument('--version', help='The version of plugin.') edit_parser.add_argument('--description', help='The description of the plugin.') - - enable_parser = subcommands.add_parser('enable', help='enable a plugin in the server') - enable_parser.set_defaults(func=plugin_enable) - enable_parser.add_argument('dn', nargs='?', help='The dn to enable') - - disable_parser = subcommands.add_parser('disable', help='disable the plugin configuration') - disable_parser.set_defaults(func=plugin_disable) - disable_parser.add_argument('dn', nargs='?', help='The dn to disable') diff --git a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py new file mode 100644 index 0000000..d33e054 --- /dev/null +++ b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py @@ -0,0 +1,16 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.plugins import AccountPolicyPlugin +from lib389.cli_conf import add_generic_plugin_parsers + + +def create_parser(subparsers): + accountpolicy_parser = subparsers.add_parser('accountpolicy', help='Manage and configure Account Policy plugin') + subcommands = accountpolicy_parser.add_subparsers(help='action') + add_generic_plugin_parsers(subcommands, AccountPolicyPlugin) diff --git a/src/lib389/lib389/cli_conf/plugins/attruniq.py b/src/lib389/lib389/cli_conf/plugins/attruniq.py new file mode 100644 index 0000000..4c04b05 --- /dev/null +++ b/src/lib389/lib389/cli_conf/plugins/attruniq.py @@ -0,0 +1,16 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.plugins import AttributeUniquenessPlugin +from lib389.cli_conf import add_generic_plugin_parsers + + +def create_parser(subparsers): + attruniq_parser = subparsers.add_parser('attruniq', help='Manage and configure Attribute Uniqueness plugin') + subcommands = attruniq_parser.add_subparsers(help='action') + add_generic_plugin_parsers(subcommands, AttributeUniquenessPlugin) diff --git a/src/lib389/lib389/cli_conf/plugins/automember.py b/src/lib389/lib389/cli_conf/plugins/automember.py index 2e346be..a4e757e 100644 --- a/src/lib389/lib389/cli_conf/plugins/automember.py +++ b/src/lib389/lib389/cli_conf/plugins/automember.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2016-2017 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -8,18 +8,16 @@ import ldap import json -from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinition, AutoMembershipDefinitions -from lib389.cli_conf.plugin import add_generic_plugin_parsers +from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions +from lib389.cli_conf import add_generic_plugin_parsers def list_definition(inst, basedn, log, args): - """ - List automember definition if instance name - is given else show all automember definitions. - - :param name: An instance - :type name: lib389.DirSrv + """List automember definition if instance name + is given else show all automember definitions. + :param name: An instance + :type name: lib389.DirSrv """ automembers = AutoMembershipDefinitions(inst) @@ -34,15 +32,19 @@ def list_definition(inst, basedn, log, args): all_definitions = automembers.list() if args.json: result = {'type': 'list', 'items': []} - for definition in all_definitions: - if args.json: - result['items'].append(definition) - else: - log.info(definition.display()) + if len(all_definitions) > 0: + for definition in all_definitions: + if args.json: + result['items'].append(definition) + else: + log.info(definition.display()) + else: + log.info("No automember definitions were found") if args.json: print(json.dumps(result)) + def create_definition(inst, basedn, log, args): """ Create automember definition. @@ -79,6 +81,7 @@ def create_definition(inst, basedn, log, args): log.info("Failed to create Automember definition: {}".format(str(e))) raise e + def edit_definition(inst, basedn, log, args): """ Edit automember definition @@ -148,7 +151,7 @@ def create_parser(subparsers): show_parser = subcommands.add_parser('list', help='List automember definition.') show_parser.set_defaults(func=list_definition) - show_parser.add_argument("name", help='Set cn for group entry.') + show_parser.add_argument("--name", help='Set cn for group entry. If not specified show all automember definitions.') edit_parser = subcommands.add_parser('edit', help='Edit automember definition.') edit_parser.set_defaults(func=edit_definition) diff --git a/src/lib389/lib389/cli_conf/plugins/dna.py b/src/lib389/lib389/cli_conf/plugins/dna.py new file mode 100644 index 0000000..50dd37f --- /dev/null +++ b/src/lib389/lib389/cli_conf/plugins/dna.py @@ -0,0 +1,16 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.plugins import DNAPlugin +from lib389.cli_conf import add_generic_plugin_parsers + + +def create_parser(subparsers): + dna_parser = subparsers.add_parser('dna', help='Manage and configure DNA plugin') + subcommands = dna_parser.add_subparsers(help='action') + add_generic_plugin_parsers(subcommands, DNAPlugin) diff --git a/src/lib389/lib389/cli_conf/plugins/linkedattr.py b/src/lib389/lib389/cli_conf/plugins/linkedattr.py new file mode 100644 index 0000000..7581e80 --- /dev/null +++ b/src/lib389/lib389/cli_conf/plugins/linkedattr.py @@ -0,0 +1,16 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.plugins import LinkedAttributesPlugin +from lib389.cli_conf import add_generic_plugin_parsers + + +def create_parser(subparsers): + linkedattr_parser = subparsers.add_parser('linkedattr', help='Manage and configure Linked Attributes plugin') + subcommands = linkedattr_parser.add_subparsers(help='action') + add_generic_plugin_parsers(subcommands, LinkedAttributesPlugin) diff --git a/src/lib389/lib389/cli_conf/plugins/managedentries.py b/src/lib389/lib389/cli_conf/plugins/managedentries.py new file mode 100644 index 0000000..18dca1b --- /dev/null +++ b/src/lib389/lib389/cli_conf/plugins/managedentries.py @@ -0,0 +1,16 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.plugins import ManagedEntriesPlugin +from lib389.cli_conf import add_generic_plugin_parsers + + +def create_parser(subparsers): + managedentries_parser = subparsers.add_parser('managedentries', help='Manage and configure Managed Entries plugin') + subcommands = managedentries_parser.add_subparsers(help='action') + add_generic_plugin_parsers(subcommands, ManagedEntriesPlugin) diff --git a/src/lib389/lib389/cli_conf/plugins/memberof.py b/src/lib389/lib389/cli_conf/plugins/memberof.py index 74287f9..b9865a8 100644 --- a/src/lib389/lib389/cli_conf/plugins/memberof.py +++ b/src/lib389/lib389/cli_conf/plugins/memberof.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2016-2017 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -9,7 +9,7 @@ import ldap from lib389.plugins import MemberOfPlugin -from lib389.cli_conf.plugin import add_generic_plugin_parsers +from lib389.cli_conf import add_generic_plugin_parsers def manage_attr(inst, basedn, log, args): diff --git a/src/lib389/lib389/cli_conf/plugins/passthroughauth.py b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py new file mode 100644 index 0000000..ef6729e --- /dev/null +++ b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py @@ -0,0 +1,16 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.plugins import PassThroughAuthenticationPlugin +from lib389.cli_conf import add_generic_plugin_parsers + + +def create_parser(subparsers): + passthroughauth_parser = subparsers.add_parser('passthroughauth', help='Manage and configure Pass-Through Authentication plugin') + subcommands = passthroughauth_parser.add_subparsers(help='action') + add_generic_plugin_parsers(subcommands, PassThroughAuthenticationPlugin) diff --git a/src/lib389/lib389/cli_conf/plugins/referint.py b/src/lib389/lib389/cli_conf/plugins/referint.py index 9d52b89..bf4d07c 100644 --- a/src/lib389/lib389/cli_conf/plugins/referint.py +++ b/src/lib389/lib389/cli_conf/plugins/referint.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2016-2017 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -9,7 +9,7 @@ import ldap from lib389.plugins import ReferentialIntegrityPlugin -from lib389.cli_conf.plugin import add_generic_plugin_parsers +from lib389.cli_conf import add_generic_plugin_parsers def manage_update_delay(inst, basedn, log, args): diff --git a/src/lib389/lib389/cli_conf/plugins/retrochangelog.py b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py new file mode 100644 index 0000000..133d811 --- /dev/null +++ b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py @@ -0,0 +1,16 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.plugins import RetroChangelogPlugin +from lib389.cli_conf import add_generic_plugin_parsers + + +def create_parser(subparsers): + retrochangelog_parser = subparsers.add_parser('retrochangelog', help='Manage and configure Retro Changelog plugin') + subcommands = retrochangelog_parser.add_subparsers(help='action') + add_generic_plugin_parsers(subcommands, RetroChangelogPlugin) diff --git a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py index 1e11504..7e1c017 100644 --- a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py +++ b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2017 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -9,7 +9,7 @@ import ldap from lib389.plugins import RootDNAccessControlPlugin -from lib389.cli_conf.plugin import add_generic_plugin_parsers +from lib389.cli_conf import add_generic_plugin_parsers def display_time(inst, basedn, log, args): diff --git a/src/lib389/lib389/cli_conf/plugins/usn.py b/src/lib389/lib389/cli_conf/plugins/usn.py index 05e34dd..59349fe 100644 --- a/src/lib389/lib389/cli_conf/plugins/usn.py +++ b/src/lib389/lib389/cli_conf/plugins/usn.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2016-2017 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -7,7 +7,7 @@ # --- END COPYRIGHT BLOCK --- from lib389.plugins import USNPlugin -from lib389.cli_conf.plugin import add_generic_plugin_parsers +from lib389.cli_conf import add_generic_plugin_parsers def display_usn_mode(inst, basedn, log, args): diff --git a/src/lib389/lib389/cli_conf/plugins/whoami.py b/src/lib389/lib389/cli_conf/plugins/whoami.py index bdb1e85..2c3e62a 100644 --- a/src/lib389/lib389/cli_conf/plugins/whoami.py +++ b/src/lib389/lib389/cli_conf/plugins/whoami.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2016-2017 Red Hat, Inc. +# Copyright (C) 2018 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -7,7 +7,7 @@ # --- END COPYRIGHT BLOCK --- from lib389.plugins import WhoamiPlugin -from lib389.cli_conf.plugin import add_generic_plugin_parsers +from lib389.cli_conf import add_generic_plugin_parsers def create_parser(subparsers): diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py index d06f299..13b62b2 100644 --- a/src/lib389/lib389/plugins.py +++ b/src/lib389/lib389/plugins.py @@ -192,7 +192,7 @@ class ManagedEntriesPlugin(Plugin): :type dn: str """ - def __init__(self, instance, dn="cn=managed entries,cn=plugins,cn=config"): + def __init__(self, instance, dn="cn=Managed Entries,cn=plugins,cn=config"): super(ManagedEntriesPlugin, self).__init__(instance, dn) @@ -897,7 +897,7 @@ class AutoMembershipDefinition(DSLdapObject): super(AutoMembershipDefinition, self).__init__(instance, dn) self._rdn_attribute = 'cn' self._must_attributes = ['cn', 'autoMemberScope', 'autoMemberFilter', 'autoMemberGroupingAttr'] - self._create_objectclasses = ['top', 'AutoMemberDefinition'] + self._create_objectclasses = ['top', 'autoMemberDefinition'] self._protected = False def get_groupattr(self): @@ -958,6 +958,40 @@ class AutoMembershipDefinitions(DSLdapObjects): self._basedn = basedn +class AutoMembershipRegexRule(DSLdapObject): + """A single instance of Auto Membership Plugin Regex Rule config entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + """ + + def __init__(self, instance, dn=None): + super(AutoMembershipRegexRule, self).__init__(instance, dn) + self._rdn_attribute = 'cn' + self._must_attributes = ['cn', 'autoMemberTargetGroup'] + self._create_objectclasses = ['top', 'autoMemberRegexRule'] + self._protected = False + + +class AutoMembershipRegexRules(DSLdapObjects): + """A DSLdapObjects entity which represents Auto Membership Plugin Regex Rule config entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all account entries below + :type basedn: str + """ + + def __init__(self, instance, basedn): + super(AutoMembershipRegexRules, self).__init__(instance) + self._objectclasses = ['top', 'autoMemberRegexRule'] + self._filterattrs = ['cn'] + self._childobject = AutoMembershipRegexRule + self._basedn = basedn + + class ContentSynchronizationPlugin(Plugin): """A single instance of Content Synchronization plugin entry @@ -1542,7 +1576,7 @@ class DNAPluginConfigs(DSLdapObjects): class Plugins(DSLdapObjects): - """A DSLdapObjects entity which represents MEP config entry + """A DSLdapObjects entity which represents plugin entry :param instance: An instance :type instance: lib389.DirSrv