This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new db11e8e Issue 51118 - UI - improve modal validation when creating an instance
db11e8e is described below
commit db11e8ecc7f964d6f7fd41b724cfb7981a1f4f69
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri May 29 13:42:43 2020 -0400
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 +-----------------
.../389-console/src/{ds.jsx => dsModals.jsx} | 1282 ++++++--------------
src/cockpit/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, 388 insertions(+), 2107 deletions(-)
diff --git a/src/cockpit/389-console/src/ds.jsx b/src/cockpit/389-console/src/ds.jsx
index 53aa5cb..691a6f2 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 = (
- <Row>
- <div className="ds-margin-top-lg ds-modal-spinner">
- <Spinner loading inline size="lg" />
- Creating instance...
- </div>
- </Row>
- );
- }
-
- return (
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title className="ds-center">Create New
Server Instance</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal>
- <FormGroup controlId="createServerId">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The instance name, this is what gets
appended to 'slapi-'. The instance name can only contain letters, numbers, and: #
% : - _"
-
- Instance Name
- </Col>
- <Col sm={7}>
- <FormControl
- id="createServerId"
- type="text"
- placeholder="Your_Instance_Name"
- value={createServerId}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createPort">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The server port number"
-
- Port
- </Col>
- <Col sm={7}>
- <FormControl
- type="number"
- min="0"
- max="65535"
- value={createPort}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createSecurePort">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The secure port number for TLS
connections"
-
- Secure Port
- </Col>
- <Col sm={7}>
- <FormControl
- type="number"
- min="0"
- max="65535"
- value={createSecurePort}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createTLSCert">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Create a self-signed certificate
database"
-
- Create Self-Signed TLS
Certificate
- </Col>
- <Col sm={7}>
- <Checkbox
- id="createTLSCert"
- checked={createTLSCert}
- onChange={this.handleFieldChange}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDM">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The DN for the unrestricted user"
-
- Directory Manager DN
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDM"
- onChange={this.handleFieldChange}
- value={createDM}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDMPassword">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Directory Manager password."
-
- Directory Manager
Password
- </Col>
- <Col sm={7}>
- <FormControl
- id="createDMPassword"
- type="password"
- placeholder="Enter password"
- onChange={this.handleFieldChange}
- value={createDMPassword}
- />
- </Col>
- </FormGroup>
- <FormGroup
controlId="createDMPasswordConfirm">
- <Col componentClass={ControlLabel} sm={5}
title="Confirm password.">
- Confirm Password
- </Col>
- <Col sm={7}>
- <FormControl
- id="createDMPasswordConfirm"
- type="password"
- placeholder="Confirm password"
- onChange={this.handleFieldChange}
- value={createDMPasswordConfirm}
- />
- </Col>
- </FormGroup>
- <hr />
- <h5 className="ds-center">Optional Database
Settings</h5>
- <FormGroup className="ds-margin-top-lg"
controlId="createDBSuffix">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="Database suffix, like
'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name (DN)"
-
- Database Suffix
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDBSuffix"
- placeholder="e.g. dc=example,dc=com"
- onChange={this.handleFieldChange}
- value={createDBSuffix}
- />
- </Col>
- </FormGroup>
- <FormGroup controlId="createDBName">
- <Col
- componentClass={ControlLabel}
- sm={5}
- title="The name for the backend database, like
'userroot'. The name can be a combination of alphanumeric characters, dashes (-),
and underscores (_). No other characters are allowed, and the name must be unique across
all backends."
-
- Database Name
- </Col>
- <Col sm={7}>
- <FormControl
- type="text"
- id="createDBName"
- placeholder="e.g. userRoot"
- onChange={this.handleFieldChange}
- value={createDBName}
- />
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBn"
- controlId="createInitDBn"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="noInit"
- name="noInit"
- inline
- checked={createInitDB === "noInit"}
- onChange={this.handleFieldChange}
-
- Do Not Initialize
Database
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBs"
- controlId="createInitDBs"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSuffix"
- name="createSuffix"
- inline
- checked={createInitDB ===
"createSuffix"}
- onChange={this.handleFieldChange}
-
- Create Suffix Entry
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBp"
- controlId="createInitDBp"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSample"
- name="createSample"
- inline
- checked={createInitDB ===
"createSample"}
- onChange={this.handleFieldChange}
-
- Create Suffix Entry
And Add Sample Entries
- </Radio>
- </Col>
- </FormGroup>
- </Form>
- {createSpinner}
- </Modal.Body>
- <Modal.Footer>
- <Button bsStyle="default"
className="btn-cancel" onClick={closeHandler}>
- Cancel
- </Button>
- <Button bsStyle="primary"
onClick={this.createInstance}>
- Create Instance
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-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 = (
- <Row>
- <div className="ds-margin-top ds-modal-spinner">
- <Spinner loading inline size="md" />
- Reloading schema files...
- </div>
- </Row>
- );
- }
-
- return (
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>Reload Schema Files</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal autoComplete="off">
- <Row title="The name of the database link.">
- <Col sm={3}>
- <ControlLabel>Schema File
Directory:</ControlLabel>
- </Col>
- <Col sm={9}>
- <FormControl
- type="text"
- id="reloadSchemaDir"
- value={reloadSchemaDir}
- onChange={this.handleFieldChange}
- />
- </Col>
- </Row>
- {spinner}
- </Form>
- </Modal.Body>
- <Modal.Footer>
- <Button bsStyle="default"
className="btn-cancel" onClick={closeHandler}>
- Cancel
- </Button>
- <Button bsStyle="primary"
onClick={this.reloadSchema}>
- Reload Schema
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-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 = (
- <Row>
- <div className="ds-margin-top-lg ds-modal-spinner">
- <Spinner loading inline size="lg" />
- Creating instance...
- </div>
- </Row>
- );
- }
-
- return (
- <div>
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close"
/>
- </button>
- <Modal.Title>Manage Backups</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <div className="ds-margin-top-xlg">
- <BackupTable
- rows={backups}
- confirmRestore={this.showConfirmRestore}
- confirmDelete={this.showConfirmBackupDelete}
- />
- </div>
- {backupSpinner}
- </Modal.Body>
- <Modal.Footer>
- <Button
- bsStyle="primary"
- onClick={this.showBackupModal}
- className="ds-margin-top"
- >
- Create Backup
- </Button>
- <Button
- bsStyle="default"
- onClick={reload}
- className="ds-left-margin ds-margin-top"
- >
- Refresh Backups
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- <BackupModal
- showModal={this.state.showBackupModal}
- closeHandler={this.closeBackupModal}
- handleChange={this.handleChange}
- saveHandler={this.validateBackup}
- spinning={this.state.backupSpinning}
- error={this.state.errObj}
- />
- <RestoreModal
- showModal={this.state.showRestoreSpinningModal}
- closeHandler={this.closeRestoreSpinningModal}
- msg={this.state.backupName}
- />
- <DeleteBackupModal
- showModal={this.state.showDelBackupSpinningModal}
- closeHandler={this.closeDelBackupSpinningModal}
- msg={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmRestore}
- closeHandler={this.closeConfirmRestore}
- actionFunc={this.restoreBackup}
- actionParam={this.state.backupName}
- msg="Are you sure you want to restore this backup?"
- msgContent={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmBackupDelete}
- closeHandler={this.closeConfirmBackupDelete}
- actionFunc={this.deleteBackup}
- actionParam={this.state.backupName}
- msg="Are you sure you want to delete this backup?"
- msgContent={this.state.backupName}
- />
- <ConfirmPopup
- showModal={this.state.showConfirmRestoreReplace}
- closeHandler={this.closeConfirmRestoreReplace}
- actionFunc={this.doBackup}
- msg="Replace Existing Backup"
- msgContent="A backup already eixsts with the same name, do you
want to replace it?"
- />
- </div>
- );
- }
-}
-
-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/ds.jsx
b/src/cockpit/389-console/src/dsModals.jsx
similarity index 53%
copy from src/cockpit/389-console/src/ds.jsx
copy to src/cockpit/389-console/src/dsModals.jsx
index 53aa5cb..8f59e08 100644
--- a/src/cockpit/389-console/src/ds.jsx
+++ b/src/cockpit/389-console/src/dsModals.jsx
@@ -1,29 +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 { 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 } from "./lib/tools.jsx";
+import { log_cmd, bad_file_name, valid_dn, valid_port } from
"./lib/tools.jsx";
+
import {
- Nav,
- NavItem,
- DropdownButton,
- MenuItem,
- TabContainer,
- TabContent,
- TabPane,
- ProgressBar,
FormControl,
FormGroup,
ControlLabel,
- Radio,
Form,
noop,
Checkbox,
@@ -35,725 +21,272 @@ import {
Button
} from "patternfly-react";
import "./css/ds.css";
-import "./css/branding.css";
-
-const staticStates = {
- noPackage: (
- <h3>
- There is no <b>389-ds-base</b> package installed on this system.
Sorry there is nothing
- to manage...
- </h3>
- ),
- noInsts: <h3>There are no Directory Server instances to manage</h3>,
- notRunning: (
- <h3>
- This server instance is not running, either start it from the
<b>Actions</b> dropdown
- menu, or choose a different instance
- </h3>
- ),
- notConnecting: (
- <h3>
- This server instance is running, but we can not connect to it. Check LDAPI is
properly
- configured on this instance.
- </h3>
- )
-};
-
-export class DSInstance extends React.Component {
- componentDidMount() {
- this.loadInstanceList();
- this.updateProgress(25);
- }
+export class CreateInstanceModal extends React.Component {
constructor(props) {
super(props);
this.state = {
- pageLoadingState: { state: "loading", jsx: "" },
- serverId: "",
- instList: [],
- backupRows: [],
- notifications: [],
- activeKey: 1,
- wasActiveList: [1],
- progressValue: 0,
- loadingOperate: false,
-
- showDeleteConfirm: false,
- modalSpinning: false,
- modalChecked: false,
-
- showSchemaReloadModal: false,
- showManageBackupsModal: false,
- showCreateInstanceModal: false
+ 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.handleServerIdChange = this.handleServerIdChange.bind(this);
this.handleFieldChange = this.handleFieldChange.bind(this);
- this.addNotification = this.addNotification.bind(this);
- this.removeNotification = this.removeNotification.bind(this);
- this.handleNavSelect = this.handleNavSelect.bind(this);
- this.loadInstanceList = this.loadInstanceList.bind(this);
- this.loadBackups = this.loadBackups.bind(this);
- this.setServerId = this.setServerId.bind(this);
- this.updateProgress = this.updateProgress.bind(this);
- this.openCreateInstanceModal = this.openCreateInstanceModal.bind(this);
- this.closeCreateInstanceModal = this.closeCreateInstanceModal.bind(this);
- this.operateInstance = this.operateInstance.bind(this);
- this.openManageBackupsModal = this.openManageBackupsModal.bind(this);
- this.closeManageBackupsModal = this.closeManageBackupsModal.bind(this);
- this.openSchemaReloadModal = this.openSchemaReloadModal.bind(this);
- this.closeSchemaReloadModal = this.closeSchemaReloadModal.bind(this);
- this.removeInstance = this.removeInstance.bind(this);
- this.showDeleteConfirm = this.showDeleteConfirm.bind(this);
- this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
- }
-
- updateProgress(value) {
- this.setState(
- prevState => ({
- progressValue: prevState.progressValue + value
- }),
- () => {
- if (this.state.progressValue > 100) {
- this.setState(prevState => ({
- pageLoadingState: {
- ...prevState.pageLoadingState,
- state: "success"
- }
- }));
- }
- }
- );
- }
-
- setServerId(serverId, action) {
- // First we need to check if the instance is alive and well
- let cmd = ["dsctl", "-j", serverId, "status"];
- log_cmd("setServerId", "Test if instance is running ", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(status_data => {
- let status_json = JSON.parse(status_data);
- if (status_json.running) {
- this.updateProgress(25);
- let cmd = [
- "dsconf",
- "-j",
- "ldapi://%2fvar%2frun%2fslapd-" + serverId +
".socket",
- "backend",
- "suffix",
- "list",
- "--suffix"
- ];
- log_cmd("setServerId", "Test if instance is alive
", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message"
})
- .done(_ => {
- this.updateProgress(25);
- this.setState(
- {
- serverId: serverId
- },
- () => {
- this.loadBackups();
- }
- );
- if (action === "restart") {
- this.setState(
- {
- serverId: ""
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("setServerId failed: ",
errMsg.desc);
- this.setState(
- {
- pageLoadingState: {
- state: "notConnecting",
- jsx:
staticStates["notConnecting"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- });
- } else {
- this.setState(
- {
- pageLoadingState: {
- state: "notRunning",
- jsx: staticStates["notRunning"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- console.log("setServerId failed: ", errMsg.desc);
- this.setState(
- {
- pageLoadingState: {
- state: "notConnecting",
- jsx: staticStates["notConnecting"]
- }
- },
- () => {
- this.setState({
- serverId: serverId
- });
- }
- );
- });
- }
-
- loadInstanceList(serverId, action) {
- if (serverId === undefined) {
- this.setState(prevState => ({
- pageLoadingState: {
- ...prevState.pageLoadingState,
- state: "loading"
- }
- }));
- }
- let cmd = ["dsctl", "-l", "-j"];
- log_cmd("loadInstanceList", "Load the instance list select",
cmd);
- cockpit
- .spawn(cmd, { superuser: true })
- .done(data => {
- this.updateProgress(25);
- let myObject = JSON.parse(data);
- this.setState({
- instList: myObject.insts,
- loadingOperate: false
- });
- // Set default value for the inst select
- if (serverId !== undefined && serverId !== "") {
- this.setState({
- wasActiveList: [this.state.activeKey]
- });
- this.setServerId(serverId, action);
- } else {
- if (myObject.insts.length > 0) {
- this.setState({
- wasActiveList: [this.state.activeKey]
- });
-
this.setServerId(myObject.insts[0].replace("slapd-", ""), action);
- } else {
- this.setState({
- serverId: "",
- pageLoadingState: {
- state: "noInsts",
- jsx: staticStates["noInsts"]
- }
- });
- }
- }
- })
- .fail(_ => {
- this.setState({
- instList: [],
- serverId: "",
- loadingOperate: false,
- pageLoadingState: {
- state: "noInsts",
- jsx: staticStates["noInsts"]
- }
- });
- });
- }
-
- loadBackups() {
- const cmd = ["dsctl", "-j", this.state.serverId,
"backups"];
- log_cmd("loadBackupsDSInstance", "Load Backups", cmd);
- cockpit.spawn(cmd, { superuser: true, err: "message" }).done(content
=> {
- this.updateProgress(25);
- const config = JSON.parse(content);
- let rows = [];
- for (let row of config.items) {
- rows.push({ name: row[0], date: [row[1]], size: [row[2]] });
- }
- this.setState({
- backupRows: rows,
- });
- });
+ this.createInstance = this.createInstance.bind(this);
+ this.validInstName = this.validInstName.bind(this);
+ this.validRootDN = this.validRootDN.bind(this);
+ this.resetModal = this.resetModal.bind(this);
}
- addNotification(type, message, timerdelay, persistent) {
- this.setState(prevState => ({
- notifications: [
- ...prevState.notifications,
- {
- key: prevState.notifications.length + 1,
- type: type,
- persistent: persistent,
- timerdelay: timerdelay,
- message: message
- }
- ]
- }));
+ componentDidMount() {
+ this.resetModal();
}
- removeNotification(notificationToRemove) {
+ resetModal() {
this.setState({
- notifications: this.state.notifications.filter(
- notification => notificationToRemove.key !== notification.key
- )
+ 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,
+ },
});
}
- handleNavSelect(key) {
- this.setState({
- activeKey: key
- });
- const { wasActiveList } = this.state;
- if (!wasActiveList.includes(key)) {
- let newList = wasActiveList.concat(key);
- this.setState({
- wasActiveList: newList
- });
- }
+ validInstName(name) {
+ return /^[\w@_:-]*$/.test(name);
}
- handleServerIdChange(e) {
- this.setState({
- pageLoadingState: { state: "loading", jsx: "" },
- progressValue: 25,
- serverId: e.target.value
- });
- this.loadInstanceList(e.target.value);
+ 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;
- if (e.target.type === "number") {
- if (e.target.value) {
- value = parseInt(e.target.value);
- } else {
- value = 1;
+ 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";
}
- this.setState({
- [e.target.id]: value
- });
- }
-
- removeInstance() {
- this.operateInstance();
- this.closeDeleteConfirm();
- }
-
- operateInstance(e) {
- this.setState({
- loadingOperate: true
- });
-
- let action = "remove";
- if (e !== undefined) {
- action = e.target.id.split("-")[0];
+ 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";
}
-
- let cmd = ["dsctl", "-j", this.state.serverId, action];
- if (action === "remove") {
- cmd = [...cmd, "--do-it"];
+ 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;
}
- log_cmd("operateInstance", `Do ${action} the instance`, cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(_ => {
- if (action === "remove") {
- this.loadInstanceList();
- } else {
- this.loadInstanceList(this.state.serverId, action);
- }
- if (action === "remove") {
- this.addNotification("success", "Instance was
successfully removed");
- } else {
- this.addNotification("success", `Instance was
successfully ${action}ed`);
- }
- })
- .fail(err => {
- let errMsg = JSON.parse(err);
- this.addNotification(
- "error",
- `Error during instance ${action} operation - ${errMsg.desc}`
- );
- this.loadInstanceList(this.state.serverId, action);
- });
- }
-
- openCreateInstanceModal() {
- this.setState({
- showCreateInstanceModal: true
- });
- }
-
- closeCreateInstanceModal() {
- this.setState({
- showCreateInstanceModal: false
- });
- }
-
- openManageBackupsModal() {
- this.setState({
- showManageBackupsModal: true
- });
- }
-
- closeManageBackupsModal() {
- this.setState({
- showManageBackupsModal: false
- });
- }
-
- openSchemaReloadModal() {
- this.setState({
- showSchemaReloadModal: true
- });
- }
-
- closeSchemaReloadModal() {
- this.setState({
- showSchemaReloadModal: false
- });
- }
-
- showDeleteConfirm() {
- this.setState({
- showDeleteConfirm: true,
- modalSpinning: false,
- modalChecked: false
- });
- }
-
- closeDeleteConfirm() {
- this.setState({
- showDeleteConfirm: false,
- modalSpinning: false,
- modalChecked: false
- });
- }
-
- render() {
- const {
- instList,
- serverId,
- progressValue,
- notifications,
- pageLoadingState,
- loadingOperate
- } = this.state;
-
- let mainContent = "";
- if (pageLoadingState.state === "loading") {
- mainContent = (
- <div id="loading-instances" className="all-pages
ds-center">
- <div id="loading-page" className="ds-center
ds-loading">
- <h4 id="loading-msg">Loading Directory Server
Configuration...</h4>
- <p className="ds-margin-top-lg">
- <span className="spinner spinner-lg
spinner-inline" />
- </p>
- <div className="progress ds-margin-top-lg">
- <ProgressBar active now={progressValue}
label={`${progressValue}%`} />
- </div>
- </div>
- </div>
- );
- } else if (pageLoadingState.state === "noInsts") {
- mainContent = (
- <div id="noInsts" className="all-pages
ds-center">
- {pageLoadingState.jsx}
- <p>
- <Button
- id="no-inst-create-btn"
- bsStyle="primary"
- onClick={this.openCreateInstanceModal}
- >
- Create New Instance
- </Button>
- </p>
- </div>
- );
- } else {
- mainContent = (
- <div id={pageLoadingState.state} className="all-pages
ds-center">
- {pageLoadingState.jsx}
- </div>
- );
+ 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";
}
-
- let operateSpinner = "";
- if (loadingOperate) {
- operateSpinner = <Spinner className="ds-operate-spinner" loading
inline size="md" />;
+ 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;
}
- return (
- <div>
- <NotificationController
- notifications={notifications}
- removeNotificationAction={this.removeNotification}
- />
- {pageLoadingState.state !== "loading" &&
- pageLoadingState.state !== "noInsts" &&
- pageLoadingState.state !== "noPackage" ? (
- <div className="ds-logo" hidden={pageLoadingState.state
=== "loading"}>
- <h2 className="ds-logo-style"
id="main-banner">
- <div className="dropdown ds-server-action">
- <select
- className="btn btn-default dropdown"
- title="Directory Server Instance List"
- id="serverId"
- value={serverId}
- onChange={this.handleServerIdChange}
-
-
{Object.entries(instList).map(([_, inst]) => (
- <option key={inst}
value={inst.replace("slapd-", "")}>
- {inst}
- </option>
- ))}
- </select>
- </div>
- {operateSpinner}
- <div className="dropdown ds-float-right">
- <DropdownButton
- pullRight
- id="ds-action"
- className="ds-action-button"
- bsStyle="primary"
- title="Actions"
-
- <MenuItem
- id="start-ds"
- eventKey="1"
- onClick={this.operateInstance}
-
- Start Instance
- </MenuItem>
- <MenuItem
- id="stop-ds"
- eventKey="2"
- onClick={this.operateInstance}
-
- Stop Instance
- </MenuItem>
- <MenuItem
- id="restart-ds"
- eventKey="3"
- onClick={this.operateInstance}
-
- Restart Instance
- </MenuItem>
- <MenuItem
- id="manage-backup-ds"
- eventKey="4"
- onClick={this.openManageBackupsModal}
-
- Manage Backups
- </MenuItem>
- <MenuItem
- id="reload-schema-ds"
- eventKey="5"
- onClick={this.openSchemaReloadModal}
-
- Reload Schema Files
- </MenuItem>
- <MenuItem
- id="remove-ds"
- eventKey="6"
- onClick={this.showDeleteConfirm}
-
- Remove Instance
- </MenuItem>
- <MenuItem
- id="create-ds"
- eventKey="7"
- onClick={this.openCreateInstanceModal}
-
- Create Instance
- </MenuItem>
- </DropdownButton>
- </div>
- </h2>
- </div>
- ) : (
- <div />
- )}
- {serverId !== "" &&
- (pageLoadingState.state === "success" || pageLoadingState.state
=== "loading") ? (
- <div>
- <div hidden={pageLoadingState.state ===
"loading"}>
- <TabContainer
- id="basic-tabs-pf"
- onSelect={this.handleNavSelect}
- activeKey={this.state.activeKey}
- >
- <div>
- <Nav className="nav nav-tabs nav-tabs-pf
collapse navbar-collapse navbar-collapse-5 ds-nav navbar navbar-default">
- <NavItem className="ds-tab-main"
eventKey={1}>
- Server Settings
- </NavItem>
- <NavItem className="ds-tab-main"
eventKey={2}>
- Database
- </NavItem>
- <NavItem className="ds-tab-main"
eventKey={3}>
- Replication
- </NavItem>
- <NavItem className="ds-tab-main"
eventKey={4}>
- Schema
- </NavItem>
- <NavItem className="ds-tab-main"
eventKey={5}>
- Plugins
- </NavItem>
- <NavItem className="ds-tab-main"
eventKey={6}>
- Monitoring
- </NavItem>
- </Nav>
- <TabContent>
- <TabPane eventKey={1}>
- <Server
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={2}>
- <Database
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={3}>
- <Replication
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={4}>
- <Schema
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={5}>
- <Plugins
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- <TabPane eventKey={6}>
- <Monitor
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- wasActiveList={this.state.wasActiveList}
- key={this.state.serverId}
- />
- </TabPane>
- </TabContent>
- </div>
- </TabContainer>
- </div>
- <div hidden={pageLoadingState.state !==
"loading"}>{mainContent}</div>
- </div>
- ) : (
- <div>{mainContent}</div>
- )}
- <CreateInstanceModal
- showModal={this.state.showCreateInstanceModal}
- closeHandler={this.closeCreateInstanceModal}
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- loadInstanceList={this.loadInstanceList}
- />
- <SchemaReloadModal
- showModal={this.state.showSchemaReloadModal}
- closeHandler={this.closeSchemaReloadModal}
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- />
- <ManageBackupsModal
- addNotification={this.addNotification}
- serverId={this.state.serverId}
- showModal={this.state.showManageBackupsModal}
- closeHandler={this.closeManageBackupsModal}
- handleChange={this.handleFieldChange}
- backups={this.state.backupRows}
- reload={this.loadBackups}
- />
- <DoubleConfirmModal
- showModal={this.state.showDeleteConfirm}
- closeHandler={this.closeDeleteConfirm}
- handleChange={this.handleFieldChange}
- actionHandler={this.removeInstance}
- spinning={this.state.modalSpinning}
- item={this.state.serverId}
- checked={this.state.modalChecked}
- mTitle="Remove Instance"
- mMsg="Are you really sure you want to delete this
instance?"
- mSpinningMsg="Removing Instance..."
- mBtnName="Remove Instance"
- />
- </div>
- );
- }
-}
-
-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);
+ // Optional settings
+ if (target_id == 'createDBCheckbox') {
+ if (!value) {
+ errObj['createDBSuffix'] = false;
+ errObj['createDBName'] = false;
} else {
- value = 1;
+ 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({
- [e.target.id]: value
+ [target_id]: value,
+ errObj: errObj,
+ createOK: all_good,
+ modalMsg: modal_msg,
});
}
@@ -764,11 +297,11 @@ class CreateInstanceModal extends React.Component {
createSecurePort,
createDM,
createDMPassword,
- createDMPasswordConfirm,
createDBSuffix,
createDBName,
createTLSCert,
- createInitDB
+ createInitDB,
+ createDBCheckbox
} = this.state;
const { closeHandler, addNotification, loadInstanceList } = this.props;
@@ -788,56 +321,12 @@ class CreateInstanceModal extends React.Component {
// 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_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");
@@ -845,59 +334,13 @@ class CreateInstanceModal extends React.Component {
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 (createDBCheckbox) {
+ setup_inf += "\n[backend-" + createDBName + "]\nsuffix =
" + createDBSuffix + "\n";
if (createInitDB === "createSample") {
- setup_inf += "\nsample_entries = yes\n";
+ setup_inf += "sample_entries = yes\n";
}
if (createInitDB === "createSuffix") {
- setup_inf += "\ncreate_suffix_entry = yes\n";
+ setup_inf += "create_suffix_entry = yes\n";
}
}
@@ -915,7 +358,6 @@ class CreateInstanceModal extends React.Component {
loadingCreate: true
});
cockpit
-
.spawn(["hostnamectl", "status",
"--static"], { superuser: true, err: "message" })
.fail(err => {
let errMsg = JSON.parse(err);
@@ -925,89 +367,91 @@ class CreateInstanceModal extends React.Component {
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
- */
+ /*
+ * 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 => {
- let errMsg = JSON.parse(err);
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to create installation file!"
${errMsg.desc}`
+ `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
- */
+ * 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 => {
- let errMsg = JSON.parse(err);
- cockpit.spawn(rm_cmd, { superuser: true });
// Remove Inf file with clear text password
+ cockpit.spawn(rm_cmd, { superuser: true, err:
"message" }); // Remove Inf file with clear text password
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to set permission on setup file
${setup_file}: ${
- errMsg.desc
- }`
+ `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...
- */
+ * 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 => {
- let errMsg = JSON.parse(err);
this.setState({
loadingCreate: false
});
addNotification(
"error",
- `Failed to populate
installation file! ${errMsg.desc}`
+ `Failed to populate
installation file! ${err.message}`
);
})
.done(_ => {
/*
- * Next, create the instance...
- */
- let cmd = ["dscreate",
"from-file", setup_file];
+ * 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(_ => {
+ .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
create instance!"
+ `${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
@@ -1019,6 +463,7 @@ class CreateInstanceModal extends React.Component {
`Successfully
created instance: slapd-${createServerId}`
);
closeHandler();
+ this.resetModal();
});
});
});
@@ -1037,12 +482,17 @@ class CreateInstanceModal extends React.Component {
createDM,
createDMPassword,
createDMPasswordConfirm,
+ createDBCheckbox,
createDBSuffix,
createDBName,
createTLSCert,
- createInitDB
+ createInitDB,
+ createOK,
+ modalMsg,
+ errObj,
} = this.state;
-
+ let errMsgClass = "";
+ let errMsg = "";
let createSpinner = "";
if (loadingCreate) {
createSpinner = (
@@ -1055,6 +505,18 @@ class CreateInstanceModal extends React.Component {
);
}
+ 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 (
<Modal show={showModal} onHide={closeHandler}>
<div className="ds-no-horizontal-scrollbar">
@@ -1071,11 +533,16 @@ class CreateInstanceModal extends React.Component {
</Modal.Header>
<Modal.Body>
<Form horizontal>
- <FormGroup controlId="createServerId">
+ <Row>
+ <Col className="ds-center" sm={12}>
+ <p className={errMsgClass}>{errMsg}</p>
+ </Col>
+ </Row>
+ <FormGroup controlId="createServerId"
className="ds-margin-top-lg">
<Col
componentClass={ControlLabel}
sm={5}
- title="The instance name, this is what gets
appended to 'slapi-'. The instance name can only contain letters, numbers, and: #
% : - _"
+ title="The instance name, this is what gets
appended to 'slapi-'. The instance name can only contain letters, numbers, and: #
@ : - _"
Instance Name
</Col>
@@ -1086,6 +553,7 @@ class CreateInstanceModal extends React.Component {
placeholder="Your_Instance_Name"
value={createServerId}
onChange={this.handleFieldChange}
+ className={errObj.createServerId ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1099,11 +567,13 @@ class CreateInstanceModal extends React.Component {
</Col>
<Col sm={7}>
<FormControl
+ id="createPort"
type="number"
min="0"
max="65535"
value={createPort}
onChange={this.handleFieldChange}
+ className={errObj.createPort ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1117,11 +587,13 @@ class CreateInstanceModal extends React.Component {
</Col>
<Col sm={7}>
<FormControl
+ id="createSecurePort"
type="number"
min="0"
max="65535"
value={createSecurePort}
onChange={this.handleFieldChange}
+ className={errObj.createSecurePort ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1155,6 +627,7 @@ class CreateInstanceModal extends React.Component {
id="createDM"
onChange={this.handleFieldChange}
value={createDM}
+ className={errObj.createDM ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1173,6 +646,7 @@ class CreateInstanceModal extends React.Component {
placeholder="Enter password"
onChange={this.handleFieldChange}
value={createDMPassword}
+ className={errObj.createDMPassword ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1187,11 +661,22 @@ class CreateInstanceModal extends React.Component {
placeholder="Confirm password"
onChange={this.handleFieldChange}
value={createDMPasswordConfirm}
+ className={errObj.createDMPasswordConfirm ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
<hr />
- <h5 className="ds-center">Optional Database
Settings</h5>
+ <FormGroup controlId="createDBCheckbox">
+ <Col componentClass={ControlLabel} sm={5}
title="Confirm password.">
+ <Checkbox
+ id="createDBCheckbox"
+ checked={createDBCheckbox}
+ onChange={this.handleFieldChange}
+
+ Create Database
+ </Checkbox>
+ </Col>
+ </FormGroup>
<FormGroup className="ds-margin-top-lg"
controlId="createDBSuffix">
<Col
componentClass={ControlLabel}
@@ -1207,6 +692,8 @@ class CreateInstanceModal extends React.Component {
placeholder="e.g. dc=example,dc=com"
onChange={this.handleFieldChange}
value={createDBSuffix}
+ disabled={!createDBCheckbox}
+ className={errObj.createDBSuffix ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
@@ -1225,61 +712,30 @@ class CreateInstanceModal extends React.Component {
placeholder="e.g. userRoot"
onChange={this.handleFieldChange}
value={createDBName}
+ disabled={!createDBCheckbox}
+ className={errObj.createDBName ?
"ds-input-bad" : ""}
/>
</Col>
</FormGroup>
<FormGroup
- key="createInitDBn"
- controlId="createInitDBn"
- disabled={false}
+ key="createInitDB"
+ controlId="createInitDB"
- <Col smOffset={5}
sm={7}>
- <Radio
- id="createInitDB"
- value="noInit"
- name="noInit"
- inline
- checked={createInitDB === "noInit"}
- onChange={this.handleFieldChange}
-
- Do Not Initialize
Database
- </Radio>
+ <Col componentClass={ControlLabel} sm={5}>
+ Database Initialization
</Col>
- </FormGroup>
- <FormGroup
- key="createInitDBs"
- controlId="createInitDBs"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
- id="createInitDB"
- value="createSuffix"
- name="createSuffix"
- inline
- checked={createInitDB ===
"createSuffix"}
- onChange={this.handleFieldChange}
-
- Create Suffix Entry
- </Radio>
- </Col>
- </FormGroup>
- <FormGroup
- key="createInitDBp"
- controlId="createInitDBp"
- disabled={false}
- >
- <Col smOffset={5} sm={7}>
- <Radio
+ <Col sm={7}>
+ <select
+ className="btn btn-default dropdown"
id="createInitDB"
- value="createSample"
- name="createSample"
- inline
- checked={createInitDB ===
"createSample"}
onChange={this.handleFieldChange}
+ disabled={!createDBCheckbox}
+ value={createInitDB}
- Create Suffix Entry
And Add Sample Entries
- </Radio>
+ <option value="noInit">Do Not
Initialize Database</option>
+ <option
value="createSuffix">Create Suffix Entry</option>
+ <option
value="createSample">Create Sample Entries</option>
+ </select>
</Col>
</FormGroup>
</Form>
@@ -1289,7 +745,11 @@ class CreateInstanceModal extends React.Component {
<Button bsStyle="default"
className="btn-cancel" onClick={closeHandler}>
Cancel
</Button>
- <Button bsStyle="primary"
onClick={this.createInstance}>
+ <Button
+ bsStyle="primary"
+ onClick={this.createInstance}
+ disabled={!createOK}
+ >
Create Instance
</Button>
</Modal.Footer>
@@ -1299,20 +759,6 @@ class CreateInstanceModal extends React.Component {
}
}
-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);
@@ -1422,21 +868,7 @@ export class SchemaReloadModal extends React.Component {
}
}
-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 {
+export class ManageBackupsModal extends React.Component {
constructor(props) {
super(props);
this.state = {
@@ -1879,6 +1311,36 @@ class ManageBackupsModal extends React.Component {
}
}
+// 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,
@@ -1892,5 +1354,3 @@ ManageBackupsModal.defaultProps = {
addNotification: noop,
serverId: ""
};
-
-export default DSInstance;
diff --git a/src/cockpit/389-console/src/lib/server/ldapi.jsx
b/src/cockpit/389-console/src/lib/server/ldapi.jsx
index c004e63..ab6da37 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}
-
/>
</Col>
</Row>
diff --git a/src/cockpit/389-console/src/lib/tools.jsx
b/src/cockpit/389-console/src/lib/tools.jsx
index ea51c73..1a41058 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 b9c5b48..083c30c 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 2aca136..7dd454d 100644
--- a/src/lib389/lib389/instance/setup.py
+++ b/src/lib389/lib389/instance/setup.py
@@ -572,7 +572,7 @@ class SetupDs(object):
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 @@ class SetupDs(object):
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)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.