As of 19/07/2022

How to install Node on Ubuntu 20.04

Use Option 3 — Installing Node Using the Node Version Manager

The beauty of this option is letting you choose different node versions for different projects

GMHazard was originally started with Node V12.18.2 & React 17

More information


Frontend directory structure: 

  • /apis - The directory contains JS files to deal with APIs(Both Core and Project via Intermediate)
    • We only have ProjectAPI. Make sure to have CoreAPI.js when the time comes
  • /assets - Any assets
    • /documents - We have some dummy documents which are used to render the Markdown on the Framework Documents tab
    • /style - Any relevant CSS files are located here. Ideally, each CSS will be used uniquely for a component/view(Which is not the current setup, some overlapping is happening, but nothing major for now, as I believe the design will continue to be changed.)
  • /components - React component
    • /common - Components that are meant to be at the atomic level or reusable(can be used in more than one component/view)
    • /Hazard - Components that are related to non-projects tab
    • /Project - Components that are related to projects tab
    • /NavBar - Components for the Navigation Bar
    • /PermissionConfig - Components for Admins to modify/allocate projects permission to users
  • /constants - Contain a single JS file that has some CONSTANT variables
  • /context - React Context - Treat as global variables. By having this, you do not need to worry about passing data between components that are not directly connected
  • /utils - Nothing to do with rendering, more of logic and simple setup(InitFontAwesome, History)
  • /views - A set of components from /components. Each view is used to render a page on a dedicated URL(Except the footer)
|-- "/apis"
|-- "/assets"
|   |-- "/documents"
|   `-- "/style"
|-- "/components"
|   |-- "/common"
|   |   |-- "/Disaggregation"
|   |   |-- "/GMS"
|   |   |-- "/HazardCurve"
|   |   |-- "/PermissionConfig"
|   |   |-- "/Scenarios"
|   |   |-- "/SiteSelection"
|   |   `-- "/UHS"
|   |-- "/Hazard"
|   |   |-- "/GMS"
|   |   |-- "/Scenarios"
|   |   |-- "/SeismicHazard"
|   |   `-- "/SiteSelection"
|   |-- "/NavBar"
|   |-- "/PermissionConfig"
|   `-- "/Project"
|       |-- "/GMS"
|       |-- "/Scenarios"
|       |-- "/SeismicHazard"
|       `-- "/SiteSelection"
|-- "/constants"
|   `-- "Constants.js"
|-- "/context"
|   |-- "GlobalContext.js"
|   `-- "index.js"
|-- "/utils"
|   |-- "/calculations"
|   |-- "History.js"
|   |-- "InitFontAwesome.js"
|   `-- "Utils.js"
`-- "/views"
    |-- "Footer.js"
    |-- "FrameworkDocView.js"
    |-- "Hazard.js"
    |-- "Home.js"
    |-- "index.js"
    |-- "PermissionConfig.js"
    |-- "Profile.js"
    |-- "ProjectCreate.js"
    `-- "Project.js"
Tree view with files
|-- "apis"
|   `-- "ProjectAPI.js"
|-- "App.js"
|-- "App.test.js"
|-- "assets"
|   |-- "documents"
|   |   |-- "15_Projects-Seismic-Hazard.md"
|   |   |-- "1_Hazard-Site-Selection.md"
|   |   |-- "2_Hazard-Seismic-Hazard.md"
|   |   |-- "3_Hazard-GMS.md"
|   |   |-- "4_Projects-Site-Selection.md"
|   |   `-- "99_Projects-GMS.md"
|   |-- "icon-question.png"
|   |-- "info-icon.png"
|   |-- "loading.svg"
|   |-- "logo.svg"
|   `-- "style"
|       |-- "AdminPage.css"
|       |-- "App.css"
|       |-- "CreateProject.css"
|       |-- "Footer.css"
|       |-- "GMSForm.css"
|       |-- "GMSPlot.css"
|       |-- "GMSViewer.css"
|       |-- "GuideTooltip.css"
|       |-- "HazardForms.css"
|       |-- "HazardPlots.css"
|       |-- "ImageMap.css"
|       |-- "index.css"
|       |-- "Messages.css"
|       |-- "MetadataBox.css"
|       |-- "Modal.css"
|       |-- "NavBar.css"
|       |-- "NZCodeSection.css"
|       |-- "PermissionDashboard.css"
|       |-- "ScenarioPlot.css"
|       |-- "ScenarioViewer.css"
|       |-- "SingleColumnView.css"
|       |-- "SiteSelectionMap.css"
|       |-- "Spinner.css"
|       |-- "TwoColumnView.css"
|       `-- "UHSPlot.css"
|-- "components"
|   |-- "common"
|   |   |-- "CustomSelect.js"
|   |   |-- "Disaggregation"
|   |   |   `-- "ContributionTable.js"
|   |   |-- "DownloadButton.js"
|   |   |-- "ErrorMessage.js"
|   |   |-- "GMS"
|   |   |   |-- "GMSAvailableGMPlot.js"
|   |   |   |-- "GMSCausalParamPlot.js"
|   |   |   |-- "GMSDisaggDistributionPlot.js"
|   |   |   |-- "GMSIMDistributionsPlot.js"
|   |   |   |-- "GMSMwRrupPlot.js"
|   |   |   `-- "GMSSpectraPlot.js"
|   |   |-- "GuideMessage.js"
|   |   |-- "GuideTooltip.js"
|   |   |-- "HazardCurve"
|   |   |   |-- "HazardBranchPlot.js"
|   |   |   `-- "HazardEnsemblePlot.js"
|   |   |-- "IMCustomSelect.js"
|   |   |-- "index.js"
|   |   |-- "Loading.js"
|   |   |-- "LoadingSpinner.js"
|   |   |-- "MetadataBox.js"
|   |   |-- "ModalComponent.js"
|   |   |-- "PermissionConfig"
|   |   |   `-- "PermissionDashboard.js"
|   |   |-- "ReactAuth0SPA.js"
|   |   |-- "Scenarios"
|   |   |   |-- "MetadataTable.js"
|   |   |   `-- "ScenarioPlot.js"
|   |   |-- "SingleColumnView.js"
|   |   |-- "SiteSelection"
|   |   |   `-- "ImageMap.js"
|   |   |-- "TwoColumnView.js"
|   |   `-- "UHS"
|   |       |-- "UHSBranchPlot.js"
|   |       `-- "UHSPlot.js"
|   |-- "Hazard"
|   |   |-- "GMS"
|   |   |   |-- "GMSForm.js"
|   |   |   |-- "GMSViewer.js"
|   |   |   `-- "index.js"
|   |   |-- "Scenarios"
|   |   |   |-- "index.js"
|   |   |   |-- "ScenarioForm.js"
|   |   |   `-- "ScenarioViewer.js"
|   |   |-- "SeismicHazard"
|   |   |   |-- "DisaggregationSection.js"
|   |   |   |-- "HazardCurveSection.js"
|   |   |   |-- "HazardForm.js"
|   |   |   |-- "HazardViewerDisaggregation.js"
|   |   |   |-- "HazardViewerHazardCurve.js"
|   |   |   |-- "HazardViewer.js"
|   |   |   |-- "HazardViewerUHS.js"
|   |   |   |-- "index.js"
|   |   |   |-- "NZS1170p5Section.js"
|   |   |   |-- "NZTASection.js"
|   |   |   `-- "UHSSection.js"
|   |   `-- "SiteSelection"
|   |       |-- "EnsembleSelect.js"
|   |       |-- "index.js"
|   |       |-- "SiteSelectionBasinDepth.js"
|   |       |-- "SiteSelectionForm.js"
|   |       |-- "SiteSelectionMap.js"
|   |       |-- "SiteSelectionMapPin.js"
|   |       |-- "SiteSelectionRegional.js"
|   |       |-- "SiteSelectionViewer.js"
|   |       |-- "SiteSelectionVS30.js"
|   |       `-- "SiteSelectionVS30SiteConditions.js"
|   |-- "NavBar"
|   |   |-- "index.js"
|   |   |-- "LoginButton.js"
|   |   |-- "LogoutButton.js"
|   |   `-- "NavBar.js"
|   |-- "PermissionConfig"
|   |   |-- "EditUserPermission.js"
|   |   |-- "index.js"
|   |   |-- "PagePermissionDashboard.js"
|   |   `-- "ProjectPermissionDashboard.js"
|   |-- "PrivateRoute.js"
|   `-- "Project"
|       |-- "GMS"
|       |   |-- "GMSForm.js"
|       |   |-- "GMSViewer.js"
|       |   `-- "index.js"
|       |-- "Scenarios"
|       |   |-- "index.js"
|       |   |-- "ScenarioForm.js"
|       |   `-- "ScenarioViewer.js"
|       |-- "SeismicHazard"
|       |   |-- "DisaggregationSection.js"
|       |   |-- "HazardCurveSection.js"
|       |   |-- "HazardForm.js"
|       |   |-- "HazardViewerDisaggregation.js"
|       |   |-- "HazardViewerHazardCurve.js"
|       |   |-- "HazardViewer.js"
|       |   |-- "HazardViewerUHS.js"
|       |   |-- "index.js"
|       |   `-- "UHSSection.js"
|       `-- "SiteSelection"
|           |-- "index.js"
|           |-- "SiteSelectionForm.js"
|           `-- "SiteSelectionViewer.js"
|-- "constants"
|   `-- "Constants.js"
|-- "context"
|   |-- "GlobalContext.js"
|   `-- "index.js"
|-- "index.js"
|-- "utils"
|   |-- "calculations"
|   |   `-- "CalculateGMSSpectra.js"
|   |-- "History.js"
|   |-- "InitFontAwesome.js"
|   `-- "Utils.js"
`-- "views"
    |-- "Footer.js"
    |-- "FrameworkDocView.js"
    |-- "Hazard.js"
    |-- "Home.js"
    |-- "index.js"
    |-- "PermissionConfig.js"
    |-- "Profile.js"
    |-- "ProjectCreate.js"
    `-- "Project.js"

Import order

  1. React always comes first, then an empty line
  2. Any third-party packages then an empty line
  3. Context and Constants then an empty line
  4. Internally used components and utils
  5. import CSS

Component structure

  1. useContext hook at the very top
  2. useState hooks
  3. useEffect hooks
  4. Functions
  5. JSX


// React
import React, { useState, useContext, useEffect, Fragment } from "react";

// Third-party packages
import Select from "react-select";
import { v4 as uuidv4 } from "uuid";
import makeAnimated from "react-select/animated";

// Context and Constants
import { GlobalContext } from "context";
import * as CONSTANTS from "constants/Constants";

// Internally made components and utils
import { GuideTooltip } from "components/common";
import { createAnnualExceedanceArray, isPSANotInIMList } from "utils/Utils";

// Import CSS
import "assets/style/HazardForms.css";

// Actual components
const UHSSection = () => {
// useContext
  const {
    projectIMs,
    projectUHSRPs,
    setProjectUHSGetClick,
    setProjectSelectedUHSRP,
    projectSiteSelectionGetClick,
  } = useContext(GlobalContext);

  const animatedComponents = makeAnimated();
   
  // useState hooks
  const [localRPs, setLocalRPs] = useState([]);
  const [rpOptions, setRPOptions] = useState([]);
  
  // useEffect hooks
  // Reset local variable to empty array when global changed to empty array (Reset)
  useEffect(() => {
    setLocalRPs([]);
    if (projectUHSRPs.length !== 0) {
      setRPOptions(createAnnualExceedanceArray(projectUHSRPs));
    } else {
      setRPOptions([]);
    }
  }, [projectSiteSelectionGetClick]);
  
  // Functions
  const getUHS = () => {
    setProjectSelectedUHSRP(localRPs);
    setProjectUHSGetClick(uuidv4());
  };

  // JSX
  return (
    <Fragment>
      <form autoComplete="off" onSubmit={(e) => e.preventDefault()}>
        <div className="form-group form-section-title">
          {CONSTANTS.UNIFORM_HAZARD_SPECTRUM}
          <GuideTooltip
            explanation={CONSTANTS.TOOLTIP_MESSAGES["PROJECT_UHS"]}
          />
        </div>
        <div className="form-group">
          <label
            id="label-uhs-return-period"
            htmlFor="uhs-return-period"
            className="control-label"
          >
            {CONSTANTS.ANNUAL_EXCEEDANCE_RATE} (years<sup>-1</sup>)
          </label>
          <Select
            id="uhs-return-period"
            closeMenuOnSelect={false}
            components={animatedComponents}
            isMulti
            placeholder={
              rpOptions.length === 0
                ? `${CONSTANTS.PLACEHOLDER_NOT_AVAILABLE}`
                : `${CONSTANTS.PLACEHOLDER_SELECT_SIGN}`
            }
            value={localRPs.length === 0 ? [] : localRPs}
            onChange={(value) => setLocalRPs(value || [])}
            options={rpOptions}
            isDisabled={rpOptions.length === 0 || isPSANotInIMList(projectIMs)}
            menuPlacement="auto"
            menuPortalTarget={document.body}
          />
        </div>
      </form>

      <div className="form-group">
        <button
          id="uhs-update-plot"
          type="button"
          className="btn btn-primary mt-2"
          disabled={localRPs.length === 0 || isPSANotInIMList(projectIMs)}
          onClick={() => getUHS()}
        >
          {CONSTANTS.GET_BUTTON}
        </button>
      </div>
    </Fragment>
  );
};

export default UHSSection;

All GMHazard APIs are in a package form

  • /api - Intmediate API is here as a proxy, so it forwards requests from the frontend to Core and/or Project API
    • `intermediate_api.py` is mainly communicating with our DB and Auth0
  • /logs - log events daily basis


Folder structure
|-- "CHANGELOG.md"
|-- "Dockerfile"
|-- "/intermediate_api"
|   |-- "/api"
|   |   |-- "core_api.py"
|   |   |-- "__init__.py"
|   |   |-- "intermediate_api.py"
|   |   `-- "project_api.py"
|   |-- "app.py"
|   |-- "auth0.py"
|   |-- "constants.py"
|   |-- "create_db.py"
|   |-- "custom_log_handler.py"
|   |-- "db.py"
|   |-- "decorators.py"
|   |-- "__init__.py"
|   |-- "/logs"
|   |   |-- "logfile.log"
|   |-- "models.py"
|   `-- "utils.py"
|-- "README.md"
|-- "requirements.txt"
`-- "setup.py"

Main features of the current database - More information

  • Track users action
  • Check whether a user has access to a certain project

DB workflow with projects

When a user access/projects, the app sends a request to the Project API via Intermediate API and the Intermediate API checks the projects between DB and Project API.

And only returns the projects to users that are matching(after the cross-check between DB and Project API)

For instance, with the Public access level:

  • DB has projects A, B, C and E
  • Projects API has projects A, B, C, D and E
  • Then user only gets A, B, C and E as available projects. Hence, make sure to add project D to the DB

For instance, with the Private access level:

  • DB(project table) has projects A, B, and C and all have access level Private
  • DB(users_project) has projects A and B
  • Project API has projects A, B, and C
  • Then user only gets A and B as available projects because this user only has access to A and B but C


(When the time comes for Private projects or adding more public/private projects, it might be a good idea to implement an interface to add projects to the DB)

The way of adding projects to the DB

  1. Make sure you are on 1p where the MariaDB container is running
  2. Run the following command, docker ps  to find a container's ID where the image label says mariadb
  3. Run the following command to find the container's IPAddress, docker inspect {replace me with container id} | grep "IPAd". This will provide an IPAddress for the mariadb container
  4. Access the container with the following command, mysql -h {replace me with the ip address} -P 3306 -u {replace me with userid for DB} -p{replace me with password for DB}
    For the -u argument, it's for a username and needs a space between -u and username, whereas -p is for password and does not need a space between -p and password
  5. Then you are now in the MariaDB. Use some SQL skills to add projects and/or users_projects(if it is for the private access level project)

We have three different components to run GMHazard

Core and Project APIs are currently running on mantle.
These are all running as a service with systemd.

Scripts are already written, so it's quite simple to deploy the latest version


There are four directories under /home/seistech/apis

  • core_api_dev
  • core_api_ea
  • project_api_dev
  • project_api_ea

The current GMHazard web app, quakecoresoft.canterbury.ac.nz/gmhazard is based on the DEV APIs


To update the API  to the latest version - Walkthrough with project_api_dev

  1. Ensure all the repositories inside the directory(In this case, project_api_dev) are updated with the master/main branch. - there are four, gmhazard, im_calculation, pro-processing and qcore
  2. Because all of those are packages, make sure to install it again if the version has changed(e.g., pip install -e apis/project_api)
  3. Use the following command to start/stop/check the status of the API service

    sudo service project_api_dev start/stop/status

    in the best practice, make sure to check the status first, then stop, then status, then start.

The intermediate API and DB are running on 1p.

These are currently implemented with Docker

More information can be found here

The current GMHazard web app, quakecoresoft.canterbury.ac.nz/gmhazard is based on the develop branch


To update the intermediate API and database to the latest version - Walkthrough with develop version

  1. Make sure to be in the directory, gmhazard/tools/deployment/docker/develop
  2. Run the following command

    ../Dockerise.sh develop
    1. Drop/remove any existing Docker containers - related to develop
    2. Change the branch to develop branch and pull to make sure its in the latest version
    3. Create a Docker container with the build date and git latest commit hash
    4. running the Intermediate API and DB

The frontend is also running on 1p

We deliver the frontend with the build version

Using the following command to build

npm run build

which will create another directory called /build

Then, we deploy it with NGINX

sudo cp -r ./build/ /var/www/gmhazard/

Finally, we should be able to access the GMHazard web app via link


  • No labels