├── .copr └── Makefile ├── .github └── workflows │ └── check-patch.yml ├── .gitignore ├── LICENSE ├── Makefile.am ├── README.md ├── autogen.sh ├── automation ├── build-artifacts.packages ├── build-artifacts.sh ├── build.packages ├── check-merged.packages ├── check-merged.sh ├── check-patch.packages └── check-patch.sh ├── cockpit-ovirt.spec.in ├── config └── commit-template.txt ├── configure.ac ├── dashboard ├── .babelrc.js ├── .eslintrc ├── Makefile.am ├── README.md ├── package.json ├── src │ ├── app.js │ ├── components │ │ ├── App.js │ │ ├── Dashboard.js │ │ ├── GlusterManagement.js │ │ ├── HeSetupFooter.js.in │ │ ├── HostedEngine.js │ │ ├── HostedEngineSetup.js │ │ ├── HostedEngineSetup │ │ │ ├── AnsiblePhaseExecution │ │ │ │ ├── AnsiblePhaseExecution.js │ │ │ │ └── AnsiblePhaseExecutionContainer.js │ │ │ ├── AnsiblePhasePreview │ │ │ │ ├── AnsiblePhasePreview.js │ │ │ │ └── AnsiblePhasePreviewContainer.js │ │ │ ├── EngineStep │ │ │ │ ├── HeWizardEngine.js │ │ │ │ └── HeWizardEngineContainer.js │ │ │ ├── Execution │ │ │ │ ├── DeploymentStatus.js │ │ │ │ └── DeploymentSuccessPanel.js │ │ │ ├── ExecutionStep │ │ │ │ ├── HeWizardExecution.js │ │ │ │ └── HeWizardExecutionContainer.js │ │ │ ├── HeSetup │ │ │ │ ├── HeSetup.js │ │ │ │ ├── HeSetupContainer.js │ │ │ │ ├── HeSetupInput │ │ │ │ │ ├── HeSetupInputPanel.js │ │ │ │ │ └── HeSetupInputPanelContainer.js │ │ │ │ ├── HeSetupMessagePanel.js │ │ │ │ ├── HeSetupNoPermissionsPanel.js │ │ │ │ ├── HeSetupOutputPanel.js │ │ │ │ ├── HeSetupSuccessPanel.js │ │ │ │ └── RestartButton.js │ │ │ ├── HeSetupWizard │ │ │ │ ├── HeSetupWizard.js │ │ │ │ └── HeSetupWizardContainer.js │ │ │ ├── MultiRowTextBox │ │ │ │ ├── InputRow.js │ │ │ │ ├── InputRowContainer.js │ │ │ │ ├── MultiRowTextBox.js │ │ │ │ └── MultiRoxTextBoxContainer.js │ │ │ ├── NetworkStep │ │ │ │ ├── HeWizardNetwork.js │ │ │ │ └── HeWizardNetworkContainer.js │ │ │ ├── PreviewStep │ │ │ │ ├── HeWizardPreview.js │ │ │ │ └── HeWizardPreviewContainer.js │ │ │ ├── ReviewStep │ │ │ │ ├── ReviewItem.js │ │ │ │ ├── ReviewItemList.js │ │ │ │ ├── ReviewStep.js │ │ │ │ └── ReviewStepPanel.js │ │ │ ├── StorageStep │ │ │ │ ├── HeWizardStorage.js │ │ │ │ ├── HeWizardStorageContainer.js │ │ │ │ └── iSCSI │ │ │ │ │ ├── Lun │ │ │ │ │ ├── Lun.js │ │ │ │ │ ├── LunContainer.js │ │ │ │ │ └── LunProp │ │ │ │ │ │ └── LunProp.js │ │ │ │ │ ├── LunList │ │ │ │ │ ├── LunList.js │ │ │ │ │ └── LunListContainer.js │ │ │ │ │ ├── Portal │ │ │ │ │ ├── Portal.js │ │ │ │ │ └── PortalContainer.js │ │ │ │ │ ├── PortalList │ │ │ │ │ ├── PortalList.js │ │ │ │ │ └── PortalListContainer.js │ │ │ │ │ ├── Target │ │ │ │ │ ├── Target.js │ │ │ │ │ └── TargetContainer.js │ │ │ │ │ ├── TargetList │ │ │ │ │ ├── TargetList.js │ │ │ │ │ └── TargetListContainer.js │ │ │ │ │ ├── TargetPortalGroup │ │ │ │ │ ├── TargetPortalGroup.js │ │ │ │ │ └── TargetPortalGroupContainer.js │ │ │ │ │ └── TargetPortalGroupList │ │ │ │ │ ├── TargetPortalGroupList.js │ │ │ │ │ └── TargetPortalGroupListContainer.js │ │ │ ├── UnmaskablePassword.js │ │ │ ├── Validation.js │ │ │ ├── VmStep │ │ │ │ ├── HeWizardVm.js │ │ │ │ ├── HeWizardVmComponents │ │ │ │ │ └── CheckboxWithInfo.js │ │ │ │ └── HeWizardVmContainer.js │ │ │ └── constants.js │ │ ├── ansible │ │ │ ├── Ansible-Wizard-Bricks.js │ │ │ ├── Ansible-Wizard-Execution.js │ │ │ ├── Ansible-Wizard-Fqdns.js │ │ │ ├── Ansible-Wizard-Hosts.js │ │ │ ├── Ansible-Wizard-Packages.js │ │ │ ├── Ansible-Wizard-Preview.js │ │ │ ├── Ansible-Wizard-Volumes.js │ │ │ ├── AnsibleSetup.js │ │ │ ├── CreateGlusterVolume.js │ │ │ ├── ExpandCluster.js │ │ │ ├── ExpandVolume.js │ │ │ └── constants.js │ │ └── common │ │ │ ├── Selectbox.js │ │ │ └── Wizard │ │ │ ├── MultiPartStep │ │ │ ├── MultiPartStep.js │ │ │ └── MultiPartStepContainer.js │ │ │ ├── SubStepTab │ │ │ ├── SubStepTab.js │ │ │ └── SubStepTabContainer.js │ │ │ ├── SubStepTabList │ │ │ ├── SubStepTabList.js │ │ │ └── SubStepTabListContainer.js │ │ │ └── Wizard.js │ ├── helpers │ │ ├── AnsibleUtil.js │ │ ├── Dashboard.js │ │ ├── HostedEngineSetup.js │ │ ├── HostedEngineSetup │ │ │ ├── AnsiblePhaseExecutor.js │ │ │ ├── AnsibleVarFilesGenerator.js │ │ │ ├── DefaultValueProvider.js │ │ │ ├── PlaybookUtil.js │ │ │ ├── PreviewGenerator.js │ │ │ ├── ReviewGenerator.js │ │ │ └── StorageUtil.js │ │ ├── HostedEngineSetupUtil.js │ │ └── HostedEngineStatus.js │ └── routes │ │ └── routes.js.in ├── static │ ├── ansible │ │ ├── expandCluster.yml │ │ ├── hc_wizard.yml │ │ ├── hc_wizard_cleanup.yml │ │ └── hc_wizard_example_inventory.yml │ ├── app.css │ ├── branding │ │ └── ovirt │ │ │ ├── ovirt-logo-highres-black.png │ │ │ └── ovirt-logo-highres.png │ ├── gdeploy-templates │ │ ├── he-common.conf │ │ └── ovirt-gluster-hc.conf │ ├── hostedEngineAnsibleFiles │ │ └── heSetup.yml │ ├── index.html.ejs │ └── manifest.json ├── webpack.config.js └── yarn.lock └── vagrant ├── README.md └── Vagrantfile /.copr/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: installdeps srpm git_config_pre 2 | 3 | installdeps: 4 | dnf -y install git make autoconf automake gcc 5 | dnf --repofrompath=ovirt-master-snapshot,https://download.copr.fedorainfracloud.org/results/ovirt/ovirt-master-snapshot/fedora-35-x86_64/ install -y ovirt-engine-nodejs-modules 6 | 7 | git_config_pre: 8 | # From git 2.35.2 we need to mark temporary directory, where the project is cloned to, as safe, otherwise 9 | # git commands won't work 10 | $(eval REPO_DIR=$(shell pwd)) 11 | git config --global --add safe.directory ${REPO_DIR} 12 | 13 | srpm: installdeps git_config_pre 14 | ./automation/build-artifacts.sh copr 15 | cp exported-artifacts/*.src.rpm $(outdir) 16 | -------------------------------------------------------------------------------- /.github/workflows/check-patch.yml: -------------------------------------------------------------------------------- 1 | name: Check patch 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build-el8: 11 | 12 | runs-on: ubuntu-latest 13 | container: 14 | image: quay.io/ovirt/buildcontainer:el8stream 15 | 16 | steps: 17 | - name: Install dependencies 18 | run: dnf install -y --setopt=tsflags=nodocs autoconf automake createrepo_c dnf dnf-plugins-core dnf-plugin-versionlock dnf-utils gettext-devel git make python3-coverage python3-pycodestyle python3-pyflakes rpm-build gettext python3-devel gcc ovirt-engine-nodejs-modules 19 | 20 | - uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | 24 | - run: automation/check-patch.sh 25 | # TODO: Split to separate steps? 26 | 27 | - name: Upload artifacts 28 | uses: ovirt/upload-rpms-action@v2 29 | with: 30 | directory: exported-artifacts/ 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode/ 3 | /cockpit-ovirt.iml 4 | /mailing 5 | /samples 6 | /tools.local 7 | 8 | /notes.txt 9 | /vdsm/transport/ 10 | /vdsm/vdsm 11 | /vdsm/dist/ 12 | /Wiki 13 | 14 | Makefile.in 15 | .npm 16 | dashboard/ovirt-dashboard 17 | dashboard/src/components/HeSetupFooter.js 18 | dashboard/src/routes/routes.js 19 | 20 | config.* 21 | *.spec 22 | *.gz 23 | aclocal.m4 24 | configure 25 | missing 26 | *Makefile 27 | autom4te.cache 28 | install-sh 29 | tmp.repos 30 | *node_modules 31 | vagrant/localvms.xml 32 | vagrant/vagrant.env 33 | dashboard/dist 34 | dashboard/package-lock.json 35 | vagrant/.vscode/tasks.json 36 | 37 | # mock_runner generated files 38 | .bash_history 39 | .node-gyp/ 40 | exported-artifacts/ 41 | rpmbuild/ 42 | mocker-*.cfg 43 | 44 | # Not autogenerated! 45 | \!.copr/Makefile -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Red Hat Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | OVIRT_CACHE_DIR ?= $(HOME)/ovirt-cache 16 | 17 | SHELL := /bin/bash 18 | 19 | SUBDIRS = \ 20 | dashboard 21 | 22 | EXTRA_DIST = \ 23 | autogen.sh \ 24 | cockpit-ovirt.spec \ 25 | cockpit-ovirt.spec.in \ 26 | README.md \ 27 | LICENSE 28 | 29 | DISTCLEANFILES = $(PACKAGE)-$(VERSION).tar.gz \ 30 | aclocal.m4 \ 31 | configure \ 32 | install-sh \ 33 | missing \ 34 | Makefile.in \ 35 | .bash_history \ 36 | mocker-*.cfg 37 | 38 | DISTCLEANDIRS = autom4te.cache \ 39 | engine-plugin \ 40 | tmp.repos \ 41 | .node-gyp \ 42 | exported-artifacts \ 43 | rpmbuild 44 | 45 | TMPREPOS = tmp.repos 46 | RPMBUILD_ARGS := 47 | RPMBUILD_ARGS += --define="_topdir `pwd`/$(TMPREPOS)" 48 | RPMBUILD_ARGS += $(if $(RELEASE_SUFFIX), --define="release_suffix $$RELEASE_SUFFIX") 49 | 50 | srpm: dist 51 | rm -fr "$(TMPREPOS)" 52 | mkdir -p $(TMPREPOS)/{SPECS,RPMS,SRPMS,SOURCES} 53 | $(RPMBUILD) $(RPMBUILD_ARGS) -ts "$(PACKAGE_TARNAME)-$(PACKAGE_VERSION).tar.gz" 54 | @echo 55 | @echo "srpm available at '$(TMPREPOS)'" 56 | @echo 57 | 58 | rpm: srpm 59 | $(RPMBUILD) $(RPMBUILD_ARGS) --rebuild "$(TMPREPOS)"/SRPMS/*.src.rpm 60 | @echo 61 | @echo "rpm(s) available at '$(TMPREPOS)'" 62 | @echo 63 | 64 | snapshot-rpm: SNAPSHOT_DATE = $(shell date --utc +%Y%m%d) 65 | snapshot-rpm: SNAPSHOT_COMMIT ?= $(shell git rev-parse --short HEAD) 66 | snapshot-rpm: 67 | make rpm RELEASE_SUFFIX=".$(SNAPSHOT_DATE).git$(SNAPSHOT_COMMIT)" 68 | 69 | publish: 70 | mkdir -p $(OVIRT_CACHE_DIR) 71 | rsync -aq $(TMPREPOS)/ $(OVIRT_CACHE_DIR)/ovirt 72 | createrepo $(OVIRT_CACHE_DIR)/ovirt 73 | 74 | distclean-local: 75 | rm -rf $(DISTCLEANDIRS) 76 | 77 | .PHONY: srpm rpm snapshot-rpm publish 78 | 79 | # vim: ts=2 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cockpit-ovirt: oVirt plugin for Cockpit Project 2 | 3 | [![Copr build status](https://copr.fedorainfracloud.org/coprs/ovirt/ovirt-master-snapshot/package/cockpit-ovirt/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/ovirt/ovirt-master-snapshot/package/cockpit-ovirt/) 4 | 5 | Welcome to the oVirt plugin for [Cockpit Project](https://cockpit-project.org/) source repository. 6 | 7 | ## About Cockpit 8 | [Cockpit](http://cockpit-project.org/) is easy-to-use sysadmin tool with web-based UI. 9 | 10 | ## About oVirt 11 | [oVirt](https://ovirt.org/) manages Virtual Machines (VMs) in a data center/cluster. 12 | Scales easily from tens to tens of thousands VMs running on multiple KVM hypervisor hosts. 13 | 14 | oVirt deals with: 15 | * VM definition, monitoring and tuning 16 | * (automatic|manual) migration 17 | * storage or network management 18 | * SLA 19 | * security 20 | * easy to use web UI 21 | * and more (see [website](https://ovirt.org/)) 22 | 23 | 24 | ## How to contribute 25 | 26 | ### Submitting patches 27 | 28 | Pull requests on github are welcome! 29 | 30 | ### Found a bug or documentation issue? 31 | To submit a bug or suggest an enhancement for cockpit-ovirt please use 32 | [GitHub issues](https://github.com/oVirt/cockpit-ovirt/issues). 33 | 34 | If you find a documentation issue on the oVirt website please navigate and click "Report an issue on GitHub" in the page footer. 35 | 36 | 37 | ## Still need help? 38 | If you have any other questions, please join [oVirt Users forum / mailing list](https://lists.ovirt.org/admin/lists/users.ovirt.org/) and ask there. 39 | 40 | 41 | ## How to build from source 42 | 43 | ### Prerequisites 44 | - Have packages `autoconf`, `automake` and `libtool` installed 45 | - Have `yarn` and `nodejs` installed 46 | - Not strictly required but **suggested**, use the `ovirt-engine-nodejs-modules` package 47 | - `git clone` the repository 48 | 49 | For build you will need [Node.js](https://nodejs.org/) >= 10. If your OS repositories don't contain 50 | required version you can always use Node Version Manager [nvm](https://github.com/creationix/nvm) to 51 | install and manage multiple Node.js versions side by side. 52 | 53 | A recent version of Node.js can be acquired via packages from [NodeSource](http://nodesource.com). 54 | See the [installation instructions](https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora) 55 | on the Node.js website. 56 | 57 | #### ovirt-engine packages 58 | Install `ovirt-engine-nodejs-modules` from the `ovirt/tested` yum repo for your platform 59 | to use the same packages that will be used by CI to build the app in offline mode. 60 | 61 | REPO=el8 # or the appropriate release and version for you 62 | dnf config-manager --add-repo http://resources.ovirt.org/repos/ovirt/tested/master/rpm/$REPO 63 | dnf install ovirt-engine-nodejs-modules 64 | 65 | The `ovirt-engine-nodejs-modules` package provides `yarn`, and a yarn offline cache. To 66 | enable their use for development or building run: 67 | 68 | source /usr/share/ovirt-engine-nodejs-modules/setup-env.sh 69 | 70 | If you want to stop using `yarn` offline, `yarn` will need to be reconfigured to remove 71 | the offline mirror added by `setup-env.sh`: 72 | 73 | yarn config delete yarn-offline-mirror 74 | 75 | 76 | ### Building 77 | 78 | Be sure to install the dependencies, then run: 79 | 80 | ./autogen.sh 81 | make 82 | 83 | If you want to download required NodeJS libraries during the build instead of 84 | providing them as system libraries, you can add --with-yarn-install flag to 85 | the configure command: 86 | 87 | ./autogen.sh --with-yarn-install 88 | make 89 | 90 | 91 | ### Building RPMs 92 | There are at least 2 easy ways to build the RPM for the project: 93 | 94 | #### Manually with `make rpm` 95 | Run the command and the RPMs will be available under `tmp.repos/` 96 | 97 | ./autogen.sh 98 | make rpm 99 | 100 | #### mock_runner 101 | Use [mock_runner](https://ovirt-infra-docs.readthedocs.io/en/latest/CI/Using_mock_runner/index.html) 102 | to run CI build artifacts locally (this method is cleanest since it runs in a chroot). 103 | When the build is complete, the RPMs will be available under `exported-artifacts/`. 104 | 105 | 106 | ## More Info 107 | * About [oVirt](https://ovirt.org/) 108 | * About [Cockpit](http://cockpit-project.org/) 109 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run this to generate configure and Makefile 3 | 4 | srcdir=`dirname $0` 5 | test -z "$srcdir" && srcdir=. 6 | 7 | THEDIR=`pwd` 8 | ( 9 | cd $srcdir 10 | die=0 11 | 12 | (autoconf --version) < /dev/null > /dev/null 2>&1 || { 13 | echo 14 | echo "You must have autoconf installed." 15 | echo "Download the appropriate package for your distribution," 16 | echo "or see http://www.gnu.org/software/autoconf" 17 | die=1 18 | } 19 | 20 | # Require libtool only if one of of LT_INIT, 21 | # AC_PROG_LIBTOOL, AM_PROG_LIBTOOL is used in configure.ac. 22 | grep -E '^[[:blank:]]*(LT_INIT|A[CM]_PROG_LIBTOOL)' configure.ac >/dev/null \ 23 | && { 24 | (libtool --version) < /dev/null > /dev/null 2>&1 || { 25 | echo 26 | echo "You must have libtool installed." 27 | echo "Download the appropriate package for your distribution," 28 | echo "or see http://www.gnu.org/software/libtool" 29 | die=1 30 | } 31 | } 32 | 33 | (automake --version) < /dev/null > /dev/null 2>&1 || { 34 | echo 35 | die=1 36 | echo "You must have automake installed." 37 | echo "Download the appropriate package for your distribution," 38 | echo "or see http://www.gnu.org/software/automake" 39 | } 40 | 41 | test $die = 1 && exit 1 42 | 43 | test -f cockpit-ovirt.spec.in || { 44 | echo "You must run this script in the top-level directory" 45 | exit 1 46 | } 47 | 48 | if test -z "$*"; then 49 | echo "I am going to run ./configure with no arguments - if you wish " 50 | echo "to pass any to it, please specify them on the $0 command line." 51 | fi 52 | 53 | aclocal 54 | 55 | # Run autoheader only if needed 56 | grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader 57 | 58 | automake --add-missing 59 | autoconf 60 | ./configure "$@" 61 | ) 62 | 63 | if test "x$OBJ_DIR" != x; then 64 | mkdir -p "$OBJ_DIR" 65 | cd "$OBJ_DIR" 66 | fi 67 | -------------------------------------------------------------------------------- /automation/build-artifacts.packages: -------------------------------------------------------------------------------- 1 | build.packages -------------------------------------------------------------------------------- /automation/build-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Is this a release - build from tag? 4 | # tags are like: cockpit-ovirt-0.11.17-1 5 | # non tags are like: cockpit-ovirt-0.11.17-1-3-gf36b431 6 | if git describe --tags --match "cockpit-ovirt*"|cut -f4- -d\-| grep -q '-'; then 7 | # This is a master build, we want to make every build 8 | # newer than all the previous builds using a timestamp, 9 | # and make it easy to locate the commit from the build 10 | # with the git commit hash. 11 | export PACKAGE_RPM_RELEASE=0 12 | export RELEASE_SUFFIX=".$(date --utc +%Y%m%d%H%M%S).git$(git rev-parse --short HEAD)" 13 | fi 14 | 15 | # Clean and then create the artifacts directory: 16 | rm -rf exported-artifacts 17 | mkdir -p exported-artifacts 18 | rm -rf tmp.repos 19 | rm -f ./*tar.gz 20 | 21 | # Prep the build 22 | export PATH="/usr/share/ovirt-engine-nodejs-modules/bin:${PATH}" 23 | ./autogen.sh 24 | 25 | # Create rpm 26 | if [[ "${1:-foo}" != "copr" ]] ; then 27 | make rpm 28 | else 29 | make srpm 30 | fi 31 | 32 | # Store any relevant artifacts in exported-artifacts for the ci system to archive 33 | [[ -d exported-artifacts ]] || mkdir -p exported-artifacts 34 | for file in $(find tmp.repos/ -iregex ".*\.\(tar\.gz\|rpm\)$"); do 35 | echo "Archiving $file" 36 | mv "$file" exported-artifacts/ 37 | done 38 | -------------------------------------------------------------------------------- /automation/build.packages: -------------------------------------------------------------------------------- 1 | make 2 | autoconf 3 | automake 4 | git 5 | dnf 6 | -------------------------------------------------------------------------------- /automation/check-merged.packages: -------------------------------------------------------------------------------- 1 | build.packages -------------------------------------------------------------------------------- /automation/check-merged.sh: -------------------------------------------------------------------------------- 1 | check-patch.sh -------------------------------------------------------------------------------- /automation/check-patch.packages: -------------------------------------------------------------------------------- 1 | build.packages -------------------------------------------------------------------------------- /automation/check-patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Test rpm building, avoiding to discover it doesn't build only after merge. 4 | # Doing this before make check because yarn install may add missing deps at 5 | # rpm level 6 | ./automation/build-artifacts.sh 7 | 8 | # TODO: Only run this if the build failed - if this works then nodejs-modules will 9 | # TODO: probably need a pre-seed or rebuild 10 | export PATH=/usr/share/ovirt-engine-nodejs-modules/bin:${PATH} 11 | yarn config delete yarn-offline-proxy 12 | 13 | ./autogen.sh --with-yarn-install && make -j8 check 14 | 15 | -------------------------------------------------------------------------------- /cockpit-ovirt.spec.in: -------------------------------------------------------------------------------- 1 | # Used for rpm-packaging of pre-bundled application with already resolved JS dependencies 2 | %global _plugindir %{_datarootdir}/cockpit/ 3 | %global _ovirtenginedir %{_datarootdir}/ovirt-engine 4 | %global product oVirt 5 | %global use_rhev %(test -z @RHEV@ && echo 1 || echo 0) 6 | %global yarn_install @YARNINSTALL@ 7 | %define debug_package %{nil} 8 | 9 | 10 | %global source_basename @PACKAGE_TARNAME@-@PACKAGE_VERSION@ 11 | 12 | Name: cockpit-ovirt 13 | Version: @PACKAGE_RPM_VERSION@ 14 | Release: @PACKAGE_RPM_RELEASE@%{?release_suffix}%{?checkout}%{?dist} 15 | Summary: Dashboard for Cockpit based on %{product} 16 | License: ASL 2.0 17 | URL: https://gerrit.ovirt.org/gitweb?p=cockpit-ovirt.git;a=summary 18 | Source0: http://resources.ovirt.org/pub/src/%{name}/%{source_basename}.tar.gz 19 | 20 | 21 | %if 0%{?rhel} 22 | ExclusiveArch: x86_64 23 | %else 24 | BuildRequires: nodejs-packaging 25 | ExclusiveArch: %{nodejs_arches} noarch 26 | %endif 27 | 28 | # nodejs-modules embeds yarn and requires nodejs 29 | BuildRequires: ovirt-engine-nodejs-modules >= 2.2.0 30 | 31 | %if 0%{?enable_autotools} 32 | BuildRequires: autoconf 33 | BuildRequires: automake 34 | BuildRequires: gettext-devel 35 | %endif 36 | 37 | %package dashboard 38 | Summary: Dashboard for Cockpit based on %{product} 39 | BuildArch: noarch 40 | 41 | Requires: cockpit 42 | Requires: cockpit-storaged 43 | Requires: ovirt-hosted-engine-setup >= 2.6.1 44 | Requires: otopi >= 1.10.0 45 | 46 | Requires: ansible-core 47 | Requires: gluster-ansible-roles 48 | 49 | %description 50 | This package provides a Cockpit dashboard for use with %{product}. 51 | 52 | %description dashboard 53 | This package provides a Cockpit dashboard for use with %{product}. 54 | 55 | %prep 56 | %setup -q -n %{source_basename} 57 | 58 | %if "%{yarn_install}" == "yes" 59 | %{warn:Building the rpm with a yarn online install is dangerous, use of ovirt-engine-nodejs-modules is highly recommended.} 60 | %else 61 | # Setup yarn and the nodejs-modules via ovirt-engine-nodejs-modules 62 | pushd dashboard 63 | source %{_datadir}/ovirt-engine-nodejs-modules/setup-env.sh 64 | popd 65 | %endif 66 | 67 | %build 68 | %if 0%{?enable_autotools} 69 | autoreconf -ivf 70 | %endif 71 | 72 | export PATH="%{_datadir}/ovirt-engine-nodejs-modules/bin:${PATH}" 73 | 74 | %if "%{yarn_install}" == "yes" 75 | %configure --with-yarn-install 76 | %else 77 | %configure 78 | %endif 79 | make 80 | 81 | %install 82 | make install DESTDIR=%{buildroot} 83 | 84 | # Create /var/lib/ovirt-hosted-engine-setup/cockpit 85 | install -dm 700 %{buildroot}%{_sharedstatedir}/ovirt-hosted-engine-setup/cockpit 86 | 87 | # Create cockpit conf file to disable inactivity timeout 88 | mkdir -p %{buildroot}/etc/cockpit/ 89 | cat <<__EOF__ >%{buildroot}/etc/cockpit/cockpit.conf 90 | [Session] 91 | IdleTimeout=0 92 | __EOF__ 93 | 94 | %files dashboard 95 | %doc README.md 96 | %license LICENSE 97 | %config /etc/cockpit/cockpit.conf 98 | %{_plugindir}/ovirt-dashboard 99 | %dir %attr(700, root, root) %{_sharedstatedir}/ovirt-hosted-engine-setup/cockpit 100 | 101 | %changelog 102 | * Thu Dec 08 2022 Shubha Kulkarni - 0.16.2-1 103 | - Updated deps for gluster-ansible-roles 104 | 105 | * Mon Aug 08 2022 Asaf Rachmani - 0.16.2-1 106 | - Updated deps 107 | 108 | * Thu Jul 14 2022 Asaf Rachmani - 0.16.1-1 109 | - Hosted Engine deployment fixes 110 | 111 | * Mon Mar 21 2022 Sandro Bonazzola - 0.16.0-1 112 | - Updated nodejs build deps 113 | - Updated deps for oVirt 4.5.0 114 | 115 | * Tue Aug 03 2021 Aviv Turgeman - 0.15.1-1 116 | - Bump to 0.15.1 117 | 118 | * Tue May 04 2021 Sandro Bonazzola - 0.15.0-1 119 | - Javascript dependencies updates 120 | - Fixes for cockpit changes in RHEL 8.4 121 | 122 | * Thu Feb 25 2021 Aviv Turgeman - 0.14.20-1 123 | - Gluster storage deployment fixes 124 | 125 | * Tue Jan 19 2021 Aviv Turgeman - 0.14.19-1 126 | - Gluster storage deployment fixes 127 | 128 | * Tue Dec 29 2020 Aviv Turgeman - 0.14.18-1 129 | - Gluster storage deployment fixes 130 | 131 | * Wed Dec 16 2020 Aviv Turgeman - 0.14.17-1 132 | - Gluster storage deployment fixes 133 | 134 | * Thu Dec 10 2020 Aviv Turgeman - 0.14.16-1 135 | - Gluster storage deployment fixes 136 | 137 | * Sun Nov 29 2020 Aviv Turgeman - 0.14.15-1 138 | - Gluster storage deployment fixes 139 | 140 | * Thu Nov 12 2020 Aviv Turgeman - 0.14.14-1 141 | - Gluster storage deployment fixes 142 | 143 | * Tue Nov 3 2020 Aviv Turgeman - 0.14.13-1 144 | - Gluster storage deployment fixes 145 | 146 | * Wed Oct 14 2020 Gal Zaidman - 0.14.12-1 147 | - Gluster storage deployment fixes 148 | 149 | * Tue Aug 18 2020 Aviv Turgeman - 0.14.11-1 150 | - Gluster storage deployment fixes 151 | 152 | * Sat Jun 20 2020 Aviv Turgeman - 0.14.10-1 153 | - Gluster storage deployment fixes 154 | 155 | * Tue Jun 09 2020 Gal Zaidman - 0.14.9-1 156 | - Gluster storage deployment fixes 157 | 158 | * Tue Jun 09 2020 Gal Zaidman - 0.14.8-1 159 | - Gluster storage deployment fixes 160 | 161 | * Tue May 26 2020 Gal Zaidman - 0.14.7-1 162 | - Gluster storage deployment fixes 163 | 164 | * Tue May 05 2020 Gal Zaidman - 0.14.6-1 165 | - Gluster storage deployment fixes 166 | 167 | * Fri Apr 17 2020 Sandro Bonazzola - 0.14.5-1 168 | - Gluster storage deployment fixes 169 | - Cockpit timeout configuration fixes 170 | 171 | * Wed Apr 08 2020 Sandro Bonazzola - 0.14.4-1 172 | - Gluster storage deployment fixes 173 | 174 | * Fri Mar 20 2020 Sandro Bonazzola - 0.14.3-1 175 | - Gluster storage deployment fixes 176 | 177 | * Wed Mar 04 2020 Gal Zaidman - 0.14.2-1 178 | - Gluster storage deployment fixes 179 | 180 | * Tue Jan 21 2020 Evgeny Slutsky - 0.14.1-1 181 | - Gluster storage deployment fixes 182 | 183 | * Fri Nov 22 2019 Sandro Bonazzola - 0.14.0-1 184 | - Rebase on upstream 0.14.0 185 | - Initial release on el8 186 | 187 | -------------------------------------------------------------------------------- /config/commit-template.txt: -------------------------------------------------------------------------------- 1 | : short summary under 50 chars 2 | 3 | Longer description using lines' length under 72 chars. 4 | 5 | With multiple paragraphs if necessary. 6 | 7 | Bug-Url: https://bugzilla.redhat.com/?????? 8 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Copyright 2016-2021 Red Hat Inc. 3 | dnl 4 | dnl Licensed under the Apache License, Version 2.0 (the "License"); 5 | dnl you may not use this file except in compliance with the License. 6 | dnl You may obtain a copy of the License at 7 | dnl 8 | dnl http://www.apache.org/licenses/LICENSE-2.0 9 | dnl 10 | dnl Unless required by applicable law or agreed to in writing, software 11 | dnl distributed under the License is distributed on an "AS IS" BASIS, 12 | dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | dnl See the License for the specific language governing permissions and 14 | dnl limitations under the License. 15 | dnl 16 | 17 | AC_PREREQ(2.60) 18 | 19 | define([VERSION_MAJOR], [0]) 20 | define([VERSION_MINOR], [16]) 21 | define([VERSION_FIX], [3]) 22 | define([VERSION_NUMBER], VERSION_MAJOR[.]VERSION_MINOR[.]VERSION_FIX) 23 | define([VERSION_RELEASE], [0.0]) 24 | define([VERSION_SUFFIX],[_master]) 25 | 26 | AC_INIT([cockpit-ovirt], VERSION_NUMBER[]VERSION_SUFFIX, [devel@ovirt.org]) 27 | PACKAGE_RPM_VERSION="VERSION_NUMBER" 28 | PACKAGE_RPM_RELEASE="VERSION_RELEASE.$(echo VERSION_SUFFIX | sed 's/^_//')" 29 | AC_SUBST([VERSION_NUMBER]) 30 | AC_SUBST([PACKAGE_RPM_VERSION]) 31 | AC_SUBST([PACKAGE_RPM_RELEASE]) 32 | AC_SUBST([PLUGINNAME], ['oVirt Cockpit Plugin']) 33 | 34 | AC_SUBST([COMMIT], 35 | [$( git ls-remote http://gerrit.ovirt.org/cockpit-ovirt.git | 36 | grep $PACKAGE_TARNAME-$PACKAGE_RPM_VERSION-$PACKAGE_RPM_RELEASE | 37 | awk '{print $1}' )]) 38 | 39 | AC_ARG_WITH([rhev], 40 | [AC_HELP_STRING([--with-rhev], 41 | [Build cockpit-ovirt for RHEV])]) 42 | 43 | if test -z "$with_rhev"; then 44 | RHEV="oVirt" 45 | INSTALL_GUIDE_LINK="https://ovirt.org/documentation/installing_ovirt_as_a_self-hosted_engine_using_the_cockpit_web_interface/" 46 | MORE_INFO_LINK="https://www.ovirt.org/" 47 | MORE_INFO_LINK_TEXT="oVirt Homepage" 48 | else 49 | RHEV="" 50 | INSTALL_GUIDE_LINK="https://access.redhat.com/documentation/en-us/red_hat_virtualization/4.4/html/installing_red_hat_virtualization_as_a_self-hosted_engine_using_the_cockpit_web_interface/index" 51 | MORE_INFO_LINK="https://access.redhat.com/documentation/en-us/red_hat_virtualization/4.4" 52 | MORE_INFO_LINK_TEXT="RHV Documentation" 53 | fi 54 | 55 | AC_SUBST(INSTALL_GUIDE_LINK) 56 | AC_SUBST(MORE_INFO_LINK) 57 | AC_SUBST(MORE_INFO_LINK_TEXT) 58 | 59 | AC_ARG_WITH([yarn-install], 60 | [AC_HELP_STRING([--with-yarn-install], 61 | [enable the online installation of needed dependencies using yarn])], 62 | [YARNINSTALL="yes"], 63 | [YARNINSTALL="no"]) 64 | 65 | AC_SUBST([YARNINSTALL]) 66 | 67 | AC_SUBST([RHEV]) 68 | AM_CONDITIONAL([RHEV], [test -z "$RHEV"]) 69 | 70 | AM_INIT_AUTOMAKE([-Wall -Werror foreign -Wno-portability tar-pax]) 71 | 72 | AC_ARG_VAR([RPMBUILD], [path to rpmbuild utility]) 73 | AC_CHECK_PROGS([RPMBUILD], [rpmbuild]) 74 | 75 | AC_ARG_VAR([YARN], [path to yarn utility]) 76 | AC_CHECK_PROGS([YARN], [yarn]) 77 | test -z "${YARN}" && AC_MSG_ERROR([yarn nodejs package manager not found]) 78 | 79 | AC_CONFIG_FILES([ 80 | Makefile 81 | dashboard/Makefile 82 | dashboard/src/routes/routes.js 83 | dashboard/src/components/HeSetupFooter.js 84 | cockpit-ovirt.spec 85 | ]) 86 | 87 | 88 | AC_OUTPUT 89 | -------------------------------------------------------------------------------- /dashboard/.babelrc.js: -------------------------------------------------------------------------------- 1 | const targetBrowsers = [ 2 | // include browsers with at least 0.5% global coverage 3 | '> 0.5%', 4 | // exclude browsers without official support or updates for 24 months 5 | 'not dead', 6 | // exclude IE - we are committed to support Edge 7 | 'not ie > 0', 8 | // include Firefox ESR (Extended Support Release) 9 | 'firefox esr', 10 | // include last 2 versions of browsers we are committed to support 11 | 'last 2 Chrome versions', 12 | 'last 2 Firefox versions', 13 | 'last 2 Edge versions', 14 | 'last 2 Safari versions' 15 | ] 16 | 17 | const env = process.env.NODE_ENV || 'development' 18 | 19 | // babel configs 20 | module.exports = { 21 | presets: [ 22 | ['@babel/preset-env', { 23 | targets: { 24 | browsers: targetBrowsers, 25 | node: 'current' 26 | }, 27 | debug: env === 'development', 28 | useBuiltIns: 'usage', 29 | corejs: 3 30 | }], 31 | '@babel/preset-react' 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /dashboard/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | } 4 | -------------------------------------------------------------------------------- /dashboard/Makefile.am: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Red Hat Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cockpitdir=$(datarootdir)/cockpit 16 | 17 | EXTRA_DIST = \ 18 | src \ 19 | static \ 20 | .babelrc.js \ 21 | .eslintrc \ 22 | package.json \ 23 | README.md \ 24 | webpack.config.js \ 25 | yarn.lock 26 | 27 | DISTCLEANDIRS = \ 28 | node_modules \ 29 | ovirt-dashboard 30 | 31 | DISTCLEANFILES = \ 32 | Makefile.in 33 | 34 | yarn-install: 35 | [[ "$(YARNINSTALL)" != "yes" ]] || $(YARN) install 36 | 37 | ovirt-dashboard: yarn-install 38 | sed -i "s/\"version\":.*$$/\"version\": \"$(VERSION_NUMBER)\",/" package.json 39 | $(YARN) run build 40 | 41 | install-data-local: 42 | $(MKDIR_P) $(DESTDIR)/$(cockpitdir) 43 | cp -rpv ovirt-dashboard $(DESTDIR)$(cockpitdir) 44 | 45 | distclean-local: 46 | rm -rf $(DISTCLEANDIRS) 47 | 48 | all: ovirt-dashboard 49 | 50 | check-local: yarn-install 51 | $(YARN) test 52 | 53 | .PHONY: ovirt-dashboard yarn-install 54 | 55 | # vim: ts=2 56 | -------------------------------------------------------------------------------- /dashboard/README.md: -------------------------------------------------------------------------------- 1 | This package provides a basic skeleton to initialize a basic ovirt dashboard for cockpit 2 | -------------------------------------------------------------------------------- /dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cockpit-ovirt-dashboard", 3 | "version": "", 4 | "description": "oVirt Cockpit Dashboard", 5 | "main": "src/app.js", 6 | "scripts": { 7 | "clean-js": "rm public/*.js", 8 | "lint": "eslint src --ext js,jsx", 9 | "lint:fix": "eslint src --fix --ext js,jsx", 10 | "test": "yarn lint", 11 | "dev": "NODE_ENV=development webpack --progress", 12 | "dev:watch": "yarn dev --watch", 13 | "prebuild": "yarn test", 14 | "build": "NODE_ENV=production webpack --progress" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://gerrit.ovirt.org/cockpit-ovirt" 19 | }, 20 | "keywords": [ 21 | "Cockpit", 22 | "oVirt", 23 | "Dashboard" 24 | ], 25 | "dependencies": { 26 | "bootstrap": "^3.3.7", 27 | "bootstrap-select": "^1.13.18", 28 | "classnames": "^2.3.1", 29 | "history": "^3.0.0", 30 | "ini": "^1.3.4", 31 | "jquery": "^3.6.0", 32 | "js-yaml": "^3.13.1", 33 | "patternfly": "^3.59.3", 34 | "react": "~16.4.0", 35 | "react-dom": "~16.4.0", 36 | "react-router": "^5.2.0", 37 | "react-router-config": "^5.1.1", 38 | "react-router-dom": "^5.2.0", 39 | "uuid": "^3.4.0" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.14.0", 43 | "@babel/preset-env": "^7.14.0", 44 | "@babel/preset-react": "^7.13.13", 45 | "babel-eslint": "^8.2.6", 46 | "babel-loader": "^8.2.2", 47 | "clean-webpack-plugin": "^3.0.0", 48 | "copy-webpack-plugin": "^5.1.2", 49 | "core-js": "^3.11.1", 50 | "css-loader": "^3.1.0", 51 | "eslint": "^4.19.1", 52 | "eslint-config-standard": "^11.0.0", 53 | "eslint-config-standard-jsx": "^5.0.0", 54 | "eslint-config-standard-react": "^6.0.0", 55 | "eslint-plugin-import": "^2.12.0", 56 | "eslint-plugin-node": "^6.0.1", 57 | "eslint-plugin-promise": "^3.8.0", 58 | "eslint-plugin-react": "^7.8.2", 59 | "eslint-plugin-react-hooks": "^4.2.0", 60 | "eslint-plugin-standard": "^3.1.0", 61 | "file-loader": "^4.0.0", 62 | "html-webpack-plugin": "^3.2.0", 63 | "inline-manifest-webpack-plugin": "^4.0.2", 64 | "mini-css-extract-plugin": "^0.8.2", 65 | "style-loader": "^0.13.2", 66 | "url-loader": "^1.1.2", 67 | "webpack": "^4.46.0", 68 | "webpack-cli": "^3.3.12" 69 | }, 70 | "author": "ovirt.org", 71 | "license": "Apache-2.0", 72 | "private": true 73 | } 74 | -------------------------------------------------------------------------------- /dashboard/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { HashRouter as Router } from 'react-router-dom' 4 | import { renderRoutes } from 'react-router-config' 5 | import routes from './routes/routes' 6 | 7 | // import patternfly 3 css requirements 8 | import 'patternfly/dist/css/patternfly.css' 9 | import 'patternfly/dist/css/patternfly-additions.css' 10 | 11 | // import and setup jQuery in the global scope (it can be used in any js without import) 12 | window.$ = window.jQuery = require('jquery') 13 | 14 | // import patternfly 3 js requirements 15 | require('bootstrap/dist/js/bootstrap') 16 | require('patternfly/dist/js/patternfly') 17 | 18 | render(( 19 | 20 | { renderRoutes(routes) } 21 | 22 | ), document.getElementById('app')); 23 | -------------------------------------------------------------------------------- /dashboard/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link, withRouter } from 'react-router-dom' 3 | import { sidebarRoutes } from '../routes/routes' 4 | import classNames from 'classnames' 5 | import {renderRoutes} from "react-router-config"; 6 | 7 | class App extends Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 |
16 | 17 |
18 |
19 | {renderRoutes(this.props.route.routes)} 20 |
21 |
22 | ) 23 | } 24 | } 25 | 26 | class Sidebar extends Component { 27 | constructor(props) { 28 | super(props); 29 | } 30 | 31 | static get defaultProps() { 32 | return { 33 | routes: sidebarRoutes 34 | } 35 | } 36 | 37 | render() { 38 | const links = []; 39 | this.props.routes.forEach(function(link) { 40 | links.push( 41 | 47 | ) 48 | }); 49 | 50 | return ( 51 |
    {links}
52 | ) 53 | } 54 | } 55 | 56 | class SidebarItem extends Component { 57 | constructor(props) { 58 | super(props); 59 | } 60 | 61 | render() { 62 | const itemClasses = classNames({ 63 | 'fa': true, 64 | 'fa-fw': true, 65 | [`${this.props.item.icon}`]: true, 66 | }); 67 | 68 | const isActive = this.props.location.pathname === this.props.item.path || 69 | (this.props.item.name === "Dashboard" && this.props.location.pathname === "/"); 70 | 71 | return ( 72 |
  • 73 | 74 | 75 |
    76 | {this.props.name} 77 | 78 |
  • 79 | ) 80 | } 81 | } 82 | 83 | const SidebarItemWithRouter = withRouter(SidebarItem); 84 | 85 | export default App; -------------------------------------------------------------------------------- /dashboard/src/components/HeSetupFooter.js.in: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const HeSetupFooter = () => { 4 | return ( 5 |
    6 |
    7 |
    8 |
    9 |
    10 |
    11 | Getting Started 12 |
    13 |
    14 | 21 |
    22 |
    23 |
    24 | 25 |
    26 |
    27 |
    28 | More Information 29 |
    30 |
    31 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 | ) 43 | }; 44 | 45 | export default HeSetupFooter; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/AnsiblePhaseExecution/AnsiblePhaseExecution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import {deploymentStatus as status, messages} from '../constants'; 4 | 5 | const AnsiblePhaseExecution = ({isLastStep, output, phaseExecutionStatus}) => { 6 | if (phaseExecutionStatus === status.SUCCESS) { 7 | return 8 | } else { 9 | return 11 | } 12 | }; 13 | 14 | export default AnsiblePhaseExecution; 15 | 16 | class OutputPanel extends Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | output: this.props.output 21 | }; 22 | 23 | this.scrollToBottom = this.scrollToBottom.bind(this); 24 | } 25 | 26 | scrollToBottom() { 27 | const scrollHeight = this.node.scrollHeight; 28 | const height = this.node.clientHeight; 29 | const maxScrollTop = scrollHeight - height; 30 | ReactDOM.findDOMNode(this.node).scrollTop = maxScrollTop > 0 ? maxScrollTop : 0; 31 | } 32 | 33 | componentDidUpdate() { 34 | this.scrollToBottom() 35 | } 36 | 37 | render() { 38 | const outputLines = this.props.output.lines.filter(n => n); 39 | const outputDiv = outputLines.map(function (line, i) { 40 | try { 41 | const ln = JSON.parse(line); 42 | const type = ln["OVEHOSTED_AC/type"].replace("OVEHOSTED_AC/", "").toUpperCase(); 43 | const data = ln["OVEHOSTED_AC/body"]; 44 | 45 | return ( 46 | 47 | [ {type} ] {data}
    48 |
    49 | ) 50 | } catch (e) { 51 | console.log("Error in Ansible JSON output. Error: " + e); 52 | } 53 | }); 54 | 55 | return ( 56 |
    57 |
    58 | 59 |
    60 |
    this.node = input}> 62 | {outputDiv} 63 |
    64 |
    65 | ) 66 | } 67 | } 68 | 69 | const Status = ({phaseExecutionStatus}) => { 70 | let msg = "Deployment in progress"; 71 | let statusIcon =
    ; 72 | if (phaseExecutionStatus === status.FAILURE) { 73 | msg = "Deployment failed"; 74 | statusIcon = ; 75 | } 76 | return ( 77 |
    78 | {statusIcon} 79 |
    {msg}
    80 |
    81 | ) 82 | }; 83 | 84 | const DeploymentSuccessPanel = ({isLastStep}) => { 85 | const message = isLastStep ? messages.ANSIBLE_LAST_PHASE_SUCCESSFUL : messages.ANSIBLE_PHASE_SUCCESSFUL; 86 | return ( 87 |
    88 |
    89 | 90 |
    91 |

    92 | {message} 93 |

    94 |
    95 | ) 96 | }; 97 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/AnsiblePhaseExecution/AnsiblePhaseExecutionContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import { deploymentStatus as status } from '../constants'; 4 | import AnsiblePhaseExecutor from "../../../helpers/HostedEngineSetup/AnsiblePhaseExecutor"; 5 | import AnsiblePhaseExecution from './AnsiblePhaseExecution' 6 | 7 | class AnsiblePhaseExecutionContainer extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | phaseExecutionStatus: status.RUNNING, 12 | output: {lines: []}, 13 | terminated: false 14 | }; 15 | 16 | this.parseOutput = this.parseOutput.bind(this); 17 | this.processExit = this.processExit.bind(this); 18 | this.resetState = this.resetState.bind(this); 19 | this.restart = this.restart.bind(this); 20 | 21 | this.phaseExecutor = new AnsiblePhaseExecutor(this.props.abortCallBack, this.props.heSetupModel); 22 | } 23 | 24 | UNSAFE_componentWillMount() { 25 | this.phaseExecutor.startSetup(this.props.phase, this.parseOutput, this.processExit); 26 | } 27 | 28 | restart() { 29 | this.resetState(); 30 | this.phaseExecutor.startSetup(this.props.phase, this.parseOutput, this.processExit); 31 | } 32 | 33 | resetState() { 34 | this.setState({ 35 | phaseExecutionStatus: status.RUNNING, 36 | terminated: false, 37 | output: {lines: []} 38 | }); 39 | } 40 | 41 | processExit(executionStatus) { 42 | const newState = {}; 43 | newState.phaseExecutionStatus = executionStatus === status.SUCCESS ? status.SUCCESS : status.FAILURE; 44 | newState.terminated = true; 45 | this.setState(newState); 46 | this.props.terminationCallBack(newState.phaseExecutionStatus, this.restart); 47 | } 48 | 49 | parseOutput(data) { 50 | let output = this.state.output; 51 | output.lines = output.lines.concat(data.lines); 52 | this.setState({ output }); 53 | } 54 | 55 | render() { 56 | return 59 | } 60 | } 61 | 62 | AnsiblePhaseExecutionContainer.propTypes = { 63 | abortCallBack: PropTypes.func.isRequired, 64 | heSetupModel: PropTypes.object.isRequired, 65 | phase: PropTypes.string.isRequired 66 | }; 67 | 68 | export default AnsiblePhaseExecutionContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/AnsiblePhasePreview/AnsiblePhasePreview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AnsiblePhaseExecutionContainer from "../AnsiblePhaseExecution/AnsiblePhaseExecutionContainer"; 3 | import ReviewStepPanelContainer from "../ReviewStep/ReviewStepPanel"; 4 | 5 | const AnsiblePhasePreview = ({abortCallBack, headerText, sections, executionStarted, heSetupModel, isLastStep, phase, 6 | terminationCallBack}) => { 7 | 8 | if (executionStarted) { 9 | return 14 | } else { 15 | return 17 | } 18 | }; 19 | 20 | export default AnsiblePhasePreview; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/AnsiblePhasePreview/AnsiblePhasePreviewContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { Component } from "react"; 3 | import AnsiblePhasePreview from "./AnsiblePhasePreview"; 4 | import PreviewGenerator from "../../../helpers/HostedEngineSetup/PreviewGenerator"; 5 | import { deploymentStatus as status } from "../constants"; 6 | import { footerButtons } from "../../common/Wizard/Wizard"; 7 | import ReviewGenerator from "../../../helpers/HostedEngineSetup/ReviewGenerator"; 8 | 9 | class AnsiblePhasePreviewContainer extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | heSetupModel: props.heSetupModel, 14 | executionStarted: false, 15 | executionTerminated: false, 16 | executionStatus: status.RUNNING, 17 | }; 18 | 19 | this.customActionBtnCallback = this.customActionBtnCallback.bind(this); 20 | this.terminationCallBack = this.terminationCallBack.bind(this); 21 | } 22 | 23 | customActionBtnCallback() { 24 | this.setState({ executionStarted: true }); 25 | const newBtnState = { 26 | buttonText: this.props.executeBtnText, 27 | disabled: true, 28 | disableBtnsList: [footerButtons.BACK], 29 | hideBtnsList: [ 30 | footerButtons.NEXT, 31 | footerButtons.FINISH, 32 | footerButtons.CLOSE, 33 | ], 34 | }; 35 | this.props.registerCustomActionBtnStateCallback( 36 | newBtnState, 37 | this.props.stepIndex, 38 | this.props.subStepIndex 39 | ); // Reset the 'Next' button after 'Execute' is pressed 40 | } 41 | 42 | terminationCallBack(executionStatus, buttonCallBack) { 43 | this.setState({ 44 | executionTerminated: true, 45 | executionStatus: executionStatus, 46 | }); 47 | const self = this; 48 | let btnState = {}; 49 | if (executionStatus === status.FAILURE) { 50 | btnState = { 51 | buttonText: this.props.reattemptBtnText 52 | ? this.props.reattemptBtnText 53 | : this.props.executeBtnText, 54 | hideBtnsList: [footerButtons.NEXT, footerButtons.FINISH], 55 | buttonCallBack: function () { 56 | const midRedeployBtnState = { 57 | buttonText: self.props.reattemptBtnText 58 | ? self.props.reattemptBtnText 59 | : self.props.executeBtnText, 60 | disabled: true, 61 | hideBtnsList: [footerButtons.NEXT, footerButtons.FINISH], 62 | disableBtnsList: [footerButtons.BACK], 63 | }; 64 | self.props.registerCustomActionBtnStateCallback( 65 | midRedeployBtnState, 66 | self.props.stepIndex, 67 | self.props.subStepIndex 68 | ); 69 | buttonCallBack(); 70 | }, 71 | }; 72 | } else if (this.props.isLastStep && executionStatus === status.SUCCESS) { 73 | btnState = { 74 | buttonText: "Close", 75 | buttonCallBack: this.props.onFinishDeploy, 76 | hideBtnsList: [ 77 | footerButtons.NEXT, 78 | footerButtons.FINISH, 79 | footerButtons.CANCEL, 80 | footerButtons.BACK, 81 | ], 82 | }; 83 | } 84 | this.props.registerCustomActionBtnStateCallback( 85 | btnState, 86 | this.props.stepIndex, 87 | this.props.subStepIndex 88 | ); 89 | } 90 | 91 | componentDidUpdate(prevProps) { 92 | if ( 93 | prevProps.registerCustomActionBtnStateCallback === null && 94 | this.props.registerCustomActionBtnStateCallback !== null && 95 | this.state.executionStatus !== status.SUCCESS 96 | ) { 97 | const self = this; 98 | const newBtnState = { 99 | buttonText: this.props.executeBtnText, 100 | buttonCallBack: self.customActionBtnCallback, 101 | moveNext: false, 102 | overrideFinish: true, 103 | }; 104 | this.props.registerCustomActionBtnStateCallback(newBtnState); 105 | } 106 | } 107 | 108 | UNSAFE_componentWillMount() { 109 | let newBtnState = {}; 110 | if (this.state.executionStatus !== status.SUCCESS) { 111 | newBtnState = { 112 | buttonText: this.props.executeBtnText, 113 | buttonCallBack: this.customActionBtnCallback, 114 | hideBtnsList: [footerButtons.NEXT, footerButtons.FINISH], 115 | }; 116 | 117 | this.props.registerCustomActionBtnStateCallback( 118 | newBtnState, 119 | this.props.stepIndex, 120 | this.props.subStepIndex 121 | ); 122 | } 123 | } 124 | 125 | shouldComponentUpdate(nextProps, nextState) { 126 | if (!this.props.validating && nextProps.validating) { 127 | const allowFwdStepChg = 128 | this.state.executionTerminated && 129 | this.state.executionStatus === status.SUCCESS; 130 | this.props.validationCallBack(allowFwdStepChg); 131 | } 132 | 133 | return true; 134 | } 135 | 136 | render() { 137 | const reviewGen = new ReviewGenerator(this.state.heSetupModel); 138 | const sections = reviewGen.getReviewSections(this.props.sections); 139 | 140 | return ( 141 | 151 | ); 152 | } 153 | } 154 | 155 | AnsiblePhasePreviewContainer.propTypes = { 156 | headerText: PropTypes.string, 157 | stepName: PropTypes.string.isRequired, 158 | heSetupModel: PropTypes.object.isRequired, 159 | sections: PropTypes.array.isRequired, 160 | phase: PropTypes.string.isRequired, 161 | }; 162 | 163 | export default AnsiblePhasePreviewContainer; 164 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/EngineStep/HeWizardEngine.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MultiRowTextBoxContainer from '../MultiRowTextBox/MultiRoxTextBoxContainer' 3 | import { getClassNames } from '../../../helpers/HostedEngineSetupUtil' 4 | import {deploymentTypes} from "../constants"; 5 | import UnmaskablePasswordContainer from "../UnmaskablePassword"; 6 | 7 | const HeWizardEngine = ({deploymentType, heSetupModel, errorMsg, errorMsgs, handleEngineConfigUpdate, 8 | handleRecipientAddressUpdate, handleRecipientAddressDelete, handleAdminPortalPwdUpdate}) => { 9 | const engineConfig = heSetupModel.engine; 10 | const notificationsConfig = heSetupModel.notifications; 11 | const isOtopiDeployment = deploymentType === deploymentTypes.OTOPI_DEPLOYMENT; 12 | 13 | return ( 14 |
    15 |
    16 | {errorMsg && 17 |
    18 |
    19 | 20 | {errorMsg} 21 |
    22 |
    23 | } 24 | 25 |
    26 |
    27 |

    Engine Credentials

    28 |
    29 |
    30 | 31 | {isOtopiDeployment && 32 |
    33 | 34 |
    35 | handleEngineConfigUpdate("adminUsername", e.target.value, "engine")} 40 | /> 41 | {errorMsgs.adminUsername && {errorMsgs.adminUsername}} 42 |
    43 |
    44 | } 45 | 46 |
    47 | 48 |
    49 | 52 | {errorMsgs.adminPassword && {errorMsgs.adminPassword}} 53 |
    54 |
    55 | 56 |
    57 |
    58 |

    Notification Settings

    59 |
    60 |
    61 | 62 |
    63 | 64 |
    65 | handleEngineConfigUpdate("smtpServer", e.target.value, "notifications")} 71 | id="he-notification-server-input" 72 | /> 73 | {errorMsgs.smtpServer && {errorMsgs.smtpServer}} 74 |
    75 |
    76 | 77 |
    78 | 79 |
    80 | handleEngineConfigUpdate("smtpPort", e.target.value, "notifications")} 86 | id="he-notification-smtp-port-input" 87 | /> 88 | {errorMsgs.smtpPort && {errorMsgs.smtpPort}} 89 |
    90 |
    91 | 92 |
    93 | 94 |
    95 | handleEngineConfigUpdate("sourceEmail", e.target.value, "notifications")} 101 | id="he-sender-email-input" 102 | /> 103 | {errorMsgs.sourceEmail && {errorMsgs.sourceEmail}} 104 |
    105 |
    106 | 107 |
    108 | 109 |
    110 |
    111 | 116 | {errorMsgs.destEmail && {errorMsgs.destEmail}} 117 |
    118 |
    119 |
    120 |
    121 |
    122 | ) 123 | }; 124 | 125 | export default HeWizardEngine; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/EngineStep/HeWizardEngineContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import { getErrorMsgForProperty, validatePropsForUiStage } from '../Validation' 4 | import { messages } from '../constants' 5 | import HeWizardEngine from './HeWizardEngine' 6 | 7 | class HeWizardEngineContainer extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | heSetupModel: props.heSetupModel, 12 | errorMsg: "", 13 | errorMsgs: {} 14 | }; 15 | 16 | this.handleRecipientAddressDelete = this.handleRecipientAddressDelete.bind(this); 17 | this.handleRecipientAddressUpdate = this.handleRecipientAddressUpdate.bind(this); 18 | this.handleAdminPortalPwdUpdate = this.handleAdminPortalPwdUpdate.bind(this); 19 | this.handleEngineConfigUpdate = this.handleEngineConfigUpdate.bind(this); 20 | this.validateConfigUpdate = this.validateConfigUpdate.bind(this); 21 | this.validateAllInputs = this.validateAllInputs.bind(this); 22 | } 23 | 24 | handleRecipientAddressDelete(index) { 25 | const addresses = this.state.heSetupModel.notifications.destEmail.value; 26 | addresses.splice(index, 1); 27 | this.setState({ addresses, errorMsgs: {} }); 28 | } 29 | 30 | handleRecipientAddressUpdate(index, address) { 31 | const addresses = this.state.heSetupModel.notifications.destEmail.value; 32 | addresses[index] = address; 33 | const errorMsgs= this.state.errorMsgs; 34 | this.setState({ addresses, errorMsgs }); 35 | } 36 | 37 | handleAdminPortalPwdUpdate(pwd) { 38 | const config = this.state.heSetupModel.engine; 39 | config.adminPassword.value = pwd; 40 | this.validateConfigUpdate("adminPassword", config); 41 | this.setState({ config }); 42 | } 43 | 44 | handleEngineConfigUpdate(propName, value, configType) { 45 | const heSetupModel = this.state.heSetupModel; 46 | 47 | heSetupModel[configType][propName].value = value; 48 | 49 | if (propName === "adminPassword") { 50 | heSetupModel.engine.adminPassword.useInAnswerFile = value !== ""; 51 | } 52 | 53 | this.validateConfigUpdate(propName, heSetupModel[configType]); 54 | this.setState({ heSetupModel }); 55 | } 56 | 57 | validateConfigUpdate(propName, config) { 58 | let errorMsg = this.state.errorMsg; 59 | const errorMsgs = this.state.errorMsgs; 60 | const prop = config[propName]; 61 | const propErrorMsg = getErrorMsgForProperty(prop); 62 | 63 | if (propErrorMsg !== "") { 64 | errorMsgs[propName] = propErrorMsg; 65 | } else { 66 | delete errorMsgs[propName]; 67 | } 68 | 69 | if (Object.keys(errorMsgs).length === 0 ){ 70 | errorMsg = ""; 71 | } 72 | 73 | this.setState({ errorMsg, errorMsgs }); 74 | } 75 | 76 | validateAllInputs() { 77 | let errorMsgs = this.state.errorMsgs; 78 | let propsAreValid = validatePropsForUiStage("Engine", this.props.heSetupModel, errorMsgs) && 79 | Object.keys(errorMsgs).length === 0; 80 | 81 | let errorMsg = ""; 82 | if (!propsAreValid) { 83 | errorMsg = messages.GENERAL_ERROR_MSG; 84 | } 85 | 86 | this.setState({ errorMsg, errorMsgs }); 87 | return propsAreValid; 88 | } 89 | 90 | shouldComponentUpdate(nextProps, nextState){ 91 | if(!this.props.validating && nextProps.validating){ 92 | this.props.validationCallBack(this.validateAllInputs()) 93 | } 94 | return true; 95 | } 96 | 97 | render() { 98 | return ( 99 | 108 | ) 109 | } 110 | } 111 | 112 | HeWizardEngineContainer.propTypes = { 113 | stepName: PropTypes.string.isRequired, 114 | heSetupModel: PropTypes.object.isRequired, 115 | deploymentType: PropTypes.string.isRequired 116 | }; 117 | 118 | export default HeWizardEngineContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/Execution/DeploymentStatus.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { deploymentStatus, messages } from '../constants'; 3 | 4 | const DeploymentStatus = ({ status, reDeployCallback }) => { 5 | let msg = messages.DEPLOYMENT_IN_PROGRESS; 6 | let statusIcon =
    ; 7 | if (status === deploymentStatus.FAILURE) { 8 | msg = messages.DEPLOYMENT_FAILED; 9 | statusIcon = ; 10 | } 11 | return ( 12 |
    13 | {statusIcon} 14 | {msg} 15 |
    16 | {status === deploymentStatus.FAILURE && 17 | 21 | } 22 |
    23 |
    24 | ) 25 | }; 26 | 27 | export default DeploymentStatus; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/Execution/DeploymentSuccessPanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { messages } from '../constants'; 3 | 4 | const DeploymentSuccessPanel = () => { 5 | return ( 6 |
    7 |
    8 | 9 |
    10 |
    11 | {messages.DEPLOYMENT_SUCCESSFUL} 12 |
    13 |
    14 | ) 15 | }; 16 | 17 | export default DeploymentSuccessPanel; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/ExecutionStep/HeWizardExecution.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { deploymentStatus } from '../constants'; 3 | import DeploymentSuccessPanel from '../Execution/DeploymentSuccessPanel' 4 | import HeSetupContainer from '../HeSetup/HeSetupContainer' 5 | 6 | // const HeWizardExecution = ({heSetupStatus, setup, startSetup}) => { 7 | const HeWizardExecution = ({heSetupStatus, heSetupModel, gDeployAnswerFilePaths, abortCallback}) => { 8 | if (heSetupStatus === deploymentStatus.SUCCESS) { 9 | return 10 | } else { 11 | return 14 | } 15 | }; 16 | 17 | export default HeWizardExecution; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/ExecutionStep/HeWizardExecutionContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import { AnswerFileGenerator } from '../../../helpers/HostedEngineSetupUtil' 4 | import { configValues, deploymentStatus } from '../constants'; 5 | import RunSetup from '../../../helpers/HostedEngineSetup' 6 | import HeWizardExecution from './HeWizardExecution' 7 | 8 | class HeWizardExecutionContainer extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | heSetupLog: "", 13 | heSetupStatus: deploymentStatus.RUNNING 14 | }; 15 | 16 | // this.setup = this.getSetup(); 17 | 18 | // this.getSetup = this.getSetup.bind(this); 19 | // this.startSetup = this.startSetup.bind(this); 20 | } 21 | 22 | // componentWillMount() { 23 | // this.startSetup(); 24 | // } 25 | 26 | // getSetup(answerFilePath) { 27 | // let answerFiles = []; 28 | // 29 | // if (typeof this.props.gDeployAnswerFilePaths !== 'undefined') { 30 | // answerFiles = this.props.gDeployAnswerFilePaths.slice(); 31 | // } 32 | // 33 | // answerFiles.push(answerFilePath); 34 | // return new RunSetup(this.props.abortCallback, answerFiles); 35 | // } 36 | // 37 | // startSetup() { 38 | // const fileGenerator = new AnswerFileGenerator(this.props.heSetupModel); 39 | // const self = this; 40 | // fileGenerator.writeConfigToFile() 41 | // .then(filePath => { 42 | // self.setup = self.getSetup(filePath) 43 | // }); 44 | // } 45 | 46 | render() { 47 | return ( 48 | 53 | // setup={this.setup} 54 | // startSetup={this.startSetup}/> 55 | ) 56 | 57 | } 58 | } 59 | 60 | HeWizardExecutionContainer.propTypes = { 61 | onSuccess: PropTypes.func.isRequired, 62 | reDeployCallback: PropTypes.func.isRequired 63 | }; 64 | 65 | export default HeWizardExecutionContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetup.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HeSetupInputContainer from './HeSetupInput/HeSetupInputPanelContainer' 3 | import HeSetupMessagePanel from './HeSetupMessagePanel' 4 | import HeSetupNoPermissionsPanel from './HeSetupNoPermissionsPanel' 5 | import HeSetupOutputPanel from './HeSetupOutputPanel' 6 | import HeSetupSuccessPanel from './HeSetupSuccessPanel' 7 | import RestartButton from './RestartButton' 8 | 9 | const HeSetup = ({denied, finishedError, output, passInput, question, restart, 10 | showInput, success, terminated}) => { 11 | return ( 12 |
    13 | {success ? 14 | : 15 | denied ? 16 | : 17 |
    18 | {output.infos.length > 0 ? 19 | 23 | : null } 24 | {output.warnings.length > 0 ? 25 | 29 | : null } 30 | 31 | {showInput ? 32 | 37 | : !terminated ? 38 |
    39 | : null } 40 | {finishedError ? 41 |
    42 | 46 | 47 |
    48 | : null } 49 |
    50 | } 51 |
    52 | ) 53 | }; 54 | 55 | export default HeSetup; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetupContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import HeSetup from './HeSetup' 4 | import { AnswerFileGenerator } from '../../../helpers/HostedEngineSetupUtil' 5 | import RunSetup from '../../../helpers/HostedEngineSetup' 6 | 7 | class HeSetupContainer extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | question: null, 12 | output: null, 13 | terminated: false, 14 | denied: false, 15 | success: false 16 | }; 17 | 18 | this.setup = null; 19 | 20 | this.resetState = this.resetState.bind(this); 21 | this.processExit = this.processExit.bind(this); 22 | this.parseOutput = this.parseOutput.bind(this); 23 | this.passInput = this.passInput.bind(this); 24 | this.restart = this.restart.bind(this); 25 | this.getSetup = this.getSetup.bind(this); 26 | this.startSetup = this.startSetup.bind(this); 27 | } 28 | 29 | resetState() { 30 | let question = { 31 | prompt: [], 32 | suggested: '', 33 | password: false, 34 | complete: false 35 | }; 36 | 37 | let output = { 38 | infos: [], 39 | warnings: [], 40 | errors: [], 41 | lines: [], 42 | }; 43 | 44 | this.setState({question: question}); 45 | this.setState({output: output}); 46 | this.setState({terminated: false}); 47 | this.setState({success: false}) 48 | } 49 | 50 | restart() { 51 | this.resetState(); 52 | this.startSetup(); 53 | this.setState({ setup: this.setup.start(this.parseOutput, this.processExit) }); 54 | } 55 | 56 | getSetup(answerFilePath) { 57 | let answerFiles = []; 58 | 59 | if (typeof this.props.gDeployAnswerFilePaths !== 'undefined') { 60 | answerFiles = this.props.gDeployAnswerFilePaths.slice(); 61 | } 62 | 63 | answerFiles.push(answerFilePath); 64 | return new RunSetup(this.props.abortCallback, answerFiles); 65 | } 66 | 67 | startSetup() { 68 | const fileGenerator = new AnswerFileGenerator(this.props.heSetupModel); 69 | const self = this; 70 | fileGenerator.writeConfigToFile() 71 | .then(filePath => { 72 | self.setup = self.getSetup(filePath) 73 | self.setState({ setup: self.setup.start(self.parseOutput, self.processExit) }); 74 | }); 75 | } 76 | 77 | UNSAFE_componentWillMount() { 78 | this.resetState(); 79 | this.startSetup(); 80 | // this.setState({setup: this.props.setup.start(this.parseOutput, 81 | // this.processExit) 82 | // }) 83 | } 84 | 85 | componentDidMount() { 86 | $(ReactDOM.findDOMNode(this)).modal('show'); 87 | } 88 | 89 | componentWillUnmount() { 90 | this.setup.close(); 91 | $(ReactDOM.findDOMNode(this)).modal('hide'); 92 | } 93 | 94 | processExit(status, accessDenied = false) { 95 | this.setState({terminated: true}); 96 | this.setState({denied: accessDenied}); 97 | this.setState({success: status === 0}); 98 | console.log(this.state.success) 99 | } 100 | 101 | passInput(input) { 102 | if (this.state.question.prompt.length > 0) { 103 | this.setup.handleInput(input); 104 | this.resetState() 105 | } 106 | } 107 | 108 | parseOutput(ret) { 109 | const question = this.state.question; 110 | question.suggested = ret.question.suggested !== '' ? 111 | ret.question.suggested : 112 | this.state.question.suggested; 113 | 114 | question.prompt = question.prompt.concat(ret.question.prompt); 115 | question.password = ret.question.password || this.state.question.password; 116 | question.complete = ret.question.complete || this.state.question.complete; 117 | 118 | this.setState({question: question}); 119 | 120 | for (let key in ret.output) { 121 | let value = this.state.output 122 | if (key === "terminated") { 123 | this.setState({terminated: ret.output.terminated}) 124 | } else { 125 | // Pop off the beginning of the box if it gets too long, since 126 | // otopi has a lot of informational messages for some steps, 127 | // and it pushes everything down the screen 128 | if (key !== "lines" && value[key].length > 10) { 129 | value[key].shift() 130 | } 131 | value[key] = value[key].concat(ret.output[key]) 132 | } 133 | this.setState({output: value }) 134 | } 135 | } 136 | 137 | render() { 138 | const finishedError = this.state.terminated && this.state.output.errors.length > 0; 139 | 140 | const showInput = !this.state.terminated && 141 | (this.state.question.prompt.length > 0 && 142 | this.state.question.complete); 143 | 144 | return ( 145 | 156 | ) 157 | } 158 | } 159 | 160 | export default HeSetupContainer; 161 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetupInput/HeSetupInputPanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | 4 | const HeSetupInputPanel = ({errorText, handleInput, handleKeyPress, handleSubmit, hasErrors, 5 | input, prompt, type}) => { 6 | const inputClass = classNames({ 7 | 'col-xs-7': true, 8 | 'form-group': true, 9 | 'has-error': hasErrors 10 | }); 11 | 12 | return ( 13 |
    14 |
    15 | 20 |
    21 | 29 | 35 |
    36 | {errorText} 37 |
    38 |
    39 | ) 40 | }; 41 | 42 | export default HeSetupInputPanel; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetupInput/HeSetupInputPanelContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import HeSetupInputPanel from './HeSetupInputPanel' 3 | 4 | class HeSetupInputPanelContainer extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | input: '' 9 | }; 10 | 11 | this.handleSubmit = this.handleSubmit.bind(this); 12 | this.handleInput = this.handleInput.bind(this); 13 | this.handleKeyPress = this.handleKeyPress.bind(this); 14 | } 15 | 16 | handleInput(e) { 17 | this.setState({input: e.target.value}) 18 | } 19 | 20 | handleKeyPress(e) { 21 | if (e.key === "Enter") { 22 | this.handleSubmit(e) 23 | } 24 | } 25 | 26 | handleSubmit(e) { 27 | e.preventDefault(); 28 | this.props.passInput(this.state.input) 29 | } 30 | 31 | UNSAFE_componentWillReceiveProps(nextProps) { 32 | const suggested = nextProps.question.suggested; 33 | this.setState({input: suggested}) 34 | } 35 | 36 | render() { 37 | const prompt = this.props.question.prompt.map(function(line, i) { 38 | return ( 39 | 40 | {line}
    41 |
    42 | ) 43 | }); 44 | 45 | const hasErrors = this.props.errors.length > 0; 46 | 47 | let errorText = null; 48 | 49 | if (hasErrors) { 50 | errorText = this.props.errors.map(function(err, i) { 51 | return {err} 52 | }) 53 | } 54 | 55 | const type = this.props.password ? 'password' : 'text'; 56 | 57 | return ( 58 | 68 | ) 69 | } 70 | } 71 | 72 | export default HeSetupInputPanelContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetupMessagePanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const HeSetupMessagePanel = ({messages, type, icon}) => { 4 | const classes = "he-setup-messages alert alert-" + type; 5 | const iconClasses = "pficon pficon-" + icon; 6 | 7 | const output = messages.map(function(message, i) { 8 | return
    9 | 10 | {message} 11 |
    12 | }, this); 13 | 14 | return ( 15 |
    {output}
    16 | ) 17 | }; 18 | 19 | export default HeSetupMessagePanel; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetupNoPermissionsPanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const HeSetupNoPermissionsPanel = () => { 4 | return ( 5 |
    6 |
    7 |
    8 | 9 |
    10 |

    11 | Hosted Engine Setup exited with "Access Denied". Does this user have 12 | permissions to run it? 13 |

    14 |
    15 |
    16 | ) 17 | }; 18 | 19 | export default HeSetupNoPermissionsPanel; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetupOutputPanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const HeSetupOutputPanel = ({output}) => { 4 | const outputDiv = output.lines.map(function(line, i) { 5 | return ( 6 | 7 | {line}
    8 |
    9 | ) 10 | }); 11 | 12 | return ( 13 |
    14 |
    15 | {outputDiv} 16 |
    17 |
    18 | ) 19 | }; 20 | 21 | export default HeSetupOutputPanel; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/HeSetupSuccessPanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const HeSetupSuccessPanel = () => { 4 | return ( 5 |
    6 |
    7 |
    8 | 9 |
    10 |

    11 | Hosted Engine Setup successfully completed! 12 |

    13 |
    14 |
    15 | ) 16 | }; 17 | 18 | export default HeSetupSuccessPanel; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetup/RestartButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class RestartButton extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.onClick = this.onClick.bind(this); 7 | } 8 | 9 | onClick() { 10 | this.props.restartCallback() 11 | } 12 | 13 | render() { 14 | return ( 15 |
    16 | 20 |
    21 | ) 22 | } 23 | } 24 | 25 | export default RestartButton; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetupWizard/HeSetupWizard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | deploymentTypes, 4 | status, 5 | messages, 6 | ansiblePhases, 7 | wizardSections as sectNames, 8 | headers, 9 | InvalidNetworkInterfacesMsg, 10 | } from "../constants"; 11 | import HeWizardNetworkContainer from "../NetworkStep/HeWizardNetworkContainer"; 12 | import HeWizardEngineContainer from "../EngineStep/HeWizardEngineContainer"; 13 | import HeWizardStorageContainer from "../StorageStep/HeWizardStorageContainer"; 14 | import HeWizardVmContainer from "../VmStep/HeWizardVmContainer"; 15 | import HeWizardPreviewContainer from "../PreviewStep/HeWizardPreviewContainer"; 16 | import Wizard from "../../common/Wizard/Wizard"; 17 | import MultiPartStepContainer from "../../common/Wizard/MultiPartStep/MultiPartStepContainer"; 18 | import AnsiblePhasePreviewContainer from "../AnsiblePhasePreview/AnsiblePhasePreviewContainer"; 19 | 20 | const HeSetupWizard = ({ 21 | abortCallback, 22 | defaultsProvider, 23 | deploymentType, 24 | finishDeploy, 25 | handleFinish, 26 | handleRedeploy, 27 | heSetupModel, 28 | isDeploymentStarted, 29 | loadingState, 30 | onSuccess, 31 | onStepChange, 32 | setup, 33 | sufficientMemAvail, 34 | systemData, 35 | libvirtRunning, 36 | virtSupported, 37 | systemDataRetrieved, 38 | gDeployAnswerFilePaths, 39 | showWizard, 40 | networkIfacesRetrieved, 41 | }) => { 42 | return ( 43 |
    44 | {loadingState === status.POLLING && ( 45 |
    46 |
    47 |
    48 |
    49 |

    Loading Wizard

    50 |
    51 |
    52 | )} 53 | 54 | {loadingState === status.SUCCESS && 55 | deploymentType === deploymentTypes.ANSIBLE_DEPLOYMENT && ( 56 | 65 | 72 | 77 | 86 | 91 | 103 | 104 | )} 105 | 106 | {loadingState === status.SUCCESS && 107 | deploymentType === deploymentTypes.OTOPI_DEPLOYMENT && ( 108 | 116 | 123 | 128 | 133 | 140 | 151 | 152 | )} 153 | 154 |
    158 |
    159 | {!virtSupported && ( 160 |
    161 |
    162 | 163 | {messages.VIRT_NOT_SUPPORTED} 164 |
    165 |
    166 | )} 167 | 168 | {!systemDataRetrieved && ( 169 |
    170 |
    171 | 172 | {messages.SYS_DATA_UNRETRIEVABLE} 173 |
    174 | {!networkIfacesRetrieved && ( 175 |
    176 | 177 | 178 |
    179 | )} 180 |
    181 | )} 182 | 183 | {!sufficientMemAvail && ( 184 |
    185 |
    186 | 187 | {messages.INSUFFICIENT_MEM_AVAIL} 188 |
    189 |
    190 | )} 191 | {!libvirtRunning && ( 192 |
    193 |
    194 | 195 | {messages.LIBVIRT_NOT_RUNNING} 196 |
    197 |
    198 | )} 199 |
    200 |
    201 |
    202 | ); 203 | }; 204 | 205 | export default HeSetupWizard; 206 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/HeSetupWizard/HeSetupWizardContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React, { Component } from "react"; 3 | import HeSetupWizard from "./HeSetupWizard"; 4 | import { 5 | HeSetupModel, 6 | isEmptyObject, 7 | } from "../../../helpers/HostedEngineSetupUtil"; 8 | import { 9 | messages, 10 | status, 11 | defaultValueProviderTasks as tasks, 12 | } from "../constants"; 13 | import DefaultValueProvider from "../../../helpers/HostedEngineSetup/DefaultValueProvider"; 14 | 15 | class HeSetupWizardContainer extends Component { 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | loadingState: status.POLLING, 20 | isDeploymentStarted: false, 21 | heSetupModel: null, 22 | gDeployAnswerFilePaths: this.props.gDeployAnswerFilePaths, 23 | systemData: null, 24 | networkIfacesRetrieved: null, 25 | }; 26 | 27 | this.state.heSetupModel = new HeSetupModel(); 28 | this.defaultsProvider = null; 29 | this.allSystemDataRetrieved = false; 30 | 31 | this.virtSupported = true; 32 | this.sufficientMemAvail = true; 33 | this.libvirtRunning = true; 34 | 35 | this.finishDeploy = this.finishDeploy.bind(this); 36 | this.handleFinish = this.handleFinish.bind(this); 37 | this.onStepChange = this.onStepChange.bind(this); 38 | this.handleReDeploy = this.handleReDeploy.bind(this); 39 | this.abortCallback = this.abortCallback.bind(this); 40 | this.init = this.init.bind(this); 41 | } 42 | 43 | onStepChange(activeStep) {} 44 | 45 | init(initResults) { 46 | let loadingStatus = status.POLLING; 47 | let systemData = null; 48 | let sysDataRetrieved = false; 49 | let networkIfacesRetrieved = false; 50 | let hostFqdnValidated = false; 51 | 52 | if (!isEmptyObject(initResults)) { 53 | sysDataRetrieved = initResults[tasks.GET_SYSTEM_DATA] === true; 54 | networkIfacesRetrieved = 55 | initResults[tasks.RETRIEVE_NETWORK_INTERFACES] === true; 56 | hostFqdnValidated = initResults[tasks.VALIDATE_FQDN] === true; 57 | } 58 | 59 | if (sysDataRetrieved) { 60 | this.libvirtRunning = this.defaultsProvider.libvirtRunning(); 61 | this.virtSupported = this.defaultsProvider.virtSupported(); 62 | this.sufficientMemAvail = this.defaultsProvider.sufficientMemAvail(); 63 | this.allSystemDataRetrieved = 64 | sysDataRetrieved && networkIfacesRetrieved && hostFqdnValidated; 65 | 66 | const loadingSuccessful = 67 | this.allSystemDataRetrieved && 68 | this.libvirtRunning && 69 | this.virtSupported && 70 | this.sufficientMemAvail; 71 | loadingStatus = loadingSuccessful ? status.SUCCESS : status.FAILURE; 72 | systemData = this.defaultsProvider.systemData; 73 | this.state.heSetupModel.setDefaultValues(this.defaultsProvider); 74 | } else { 75 | loadingStatus = status.FAILURE; 76 | } 77 | 78 | this.setState({ 79 | loadingState: loadingStatus, 80 | systemData: systemData, 81 | networkIfacesRetrieved, 82 | networkIfacesRetrieved, 83 | }); 84 | } 85 | 86 | finishDeploy() { 87 | this.props.onFinishDeploy(); 88 | } 89 | 90 | handleFinish() { 91 | this.setState({ isDeploymentStarted: true }); 92 | } 93 | 94 | handleReDeploy() { 95 | this.setState({ isDeploymentStarted: false }); 96 | } 97 | 98 | abortCallback() { 99 | this.setState({ isDeploymentStarted: false }); 100 | this.props.onClose(); 101 | } 102 | 103 | UNSAFE_componentWillMount() { 104 | this.defaultsProvider = new DefaultValueProvider(this.init); 105 | } 106 | 107 | componentDidMount() { 108 | if (this.state.gDeployAnswerFilePaths) { 109 | console.log(messages.ADD_GDEPLOY_PROPS_TO_ANS_FILE); 110 | const gdeployAnsFiles = this.state.gDeployAnswerFilePaths; 111 | const glusterAnsFile = gdeployAnsFiles[0]; 112 | const setupModel = this.state.heSetupModel.model; 113 | this.state.heSetupModel.addGlusterValues(glusterAnsFile, setupModel); 114 | setupModel.storage.domainType.value = "glusterfs"; 115 | this.setState({ setupModel }); 116 | } else { 117 | console.log(messages.NO_GDEPLOY_ANSWER_FILES_FOUND); 118 | } 119 | } 120 | 121 | render() { 122 | return ( 123 | 145 | ); 146 | } 147 | } 148 | 149 | HeSetupWizardContainer.propTypes = { 150 | deploymentType: PropTypes.string, 151 | gDeployAnswerFilePaths: PropTypes.array, 152 | onClose: PropTypes.func.isRequired, 153 | onSuccess: PropTypes.func.isRequired, 154 | onFinishDeploy: PropTypes.func.isRequired, 155 | }; 156 | 157 | export default HeSetupWizardContainer; 158 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/MultiRowTextBox/InputRow.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | 4 | const InputRow = ({disableAddButton, disableDeleteButton, errorMsgs, handleAdd, hideAddButton, 5 | onValueChange, onValueDelete, value }) => { 6 | const input = classNames( 7 | "form-group", 8 | "col-md-8", 9 | "multi-row-text-box-input", 10 | { "has-error": errorMsgs && errorMsgs.value } 11 | ); 12 | 13 | const addButton = classNames( 14 | "btn", "btn-primary", "wizard-pf-next", "multi-row-text-box-add-button", 15 | {"hidden": hideAddButton} 16 | ); 17 | 18 | const addIcon = classNames( 19 | "i", "fa", "fa-plus", 20 | {"hidden": hideAddButton} 21 | ); 22 | 23 | return ( 24 |
    25 |
    26 | onValueChange(e.target.value)} 29 | /> 30 | {errorMsgs && errorMsgs.name && 31 | 32 | {errorMsgs.name} 33 | 34 | } 35 |
    36 | 37 |
    38 | 42 | 43 | 47 |
    48 | 49 |
    50 |
    51 | ) 52 | }; 53 | 54 | export default InputRow; 55 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/MultiRowTextBox/InputRowContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import InputRow from './InputRow' 3 | 4 | class InputRowContainer extends Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | 9 | this.onValueChange = this.onValueChange.bind(this); 10 | this.onValueDelete = this.onValueDelete.bind(this); 11 | } 12 | 13 | onValueChange(value) { 14 | this.props.changeCallBack(this.props.index, value); 15 | } 16 | 17 | onValueDelete() { 18 | this.props.deleteCallBack(this.props.index); 19 | } 20 | 21 | render () { 22 | return ( 23 | 33 | ) 34 | } 35 | } 36 | 37 | export default InputRowContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/MultiRowTextBox/MultiRowTextBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import InputRowContainer from './InputRowContainer' 3 | 4 | const MultiRowTextBox = ({disableDeleteButton, errorMsgs, handleAdd, handleValueDelete, handleValueUpdate, 5 | limitRows, values}) => { 6 | const valueRows = []; 7 | 8 | for (let i = 0; i < values.length; i++) { 9 | let isLastValue = i === (values.length - 1); 10 | let hideAddButton = !isLastValue || (isLastValue && limitRows); 11 | let disableAddButton = values[i] === "" || disableDeleteButton; 12 | 13 | valueRows.push( 14 | 26 | ) 27 | } 28 | 29 | return ( 30 |
    {valueRows}
    31 | ) 32 | }; 33 | 34 | export default MultiRowTextBox; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/MultiRowTextBox/MultiRoxTextBoxContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import MultiRowTextBox from './MultiRowTextBox' 4 | 5 | class MultiRowTextBoxContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | values: props.values, 10 | errorMsg: "", 11 | errorMsgs: {} 12 | }; 13 | 14 | this.handleAdd = this.handleAdd.bind(this); 15 | } 16 | 17 | handleAdd() { 18 | const values = this.state.values; 19 | values.push(""); 20 | this.setState({ values }) 21 | } 22 | 23 | render() { 24 | const values = this.state.values; 25 | let disableDeleteButton = false; 26 | 27 | const rowLimitProvided = typeof this.props.rowLimit !== "undefined"; 28 | let limitRows = rowLimitProvided && (values.length === this.props.rowLimit); 29 | 30 | if (values.length === 0) { 31 | values.push(""); 32 | disableDeleteButton = true; 33 | } else if (values.length === 1 && values[0] === "") { 34 | disableDeleteButton = true; 35 | } 36 | 37 | return ( 38 | 46 | ) 47 | } 48 | } 49 | 50 | MultiRowTextBoxContainer.propTypes = { 51 | values: PropTypes.array.isRequired, 52 | itemType: PropTypes.string.isRequired, 53 | rowLimit: PropTypes.number, 54 | handleValueUpdate: PropTypes.func.isRequired, 55 | handleValueDelete: PropTypes.func.isRequired 56 | }; 57 | 58 | export default MultiRowTextBoxContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/NetworkStep/HeWizardNetwork.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Selectbox from '../../common/Selectbox' 3 | import {deploymentTypes, status as gwState} from '../constants' 4 | import { getClassNames } from '../../../helpers/HostedEngineSetupUtil' 5 | 6 | const HeWizardNetwork = ({deploymentType, errorMsg, errorMsgs, gatewayState, interfaces, networkConfig, 7 | handleNetworkConfigUpdate}) => { 8 | const gatewayPingPending = gatewayState === gwState.POLLING; 9 | const isOtopiDeployment = deploymentType === deploymentTypes.OTOPI_DEPLOYMENT; 10 | 11 | return ( 12 |
    13 |
    14 | {errorMsg && 15 |
    16 |
    17 | 18 | {errorMsg} 19 |
    20 |
    21 | } 22 | 23 |
    24 |
    25 |

    Network Settings

    26 |
    27 |
    28 | 29 |
    30 | 31 |
    32 |
    33 | handleNetworkConfigUpdate("bridgeIf", e)} 36 | /> 37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 |
    44 | handleNetworkConfigUpdate("bridgeName", e.target.value)} 49 | /> 50 | {errorMsgs.bridgeName && {errorMsgs.bridgeName}} 51 |
    52 |
    53 | 54 | {isOtopiDeployment && 55 |
    56 | 57 |
    58 | handleNetworkConfigUpdate("firewallManager", e.target.checked)} 61 | /> 62 |
    63 |
    64 | } 65 | 66 |
    67 | 68 |
    69 | handleNetworkConfigUpdate("gateway", e.target.value)} 74 | // onBlur={(e) => this.checkGatewayPingability(e.target.value)} 75 | /> 76 | {errorMsgs.gateway && {errorMsgs.gateway}} 77 | {gatewayPingPending && 78 |
    79 |
    80 | Verifying IP address... 81 |
    82 | } 83 |
    84 |
    85 | 86 |
    87 | ) 88 | }; 89 | 90 | export default HeWizardNetwork; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/NetworkStep/HeWizardNetworkContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import { pingGateway } from '../../../helpers/HostedEngineSetupUtil' 4 | import { validatePropsForUiStage, getErrorMsgForProperty } from '../Validation' 5 | import { defaultInterfaces, messages, status as gwState, configFileTypes as types } from '../constants' 6 | import HeWizardNetwork from './HeWizardNetwork' 7 | 8 | class HeWizardNetworkContainer extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | networkConfig: props.heSetupModel.network, 13 | errorMsg: "", 14 | errorMsgs: {}, 15 | gatewayState: gwState.EMPTY, 16 | interfaces: defaultInterfaces 17 | }; 18 | 19 | this.lastGatewayAddress = ""; 20 | 21 | this.checkGatewayPingability = this.checkGatewayPingability.bind(this); 22 | this.setDefaultValues = this.setDefaultValues.bind(this); 23 | this.handleNetworkConfigUpdate = this.handleNetworkConfigUpdate.bind(this); 24 | this.validateConfigUpdate = this.validateConfigUpdate.bind(this); 25 | this.validateAllInputs = this.validateAllInputs.bind(this); 26 | } 27 | 28 | UNSAFE_componentWillMount() { 29 | this.setDefaultValues(); 30 | } 31 | 32 | checkGatewayPingability(address) { 33 | let errorMsg = this.state.errorMsg; 34 | errorMsg = ""; 35 | 36 | let errorMsgs = this.state.errorMsgs; 37 | errorMsgs.gateway = ""; 38 | 39 | let gatewayState = this.state.gatewayState; 40 | gatewayState = gwState.POLLING; 41 | 42 | this.setState({ gatewayState, errorMsg, errorMsgs }); 43 | 44 | this.lastGatewayAddress = address; 45 | 46 | let self = this; 47 | pingGateway(address) 48 | .done(function() { 49 | if (address === self.lastGatewayAddress) { 50 | gatewayState = gwState.SUCCESS; 51 | self.setState({errorMsg, gatewayState}); 52 | } 53 | }) 54 | .fail(function() { 55 | if (address === self.lastGatewayAddress) { 56 | errorMsg = messages.GENERAL_ERROR_MSG; 57 | errorMsgs.gateway = messages.IP_NOT_PINGABLE; 58 | gatewayState = gwState.FAILURE; 59 | self.setState({errorMsg, errorMsgs, gatewayState}); 60 | } 61 | }); 62 | } 63 | 64 | setDefaultValues() { 65 | const defaultsProvider = this.props.defaultsProvider; 66 | 67 | this.setState({ interfaces: defaultsProvider.getNetworkInterfaces() }); 68 | this.handleNetworkConfigUpdate("bridgeIf", defaultsProvider.getDefaultInterface()); 69 | this.handleNetworkConfigUpdate("gateway", defaultsProvider.getDefaultGateway()); 70 | } 71 | 72 | handleNetworkConfigUpdate(property, value) { 73 | const networkConfig = this.state.networkConfig; 74 | 75 | if (property === "firewallManager") { 76 | networkConfig.firewallManager.value = value ? "iptables" : "None"; 77 | networkConfig.firewallManager.type = value ? types.STRING : types.NONE; 78 | } else { 79 | networkConfig[property].value = value; 80 | } 81 | 82 | this.setState({ networkConfig }); 83 | this.validateConfigUpdate(property, networkConfig); 84 | } 85 | 86 | validateConfigUpdate(propName, config) { 87 | let errorMsg = this.state.errorMsg; 88 | const errorMsgs = {}; 89 | const prop = config[propName]; 90 | const propErrorMsg = getErrorMsgForProperty(prop); 91 | 92 | if (propErrorMsg !== "") { 93 | errorMsgs[propName] = propErrorMsg; 94 | } else { 95 | errorMsg = ""; 96 | } 97 | 98 | if (propName === "gateway" && propErrorMsg === "") { 99 | this.checkGatewayPingability(prop.value); 100 | } 101 | 102 | this.setState({ errorMsg, errorMsgs }); 103 | } 104 | 105 | validateAllInputs() { 106 | let errorMsg = ""; 107 | let errorMsgs = {}; 108 | let propsAreValid = validatePropsForUiStage("Network", this.props.heSetupModel, errorMsgs) || 109 | this.state.gatewayState === gwState.FAILED; 110 | 111 | if (!propsAreValid) { 112 | errorMsg = messages.GENERAL_ERROR_MSG; 113 | } 114 | 115 | this.setState({ errorMsg, errorMsgs }); 116 | return propsAreValid; 117 | } 118 | 119 | shouldComponentUpdate(nextProps, nextState){ 120 | if(!this.props.validating && nextProps.validating){ 121 | this.props.validationCallBack(this.validateAllInputs()) 122 | } 123 | 124 | return true; 125 | } 126 | 127 | render() { 128 | return 136 | } 137 | } 138 | 139 | HeWizardNetworkContainer.propTypes = { 140 | stepName: PropTypes.string.isRequired, 141 | heSetupModel: PropTypes.object.isRequired, 142 | deploymentType: PropTypes.string.isRequired 143 | }; 144 | 145 | export default HeWizardNetworkContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/PreviewStep/HeWizardPreview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HeWizardExecutionContainer from '../ExecutionStep/HeWizardExecutionContainer' 3 | 4 | const HeWizardPreview = ({gDeployAnswerFilePaths, isDeploymentStarted, onSuccess, reDeployCallback, setup, heSetupModel, abortCallback, sectionRows}) => { 5 | if (isDeploymentStarted) { 6 | return ( 7 | 14 | ); 15 | } else { 16 | return ( 17 |
    18 | 19 |
    { sectionRows.storageRows }
    20 | 21 | 22 |
    { sectionRows.networkRows }
    23 | 24 | 25 |
    { sectionRows.vmRows }
    26 | 27 | 28 |
    { sectionRows.engineRows }
    29 |
    30 | ) 31 | } 32 | }; 33 | 34 | export default HeWizardPreview; 35 | 36 | const PreviewSectionHeader = ({title, firstHeader}) => { 37 | const firstHeaderClassNames = "he-first-preview-header col-sm-4"; 38 | const headerClassNames = "he-preview-header col-sm-4"; 39 | 40 | return ( 41 |
    42 | 43 |

    {title}

    44 | 45 |
    46 | ) 47 | }; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/PreviewStep/HeWizardPreviewContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import HeWizardPreview from './HeWizardPreview' 4 | 5 | class HeWizardPreviewContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | heSetupModel: props.heSetupModel, 10 | isEditing: false, 11 | isChanged: false 12 | }; 13 | 14 | this.getDisplayValue = this.getDisplayValue.bind(this); 15 | } 16 | 17 | getDisplayValue(prop) { 18 | if (prop.name === "cpu") { 19 | return prop.value.replace("model_", "").trim(); 20 | } else if (prop.name === "firewallManager") { 21 | return prop.value === "iptables" ? "yes" : "no"; 22 | } else if (typeof prop.value === "boolean") { 23 | return prop.value ? "yes" : "no"; 24 | } else { 25 | return prop.value.toString(); 26 | } 27 | } 28 | 29 | render() { 30 | let model = this.state.heSetupModel; 31 | 32 | const sectionRows = { 33 | storageRows: [], 34 | networkRows: [], 35 | vmRows: [], 36 | engineRows: [] 37 | }; 38 | 39 | let idx = 0; 40 | 41 | Object.getOwnPropertyNames(model).forEach( 42 | function(sectionName) { 43 | let section = model[sectionName]; 44 | Object.getOwnPropertyNames(section).forEach( 45 | function(propName) { 46 | let prop = section[propName]; 47 | 48 | if (!prop.showInReview) { 49 | return; 50 | } 51 | 52 | let previewRow = ; 54 | 55 | switch (prop.uiStage) { 56 | case "Storage": 57 | sectionRows.storageRows.push(previewRow); 58 | break; 59 | case "Network": 60 | sectionRows.networkRows.push(previewRow); 61 | break; 62 | case "VM": 63 | sectionRows.vmRows.push(previewRow); 64 | break; 65 | case "Engine": 66 | sectionRows.engineRows.push(previewRow); 67 | break; 68 | default: 69 | break; 70 | } 71 | }, this) 72 | }, this); 73 | 74 | return ( 75 | 85 | ) 86 | } 87 | } 88 | 89 | HeWizardPreviewContainer.propTypes = { 90 | stepName: PropTypes.string.isRequired, 91 | heSetupModel: PropTypes.object.isRequired, 92 | isDeploymentStarted: PropTypes.bool.isRequired, 93 | }; 94 | 95 | const PreviewRow = ({property, value}) => { 96 | return ( 97 |
    98 | 99 | 100 |
    101 | ) 102 | }; 103 | 104 | export default HeWizardPreviewContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/ReviewStep/ReviewItem.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | 4 | class ReviewItemContainer extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render() { 10 | return ( 11 | 13 | ) 14 | } 15 | } 16 | 17 | ReviewItemContainer.propTypes = { 18 | reviewItem: PropTypes.object.isRequired 19 | }; 20 | 21 | const ReviewItem = ({itemLabel, itemValue}) => { 22 | return ( 23 |
    24 | { itemLabel }: 25 | { itemValue === "" ? (None) : itemValue } 26 |
    27 | ) 28 | }; 29 | 30 | export default ReviewItemContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/ReviewStep/ReviewItemList.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import ReviewItemContainer from './ReviewItem' 4 | 5 | class ReviewItemListContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | return ( 12 | 14 | ) 15 | } 16 | } 17 | 18 | ReviewItemListContainer.propTypes = { 19 | collapsed: PropTypes.bool.isRequired, 20 | reviewItems: PropTypes.array.isRequired 21 | }; 22 | 23 | const ReviewItemList = ({collapsed, reviewItemsList}) => { 24 | const reviewItems = []; 25 | let idx = 0; 26 | reviewItemsList.forEach( 27 | function(item) { 28 | reviewItems.push(); 30 | } 31 | ); 32 | 33 | const contentClasses = collapsed ? "wizard-pf-review-content collapse" : "wizard-pf-review-content"; 34 | 35 | return ( 36 |
    37 |
    38 | { reviewItems } 39 |
    40 |
    41 | ) 42 | }; 43 | 44 | export default ReviewItemListContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/ReviewStep/ReviewStep.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import ReviewItemListContainer from "./ReviewItemList"; 4 | 5 | class ReviewStepContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | collapsed: false 10 | }; 11 | 12 | this.handleClick = this.handleClick.bind(this); 13 | } 14 | 15 | handleClick() { 16 | const collapsedState = this.state.collapsed; 17 | this.setState({ collapsed: !collapsedState }); 18 | } 19 | 20 | render() { 21 | return ( 22 | 27 | ) 28 | } 29 | } 30 | 31 | ReviewStepContainer.propTypes = { 32 | stepName: PropTypes.string.isRequired, 33 | isSubStep: PropTypes.bool.isRequired, 34 | reviewItems: PropTypes.array.isRequired 35 | }; 36 | 37 | const ReviewStep = ({collapsed, handleClick, isSubStep, stepName, reviewItems}) => { 38 | const anchorClasses = collapsed ? "collapsed" : ""; 39 | return ( 40 |
  • 41 | handleClick()}>{ stepName } 42 | 44 |
  • 45 | ) 46 | }; 47 | 48 | export default ReviewStepContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/ReviewStep/ReviewStepPanel.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import ReviewStepContainer from "./ReviewStep"; 4 | 5 | class ReviewStepPanelContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | reviewSteps: this.props.reviewSteps 10 | } 11 | } 12 | 13 | render() { 14 | return ( 15 | 17 | ) 18 | } 19 | } 20 | 21 | ReviewStepPanelContainer.propTypes = { 22 | headerText: PropTypes.string, 23 | reviewSteps: PropTypes.object.isRequired 24 | }; 25 | 26 | const ReviewStepPanel = ({headerText, reviewSteps}) => { 27 | const steps = []; 28 | let idx = 0; 29 | reviewSteps.forEach( 30 | function(step) { 31 | steps.push(); 35 | } 36 | ); 37 | 38 | return ( 39 |
    40 |
    41 |
    42 | { headerText } 43 |
    44 |
    45 |
    46 |
      47 | { steps } 48 |
    49 |
    50 |
    51 | ) 52 | }; 53 | 54 | export default ReviewStepPanelContainer; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/Lun/Lun.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { messages, resourceConstants } from "../../../constants"; 3 | import LunProp from "./LunProp/LunProp"; 4 | 5 | const lunStatus = { 6 | USED: "used", 7 | FREE: "free", 8 | }; 9 | 10 | const Lun = ({ handleLunSelection, lun, selectedLun, storageConfig }) => { 11 | function bytesToGiB(bytes) { 12 | return bytes / Math.pow(2, 30); 13 | } 14 | 15 | const minLunSizeInGiB = 16 | storageConfig.imgSizeGB.value + resourceConstants.LUN_STORAGE_OVERHEAD_GIB; 17 | const lunSizeInGiB = bytesToGiB(lun.size); 18 | const lunTooSmall = lunSizeInGiB < minLunSizeInGiB; 19 | const lunDirty = lun.status === lunStatus.USED; 20 | const disableLun = lunTooSmall || lunDirty; 21 | const lunProps = [ 22 | { key: "size", label: "Size (GiB)", info: lunSizeInGiB.toFixed(2) }, 23 | { key: "description", label: "Description", info: lun.description }, 24 | { key: "status", label: "Status", info: lun.status }, 25 | { key: "numPaths", label: "Number of Paths", info: lun.numPaths }, 26 | ]; 27 | 28 | return ( 29 |
    30 |
    31 |
    32 | {disableLun && ( 33 | 37 | )} 38 | handleLunSelection(e.target.value)} 45 | /> 46 |  ID: {lun.guid} 47 |
    48 | {lunTooSmall ? ( 49 | 50 | ) : null} 51 | {lunDirty ? ( 52 | 53 | ) : null} 54 | {lunProps.map((lunProp) => { 55 | return ( 56 | 61 | ); 62 | })} 63 |
    64 |
    65 | ); 66 | }; 67 | 68 | export default Lun; 69 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/Lun/LunContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import Lun from './Lun' 4 | 5 | class LunContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | lun: this.props.lun, 10 | handleLunSelection: this.props.handleLunSelection, 11 | selectedLun: this.props.selectedLun 12 | }; 13 | } 14 | 15 | render() { 16 | return 20 | } 21 | } 22 | 23 | LunContainer.propTypes = { 24 | lun: PropTypes.object.isRequired, 25 | handleLunSelection: PropTypes.func.isRequired, 26 | selectedLun: PropTypes.string.isRequired 27 | }; 28 | 29 | export default LunContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/Lun/LunProp/LunProp.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const lunProp = (props) => ( 4 |
    5 | 6 | {props.label} 7 | {": "} 8 | 9 | {props.info} 10 |
    11 | ); 12 | 13 | export default lunProp; 14 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/LunList/LunList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LunContainer from '../Lun/LunContainer' 3 | 4 | const LunList = ({handleLunSelection, lunList, selectedLun, storageConfig}) => { 5 | const luns = []; 6 | 7 | lunList.forEach(function(lun, idx) { 8 | luns.push(); 13 | }); 14 | 15 | return ( 16 |
    17 |
    The following luns have been found on the requested target:
    18 | { luns } 19 |
    20 | ) 21 | }; 22 | 23 | export default LunList; 24 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/LunList/LunListContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import LunList from './LunList' 4 | 5 | class LunListContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | lunList: this.props.lunList, 10 | selectedLun: this.props.selectedLun, 11 | storageConfig: this.props.storageConfig 12 | }; 13 | } 14 | 15 | render() { 16 | return 20 | } 21 | } 22 | 23 | LunListContainer.propTypes = { 24 | lunList: PropTypes.array.isRequired, 25 | handleLunSelection: PropTypes.func.isRequired, 26 | selectedLun: PropTypes.string.isRequired 27 | }; 28 | 29 | export default LunListContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/Portal/Portal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Portal = ({portal}) => { 4 | return ( 5 |
    6 | {portal.portal.slice(0, portal.portal.indexOf(","))} 7 |
    8 | ) 9 | }; 10 | 11 | export default Portal; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/Portal/PortalContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import Portal from './Portal' 4 | 5 | class PortalContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | portal: this.props.portal 10 | }; 11 | } 12 | 13 | render() { 14 | return 15 | } 16 | } 17 | 18 | PortalContainer.propTypes = { 19 | portal: PropTypes.object.isRequired 20 | }; 21 | 22 | export default PortalContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/PortalList/PortalList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PortalContainer from '../Portal/PortalContainer' 3 | 4 | const PortalList = ({portalList}) => { 5 | const portals = []; 6 | 7 | portalList.forEach(function(portal, idx) { 8 | portals.push(); 9 | }); 10 | 11 | return ( 12 | 13 | { portals } 14 | 15 | ) 16 | }; 17 | 18 | export default PortalList; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/PortalList/PortalListContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import PortalList from './PortalList' 4 | 5 | class PortalListContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | portalList: this.props.portalList 10 | }; 11 | } 12 | 13 | render() { 14 | return 15 | } 16 | } 17 | 18 | PortalListContainer.propTypes = { 19 | portalList: PropTypes.array.isRequired 20 | }; 21 | 22 | export default PortalListContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/Target/Target.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TargetPortalGroupListContainer from "../TargetPortalGroupList/TargetPortalGroupListContainer"; 3 | 4 | const Target = ({handleTargetSelection, selectedTarget, target}) => { 5 | 6 | return ( 7 |
    8 |
    9 | handleTargetSelection(e.target.value, target.tpgts)} /> 15 |  { target.name },  16 | 17 |
    18 |
    19 | ) 20 | }; 21 | 22 | export default Target; 23 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/Target/TargetContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import Target from './Target' 4 | 5 | class TargetContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | target: this.props.target, 10 | handleTargetSelection: this.props.handleTargetSelection, 11 | selectedTarget: this.props.selectedTarget 12 | }; 13 | } 14 | 15 | render() { 16 | return 19 | } 20 | } 21 | 22 | TargetContainer.propTypes = { 23 | target: PropTypes.object.isRequired, 24 | handleTargetSelection: PropTypes.func.isRequired, 25 | selectedTarget: PropTypes.string.isRequired 26 | }; 27 | 28 | export default TargetContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/TargetList/TargetList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TargetContainer from "../Target/TargetContainer"; 3 | 4 | const TargetList = ({handleTargetSelection, selectedTarget, targetList}) => { 5 | const targets = []; 6 | 7 | Object.getOwnPropertyNames(targetList).forEach(function(tgt, idx) { 8 | targets.push(); 12 | }); 13 | 14 | return ( 15 |
    16 |
    The following targets have been found:
    17 | { targets } 18 |
    19 | ) 20 | }; 21 | 22 | export default TargetList; 23 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/TargetList/TargetListContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import TargetList from './TargetList' 4 | 5 | class TargetListContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | targetList: this.props.targetList, 10 | selectedTarget: Object.getOwnPropertyNames(this.props.targetList)[0], 11 | storageConfig: this.props.storageConfig 12 | }; 13 | } 14 | 15 | 16 | render() { 17 | return ( 18 | 21 | ) 22 | } 23 | } 24 | 25 | TargetListContainer.propTypes = { 26 | targetList: PropTypes.object.isRequired 27 | }; 28 | 29 | export default TargetListContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/TargetPortalGroup/TargetPortalGroup.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PortalListContainer from '../PortalList/PortalListContainer' 3 | 4 | const TargetPortalGroup = ({targetPortalGroup}) => { 5 | 6 | return ( 7 | 8 | 9 | TPGT: { targetPortalGroup.name } 10 | 11 |
    12 |
    13 | 14 |
    15 |
    16 |
    17 | ) 18 | }; 19 | 20 | export default TargetPortalGroup; 21 | -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/TargetPortalGroup/TargetPortalGroupContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import TargetPortalGroup from './TargetPortalGroup' 4 | 5 | class TargetPortalGroupContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | targetPortalGroup: this.props.targetPortalGroup 10 | }; 11 | } 12 | 13 | render() { 14 | return 15 | } 16 | } 17 | 18 | TargetPortalGroupContainer.propTypes = { 19 | targetPortalGroup: PropTypes.object.isRequired 20 | }; 21 | 22 | export default TargetPortalGroupContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/TargetPortalGroupList/TargetPortalGroupList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TargetPortalGroupContainer from "../TargetPortalGroup/TargetPortalGroupContainer"; 3 | 4 | const TargetPortalGroupList = ({targetPortalGroupList}) => { 5 | const tpgts = []; 6 | 7 | Object.getOwnPropertyNames(targetPortalGroupList).forEach(function(tpgt, idx) { 8 | tpgts.push(); 10 | }); 11 | 12 | return ( 13 | 14 | { tpgts } 15 | 16 | ) 17 | }; 18 | 19 | export default TargetPortalGroupList; -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/StorageStep/iSCSI/TargetPortalGroupList/TargetPortalGroupListContainer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import TargetPortalGroupList from "./TargetPortalGroupList"; 4 | 5 | class TargetPortalGroupListContainer extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | tpgtList: this.props.tpgtList 10 | }; 11 | } 12 | 13 | render() { 14 | return 15 | } 16 | } 17 | 18 | TargetPortalGroupListContainer.propTypes = { 19 | tpgtList: PropTypes.object.isRequired 20 | }; 21 | 22 | export default TargetPortalGroupListContainer -------------------------------------------------------------------------------- /dashboard/src/components/HostedEngineSetup/UnmaskablePassword.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | 4 | const masked = "password-mask-icon fa fa-eye"; 5 | const unmasked = "password-mask-icon fa fa-eye-slash"; 6 | 7 | class UnmaskablePasswordContainer extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | maskIconClasses: masked, 12 | inputType: "password", 13 | value: props.value 14 | }; 15 | 16 | this.toggleMask = this.toggleMask.bind(this); 17 | } 18 | 19 | toggleMask () { 20 | const iconClasses = this.state.maskIconClasses === masked ? unmasked : masked; 21 | const type = this.state.inputType === "password" ? "text" : "password"; 22 | this.setState({ maskIconClasses: iconClasses, inputType: type }); 23 | } 24 | 25 | changeHandler(e) { 26 | this.props.onChangeHandler(e.target.value); 27 | } 28 | 29 | render() { 30 | return ( 31 | 32 | this.changeHandler(e)} 35 | className="form-control" 36 | id={this.props.id} /> 37 | 38 | this.toggleMask()}> 39 |