From db11e8ecc7f964d6f7fd41b724cfb7981a1f4f69 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Fri, 29 May 2020 13:42:43 -0400 Subject: [PATCH] Issue 51118 - UI - improve modal validation when creating an instance Description: Do not enable the "create" button until all the fields are valid (DN's, port numbers, passwords, etc). Improve layout and handling of optional database settings. Add a json argument to dscreate so the UI can report any failure text. Also improve error reporting in dscreate. relates: https://pagure.io/389-ds-base/issue/51118 Reviewed by: firstyear & spichugi(Thanks!!) Improve validation error messages Fix allowed characters --- src/cockpit/389-console/src/ds.jsx | 1191 +-------------- src/cockpit/389-console/src/dsModals.jsx | 1356 +++++++++++++++++ .../389-console/src/lib/server/ldapi.jsx | 1 - src/cockpit/389-console/src/lib/tools.jsx | 2 +- src/lib389/cli/dscreate | 12 +- src/lib389/lib389/instance/setup.py | 7 +- 6 files changed, 1373 insertions(+), 1196 deletions(-) create mode 100644 src/cockpit/389-console/src/dsModals.jsx diff --git a/src/cockpit/389-console/src/ds.jsx b/src/cockpit/389-console/src/ds.jsx index 53aa5cb797..691a6f265e 100644 --- a/src/cockpit/389-console/src/ds.jsx +++ b/src/cockpit/389-console/src/ds.jsx @@ -1,16 +1,15 @@ import cockpit from "cockpit"; import React from "react"; -import PropTypes from "prop-types"; import { Plugins } from "./plugins.jsx"; import { Database } from "./database.jsx"; import { Monitor } from "./monitor.jsx"; import { Schema } from "./schema.jsx"; import { Replication } from "./replication.jsx"; import { Server } from "./server.jsx"; -import { ConfirmPopup, DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx"; -import { BackupTable } from "./lib/database/databaseTables.jsx"; -import { BackupModal, RestoreModal, DeleteBackupModal } from "./lib/database/backups.jsx"; -import { log_cmd, bad_file_name } from "./lib/tools.jsx"; +import { DoubleConfirmModal, NotificationController } from "./lib/notifications.jsx"; +import { ManageBackupsModal, SchemaReloadModal, CreateInstanceModal } from "./dsModals.jsx"; +import { log_cmd } from "./lib/tools.jsx"; + import { Nav, NavItem, @@ -20,18 +19,7 @@ import { TabContent, TabPane, ProgressBar, - FormControl, - FormGroup, - ControlLabel, - Radio, - Form, - noop, - Checkbox, Spinner, - Row, - Modal, - Icon, - Col, Button } from "patternfly-react"; import "./css/ds.css"; @@ -722,1175 +710,4 @@ export class DSInstance extends React.Component { } } -class CreateInstanceModal extends React.Component { - constructor(props) { - super(props); - this.state = { - createServerId: "", - createPort: 389, - createSecurePort: 636, - createDM: "cn=Directory Manager", - createDMPassword: "", - createDMPasswordConfirm: "", - createDBSuffix: "", - createDBName: "", - createTLSCert: true, - createInitDB: "noInit", - loadingCreate: false - }; - - this.handleFieldChange = this.handleFieldChange.bind(this); - this.createInstance = this.createInstance.bind(this); - } - - handleFieldChange(e) { - let value = e.target.type === "checkbox" ? e.target.checked : e.target.value; - if (e.target.type === "number") { - if (e.target.value) { - value = parseInt(e.target.value); - } else { - value = 1; - } - } - this.setState({ - [e.target.id]: value - }); - } - - createInstance() { - const { - createServerId, - createPort, - createSecurePort, - createDM, - createDMPassword, - createDMPasswordConfirm, - createDBSuffix, - createDBName, - createTLSCert, - createInitDB - } = this.state; - const { closeHandler, addNotification, loadInstanceList } = this.props; - - let setup_inf = - "[general]\n" + - "config_version = 2\n" + - "full_machine_name = FQDN\n\n" + - "[slapd]\n" + - "user = dirsrv\n" + - "group = dirsrv\n" + - "instance_name = INST_NAME\n" + - "port = PORT\n" + - "root_dn = ROOTDN\n" + - "root_password = ROOTPW\n" + - "secure_port = SECURE_PORT\n" + - "self_sign_cert = SELF_SIGN\n"; - - // Server ID - let newServerId = createServerId; - if (newServerId === "") { - addNotification("warning", "Instance Name is required."); - return; - } - newServerId = newServerId.replace(/^slapd-/i, ""); // strip "slapd-" - if (newServerId === "admin") { - addNotification("warning", "Instance Name 'admin' is reserved, please choose a different name"); - return; - } - if (newServerId.length > 80) { - addNotification( - "warning", - "Instance name is too long, it must not exceed 80 characters" - ); - return; - } - if (newServerId.match(/^[#%:A-Za-z0-9_-]+$/g)) { - setup_inf = setup_inf.replace("INST_NAME", newServerId); - } else { - addNotification( - "warning", - "Instance name can only contain letters, numbers, and: # % : - _" - ); - return; - } - - // Port - if (createPort < 1 || createPort > 65535) { - addNotification("warning", "Port must be a number between 1 and 65534!"); - return; - } else { - setup_inf = setup_inf.replace("PORT", createPort); - } - - // Secure Port - if (createSecurePort < 1 || createSecurePort > 65535) { - addNotification("warning", "Secure Port must be a number between 1 and 65534!"); - return; - } else { - setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort); - } - - // Root DN - if (createDM === "") { - addNotification("warning", "You must provide a Directory Manager DN"); - return; - } else { - setup_inf = setup_inf.replace("ROOTDN", createDM); - } - - // Setup Self-Signed Certs - if (createTLSCert) { - setup_inf = setup_inf.replace("SELF_SIGN", "True"); - } else { - setup_inf = setup_inf.replace("SELF_SIGN", "False"); - } - - // Root DN password - if (createDMPassword != createDMPasswordConfirm) { - addNotification("warning", "Directory Manager passwords do not match!"); - return; - } else if (createDMPassword == "") { - addNotification("warning", "Directory Manager password can not be empty!"); - return; - } else if (createDMPassword.length < 8) { - addNotification( - "warning", - "Directory Manager password must have at least 8 characters" - ); - return; - } else { - setup_inf = setup_inf.replace("ROOTPW", createDMPassword); - } - - // Backend/Suffix - if ( - (createDBName != "" && createDBSuffix == "") || - (createDBName == "" && createDBSuffix != "") - ) { - if (createDBName == "") { - addNotification( - "warning", - "If you specify a backend suffix, you must also specify a backend name" - ); - return; - } else { - addNotification( - "warning", - "If you specify a backend name, you must also specify a backend suffix" - ); - return; - } - } - if (createDBName != "") { - // We definitely have a backend name and suffix, next validate the suffix is a DN - let dn_regex = new RegExp("^([A-Za-z]+=.*)"); - if (dn_regex.test(createDBSuffix)) { - // It's valid, add it - setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n"; - } else { - // Not a valid DN - addNotification("warning", "Invalid DN for Backend Suffix"); - return; - } - - if (createInitDB === "createSample") { - setup_inf += "\nsample_entries = yes\n"; - } - if (createInitDB === "createSuffix") { - setup_inf += "\ncreate_suffix_entry = yes\n"; - } - } - - /* - * Here are steps we take to create the instance - * - * [1] Get FQDN Name for nsslapd-localhost setting in setup file - * [2] Create a file for the inf setup parameters - * [3] Set strict permissions on that file - * [4] Populate the new setup file with settings (including cleartext password) - * [5] Create the instance - * [6] Remove setup file - */ - this.setState({ - loadingCreate: true - }); - cockpit - - .spawn(["hostnamectl", "status", "--static"], { superuser: true, err: "message" }) - .fail(err => { - let errMsg = JSON.parse(err); - this.setState({ - loadingCreate: false - }); - addNotification("error", `Failed to get hostname!", ${errMsg.desc}`); - }) - .done(data => { - /* - * We have FQDN, so set the hostname in inf file, and create the setup file - */ - setup_inf = setup_inf.replace("FQDN", data); - let setup_file = "/tmp/389-setup-" + new Date().getTime() + ".inf"; - let rm_cmd = ["rm", setup_file]; - let create_file_cmd = ["touch", setup_file]; - cockpit - .spawn(create_file_cmd, { superuser: true, err: "message" }) - .fail(err => { - let errMsg = JSON.parse(err); - this.setState({ - loadingCreate: false - }); - addNotification( - "error", - `Failed to create installation file!" ${errMsg.desc}` - ); - }) - .done(_ => { - /* - * We have our new setup file, now set permissions on that setup file before we add sensitive data - */ - let chmod_cmd = ["chmod", "600", setup_file]; - cockpit - .spawn(chmod_cmd, { superuser: true, err: "message" }) - .fail(err => { - let errMsg = JSON.parse(err); - cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password - this.setState({ - loadingCreate: false - }); - addNotification( - "error", - `Failed to set permission on setup file ${setup_file}: ${ - errMsg.desc - }` - ); - }) - .done(_ => { - /* - * Success we have our setup file and it has the correct permissions. - * Now populate the setup file... - */ - let cmd = [ - "/bin/sh", - "-c", - '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file - ]; - cockpit - .spawn(cmd, { superuser: true, err: "message" }) - .fail(err => { - let errMsg = JSON.parse(err); - this.setState({ - loadingCreate: false - }); - addNotification( - "error", - `Failed to populate installation file! ${errMsg.desc}` - ); - }) - .done(_ => { - /* - * Next, create the instance... - */ - let cmd = ["dscreate", "from-file", setup_file]; - cockpit - .spawn(cmd, { - superuser: true, - err: "message" - }) - .fail(_ => { - cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password - this.setState({ - loadingCreate: false - }); - addNotification( - "error", - "Failed to create instance!" - ); - }) - .done(_ => { - // Success!!! Now cleanup everything up... - cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password - this.setState({ - loadingCreate: false - }); - - loadInstanceList(createServerId); - addNotification( - "success", - `Successfully created instance: slapd-${createServerId}` - ); - closeHandler(); - }); - }); - }); - }); - }); - } - - render() { - const { showModal, closeHandler } = this.props; - - const { - loadingCreate, - createServerId, - createPort, - createSecurePort, - createDM, - createDMPassword, - createDMPasswordConfirm, - createDBSuffix, - createDBName, - createTLSCert, - createInitDB - } = this.state; - - let createSpinner = ""; - if (loadingCreate) { - createSpinner = ( - -
- - Creating instance... -
-
- ); - } - - return ( - -
- - - Create New Server Instance - - -
- - - Instance Name - - - - - - - - Port - - - - - - - - Secure Port - - - - - - - - Create Self-Signed TLS Certificate - - - - - - - - Directory Manager DN - - - - - - - - Directory Manager Password - - - - - - - - Confirm Password - - - - - -
-
Optional Database Settings
- - - Database Suffix - - - - - - - - Database Name - - - - - - - - - Do Not Initialize Database - - - - - - - Create Suffix Entry - - - - - - - Create Suffix Entry And Add Sample Entries - - - -
- {createSpinner} -
- - - - -
-
- ); - } -} - -CreateInstanceModal.propTypes = { - showModal: PropTypes.bool, - closeHandler: PropTypes.func, - addNotification: PropTypes.func, - loadInstanceList: PropTypes.func -}; - -CreateInstanceModal.defaultProps = { - showModal: false, - closeHandler: noop, - addNotification: noop, - loadInstanceList: noop -}; - -export class SchemaReloadModal extends React.Component { - constructor(props) { - super(props); - this.state = { - reloadSchemaDir: "", - loadingSchemaTask: false - }; - - this.reloadSchema = this.reloadSchema.bind(this); - this.handleFieldChange = this.handleFieldChange.bind(this); - } - - handleFieldChange(e) { - this.setState({ - [e.target.id]: e.target.value - }); - } - - reloadSchema(e) { - const { addNotification, serverId, closeHandler } = this.props; - const { reloadSchemaDir } = this.state; - - this.setState({ - loadingSchemaTask: true - }); - - let cmd = ["dsconf", "-j", serverId, "schema", "reload", "--wait"]; - if (reloadSchemaDir !== "") { - cmd = [...cmd, "--schemadir", reloadSchemaDir]; - } - log_cmd("reloadSchemaDir", "Reload schema files", cmd); - cockpit - .spawn(cmd, { superuser: true, err: "message" }) - .done(data => { - addNotification("success", "Successfully reloaded schema"); - this.setState({ - loadingSchemaTask: false - }); - closeHandler(); - }) - .fail(err => { - let errMsg = JSON.parse(err); - addNotification("error", `Failed to reload schema files - ${errMsg.desc}`); - closeHandler(); - }); - } - - render() { - const { loadingSchemaTask, reloadSchemaDir } = this.state; - const { showModal, closeHandler } = this.props; - - let spinner = ""; - if (loadingSchemaTask) { - spinner = ( - -
- - Reloading schema files... -
-
- ); - } - - return ( - -
- - - Reload Schema Files - - -
- - - Schema File Directory: - - - - - - {spinner} -
-
- - - - -
-
- ); - } -} - -SchemaReloadModal.propTypes = { - showModal: PropTypes.bool, - closeHandler: PropTypes.func, - addNotification: PropTypes.func, - serverId: PropTypes.string -}; - -SchemaReloadModal.defaultProps = { - showModal: false, - closeHandler: noop, - addNotification: noop, - serverId: "" -}; - -class ManageBackupsModal extends React.Component { - constructor(props) { - super(props); - this.state = { - activeKey: 1, - showConfirmBackupDelete: false, - showConfirmBackup: false, - showConfirmRestore: false, - showConfirmRestoreReplace: false, - showConfirmLDIFReplace: false, - showRestoreSpinningModal: false, - showDelBackupSpinningModal: false, - showBackupModal: false, - backupSpinning: false, - backupName: "", - deleteBackup: "", - errObj: {} - }; - - this.handleNavSelect = this.handleNavSelect.bind(this); - this.handleChange = this.handleChange.bind(this); - - // Backups - this.doBackup = this.doBackup.bind(this); - this.deleteBackup = this.deleteBackup.bind(this); - this.restoreBackup = this.restoreBackup.bind(this); - this.showConfirmRestore = this.showConfirmRestore.bind(this); - this.closeConfirmRestore = this.closeConfirmRestore.bind(this); - this.showConfirmBackup = this.showConfirmBackup.bind(this); - this.closeConfirmBackup = this.closeConfirmBackup.bind(this); - this.showConfirmBackupDelete = this.showConfirmBackupDelete.bind(this); - this.closeConfirmBackupDelete = this.closeConfirmBackupDelete.bind(this); - this.showBackupModal = this.showBackupModal.bind(this); - this.closeBackupModal = this.closeBackupModal.bind(this); - this.showRestoreSpinningModal = this.showRestoreSpinningModal.bind(this); - this.closeRestoreSpinningModal = this.closeRestoreSpinningModal.bind(this); - this.showDelBackupSpinningModal = this.showDelBackupSpinningModal.bind(this); - this.closeDelBackupSpinningModal = this.closeDelBackupSpinningModal.bind(this); - this.validateBackup = this.validateBackup.bind(this); - this.closeConfirmRestoreReplace = this.closeConfirmRestoreReplace.bind(this); - } - - closeExportModal() { - this.setState({ - showExportModal: false - }); - } - - showDelBackupSpinningModal() { - this.setState({ - showDelBackupSpinningModal: true - }); - } - - closeDelBackupSpinningModal() { - this.setState({ - showDelBackupSpinningModal: false - }); - } - - showRestoreSpinningModal() { - this.setState({ - showRestoreSpinningModal: true - }); - } - - closeRestoreSpinningModal() { - this.setState({ - showRestoreSpinningModal: false - }); - } - - showBackupModal() { - this.setState({ - showBackupModal: true, - backupSpinning: false, - backupName: "" - }); - } - - closeBackupModal() { - this.setState({ - showBackupModal: false - }); - } - - showConfirmBackup(item) { - // call deleteLDIF - this.setState({ - showConfirmBackup: true, - backupName: item.name - }); - } - - closeConfirmBackup() { - // call importLDIF - this.setState({ - showConfirmBackup: false - }); - } - - showConfirmRestore(item) { - this.setState({ - showConfirmRestore: true, - backupName: item.name - }); - } - - closeConfirmRestore() { - // call importLDIF - this.setState({ - showConfirmRestore: false - }); - } - - showConfirmBackupDelete(item) { - // calls deleteBackup - this.setState({ - showConfirmBackupDelete: true, - backupName: item.name - }); - } - - closeConfirmBackupDelete() { - // call importLDIF - this.setState({ - showConfirmBackupDelete: false - }); - } - - closeConfirmRestoreReplace() { - this.setState({ - showConfirmRestoreReplace: false - }); - } - - validateBackup() { - for (let i = 0; i < this.props.backups.length; i++) { - if (this.state.backupName == this.props.backups[i]["name"]) { - this.setState({ - showConfirmRestoreReplace: true - }); - return; - } - } - this.doBackup(); - } - - doBackup() { - this.setState({ - backupSpinning: true - }); - - let cmd = ["dsctl", "-j", this.props.serverId, "status"]; - cockpit - .spawn(cmd, { superuser: true }) - .done(status_data => { - let status_json = JSON.parse(status_data); - if (status_json.running == true) { - let cmd = [ - "dsconf", - "-j", - "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", - "backup", - "create" - ]; - if (this.state.backupName != "") { - if (bad_file_name(this.state.backupName)) { - this.props.addNotification( - "warning", - `Backup name should not be a path. All backups are stored in the server's backup directory` - ); - return; - } - cmd.push(this.state.backupName); - } - - log_cmd("doBackup", "Add backup task online", cmd); - cockpit - .spawn(cmd, { superuser: true, err: "message" }) - .done(content => { - this.props.reload(); - this.closeBackupModal(); - this.props.addNotification("success", `Server has been backed up`); - }) - .fail(err => { - let errMsg = JSON.parse(err); - this.props.reload(); - this.closeBackupModal(); - this.props.addNotification( - "error", - `Failure backing up server - ${errMsg.desc}` - ); - }); - } else { - const cmd = ["dsctl", "-j", this.props.serverId, "db2bak"]; - if (this.state.backupName != "") { - if (bad_file_name(this.state.backupName)) { - this.props.addNotification( - "warning", - `Backup name should not be a path. All backups are stored in the server's backup directory` - ); - return; - } - cmd.push(this.state.backupName); - } - log_cmd("doBackup", "Doing backup of the server offline", cmd); - cockpit - .spawn(cmd, { superuser: true }) - .done(content => { - this.props.reload(); - this.closeBackupModal(); - this.props.addNotification("success", `Server has been backed up`); - }) - .fail(err => { - let errMsg = JSON.parse(err); - this.props.reload(); - this.closeBackupModal(); - this.props.addNotification( - "error", - `Failure backing up server - ${errMsg.desc}` - ); - }); - } - }) - .fail(err => { - let errMsg = JSON.parse(err); - console.log("Failed to check the server status", errMsg.desc); - }); - } - - restoreBackup() { - this.showRestoreSpinningModal(); - let cmd = ["dsctl", "-j", this.props.serverId, "status"]; - cockpit - .spawn(cmd, { superuser: true }) - .done(status_data => { - let status_json = JSON.parse(status_data); - if (status_json.running == true) { - const cmd = [ - "dsconf", - "-j", - "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", - "backup", - "restore", - this.state.backupName - ]; - log_cmd("restoreBackup", "Restoring server online", cmd); - cockpit - .spawn(cmd, { superuser: true, err: "message" }) - .done(content => { - this.closeRestoreSpinningModal(); - this.props.addNotification("success", `Server has been restored`); - }) - .fail(err => { - let errMsg = JSON.parse(err); - this.closeRestoreSpinningModal(); - this.props.addNotification( - "error", - `Failure restoring up server - ${errMsg.desc}` - ); - }); - } else { - const cmd = [ - "dsctl", - "-j", - this.props.serverId, - "bak2db", - this.state.backupName - ]; - log_cmd("restoreBackup", "Restoring server offline", cmd); - cockpit - .spawn(cmd, { superuser: true, err: "message" }) - .done(content => { - this.closeRestoreSpinningModal(); - this.props.addNotification("success", `Server has been restored`); - }) - .fail(err => { - let errMsg = JSON.parse(err); - this.closeRestoreSpinningModal(); - this.props.addNotification( - "error", - `Failure restoring up server - ${errMsg.desc}` - ); - }); - } - }) - .fail(err => { - let errMsg = JSON.parse(err); - console.log("Failed to check the server status", errMsg.desc); - }); - } - - deleteBackup(e) { - // Show confirmation - this.showDelBackupSpinningModal(); - - const cmd = [ - "dsctl", - "-j", - this.props.serverId, - "backups", - "--delete", - this.state.backupName - ]; - log_cmd("deleteBackup", "Deleting backup", cmd); - cockpit - .spawn(cmd, { superuser: true, err: "message" }) - .done(content => { - this.props.reload(); - this.closeDelBackupSpinningModal(); - this.props.addNotification("success", `Backup was successfully deleted`); - }) - .fail(err => { - let errMsg = JSON.parse(err); - this.props.reload(); - this.closeDelBackupSpinningModal(); - this.props.addNotification("error", `Failure deleting backup - ${errMsg.desc}`); - }); - } - - handleNavSelect(key) { - this.setState({ activeKey: key }); - } - - handleChange(e) { - const value = e.target.type === "checkbox" ? e.target.checked : e.target.value; - let valueErr = false; - let errObj = this.state.errObj; - if (value == "") { - valueErr = true; - } - errObj[e.target.id] = valueErr; - this.setState({ - [e.target.id]: value, - errObj: errObj - }); - } - - render() { - const { showModal, closeHandler, backups, reload, loadingBackup } = this.props; - - let backupSpinner = ""; - if (loadingBackup) { - backupSpinner = ( - -
- - Creating instance... -
-
- ); - } - - return ( -
- -
- - - Manage Backups - - -
- -
- {backupSpinner} -
- - - - -
-
- - - - - - -
- ); - } -} - -ManageBackupsModal.propTypes = { - showModal: PropTypes.bool, - closeHandler: PropTypes.func, - addNotification: PropTypes.func, - serverId: PropTypes.string -}; - -ManageBackupsModal.defaultProps = { - showModal: false, - closeHandler: noop, - addNotification: noop, - serverId: "" -}; - export default DSInstance; diff --git a/src/cockpit/389-console/src/dsModals.jsx b/src/cockpit/389-console/src/dsModals.jsx new file mode 100644 index 0000000000..8f59e0844c --- /dev/null +++ b/src/cockpit/389-console/src/dsModals.jsx @@ -0,0 +1,1356 @@ +import cockpit from "cockpit"; +import React from "react"; +import PropTypes from "prop-types"; +import { ConfirmPopup } from "./lib/notifications.jsx"; +import { BackupTable } from "./lib/database/databaseTables.jsx"; +import { BackupModal, RestoreModal, DeleteBackupModal } from "./lib/database/backups.jsx"; +import { log_cmd, bad_file_name, valid_dn, valid_port } from "./lib/tools.jsx"; + +import { + FormControl, + FormGroup, + ControlLabel, + Form, + noop, + Checkbox, + Spinner, + Row, + Modal, + Icon, + Col, + Button +} from "patternfly-react"; +import "./css/ds.css"; + +export class CreateInstanceModal extends React.Component { + constructor(props) { + super(props); + this.state = { + createServerId: "", + createPort: 389, + createSecurePort: 636, + createDM: "cn=Directory Manager", + createDMPassword: "", + createDMPasswordConfirm: "", + createDBCheckbox: false, + createDBSuffix: "", + createDBName: "", + createTLSCert: true, + createInitDB: "noInit", + loadingCreate: false, + createOK: false, + modalMsg: "", + errObj: {}, + }; + + this.handleFieldChange = this.handleFieldChange.bind(this); + this.createInstance = this.createInstance.bind(this); + this.validInstName = this.validInstName.bind(this); + this.validRootDN = this.validRootDN.bind(this); + this.resetModal = this.resetModal.bind(this); + } + + componentDidMount() { + this.resetModal(); + } + + resetModal() { + this.setState({ + createServerId: "", + createPort: 389, + createSecurePort: 636, + createDM: "cn=Directory Manager", + createDMPassword: "", + createDMPasswordConfirm: "", + createDBCheckbox: false, + createDBSuffix: "", + createDBName: "", + createTLSCert: true, + createInitDB: "noInit", + loadingCreate: false, + createOK: false, + modalMsg: "", + errObj: { + createServerId: true, + createDMPassword: true, + createDMPasswordConfirm: true, + createDBSuffix: false, + createDBName: false, + }, + }); + } + + validInstName(name) { + return /^[\w@_:-]*$/.test(name); + } + + validRootDN(dn) { + // Validate a DN for Directory Manager. We have to be stricter than + // valid_dn() and only allow stand ascii characters for the value + if (dn.endsWith(",")) { + return false; + } + // Check that the attr is only letters [A-Za-z]+ and the value is standard + // ascii [ -~]+ that it does not start with a space \\S + let dn_regex = new RegExp("^([A-Za-z]+=\\S[ -~]+$)"); + let result = dn_regex.test(dn); + return result; + } + + handleFieldChange(e) { + let value = e.target.type === "checkbox" ? e.target.checked : e.target.value; + let target_id = e.target.id; + let valueErr = false; + let errObj = this.state.errObj; + let all_good = true; + let modal_msg = ""; + + errObj[target_id] = valueErr; + if (target_id == 'createServerId') { + if (value == "") { + all_good = false; + errObj['createServerId'] = true; + } else if (value > 80) { + all_good = false; + errObj['createServerId'] = true; + modal_msg = "Instance name must be less than 80 characters"; + } else if (!this.validInstName(value)) { + all_good = false; + errObj['createServerId'] = true; + modal_msg = "Instance name can only contain letters, numbers, and these 4 characters: - @ : _"; + } + } else if (this.state.createServerId == "") { + all_good = false; + errObj['createServerId'] = true; + } else if (!this.validInstName(this.state.createServerId)) { + all_good = false; + errObj['createServerId'] = true; + modal_msg = "Not all required fields have values"; + } + if (target_id == 'createPort') { + if (value == "") { + all_good = false; + errObj['createPort'] = true; + } else if (!valid_port(value)) { + all_good = false; + errObj['createPort'] = true; + modal_msg = "Invalid Port number. The port must be between 1 and 65534"; + } + } else if (this.state.createPort == "") { + all_good = false; + errObj['createPort'] = true; + } else if (!valid_port(this.state.createPort)) { + all_good = false; + errObj['createPort'] = true; + modal_msg = "Invalid Port number. The port must be between 1 and 65534"; + } + if (target_id == 'createSecurePort') { + if (value == "") { + all_good = false; + errObj['createSecurePort'] = true; + } else if (!valid_port(value)) { + all_good = false; + errObj['createSecurePort'] = true; + modal_msg = "Invalid Secure Port number. Port must be between 1 and 65534"; + } + } else if (this.state.createSecurePort == "") { + all_good = false; + errObj['createSecurePort'] = true; + } + if (target_id == 'createDM') { + if (value == "") { + all_good = false; + errObj['createDM'] = true; + } + if (!this.validRootDN(value)) { + all_good = false; + errObj['createDM'] = true; + modal_msg = "Invalid DN for Directory Manager"; + } + } else if (this.state.createDM == "") { + all_good = false; + errObj['createDM'] = true; + } else if (!this.validRootDN(this.state.createDM)) { + all_good = false; + errObj['createDM'] = true; + modal_msg = "Invalid DN for Directory Manager"; + } + if (e.target.id == 'createDMPassword') { + if (value == "") { + all_good = false; + errObj['createDMPassword'] = true; + } else if (value != this.state.createDMPasswordConfirm) { + all_good = false; + errObj['createDMPassword'] = true; + errObj['createDMPasswordConfirm'] = true; + modal_msg = "Passwords Do Not Match"; + } else if (value.length < 8) { + all_good = false; + errObj['createDMPassword'] = true; + modal_msg = "Directory Manager password must be at least 8 characters long"; + } else { + errObj['createDMPassword'] = false; + errObj['createDMPasswordConfirm'] = false; + } + } else if (this.state.createDMPassword == "") { + all_good = false; + errObj['createDMPasswordConfirm'] = true; + } + if (e.target.id == 'createDMPasswordConfirm') { + if (value == "") { + all_good = false; + errObj['createDMPasswordConfirm'] = true; + } else if (value != this.state.createDMPassword) { + all_good = false; + errObj['createDMPassword'] = true; + errObj['createDMPasswordConfirm'] = true; + modal_msg = "Passwords Do Not Match"; + } else if (value.length < 8) { + all_good = false; + errObj['createDMPasswordConfirm'] = true; + modal_msg = "Directory Manager password must be at least 8 characters long"; + } else { + errObj['createDMPassword'] = false; + errObj['createDMPasswordConfirm'] = false; + } + } else if (this.state.createDMPasswordConfirm == "") { + all_good = false; + errObj['createDMPasswordConfirm'] = true; + } + + // Optional settings + if (target_id == 'createDBCheckbox') { + if (!value) { + errObj['createDBSuffix'] = false; + errObj['createDBName'] = false; + } else { + if (this.state.createDBSuffix == "") { + all_good = false; + errObj['createDBSuffix'] = true; + } else if (!valid_dn(this.state.createDBSuffix)) { + all_good = false; + errObj['createDBSuffix'] = true; + modal_msg = "Invalid DN for suffix"; + } + if (this.state.createDBName == "") { + all_good = false; + errObj['createDBName'] = true; + } else if (!valid_dn(this.state.createDBName)) { + all_good = false; + errObj['createDBName'] = true; + modal_msg = "Invalid name for database"; + } + } + } else if (this.state.createDBCheckbox) { + if (target_id == 'createDBSuffix') { + if (value == "") { + all_good = false; + errObj['createDBSuffix'] = true; + } else if (!valid_dn(value)) { + all_good = false; + errObj['createDBSuffix'] = true; + modal_msg = "Invalid DN for suffix"; + } + } else if (this.state.createDBSuffix == "") { + all_good = false; + errObj['createDBSuffix'] = true; + } else if (!valid_dn(this.state.createDBSuffix)) { + all_good = false; + errObj['createDBSuffix'] = true; + modal_msg = "Invalid DN for suffix"; + } + if (target_id == 'createDBName') { + if (value == "") { + all_good = false; + errObj['createDBName'] = true; + } else if (/\s/.test(value)) { + // name has some kind of white space character + all_good = false; + errObj['createDBName'] = true; + modal_msg = "Database name can not contain any spaces"; + } + } else if (this.state.createDBName == "") { + all_good = false; + errObj['createDBName'] = true; + } else if (/\s/.test(this.state.createDBName)) { + all_good = false; + errObj['createDBName'] = true; + modal_msg = "Invalid database name"; + } + } else { + errObj['createDBSuffix'] = false; + errObj['createDBName'] = false; + } + + this.setState({ + [target_id]: value, + errObj: errObj, + createOK: all_good, + modalMsg: modal_msg, + }); + } + + createInstance() { + const { + createServerId, + createPort, + createSecurePort, + createDM, + createDMPassword, + createDBSuffix, + createDBName, + createTLSCert, + createInitDB, + createDBCheckbox + } = this.state; + const { closeHandler, addNotification, loadInstanceList } = this.props; + + let setup_inf = + "[general]\n" + + "config_version = 2\n" + + "full_machine_name = FQDN\n\n" + + "[slapd]\n" + + "user = dirsrv\n" + + "group = dirsrv\n" + + "instance_name = INST_NAME\n" + + "port = PORT\n" + + "root_dn = ROOTDN\n" + + "root_password = ROOTPW\n" + + "secure_port = SECURE_PORT\n" + + "self_sign_cert = SELF_SIGN\n"; + + // Server ID + let newServerId = createServerId; + newServerId = newServerId.replace(/^slapd-/i, ""); // strip "slapd-" + setup_inf = setup_inf.replace("INST_NAME", newServerId); + setup_inf = setup_inf.replace("PORT", createPort); + setup_inf = setup_inf.replace("SECURE_PORT", createSecurePort); + setup_inf = setup_inf.replace("ROOTDN", createDM); + setup_inf = setup_inf.replace("ROOTPW", createDMPassword); + // Setup Self-Signed Certs + if (createTLSCert) { + setup_inf = setup_inf.replace("SELF_SIGN", "True"); + } else { + setup_inf = setup_inf.replace("SELF_SIGN", "False"); + } + + if (createDBCheckbox) { + setup_inf += "\n[backend-" + createDBName + "]\nsuffix = " + createDBSuffix + "\n"; + if (createInitDB === "createSample") { + setup_inf += "sample_entries = yes\n"; + } + if (createInitDB === "createSuffix") { + setup_inf += "create_suffix_entry = yes\n"; + } + } + + /* + * Here are steps we take to create the instance + * + * [1] Get FQDN Name for nsslapd-localhost setting in setup file + * [2] Create a file for the inf setup parameters + * [3] Set strict permissions on that file + * [4] Populate the new setup file with settings (including cleartext password) + * [5] Create the instance + * [6] Remove setup file + */ + this.setState({ + loadingCreate: true + }); + cockpit + .spawn(["hostnamectl", "status", "--static"], { superuser: true, err: "message" }) + .fail(err => { + let errMsg = JSON.parse(err); + this.setState({ + loadingCreate: false + }); + addNotification("error", `Failed to get hostname!", ${errMsg.desc}`); + }) + .done(data => { + /* + * We have FQDN, so set the hostname in inf file, and create the setup file + */ + setup_inf = setup_inf.replace("FQDN", data); + let setup_file = "/tmp/389-setup-" + new Date().getTime() + ".inf"; + let rm_cmd = ["rm", setup_file]; + let create_file_cmd = ["touch", setup_file]; + log_cmd("createInstance", "Setting FQDN...", create_file_cmd); + cockpit + .spawn(create_file_cmd, { superuser: true, err: "message" }) + .fail(err => { + this.setState({ + loadingCreate: false + }); + addNotification( + "error", + `Failed to create installation file!" ${err.message}` + ); + }) + .done(_ => { + /* + * We have our new setup file, now set permissions on that setup file before we add sensitive data + */ + let chmod_cmd = ["chmod", "600", setup_file]; + log_cmd("createInstance", "Setting initial INF file permissions...", chmod_cmd); + cockpit + .spawn(chmod_cmd, { superuser: true, err: "message" }) + .fail(err => { + cockpit.spawn(rm_cmd, { superuser: true, err: "message" }); // Remove Inf file with clear text password + this.setState({ + loadingCreate: false + }); + addNotification( + "error", + `Failed to set permissions on setup file ${setup_file}: ${err.message}` + ); + }) + .done(_ => { + /* + * Success we have our setup file and it has the correct permissions. + * Now populate the setup file... + */ + let cmd = [ + "/bin/sh", + "-c", + '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file + ]; + // Do not log inf file as it contains the DM password + log_cmd("createInstance", "Apply changes to INF file...", ""); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .fail(err => { + this.setState({ + loadingCreate: false + }); + addNotification( + "error", + `Failed to populate installation file! ${err.message}` + ); + }) + .done(_ => { + /* + * Next, create the instance... + */ + let cmd = ["dscreate", "-j", "from-file", setup_file]; + log_cmd("createInstance", "Creating instance...", cmd); + cockpit + .spawn(cmd, { + superuser: true, + err: "message" + }) + .fail(err => { + let errMsg = JSON.parse(err); + cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password + this.setState({ + loadingCreate: false + }); + addNotification( + "error", + `${errMsg.desc}` + ); + }) + .done(_ => { + // Success!!! Now cleanup everything up... + log_cmd("createInstance", "Instance creation compelete, clean everything up...", rm_cmd); + cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear text password + this.setState({ + loadingCreate: false + }); + + loadInstanceList(createServerId); + addNotification( + "success", + `Successfully created instance: slapd-${createServerId}` + ); + closeHandler(); + this.resetModal(); + }); + }); + }); + }); + }); + } + + render() { + const { showModal, closeHandler } = this.props; + + const { + loadingCreate, + createServerId, + createPort, + createSecurePort, + createDM, + createDMPassword, + createDMPasswordConfirm, + createDBCheckbox, + createDBSuffix, + createDBName, + createTLSCert, + createInitDB, + createOK, + modalMsg, + errObj, + } = this.state; + let errMsgClass = ""; + let errMsg = ""; + let createSpinner = ""; + if (loadingCreate) { + createSpinner = ( + +
+ + Creating instance... +
+
+ ); + } + + if (modalMsg == "") { + // No errors, but to keep the modal nice and stable during input + // field validation we need "invisible" text to keep the modal form + // from jumping up and down. + errMsgClass = "ds-clear-text"; + errMsg = "no errors"; + } else { + // We have error text to report + errMsgClass = "ds-modal-error"; + errMsg = modalMsg; + } + + return ( + +
+ + + Create New Server Instance + + +
+ + +

{errMsg}

+ +
+ + + Instance Name + + + + + + + + Port + + + + + + + + Secure Port + + + + + + + + Create Self-Signed TLS Certificate + + + + + + + + Directory Manager DN + + + + + + + + Directory Manager Password + + + + + + + + Confirm Password + + + + + +
+ + + + Create Database + + + + + + Database Suffix + + + + + + + + Database Name + + + + + + + + Database Initialization + + + + + +
+ {createSpinner} +
+ + + + +
+
+ ); + } +} + +export class SchemaReloadModal extends React.Component { + constructor(props) { + super(props); + this.state = { + reloadSchemaDir: "", + loadingSchemaTask: false + }; + + this.reloadSchema = this.reloadSchema.bind(this); + this.handleFieldChange = this.handleFieldChange.bind(this); + } + + handleFieldChange(e) { + this.setState({ + [e.target.id]: e.target.value + }); + } + + reloadSchema(e) { + const { addNotification, serverId, closeHandler } = this.props; + const { reloadSchemaDir } = this.state; + + this.setState({ + loadingSchemaTask: true + }); + + let cmd = ["dsconf", "-j", serverId, "schema", "reload", "--wait"]; + if (reloadSchemaDir !== "") { + cmd = [...cmd, "--schemadir", reloadSchemaDir]; + } + log_cmd("reloadSchemaDir", "Reload schema files", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(data => { + addNotification("success", "Successfully reloaded schema"); + this.setState({ + loadingSchemaTask: false + }); + closeHandler(); + }) + .fail(err => { + let errMsg = JSON.parse(err); + addNotification("error", `Failed to reload schema files - ${errMsg.desc}`); + closeHandler(); + }); + } + + render() { + const { loadingSchemaTask, reloadSchemaDir } = this.state; + const { showModal, closeHandler } = this.props; + + let spinner = ""; + if (loadingSchemaTask) { + spinner = ( + +
+ + Reloading schema files... +
+
+ ); + } + + return ( + +
+ + + Reload Schema Files + + +
+ + + Schema File Directory: + + + + + + {spinner} +
+
+ + + + +
+
+ ); + } +} + +export class ManageBackupsModal extends React.Component { + constructor(props) { + super(props); + this.state = { + activeKey: 1, + showConfirmBackupDelete: false, + showConfirmBackup: false, + showConfirmRestore: false, + showConfirmRestoreReplace: false, + showConfirmLDIFReplace: false, + showRestoreSpinningModal: false, + showDelBackupSpinningModal: false, + showBackupModal: false, + backupSpinning: false, + backupName: "", + deleteBackup: "", + errObj: {} + }; + + this.handleNavSelect = this.handleNavSelect.bind(this); + this.handleChange = this.handleChange.bind(this); + + // Backups + this.doBackup = this.doBackup.bind(this); + this.deleteBackup = this.deleteBackup.bind(this); + this.restoreBackup = this.restoreBackup.bind(this); + this.showConfirmRestore = this.showConfirmRestore.bind(this); + this.closeConfirmRestore = this.closeConfirmRestore.bind(this); + this.showConfirmBackup = this.showConfirmBackup.bind(this); + this.closeConfirmBackup = this.closeConfirmBackup.bind(this); + this.showConfirmBackupDelete = this.showConfirmBackupDelete.bind(this); + this.closeConfirmBackupDelete = this.closeConfirmBackupDelete.bind(this); + this.showBackupModal = this.showBackupModal.bind(this); + this.closeBackupModal = this.closeBackupModal.bind(this); + this.showRestoreSpinningModal = this.showRestoreSpinningModal.bind(this); + this.closeRestoreSpinningModal = this.closeRestoreSpinningModal.bind(this); + this.showDelBackupSpinningModal = this.showDelBackupSpinningModal.bind(this); + this.closeDelBackupSpinningModal = this.closeDelBackupSpinningModal.bind(this); + this.validateBackup = this.validateBackup.bind(this); + this.closeConfirmRestoreReplace = this.closeConfirmRestoreReplace.bind(this); + } + + closeExportModal() { + this.setState({ + showExportModal: false + }); + } + + showDelBackupSpinningModal() { + this.setState({ + showDelBackupSpinningModal: true + }); + } + + closeDelBackupSpinningModal() { + this.setState({ + showDelBackupSpinningModal: false + }); + } + + showRestoreSpinningModal() { + this.setState({ + showRestoreSpinningModal: true + }); + } + + closeRestoreSpinningModal() { + this.setState({ + showRestoreSpinningModal: false + }); + } + + showBackupModal() { + this.setState({ + showBackupModal: true, + backupSpinning: false, + backupName: "" + }); + } + + closeBackupModal() { + this.setState({ + showBackupModal: false + }); + } + + showConfirmBackup(item) { + // call deleteLDIF + this.setState({ + showConfirmBackup: true, + backupName: item.name + }); + } + + closeConfirmBackup() { + // call importLDIF + this.setState({ + showConfirmBackup: false + }); + } + + showConfirmRestore(item) { + this.setState({ + showConfirmRestore: true, + backupName: item.name + }); + } + + closeConfirmRestore() { + // call importLDIF + this.setState({ + showConfirmRestore: false + }); + } + + showConfirmBackupDelete(item) { + // calls deleteBackup + this.setState({ + showConfirmBackupDelete: true, + backupName: item.name + }); + } + + closeConfirmBackupDelete() { + // call importLDIF + this.setState({ + showConfirmBackupDelete: false + }); + } + + closeConfirmRestoreReplace() { + this.setState({ + showConfirmRestoreReplace: false + }); + } + + validateBackup() { + for (let i = 0; i < this.props.backups.length; i++) { + if (this.state.backupName == this.props.backups[i]["name"]) { + this.setState({ + showConfirmRestoreReplace: true + }); + return; + } + } + this.doBackup(); + } + + doBackup() { + this.setState({ + backupSpinning: true + }); + + let cmd = ["dsctl", "-j", this.props.serverId, "status"]; + cockpit + .spawn(cmd, { superuser: true }) + .done(status_data => { + let status_json = JSON.parse(status_data); + if (status_json.running == true) { + let cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "backup", + "create" + ]; + if (this.state.backupName != "") { + if (bad_file_name(this.state.backupName)) { + this.props.addNotification( + "warning", + `Backup name should not be a path. All backups are stored in the server's backup directory` + ); + return; + } + cmd.push(this.state.backupName); + } + + log_cmd("doBackup", "Add backup task online", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + this.props.reload(); + this.closeBackupModal(); + this.props.addNotification("success", `Server has been backed up`); + }) + .fail(err => { + let errMsg = JSON.parse(err); + this.props.reload(); + this.closeBackupModal(); + this.props.addNotification( + "error", + `Failure backing up server - ${errMsg.desc}` + ); + }); + } else { + const cmd = ["dsctl", "-j", this.props.serverId, "db2bak"]; + if (this.state.backupName != "") { + if (bad_file_name(this.state.backupName)) { + this.props.addNotification( + "warning", + `Backup name should not be a path. All backups are stored in the server's backup directory` + ); + return; + } + cmd.push(this.state.backupName); + } + log_cmd("doBackup", "Doing backup of the server offline", cmd); + cockpit + .spawn(cmd, { superuser: true }) + .done(content => { + this.props.reload(); + this.closeBackupModal(); + this.props.addNotification("success", `Server has been backed up`); + }) + .fail(err => { + let errMsg = JSON.parse(err); + this.props.reload(); + this.closeBackupModal(); + this.props.addNotification( + "error", + `Failure backing up server - ${errMsg.desc}` + ); + }); + } + }) + .fail(err => { + let errMsg = JSON.parse(err); + console.log("Failed to check the server status", errMsg.desc); + }); + } + + restoreBackup() { + this.showRestoreSpinningModal(); + let cmd = ["dsctl", "-j", this.props.serverId, "status"]; + cockpit + .spawn(cmd, { superuser: true }) + .done(status_data => { + let status_json = JSON.parse(status_data); + if (status_json.running == true) { + const cmd = [ + "dsconf", + "-j", + "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "backup", + "restore", + this.state.backupName + ]; + log_cmd("restoreBackup", "Restoring server online", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + this.closeRestoreSpinningModal(); + this.props.addNotification("success", `Server has been restored`); + }) + .fail(err => { + let errMsg = JSON.parse(err); + this.closeRestoreSpinningModal(); + this.props.addNotification( + "error", + `Failure restoring up server - ${errMsg.desc}` + ); + }); + } else { + const cmd = [ + "dsctl", + "-j", + this.props.serverId, + "bak2db", + this.state.backupName + ]; + log_cmd("restoreBackup", "Restoring server offline", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + this.closeRestoreSpinningModal(); + this.props.addNotification("success", `Server has been restored`); + }) + .fail(err => { + let errMsg = JSON.parse(err); + this.closeRestoreSpinningModal(); + this.props.addNotification( + "error", + `Failure restoring up server - ${errMsg.desc}` + ); + }); + } + }) + .fail(err => { + let errMsg = JSON.parse(err); + console.log("Failed to check the server status", errMsg.desc); + }); + } + + deleteBackup(e) { + // Show confirmation + this.showDelBackupSpinningModal(); + + const cmd = [ + "dsctl", + "-j", + this.props.serverId, + "backups", + "--delete", + this.state.backupName + ]; + log_cmd("deleteBackup", "Deleting backup", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + this.props.reload(); + this.closeDelBackupSpinningModal(); + this.props.addNotification("success", `Backup was successfully deleted`); + }) + .fail(err => { + let errMsg = JSON.parse(err); + this.props.reload(); + this.closeDelBackupSpinningModal(); + this.props.addNotification("error", `Failure deleting backup - ${errMsg.desc}`); + }); + } + + handleNavSelect(key) { + this.setState({ activeKey: key }); + } + + handleChange(e) { + const value = e.target.type === "checkbox" ? e.target.checked : e.target.value; + let valueErr = false; + let errObj = this.state.errObj; + if (value == "") { + valueErr = true; + } + errObj[e.target.id] = valueErr; + this.setState({ + [e.target.id]: value, + errObj: errObj + }); + } + + render() { + const { showModal, closeHandler, backups, reload, loadingBackup } = this.props; + + let backupSpinner = ""; + if (loadingBackup) { + backupSpinner = ( + +
+ + Creating instance... +
+
+ ); + } + + return ( +
+ +
+ + + Manage Backups + + +
+ +
+ {backupSpinner} +
+ + + + +
+
+ + + + + + +
+ ); + } +} + +// Proptyes and defaults + +CreateInstanceModal.propTypes = { + showModal: PropTypes.bool, + closeHandler: PropTypes.func, + addNotification: PropTypes.func, + loadInstanceList: PropTypes.func +}; + +CreateInstanceModal.defaultProps = { + showModal: false, + closeHandler: noop, + addNotification: noop, + loadInstanceList: noop +}; + +SchemaReloadModal.propTypes = { + showModal: PropTypes.bool, + closeHandler: PropTypes.func, + addNotification: PropTypes.func, + serverId: PropTypes.string +}; + +SchemaReloadModal.defaultProps = { + showModal: false, + closeHandler: noop, + addNotification: noop, + serverId: "" +}; + +ManageBackupsModal.propTypes = { + showModal: PropTypes.bool, + closeHandler: PropTypes.func, + addNotification: PropTypes.func, + serverId: PropTypes.string +}; + +ManageBackupsModal.defaultProps = { + showModal: false, + closeHandler: noop, + addNotification: noop, + serverId: "" +}; diff --git a/src/cockpit/389-console/src/lib/server/ldapi.jsx b/src/cockpit/389-console/src/lib/server/ldapi.jsx index c004e639f5..ab6da3789e 100644 --- a/src/cockpit/389-console/src/lib/server/ldapi.jsx +++ b/src/cockpit/389-console/src/lib/server/ldapi.jsx @@ -200,7 +200,6 @@ export class ServerLDAPI extends React.Component { type="text" value={this.state['nsslapd-ldapientrysearchbase']} onChange={this.handleChange} - /> diff --git a/src/cockpit/389-console/src/lib/tools.jsx b/src/cockpit/389-console/src/lib/tools.jsx index ea51c73539..1a41058f24 100644 --- a/src/cockpit/389-console/src/lib/tools.jsx +++ b/src/cockpit/389-console/src/lib/tools.jsx @@ -148,7 +148,7 @@ export function valid_dn(dn) { if (dn.endsWith(",")) { return false; } - let dn_regex = new RegExp("^([A-Za-z]+=.*)"); + let dn_regex = new RegExp("^([A-Za-z])+=\\S.*"); let result = dn_regex.test(dn); return result; } diff --git a/src/lib389/cli/dscreate b/src/lib389/cli/dscreate index b9c5b48ea8..083c30c876 100755 --- a/src/lib389/cli/dscreate +++ b/src/lib389/cli/dscreate @@ -1,7 +1,7 @@ #!/usr/bin/python3 # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2018 Red Hat, Inc. +# Copyright (C) 2020 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -11,9 +11,9 @@ # PYTHON_ARGCOMPLETE_OK import argparse, argcomplete -import logging import sys import signal +import json from lib389 import DirSrv from lib389.cli_ctl import instance as cli_instance from lib389.cli_base import setup_script_logger @@ -23,6 +23,9 @@ parser = argparse.ArgumentParser() parser.add_argument('-v', '--verbose', help="Display verbose operation tracing during command execution", action='store_true', default=False, dest='verbose') +parser.add_argument('-j', '--json', + help="Return the result as a json message", + action='store_true', default=False, dest='json') subparsers = parser.add_subparsers(help="action") fromfile_parser = subparsers.add_parser('from-file', help="Create an instance of Directory Server from an inf answer file") @@ -76,7 +79,10 @@ if __name__ == '__main__': except Exception as e: log.debug(e, exc_info=True) msg = format_error_to_dict(e) - log.error("Error: %s" % " - ".join(str(val) for val in msg.values())) + if args and args.json: + sys.stderr.write(json.dumps(msg)) + else: + log.error("Error: %s" % " - ".join(str(val) for val in msg.values())) result = False # Done! diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py index 2aca136763..7dd454d6c8 100644 --- a/src/lib389/lib389/instance/setup.py +++ b/src/lib389/lib389/instance/setup.py @@ -572,7 +572,7 @@ def _prepare_ds(self, general, slapd, backends): assert_c(slapd['instance_name'] != 'admin', "Server identifier \"admin\" is reserved, please choose a different identifier") # Check that valid characters are used - safe = re.compile(r'^[#%:\w@_-]+$').search + safe = re.compile(r'^[:\w@_-]+$').search assert_c(bool(safe(slapd['instance_name'])), "Server identifier has invalid characters, please choose a different value") # Check if the instance exists or not. @@ -669,11 +669,10 @@ def create_from_args(self, general, slapd, backends=[], extra=None): self._install_ds(general, slapd, backends) except ValueError as e: if DEBUGGING is False: - self.log.fatal("Error: " + str(e) + ", removing incomplete installation...") self._remove_failed_install(slapd['instance_name']) else: - self.log.fatal("Error: " + str(e) + ", preserving incomplete installation for analysis...") - raise ValueError("Instance creation failed!") + self.log.fatal(f"Error: {str(e)}, preserving incomplete installation for analysis...") + raise ValueError(f"Instance creation failed! {str(e)}") # Call the child api to do anything it needs. self._install(extra)