diff --git a/.dockerignore b/.dockerignore index ccf5896..3987859 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,32 @@ +# dependencies +/node_modules + +# IDEs and editors +/.idea +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.angular/cache +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings +.env .github/ docs/ .DS_Store @@ -12,4 +41,10 @@ LICENSE Makefile PULL_REQUEST_TEMPLATE.md README.md -requirements.txt + +# System Files +.DS_Store +Thumbs.db + +# Sub repos +/.gitmodules \ No newline at end of file diff --git a/.gitignore b/.gitignore index e43b0f9..1e93735 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,36 @@ +# dependencies +/node_modules + +# IDEs and editors +/.idea +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.angular/cache +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings +.env + +# System Files .DS_Store +Thumbs.db + +# Sub repos +/.gitmodules \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b6169..639bd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,22 +8,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Thing 5 -- Thing 4 - -## [1.0.1] - yyyy-mm-dd - -### Added to 1.0.1 - -- Thing 3 - -### Fixed in 1.0.1 - -- Thing 2 - -## [1.0.0] - yyyy-mm-dd - -### Added to 1.0.0 - -- Thing 2 -- Thing 1 +- initial release diff --git a/Dockerfile b/Dockerfile index 10be716..cddc819 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ENV REFRESHED_AT=2022-01-06 LABEL Name="senzing/entity-search-web-app-console" \ Maintainer="support@senzing.com" \ - Version="0.0.0" + Version="1.0.0" HEALTHCHECK CMD ["/app/healthcheck.sh"] @@ -13,24 +13,76 @@ HEALTHCHECK CMD ["/app/healthcheck.sh"] USER root -# Install packages via PIP. +# Install packages via apt. + +RUN apt-get update \ + && apt-get -y install \ + build-essential \ + elfutils \ + fio \ + htop \ + iotop \ + ipython3 \ + itop \ + less \ + libpq-dev \ + net-tools \ + odbc-postgresql \ + procps \ + pstack \ + python-dev \ + python-pyodbc \ + python-setuptools \ + strace \ + telnet \ + tree \ + unixodbc \ + unixodbc-dev \ + vim \ + zip + +# Install Nodejs +RUN apt-get -y install curl software-properties-common \ + && curl -sL https://deb.nodesource.com/setup_16.x | bash - + +RUN apt-get -yq install \ + nodejs \ + && npm install -g npm \ + && node -v + +# Remove old lists +RUN rm -rf /var/lib/apt/lists/* + +# Install packages via pip. COPY requirements.txt ./ RUN pip3 install --upgrade pip \ && pip3 install -r requirements.txt \ && rm requirements.txt -# Install packages via apt. - # Copy files from repository. - COPY ./rootfs / +COPY ./run /app/run +COPY package.json /app +COPY package-lock.json /app + +# Install packages via npm +WORKDIR /app +#RUN npm install -g npm +RUN npm i --production + +# update npm vulnerabilites +#RUN npm -g uninstall npm +#RUN rm -fr /usr/local/lib/node_modules/npm # Make non-root container. +RUN addgroup --gid 1004 consoleusers \ + && useradd -u 1001 -g 1004 -m senzing -s /bin/bash USER 1001 # Runtime execution. - WORKDIR /app -CMD ["/app/sleep-infinity.sh"] +ENTRYPOINT [ "node" ] +CMD ["./run/xterm"] +#CMD ["/app/sleep-infinity.sh"] diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 0b11a2f..ad17e15 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ ## Which issue does this address -Issue number: #nnn +resolves #nnn ## Why was change needed diff --git a/README.md b/README.md index ed5240d..77803c6 100644 --- a/README.md +++ b/README.md @@ -1 +1,80 @@ -# entity-search-web-app-console \ No newline at end of file +# entity-search-web-app-console + +## Synopsis + +The `senzing/entity-search-web-app-console` docker image is used for creating +a running Docker container configured to run Senzing programs and utilities through the entity-search-web-app's xterm console(/admin/console). For security and modularity reasons the web based console is a separate container from the webapp itself. + +## Overview + +The default behavior when running `docker run` is for the container to spin up a PTY and serve up a bidirection websocket server to allow the webapp to send and recieve input to that PTY. + +### Prerequisite software + +The following software programs need to be installed: + +1. [docker](https://github.com/Senzing/knowledge-base/blob/master/HOWTO/install-docker.md) +2. [docker-compose](https://github.com/Senzing/knowledge-base/blob/master/HOWTO/install-docker-compose.md) + +### Pull latest docker images + +1. Pull the latest release of this app from [Docker Hub](https://hub.docker.com/r/senzing/entity-search-web-app-console). + Example: + + ```console + sudo docker pull senzing/entity-search-web-app-console + ``` + +### Configuration + +#### Environment Variables + + - `SENZING_CONSOLE_SERVER_PORT` is the port to run the xterm server on. + - `SENZING_CONSOLE_SERVER_URL` is the full url that the console server should respond to. + So if your console server is running on port `2370` it would be `https://mydomain:2730`. If you are running behind a virtual path like `/app/console` it would be `https://mydomain:2730/app/console`. This variable has to be mirrored in the configuration of the (entity-search-web-app)[https://github.com/Senzing/entity-search-web-app] configuration. If misconfigured the console functionality will not be available in the UI. + +#### Cmdline Switches + + - `webServerPortNumber` is the port to run the xterm server on. + - `confServerPortNumber` is the full url that the console server should respond to. + So if your console server is running on port `2370` it would be `https://mydomain:2730`. If you are running behind a virtual path like `/app/console` it would be `https://mydomain:2730/app/console`. This variable has to be mirrored in the configuration of the (entity-search-web-app)[https://github.com/Senzing/entity-search-web-app] configuration. If misconfigured the console functionality will not be available in the UI. + +### Air Gapped Environments + +Obviously if your deployment environment is highly restricted you're probably going +to run in to issues downloading the latest images from that context. +Please refer to +"[Install docker image in an air-gapped environment](https://github.com/Senzing/knowledge-base/blob/master/HOWTO/install-docker-image-in-air-gapped-enviroment.md)" +for how to procedure regarding this use-case. + +The short version is find a machine with network access, then: + +1. Pull the docker images you need to that machine. +2. Package them as a tar file. Example: + + ```console + sudo docker save senzing/entity-search-web-app-console --output senzing-entity-search-web-app-console-latest.tar + ``` + +3. Copy that to the deployment machine. +4. Load via + + ```console + sudo docker load --input senzing-entity-search-web-app-console-latest.tar + ``` + +### Building from Source + +1. Build the web app console. + Example: + + ```console + sudo docker build --tag senzing/entity-search-web-app-console . + ``` + +2. Run the app. + Example: + + ```console + sudo docker-compose up + ``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cccbdf3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,123 @@ +version: '3.3' +services: + postgres: + container_name: senzing-postgres + environment: +# See https://github.com/docker-library/docs/blob/master/postgres/README.md#environment-variables + POSTGRES_DB: ${POSTGRES_DB:-G2} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + POSTGRES_USERNAME: ${POSTGRES_USERNAME:-postgres} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USERNAME:-postgres}"] + interval: 10s + timeout: 5s + retries: 5 + image: postgres:11.6 + networks: + - sz-api-network + ports: + - 5432:5432 + restart: always + volumes: + - ${POSTGRES_DIR:-/var/lib/postgresql/data}:/var/lib/postgresql/data + + postgresinit: + container_name: senzing-postgresql-init + depends_on: + - postgres + environment: + SENZING_DATABASE_URL: "postgresql://${POSTGRES_USERNAME:-postgres}:${POSTGRES_PASSWORD:-postgres}@${POSTGRES_HOST:-senzing-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-G2}" + SENZING_SQL_FILE: /opt/senzing/g2/resources/schema/g2core-schema-postgresql-create.sql + image: senzing/postgresql-client:${SENZING_DOCKER_IMAGE_VERSION_POSTGRESQL_CLIENT:-1.0.0} + networks: + - sz-api-network + restart: on-failure + volumes: + - ${SENZING_G2_DIR:-/opt/senzing/g2}:/opt/senzing/g2 + + senzing-poc-server: + container_name: senzing-poc-server + command: + - -httpPort + - "8250" + - -bindAddr + - all + - -iniFile + - /etc/opt/senzing/G2Module.ini + - -allowedOrigins + - "*" + - -enableAdmin + - --sqs-load-url + - https://sqs.us-west-2.amazonaws.com/528659257769/ara-queue +# - --debug + depends_on: + - postgres + environment: +# See https://github.com/Senzing/senzing-api-server#configuration + SENZING_DATABASE_URL: "postgresql://${POSTGRES_USERNAME:-postgres}:${POSTGRES_PASSWORD:-postgres}@${POSTGRES_HOST:-senzing-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-G2}" + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN} + AWS_REGION: ${AWS_REGION} + AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION} + SENZING_SQS_QUEUE_URL: ${SENZING_SQS_QUEUE_URL} + image: senzing/senzing-poc-server:${SENZING_DOCKER_IMAGE_VERSION_SENZING_POC_SERVER:-latest} + networks: + - sz-api-network + ports: + - 8250:8250 + restart: 'no' + volumes: + - ${SENZING_DATA_VERSION_DIR:-/opt/senzing/data/2.0.0}:/opt/senzing/data + - ${SENZING_ETC_DIR:-/etc/opt/senzing}:/etc/opt/senzing + - ${SENZING_G2_DIR:-/opt/senzing/g2}:/opt/senzing/g2 + + senzing-webapp: + image: senzing/entity-search-web-app:${SENZING_DOCKER_IMAGE_VERSION_ENTITY_SEARCH_WEB_APP:-latest} + container_name: senzing-webapp +# command: "start:docker" + depends_on: + - senzing-poc-server + environment: + SENZING_API_SERVER_URL: "http://senzing-poc-server:8250" + SENZING_WEB_SERVER_ADMIN_AUTH_MODE: NONE + SENZING_WEB_SERVER_URL: "http://senzing-webapp:8251" + SENZING_WEB_SERVER_PORT: 8251 + SENZING_WEB_SERVER_API_PATH: '/api' + SENZING_CONSOLE_SERVER_URL: 'ws://localhost:8273' + + networks: + - sz-api-network +# volumes: +# - '/app/node_modules' + ports: + - 8251:8251 + - 8255:8255 + + restart: 'always' + + senzing-webapp-console: + image: senzing/entity-search-web-app-console + container_name: senzing-webapp-console + depends_on: + - senzing-webapp + build: + context: . + dockerfile: Dockerfile + environment: + SENZING_CONSOLE_SERVER_PORT: 8273 + + networks: + - sz-api-network + volumes: + - ${SENZING_DATA_VERSION_DIR:-/opt/senzing/data/2.0.0}:/opt/senzing/data + - ${SENZING_ETC_DIR:-/etc/opt/senzing}:/etc/opt/senzing + - ${SENZING_G2_DIR:-/opt/senzing/g2}:/opt/senzing/g2 + - ${SENZING_VAR_DIR:-/var/opt/senzing}:/var/opt/senzing + ports: + - 8273:8273 + + restart: 'no' + +networks: + sz-api-network: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..06358bf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,539 @@ +{ + "name": "@senzing/entity-search-web-app-console", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@socket.io/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==" + }, + "@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==" + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + }, + "@types/node": { + "version": "17.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", + "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "requires": { + "bytes": "3.1.1", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" + } + }, + "bytes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "engine.io": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.2.tgz", + "integrity": "sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.0", + "ws": "~8.2.3" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "engine.io-parser": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", + "requires": { + "@socket.io/base64-arraybuffer": "~1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", + "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.6", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "requires": { + "mime-db": "1.51.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-pty": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.10.1.tgz", + "integrity": "sha512-JTdtUS0Im/yRsWJSx7yiW9rtpfmxqxolrtnyKwPLI+6XqTAPW/O2MjS8FYL4I5TsMbH2lVgDb2VMjp+9LoQGNg==", + "requires": { + "nan": "^2.14.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "requires": { + "bytes": "3.1.1", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "socket.io": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", + "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.1.0", + "socket.io-adapter": "~2.3.3", + "socket.io-parser": "~4.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-adapter": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", + "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==" + }, + "socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..162d146 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "@senzing/entity-search-web-app-console", + "version": "1.0.0", + "description": "A nodejs websocket server that spawns a PTY to provide shell access to EDA Tools and utilities", + "main": "run xterm/index.js", + "scripts": { + "start": "node run/xterm -- consoleServerUrl=\"ws://localhost:8273/app/console\"" + }, + "author": "Ara Winters ara@senzing.com", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Senzing/entity-search-web-app-console.git" + }, + "dependencies": { + "express": "^4.17.2", + "node-pty": "^0.10.1", + "socket.io": "^4.4.1" + } +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..126fc1e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +click==7.0 +csvkit +eventlet +flask-socketio==3.3.1 +flask==1.0.2 +fuzzywuzzy +itsdangerous==1.1.0 +jinja2==2.11.3 +markupsafe==1.1.1 +pandas +ptable +pyodbc +pysnooper +python-engineio==3.9.0 +python-levenshtein +python-socketio==3.1.2 +setuptools +six==1.12.0 +werkzeug==0.15.3 +psycopg2 \ No newline at end of file diff --git a/run/runtime.datastore.config.js b/run/runtime.datastore.config.js new file mode 100644 index 0000000..6dd922e --- /dev/null +++ b/run/runtime.datastore.config.js @@ -0,0 +1,925 @@ +//const { calcProjectFileAndBasePath } = require("@angular/compiler-cli"); +const { env } = require("process"); +const { getHostnameFromUrl, getPortFromUrl, getProtocolFromUrl, getRootFromUrl, replaceProtocol, getPathFromUrl } = require("./utils"); + +function getCommandLineArgsAsJSON() { + // grab cmdline args + let cl = process.argv; + let cmdLineArgs = undefined; + // import args in to "cl" JSON style object + if(cl && cl.forEach){ + cmdLineArgs = {}; + cl.forEach( (val, ind, arr) => { + let retVal = val; + let retKey = val; + if(val && val.indexOf && val.indexOf('=')){ + retKey = (val.split('='))[0]; + retVal = (val.split('='))[1]; + } + cmdLineArgs[ retKey ] = retVal; + }) + } + return cmdLineArgs; +} + +function createCorsConfigFromInput( dirToWriteTo ) { + // return value + let retConfig = undefined; + + // grab env vars + let env = process.env; + if(env.SENZING_WEB_SERVER_CORS_ALLOWED_ORIGIN){ + retConfig = { + "origin": env.SENZING_WEB_SERVER_CORS_ALLOWED_ORIGIN, + "optionsSuccessStatus": 200, + "optionsFailureStatus": 401 + }; + } + + // grab cmdline args + let corsOpts = getCommandLineArgsAsJSON(); + if(corsOpts && corsOpts.corsAllowedOrigin) { + retConfig = { + "origin": corsOpts.corsAllowedOrigin, + "optionsSuccessStatus": corsOpts.corsSuccessResponseCode ? corsOpts.corsSuccessResponseCode : 200, + "optionsFailureStatus": corsOpts.corsFailureResponseCode ? corsOpts.corsFailureResponseCode : 401 + }; + } + + return retConfig; +} + +/** get command line and env vars as options for the AuthModule */ +function getOptionsFromInput() { + // grab env vars + let env = process.env; + let authOpts = getCommandLineArgsAsJSON(); + if(env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE) { + authOpts = authOpts && authOpts !== undefined ? authOpts : { + adminAuthMode: env.SENZING_WEB_SERVER_ADMIN_SECRET + } + } + if(env.SENZING_WEB_SERVER_ADMIN_SECRET) { + authOpts = authOpts && authOpts !== undefined ? authOpts : { + adminSecret: env.SENZING_WEB_SERVER_ADMIN_SECRET + } + } + if(env.SENZING_WEB_SERVER_ADMIN_SEED) { + authOpts = authOpts && authOpts !== undefined ? authOpts : { + adminToken: env.SENZING_WEB_SERVER_ADMIN_SEED + } + } + return authOpts; +} + +function getTestingOptionsFromInput() { + let retConfig = undefined; + if(env) { + + } + let cmdLineOpts = getCommandLineArgsAsJSON(); + if(cmdLineOpts && cmdLineOpts !== undefined) { + if(cmdLineOpts.testWebServerStartup === true) { + retConfig = !retConfig ? {} : retConfig; + retConfig.testWebServerStartup = cmdLineOpts.testWebServerStartup; + } + } + return retConfig; +} + +function getStreamServerOptionsFromInput() { + let retConfig = undefined; + let webServerCfg = getWebServerOptionsFromInput(); + // ------------- set sane defaults + retConfigDefaults = { + protocol: (getProtocolFromUrl(webServerCfg.apiServerUrl) === 'https' ? 'wss':'ws'), + target: replaceProtocol((getProtocolFromUrl(webServerCfg.apiServerUrl) === 'https' ? 'wss':'ws'), webServerCfg.apiServerUrl), + proxy: { + protocol: (getProtocolFromUrl(webServerCfg.apiServerUrl) === 'https' ? 'wss':'ws'), + hostname: webServerCfg.hostname ? webServerCfg.hostname : 'localhost', + port: 8255 + } + }; + retConfig = Object.assign({}, retConfigDefaults); + + // check to see if operating under virtual dir + if(webServerCfg.path && webServerCfg.path.endsWith && !webServerCfg.path.endsWith('/')) { + retConfig.proxy.path = webServerCfg.path; + } + + // update defaults with ENV options(if present) + if(env){ + retConfig.proxy.port = env.SENZING_STREAM_SERVER_PORT ? env.SENZING_STREAM_SERVER_PORT : retConfig.proxy.port; + } + + // ------------- now get cmdline options and override any defaults or ENV options + let cmdLineOpts = getCommandLineArgsAsJSON(); + if(cmdLineOpts && cmdLineOpts !== undefined) { + if(cmdLineOpts.streamServerPort){ + retConfig.proxy.port = cmdLineOpts.streamServerPort; + } + } + + // if "streamClientUrl" is defined use that instead + /* + if(webServerCfg.streamClientUrl) { + let hostnameFromUrL = getHostnameFromUrl(retOpts.streamClientUrl); + let portFromUrl = getPortFromUrl(retOpts.streamClientUrl); + + if(hostnameFromUrL && hostnameFromUrL !== retConfig.proxy.hostname){ + // override from url + retConfig.proxy.hostname = hostnameFromUrL; + } + if(portFromUrl && portFromUrl !== retConfig.proxy.port){ + // override from url + retConfig.proxy.port = portFromUrl; + } + } + */ + + // make sure the "url" is updated if anything has been overridden + retConfig.proxy.url = webServerCfg.streamClientUrl ? webServerCfg.streamClientUrl : ( + (retConfig.proxy.protocol ? retConfig.proxy.protocol : 'ws') + + '://'+ retConfig.proxy.hostname + + ':'+ (retConfig.proxy.port ? retConfig.proxy.port : '8255') + + (retConfig.proxy.path ? retConfig.proxy.path : '') + ); + + return retConfig; +} + +function createCspConfigFromInput() { + let retConfig = undefined; + let streamCfg = getStreamServerOptionsFromInput(); + let consoleCfg = getConsoleServerOptionsFromInput(); + + // ------------- set sane defaults + retConfigDefaults = { + directives: { + 'default-src': [`'self'`], + 'connect-src': [`'self'`], + 'script-src': [`'self'`, `'unsafe-eval'`,`'unsafe-inline'`], + 'img-src': [`'self'`, `data:`], + 'style-src': [`'self'`, `'unsafe-inline'`,'https://fonts.googleapis.com'], + 'font-src': [`'self'`, `https://fonts.gstatic.com`,`https://fonts.googleapis.com`] + }, + reportOnly: false + }; + retConfig = Object.assign({}, retConfigDefaults); + // ------------- check ENV vars + if(env.SENZING_WEB_SERVER_CSP_DEFAULT_SRC) { + retConfig.directives['default-src'].push(env.SENZING_WEB_SERVER_CSP_DEFAULT_SRC); + } + if(env.SENZING_WEB_SERVER_CSP_CONNECT_SRC) { + retConfig.directives['connect-src'].push(env.SENZING_WEB_SERVER_CSP_CONNECT_SRC); + } + if(env.SENZING_WEB_SERVER_CSP_SCRIPT_SRC) { + retConfig.directives['script-src'].push(env.SENZING_WEB_SERVER_CSP_SCRIPT_SRC); + } + if(env.SENZING_WEB_SERVER_CSP_IMG_SRC) { + retConfig.directives['img-src'].push(env.SENZING_WEB_SERVER_CSP_IMG_SRC); + } + /* + if(env.SENZING_WEB_SERVER_HOSTNAME) { + retConfig.directives['connect-src'].push('ws://'+env.SENZING_WEB_SERVER_HOSTNAME+':8555'); + retConfig.directives['connect-src'].push('wss://'+env.SENZING_WEB_SERVER_HOSTNAME+':8443'); + } + if(env.SENZING_WEB_SERVER_CSP_STREAM_SERVER_URL) { + retConfig.directives['connect-src'].push(env.SENZING_WEB_SERVER_CSP_STREAM_SERVER_URL); + }*/ + if(env.SENZING_WEB_SERVER_CSP_SCRIPT_SRC) { + retConfig.directives['script-src'].push(env.SENZING_WEB_SERVER_CSP_SCRIPT_SRC); + } + + if(env.SENZING_WEB_SERVER_CSP_STYLE_SRC) { + retConfig.directives['style-src'].push(env.SENZING_WEB_SERVER_CSP_STYLE_SRC); + } + if(env.SENZING_WEB_SERVER_CSP_FONT_SRC) { + retConfig.directives['font-src'].push(env.SENZING_WEB_SERVER_CSP_FONT_SRC); + } + // ------------- now get cmdline options and override any defaults or ENV options + let cmdLineOpts = getCommandLineArgsAsJSON(); + if(cmdLineOpts && cmdLineOpts !== undefined) { + if(cmdLineOpts.webServerCspDefaultSrc){ + retConfig.directives['default-src'] = retConfigDefaults.directives['default-src'] + retConfig.directives['default-src'].push(cmdLineOpts.webServerCspDefaultSrc); + } + if(cmdLineOpts.webServerCspConnectSrc){ + retConfig.directives['connect-src'] = retConfigDefaults.directives['connect-src'] + retConfig.directives['connect-src'].push(cmdLineOpts.webServerCspConnectSrc); + } + if(cmdLineOpts.webServerCspScriptSrc){ + retConfig.directives['script-src'] = retConfigDefaults.directives['script-src'] + retConfig.directives['script-src'].push(cmdLineOpts.webServerCspScriptSrc); + } + if(cmdLineOpts.webServerCspImgSrc){ + retConfig.directives['img-src'] = retConfigDefaults.directives['img-src'] + retConfig.directives['img-src'].push(cmdLineOpts.webServerCspImgSrc); + } + if(cmdLineOpts.webServerCspStyleSrc){ + retConfig.directives['style-src'] = retConfigDefaults.directives['style-src'] + retConfig.directives['style-src'].push(cmdLineOpts.webServerCspStyleSrc); + } + if(cmdLineOpts.webServerCspFontSrc){ + retConfig.directives['font-src'] = retConfigDefaults.directives['font-src'] + retConfig.directives['font-src'].push(cmdLineOpts.webServerCspFontSrc); + } + } + // ------------- add streaming proxy information to connect src + if( streamCfg && streamCfg.target) { + retConfig.directives['connect-src'].push(streamCfg.target); + } + if( streamCfg && streamCfg.proxy && streamCfg.proxy.url ) { + retConfig.directives['connect-src'].push(streamCfg.proxy.url); + retConfig.directives['connect-src'].push( getRootFromUrl( streamCfg.proxy.url ) ); + } + // ------------- add console stream socket to connect src + if( consoleCfg && consoleCfg.enabled && consoleCfg.url ) { + retConfig.directives['connect-src'].push(consoleCfg.url); + retConfig.directives['connect-src'].push( getRootFromUrl( consoleCfg.url ) ); + } + + return retConfig; +} + +/** get auth conf template */ +function createAuthConfigFromInput() { + // return value + let retConfig = undefined; + // grab env vars + let env = process.env; + + // check for virtual directory + let cmdLineOpts = getCommandLineArgsAsJSON(); + let _virtualDir = (env.SENZING_WEB_SERVER_VIRTUAL_PATH) ? env.SENZING_WEB_SERVER_VIRTUAL_PATH : ''; + _virtualDir = (cmdLineOpts && cmdLineOpts.virtualPath) ? cmdLineOpts.virtualPath : _virtualDir; + + // -------------------- start ENV vars import ------------------ + if(env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.admin = {}; + if(env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE === 'JWT'){ + retConfig.admin = { + "mode": "JWT", + "checkUrl": env.SENZING_WEB_SERVER_ADMIN_AUTH_STATUS ? env.SENZING_WEB_SERVER_ADMIN_AUTH_STATUS : _virtualDir+"/admin/auth/jwt/status", + "redirectOnFailure": true, + "loginUrl": env.SENZING_WEB_SERVER_ADMIN_AUTH_REDIRECT ? env.SENZING_WEB_SERVER_ADMIN_AUTH_REDIRECT : _virtualDir+"/admin/login" + } + } else if(env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE === 'SSO') { + retConfig.admin = { + "mode": "SSO", + "checkUrl": env.SENZING_WEB_SERVER_ADMIN_AUTH_STATUS ? env.SENZING_WEB_SERVER_ADMIN_AUTH_STATUS : _virtualDir+"/admin/auth/sso/status", + "redirectOnFailure": true, + "loginUrl": env.SENZING_WEB_SERVER_ADMIN_AUTH_REDIRECT ? env.SENZING_WEB_SERVER_ADMIN_AUTH_REDIRECT : _virtualDir+"/admin/login" + } + } + } + if(env.SENZING_WEB_SERVER_OPERATOR_AUTH_MODE) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.operator = {}; + if(SENZING_WEB_SERVER_OPERATOR_AUTH_MODE === 'JWT'){ + retConfig.operator = { + "mode": "JWT", + "checkUrl": env.SENZING_WEB_SERVER_OPERATOR_AUTH_STATUS ? env.SENZING_WEB_SERVER_OPERATOR_AUTH_STATUS : _virtualDir+"/auth/jwt/status", + "redirectOnFailure": true, + "loginUrl": env.SENZING_WEB_SERVER_OPERATOR_AUTH_REDIRECT ? env.SENZING_WEB_SERVER_OPERATOR_AUTH_REDIRECT : _virtualDir+"/login" + } + } else if(env.SENZING_WEB_SERVER_OPERATOR_AUTH_MODE === 'SSO') { + retConfig.operator = { + "mode": "SSO", + "checkUrl": env.SENZING_WEB_SERVER_OPERATOR_AUTH_STATUS ? env.SENZING_WEB_SERVER_OPERATOR_AUTH_STATUS : _virtualDir+"/auth/sso/status", + "redirectOnFailure": true, + "loginUrl": env.SENZING_WEB_SERVER_OPERATOR_AUTH_REDIRECT ? env.SENZING_WEB_SERVER_OPERATOR_AUTH_REDIRECT : _virtualDir+"/login" + } + } + } + if(env.SENZING_WEB_SERVER_PORT) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.port = env.SENZING_WEB_SERVER_PORT; + } + if(env.SENZING_WEB_SERVER_HOSTNAME) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.hostname = env.SENZING_WEB_SERVER_HOSTNAME; + } + if(env.SENZING_WEB_SERVER_ADMIN_SECRET) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.admin = retConfig.admin !== undefined ? retConfig.admin : {}; + retConfig.admin.secret = env.SENZING_WEB_SERVER_ADMIN_SECRET; + } + if(env.SENZING_WEB_SERVER_ADMIN_SEED) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.admin = retConfig.admin !== undefined ? retConfig.admin : {}; + retConfig.admin.token = env.SENZING_WEB_SERVER_ADMIN_SEED; + } + // -------------------- end ENV vars import ------------------ + // -------------------- start CMD LINE ARGS import ----------- + // grab cmdline args + let cl = process.argv; + let authOpts = getCommandLineArgsAsJSON(); + // now check our imported cmdline args + if(authOpts && authOpts !== undefined && authOpts.adminAuthMode && authOpts.adminAuthMode !== undefined) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.admin = retConfig && retConfig.admin ? retConfig.admin : {}; + + if(authOpts.adminAuthMode === 'JWT') { + retConfig.admin = { + "mode": "JWT", + "checkUrl": authOpts.adminAuthStatusUrl ? authOpts.adminAuthStatusUrl : _virtualDir+"/admin/auth/jwt/status", + "redirectOnFailure": true, + "loginUrl": authOpts.adminAuthRedirectUrl ? authOpts.adminAuthRedirectUrl : _virtualDir+"/admin/login" + } + } else if (authOpts.adminAuthMode === 'SSO') { + retConfig.admin = { + "mode": "SSO", + "checkUrl": authOpts.adminAuthStatusUrl ? authOpts.adminAuthStatusUrl : _virtualDir+"/admin/auth/sso/status", + "redirectOnFailure": authOpts.adminAuthRedirectOnFailure ? authOpts.adminAuthRedirectOnFailure : true, + "loginUrl": authOpts.adminAuthRedirectUrl ? authOpts.adminAuthRedirectUrl : _virtualDir+"/admin/login" + } + } + } + if(authOpts && authOpts !== undefined && authOpts.operatorAuthMode && authOpts.operatorAuthMode !== undefined) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.operator = retConfig && retConfig.operator ? retConfig.operator : {}; + if(authOpts.operatorAuthMode === 'JWT') { + retConfig.operator = { + "mode": "JWT", + "checkUrl": authOpts.operatorAuthStatusUrl ? authOpts.operatorAuthStatusUrl : _virtualDir+"/auth/jwt/status", + "redirectOnFailure": true, + "loginUrl": authOpts.operatorAuthRedirectUrl ? authOpts.operatorAuthRedirectUrl : _virtualDir+"/login" + } + } else if (authOpts.adminAuthMode === 'SSO') { + retConfig.operator = { + "mode": "SSO", + "checkUrl": authOpts.operatorAuthStatusUrl ? authOpts.operatorAuthStatusUrl : _virtualDir+"/auth/sso/status", + "redirectOnFailure": true, + "loginUrl": authOpts.operatorAuthRedirectUrl ? authOpts.operatorAuthRedirectUrl : _virtualDir+"/login" + } + } + } + if(authOpts && authOpts !== undefined && authOpts.authServerPortNumber && authOpts.authServerPortNumber !== undefined) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.port = authOpts.authServerPortNumber; + } + if(authOpts && authOpts !== undefined && authOpts.authServerHostName && authOpts.authServerHostName !== undefined) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.hostname = authOpts.authServerHostName; + } + if(authOpts && authOpts !== undefined && authOpts.adminAuthSecret ) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.admin = retConfig.admin !== undefined ? retConfig.admin : {}; + retConfig.admin.secret = authOpts.adminAuthSecret; + } + if(authOpts && authOpts !== undefined && authOpts.adminAuthToken ) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.admin = retConfig.admin !== undefined ? retConfig.admin : {}; + retConfig.admin.token = authOpts.adminAuthToken; + } + if(authOpts && authOpts !== undefined && authOpts.operatorAuthSecret ) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.operator = retConfig.operator !== undefined ? retConfig.operator : {}; + retConfig.operator.secret = authOpts.operatorAuthSecret; + } + if(authOpts && authOpts !== undefined && authOpts.operatorAuthToken ) { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.operator = retConfig.operator !== undefined ? retConfig.operator : {}; + retConfig.operator.token = authOpts.operatorAuthToken; + } + + // _virtualDir is assigned at the very beginning + // and is both env and cmd + if(_virtualDir && _virtualDir !== '' && _virtualDir !== '/') { + retConfig = retConfig !== undefined ? retConfig : {}; + retConfig.virtualPath = _virtualDir; + } + // -------------------- end CMD LINE ARGS import ----------- + + //console.log('AUTH TEMPLATE: ', authTemplate, fs.existsSync(authTemplate)); + //console.log('AUTH OPTS: ', JSON.stringify(authOpts, null, 2)); + //console.log('ENV VARS: ', JSON.stringify(process.env.SENZING_WEB_AUTH_SERVER_ADMIN_MODE, null, 2)); + //console.log('Write to Directory: ', __dirname); + + return retConfig; +} +function getConfigServerOptionsFromInput() { + let retOpts = { + port: 4200 + } + if(env){ + retOpts.port = env.SENZING_WEB_SERVER_PORT ? env.SENZING_WEB_SERVER_PORT : retOpts.port; + retOpts.port = env.SENZING_CONF_SERVER_PORT ? env.SENZING_CONF_SERVER_PORT : retOpts.port; + } + let cmdLineOpts = getCommandLineArgsAsJSON(); + if(cmdLineOpts && cmdLineOpts !== undefined) { + retOpts.port = cmdLineOpts.webServerPortNumber ? cmdLineOpts.webServerPortNumber : retOpts.port; + retOpts.port = cmdLineOpts.confServerPortNumber ? cmdLineOpts.confServerPortNumber : retOpts.port; + } + return retOpts; +} + +function createConsoleServerOptionsFromInput() { + let retOpts = getConsoleServerOptionsFromInput(); + return retOpts; +} + +function getConsoleServerOptionsFromInput() { + let retOpts = { + port: 8273, + enabled: true, + options: { + transports: ['websocket'], + reconnectionAttempts: 3 + } + } + if(env){ + //retOpts.port = env.SENZING_WEB_SERVER_PORT ? env.SENZING_WEB_SERVER_PORT : retOpts.port; + retOpts.port = env.SENZING_CONSOLE_SERVER_PORT ? env.SENZING_CONSOLE_SERVER_PORT : retOpts.port; + if(env.SENZING_CONSOLE_SERVER_URL) { + retOpts.url = env.SENZING_CONSOLE_SERVER_URL? env.SENZING_CONSOLE_SERVER_URL : retOpts.url; + retOpts.enabled = true; + } + } + let cmdLineOpts = getCommandLineArgsAsJSON(); + if(cmdLineOpts && cmdLineOpts !== undefined) { + //retOpts.port = cmdLineOpts.webServerPortNumber ? cmdLineOpts.webServerPortNumber : retOpts.port; + retOpts.port = cmdLineOpts.consoleServerPortNumber ? cmdLineOpts.consoleServerPortNumber : retOpts.port; + if(cmdLineOpts.consoleServerUrl) { + retOpts.url = cmdLineOpts.consoleServerUrl ? cmdLineOpts.consoleServerUrl : retOpts.url; + retOpts.port = getPortFromUrl(retOpts.url); + retOpts.enabled = true; + } + } + return retOpts; +} + +function getWebServerOptionsFromInput() { + let retOpts = { + protocol: 'http', + port: 4200, + hostname: 'localhost', + path: '/', + apiPath: '/api', + authPath: 'http://localhost:4200', + authMode: 'JWT', + apiServerUrl: 'http://localhost:8250', + streamLoading: false, + ssl: { + certPath: "/run/secrets/server.cert", + keyPath: "/run/secrets/server.key" + } + } + // update defaults with ENV options(if present) + if(env){ + retOpts.protocol = env.SENZING_WEB_SERVER_PROTOCOL ? env.SENZING_WEB_SERVER_PROTOCOL : retOpts.protocol; + retOpts.port = env.SENZING_WEB_SERVER_PORT ? env.SENZING_WEB_SERVER_PORT : retOpts.port; + retOpts.hostname = env.SENZING_WEB_SERVER_HOSTNAME ? env.SENZING_WEB_SERVER_HOSTNAME : retOpts.hostname; + retOpts.url = env.SENZING_WEB_SERVER_URL ? env.SENZING_WEB_SERVER_URL : retOpts.url; + retOpts.apiPath = env.SENZING_WEB_SERVER_API_PATH ? env.SENZING_WEB_SERVER_API_PATH : retOpts.apiPath; + retOpts.authPath = env.SENZING_WEB_SERVER_AUTH_PATH ? env.SENZING_WEB_SERVER_AUTH_PATH : retOpts.authPath; + retOpts.authMode = env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE ? env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE : retOpts.authMode; + retOpts.apiServerUrl = env.SENZING_API_SERVER_URL ? env.SENZING_API_SERVER_URL : retOpts.apiServerUrl; + retOpts.path = env.SENZING_WEB_SERVER_VIRTUAL_PATH ? env.SENZING_WEB_SERVER_VIRTUAL_PATH : retOpts.path; + + if(env.SENZING_WEB_SERVER_STREAM_CLIENT_URL) { + retOpts.streamClientUrl = env.SENZING_WEB_SERVER_STREAM_CLIENT_URL? env.SENZING_WEB_SERVER_STREAM_CLIENT_URL : retOpts.url; + } + if(env.SENZING_WEB_SERVER_SSL_CERT_PATH) { + retOpts.ssl.certPath = env.SENZING_WEB_SERVER_SSL_CERT_PATH; + } + if(env.SENZING_WEB_SERVER_SSL_KEY_PATH) { + retOpts.ssl.keyPath = env.SENZING_WEB_SERVER_SSL_KEY_PATH; + } + if(env.SENZING_WEB_SERVER_BASIC_AUTH_JSON) { + retOpts.authBasicJson = env.SENZING_WEB_SERVER_BASIC_AUTH_JSON; + } + } + + // now get cmdline options and override any defaults or ENV options + let cmdLineOpts = getCommandLineArgsAsJSON(); + if(cmdLineOpts && cmdLineOpts !== undefined) { + retOpts.protocol = cmdLineOpts.protocol ? cmdLineOpts.protocol : retOpts.protocol; + retOpts.port = cmdLineOpts.webServerPortNumber ? cmdLineOpts.webServerPortNumber : retOpts.port; + retOpts.hostname = cmdLineOpts.webServerHostName ? cmdLineOpts.webServerHostName : retOpts.hostname; + retOpts.url = cmdLineOpts.webServerUrl ? cmdLineOpts.webServerUrl : retOpts.url; + retOpts.apiPath = cmdLineOpts.webServerApiPath ? cmdLineOpts.webServerApiPath : retOpts.apiPath; + retOpts.authPath = cmdLineOpts.webServerAuthPath ? cmdLineOpts.webServerAuthPath : retOpts.authPath; + retOpts.authMode = cmdLineOpts.webServerAuthMode ? cmdLineOpts.webServerAuthMode : retOpts.authMode; + retOpts.apiServerUrl = cmdLineOpts.apiServerUrl ? cmdLineOpts.apiServerUrl : retOpts.apiServerUrl; + retOpts.path = cmdLineOpts.virtualPath ? cmdLineOpts.virtualPath : retOpts.path; + + if(cmdLineOpts.streamClientUrl) { + retOpts.streamClientUrl = cmdLineOpts.streamClientUrl ? cmdLineOpts.streamClientUrl : retOpts.url; + } + if(retOpts.sslCertPath) { + retOpts.ssl = retOpts.ssl ? retOpts.ssl : {}; + retOpts.ssl.certPath = retOpts.sslCertPath; + } + if(retOpts.sslKeyPath) { + retOpts.ssl = retOpts.ssl ? retOpts.ssl : {}; + retOpts.ssl.keyPath = retOpts.sslKeyPath; + } + } + + if(retOpts.ssl && retOpts.ssl !== undefined && retOpts.ssl.certPath && retOpts.ssl.keyPath) { + + } else { + // for SSL support we need both options + // remove "ssl" node when invalid + retOpts.ssl = undefined; + delete retOpts.ssl; + } + // if we just have "hostname" construct "url" from that + retOpts.url = !retOpts.url && retOpts.hostname ? ((retOpts.protocol ? retOpts.protocol + '://' : 'http://') + retOpts.hostname +(retOpts.port ? ':'+ retOpts.port : '')) : retOpts.url; + let hostnameFromUrL = getHostnameFromUrl(retOpts.url); + let portFromUrl = getPortFromUrl(retOpts.url); + + if(hostnameFromUrL && hostnameFromUrL !== retOpts.hostname){ + // override from url + retOpts.hostname = hostnameFromUrL; + } + if(portFromUrl && portFromUrl !== retOpts.port){ + // override from url + retOpts.port = portFromUrl; + } + return retOpts; +} + +function createWebServerConfigFromInput() { + let retOpts = getWebServerOptionsFromInput(); + return retOpts; +} + +function getProxyServerOptionsFromInput() { + let retOpts = { + authServerHostName: "localhost", + authServerPortNumber: 8080, + confServerHostName: "localhost", + confServerPortNumber: 8080, + logLevel: "error", + apiServerUrl: "", + configPath: "", + adminAuthPath: "http://localhost:8080", + jwtPathRewrite: "/jwt", + ssoPathRewrite: "/sso", + adminJwtPathRewrite: "/jwt/admin", + adminSsoPathRewrite: "/sso/admin", + writeToFile: false, + }; + + // update defaults with ENV options(if present) + if(env){ + if(env.SENZING_WEB_SERVER_PROXY_LOGLEVEL) { + retOpts.logLevel = env.SENZING_WEB_SERVER_PROXY_LOGLEVEL; + } + if(env.SENZING_AUTH_SERVER_HOSTNAME || env.SENZING_WEB_SERVER_HOSTNAME) { + retOpts.authServerHostName = (env.SENZING_AUTH_SERVER_HOSTNAME) ? env.SENZING_AUTH_SERVER_HOSTNAME : env.SENZING_WEB_SERVER_HOSTNAME; + retOpts.confServerHostName = retOpts.authServerHostName; + } + if(env.SENZING_AUTH_SERVER_PORT || env.SENZING_WEB_SERVER_PORT) { + retOpts.authServerPortNumber = (env.SENZING_AUTH_SERVER_PORT) ? env.SENZING_AUTH_SERVER_PORT : env.SENZING_WEB_SERVER_PORT; + retOpts.confServerPortNumber = retOpts.authServerPortNumber; + retOpts.adminAuthPath = "http://"+ retOpts.authServerHostName +":"+ retOpts.authServerPortNumber; + retOpts.configPath = "http://"+ retOpts.confServerHostName +":"+ retOpts.confServerPortNumber; + } + if(env.SENZING_WEB_SERVER_AUTH_PATH) { + retOpts.adminAuthPath = env.SENZING_WEB_SERVER_AUTH_PATH; + } + if(env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH) { + retOpts.adminAuthPath = env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH; + } + if(env.SENZING_API_SERVER_URL) { + retOpts.apiServerUrl = env.SENZING_API_SERVER_URL; + } + if(env.SENZING_WEB_SERVER_URL) { + retOpts.configPath = env.SENZING_WEB_SERVER_URL; + } + if(env.SENZING_WEB_SERVER_INTERNAL_URL) { + retOpts.configPath = env.SENZING_WEB_SERVER_INTERNAL_URL; + } + /*if(env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE) { + retOpts.authMode = env.SENZING_WEB_SERVER_ADMIN_AUTH_MODE; + }*/ + + if(env.SENZING_AUTH_SERVER_JWTPATH_REWRITE) { + retOpts.jwtPathRewrite = env.SENZING_AUTH_SERVER_JWTPATH_REWRITE; + } + if(env.SENZING_AUTH_SERVER_SSOPATH_REWRITE) { + retOpts.ssoPathRewrite = env.SENZING_AUTH_SERVER_SSOPATH_REWRITE; + } + if(env.SENZING_AUTH_SERVER_ADMIN_JWTPATH_REWRITE) { + retOpts.adminJwtPathRewrite = env.SENZING_AUTH_SERVER_ADMIN_JWTPATH_REWRITE; + } + if(env.SENZING_AUTH_SERVER_ADMIN_SSOPATH_REWRITE) { + retOpts.adminSsoPathRewrite = env.SENZING_AUTH_SERVER_ADMIN_SSOPATH_REWRITE; + } + if(env.SENZING_AUTH_SERVER_WRITE_CONFIG_TO_FILE === 'true' || env.SENZING_AUTH_SERVER_WRITE_CONFIG_TO_FILE === 'TRUE') { + retOpts.writeToFile = true; + } + } + + // now get cmdline options and override any defaults or ENV options + let cmdLineOpts = getCommandLineArgsAsJSON(); + if(cmdLineOpts && cmdLineOpts !== undefined) { + if(cmdLineOpts.authServerPortNumber) { + retOpts.authServerPortNumber = cmdLineOpts.authServerPortNumber; + retOpts.confServerPortNumber = retOpts.authServerPortNumber; + + retOpts.adminAuthPath = "http://"+ retOpts.authServerHostName +":"+ retOpts.authServerPortNumber; + retOpts.configPath = "http://"+ retOpts.confServerHostName +":"+ retOpts.confServerPortNumber; + } + if(cmdLineOpts.proxyLogLevel) { + retOpts.logLevel = cmdLineOpts.proxyLogLevel; + } + if(cmdLineOpts.adminAuthPath) { + retOpts.adminAuthPath = cmdLineOpts.adminAuthPath; + } + if(cmdLineOpts.apiServerUrl && cmdLineOpts.apiServerUrl !== undefined) { + retOpts.apiServerUrl = cmdLineOpts.apiServerUrl; + } + if(cmdLineOpts.webServerUrl && cmdLineOpts.webServerUrl !== undefined) { + retOpts.configPath = cmdLineOpts.webServerUrl; + } + if(cmdLineOpts.webServerInternalUrl) { + retOpts.configPath = cmdLineOpts.webServerInternalUrl; + } + if(cmdLineOpts.proxyJWTPathRewrite) { + retOpts.jwtPathRewrite = cmdLineOpts.proxyJWTPathRewrite; + } + if(cmdLineOpts.proxySSOPathRewrite) { + retOpts.ssoPathRewrite = cmdLineOpts.proxySSOPathRewrite; + } + if(cmdLineOpts.proxyAdminJWTPathRewrite) { + retOpts.adminJwtPathRewrite = cmdLineOpts.proxyAdminJWTPathRewrite; + } else if(cmdLineOpts.virtualPath) { + retOpts.adminJwtPathRewrite = cmdLineOpts.virtualPath + "/jwt/admin"; + } + if(cmdLineOpts.proxyAdminSSOPathRewrite) { + retOpts.adminSsoPathRewrite = cmdLineOpts.proxyAdminSSOPathRewrite; + } else if(cmdLineOpts.virtualPath) { + retOpts.adminSsoPathRewrite = cmdLineOpts.virtualPath + "/sso/admin"; + } + if(cmdLineOpts.writeProxyConfigToFile === 'true' || cmdLineOpts.writeProxyConfigToFile === 'TRUE') { + retOpts.writeToFile = true; + } + } + return retOpts; +} + +function createProxyConfigFromInput() { + let retConfig = undefined; + let proxyOpts = getProxyServerOptionsFromInput(); + let cmdLineOpts = getCommandLineArgsAsJSON(); + let webSrvrOpts = getWebServerOptionsFromInput(); + + let _virtualDir = (env.SENZING_WEB_SERVER_VIRTUAL_PATH) ? env.SENZING_WEB_SERVER_VIRTUAL_PATH : '/'; + _virtualDir = (cmdLineOpts && cmdLineOpts.virtualPath) ? cmdLineOpts.virtualPath : _virtualDir; + + if(proxyOpts.apiServerUrl && proxyOpts.apiServerUrl !== undefined) { + retConfig = retConfig !== undefined ? retConfig : {}; + let _pathRewriteObj = {}; + let _apiVDir = _virtualDir && _virtualDir !== '/' ? _virtualDir : ''; + let _apiFullPath = webSrvrOpts && webSrvrOpts.apiPath ? webSrvrOpts.apiPath : '/api'; + + // if apiPath unspecified AND virtualDir specified + // serve api requests from under virtual dir path + if(_apiFullPath === '/api' && _apiVDir !== '') { + _apiFullPath = (_apiVDir + (_apiFullPath ? _apiFullPath : '/api')).replace("//","/"); + } else { + _apiFullPath = (_apiFullPath ? _apiFullPath : '/api').replace("//","/"); + } + _pathRewriteObj["^"+ _apiFullPath ] = ""; + + retConfig[ _apiFullPath ] = { + "target": proxyOpts.apiServerUrl, + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": _pathRewriteObj + } + } + + let appendBasePathToKeys = (obj) => { + if(_virtualDir !== '/') { + // re-key object + let _retObj = {}; + for(let pathKey in obj) { + let newKey = !pathKey.startsWith('/api') ? (_virtualDir + pathKey) : pathKey; + newKey = newKey.replace("//","/"); + let newVal = obj[pathKey]; + if(newVal && newVal.pathRewrite) { + // check pathRewrite for vdir pathing + let _pathRewriteObj = {}; + console.log(''); + for(let rewriteKey in newVal.pathRewrite) { + if(rewriteKey && rewriteKey.substring && rewriteKey.indexOf('^/') === 0) { + // append virtual dir to rewrite + _pathRewriteObj[ ('^'+ (_virtualDir + rewriteKey.substring(1)).replace("//","/") ) ] = newVal.pathRewrite[ rewriteKey ]; + } else { + // just return same value + _pathRewriteObj[ rewriteKey ] = newVal.pathRewrite[ rewriteKey ]; + } + } + // update rewrite with any modifications + newVal.pathRewrite = _pathRewriteObj; + } + _retObj[newKey] = newVal; + } + return _retObj; + } else { + // just return object + return obj; + } + } + + // ------------------------ config endpoints + retConfig = retConfig !== undefined ? retConfig : {}; + let mergeObj = appendBasePathToKeys({ + "/config/console": { + "target": proxyOpts.configPath + "/conf/console/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/console": "" + } + }, + "/config/cors": { + "target": proxyOpts.configPath + "/conf/cors/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/cors": "" + } + }, + "/config/csp": { + "target": proxyOpts.configPath + "/conf/csp/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/csp": "" + } + }, + "/config/package": { + "target": proxyOpts.configPath + "/conf/package", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/package": "" + } + }, + "/config/server": { + "target": proxyOpts.configPath + "/conf/server/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/server": "" + } + }, + "/config/streams": { + "target": proxyOpts.configPath + "/conf/streams/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/streams": "" + } + }, + "/health/proxy": { + "target": proxyOpts.configPath + "/status/proxy/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/status/proxy": "" + } + } + }); + retConfig = Object.assign(retConfig, mergeObj); + + if(env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH) { + retConfig = retConfig !== undefined ? retConfig : {}; + let mergeObj = appendBasePathToKeys({ + "/admin/auth/jwt/*": { + "target": env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH, + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/admin/auth/jwt": proxyOpts.adminJwtPathRewrite + } + }, + "/admin/auth/sso/*": { + "target": env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH, + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/admin/auth/sso": proxyOpts.adminSsoPathRewrite + } + }, + "/auth/jwt/*": { + "target": env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH + "/jwt/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/auth/jwt": proxyOpts.jwtPathRewrite + } + }, + "/auth/sso/*": { + "target": env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH + "/sso/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/auth/sso": proxyOpts.ssoPathRewrite + } + }, + "/config/auth": { + "target": env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH + "/conf/auth/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/auth": "" + } + }, + "/cors/test": { + "target": env.SENZING_WEB_SERVER_ADMIN_AUTH_PATH + "/cors/test/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "withCredentials": true, + "pathRewrite": { + "^/cors/test": "" + } + } + }); + retConfig = Object.assign(retConfig, mergeObj); + } + // -------------------- start CMD LINE ARGS import ----------- + // now check our imported cmdline args + if(proxyOpts.adminAuthPath && proxyOpts.adminAuthPath !== undefined) { + retConfig = retConfig !== undefined ? retConfig : {}; + let mergeObj = appendBasePathToKeys({ + "/admin/auth/jwt/*": { + "target": proxyOpts.adminAuthPath, + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/admin/auth/jwt": proxyOpts.adminJwtPathRewrite + } + }, + "/admin/auth/sso/*": { + "target": proxyOpts.adminAuthPath, + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/admin/auth/sso": proxyOpts.adminSsoPathRewrite + } + }, + "/auth/jwt/*": { + "target": proxyOpts.adminAuthPath + "/jwt/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/auth/jwt": proxyOpts.jwtPathRewrite + } + }, + "/auth/sso/*": { + "target": proxyOpts.adminAuthPath + "/sso/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/auth/sso": proxyOpts.ssoPathRewrite + } + }, + "/config/auth": { + "target": proxyOpts.adminAuthPath + "/conf/auth/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "pathRewrite": { + "^/config/auth": "" + } + }, + "/cors/test": { + "target": proxyOpts.adminAuthPath + "/cors/test/", + "secure": true, + "logLevel": proxyOpts.logLevel, + "withCredentials": true, + "pathRewrite": { + "^/cors/test": "" + } + } + }); + retConfig = Object.assign(retConfig, mergeObj); + } + // -------------------- end CMD LINE ARGS import ----------- + + return retConfig; +} + +module.exports = { + "web": createWebServerConfigFromInput(), + "auth": createAuthConfigFromInput(), + "console": createConsoleServerOptionsFromInput(), + "cors": createCorsConfigFromInput(), + "csp": createCspConfigFromInput(), + "proxy": createProxyConfigFromInput(), + "stream": getStreamServerOptionsFromInput(), + "testing": getTestingOptionsFromInput(), + "proxyServerOptions": getProxyServerOptionsFromInput(), + "webServerOptions": getWebServerOptionsFromInput(), + "configServerOptions": getConfigServerOptionsFromInput(), + "consoleServerOptions": getConsoleServerOptionsFromInput(), + "getCommandLineArgsAsJSON": getCommandLineArgsAsJSON +} \ No newline at end of file diff --git a/run/runtime.datastore.js b/run/runtime.datastore.js new file mode 100644 index 0000000..85afa7a --- /dev/null +++ b/run/runtime.datastore.js @@ -0,0 +1,360 @@ +const path = require('path'); +const fs = require('fs'); +const http = require('http'); +const { getPortFromUrl, getHostnameFromUrl, replaceProtocol } = require('./utils'); +let EventEmitter = require('events').EventEmitter; + +class inMemoryConfig extends EventEmitter { + // default web server Configuration + webConfiguration = { + protocol: 'http', + port: 8080, + hostname: 'senzing-webapp', + path: '/', + apiPath: '/api', + authPath: 'http://senzing-webapp:8080', + authMode: 'JWT', + webServerUrl: 'http://senzing-webapp:8080', + apiServerUrl: 'http://senzing-api-server:8080', + /*streamServerUrl: 'ws://localhost:8255', // usually(99%) the address of the LOCAL stream server/proxy + streamServerPort: 8255, // port number the local stream server proxy should run on + streamServerDestUrl: 'ws://localhost:8256', // url that the stream proxy should forward sockets to (streamproducer, api server)*/ + ssl: { + certPath: "/run/secrets/server.cert", + keyPath: "/run/secrets/server.key" + } + }; + + // default Auth Configuration + // we default to "JWT" since we don't want admin functionality + // to be wide open + authConfiguration = { + "hostname": "localhost", + "port": 8080, + "admin": { + "mode": "JWT", + "checkUrl": "/admin/auth/jwt/status", + "redirectOnFailure": true, + "loginUrl": "/admin/login" + } + }; + // CORS(cross-origin-request) configuration + corsConfiguration = undefined; + // CSP (content-security-policy) configuration + cspConfiguration = { + directives: { + 'default-src': [`'self'`], + 'connect-src': [`'self'`], + 'script-src': [`'self'`, `'unsafe-eval'`,`'unsafe-inline'`], + 'img-src': [`'self'`, `data:`], + 'style-src': [`'self'`, `'unsafe-inline'`, 'https://fonts.googleapis.com'], + 'font-src': [`'self'`, `https://fonts.gstatic.com`, `https://fonts.googleapis.com`] + }, + reportOnly: false + }; + // xterm console options + consoleConfiguration = { + enabled: false + } + // reverse proxy configuration + // the reverse proxy allows pointing at resources + // that are local to the webserver, but are then passed + // to the api server + proxyConfiguration = undefined; + + // Stream related configuration + // defines endpoints, proxy ports/domains etc + streamServerConfiguration = undefined; + + // initial timer for checking if API Server is up + apiServerInitializedTimer = undefined; + + // options used for package information + configServerOptions = { + port: 8080 + }; + + // options used for testing purposes + testOptionsConfiguration = undefined; + + // will be set to "true" if initial response + // from api server recieved + _apiServerIsReady = false; + _initialized = false; + + constructor(options, noApiServerConfirmation) { + super(); + if(options) { + this.config = options; + } + let waitForApiServerConfirmation = noApiServerConfirmation ? false : true; + if(waitForApiServerConfirmation) { + this.on('apiServerReady', this.onApiServerReady.bind(this)); + this.apiServerInitializedTimer = setInterval(this.checkIfApiServerInitialized.bind(this), 2000); + this.checkIfApiServerInitialized(); + //console.info("inMemoryConfig.constructor: ", "\n\n", JSON.stringify(this.config, undefined, 2)); + } + } + + get apiServerIsReady() { + return this._apiServerIsReady === true; + } + get initialized() { + return this._apiServerIsReady === true; + } + + // get an JSON object representing all of the configuration + // options specified through either the command line args or env vars + get config() { + let retValue = { + auth: this.authConfiguration + } + if(this.consoleConfiguration && this.consoleConfiguration !== undefined && this.consoleConfiguration !== null) { + retValue.console = this.consoleConfiguration; + } + if(this.corsConfiguration && this.corsConfiguration !== undefined && this.corsConfiguration !== null) { + retValue.cors = this.corsConfiguration; + } + if(this.cspConfiguration && this.cspConfiguration !== undefined && this.cspConfiguration !== null) { + retValue.csp = this.cspConfiguration; + } + if(this.proxyConfiguration && this.proxyConfiguration !== undefined && this.proxyConfiguration !== null) { + retValue.proxy = this.proxyConfiguration; + } + if(this.webConfiguration && this.webConfiguration !== undefined && this.webConfiguration !== null) { + retValue.web = this.webConfiguration; + } + if(this.streamServerConfiguration && this.streamServerConfiguration !== undefined && this.streamServerConfiguration !== null) { + retValue.stream = this.streamServerConfiguration; + } + if(this.testOptionsConfiguration && this.testOptionsConfiguration !== undefined && this.testOptionsConfiguration !== null) { + retValue.testing = this.testOptionsConfiguration; + } + if(this.configServerOptions && this.configServerOptions !== undefined && this.configServerOptions !== null) { + retValue.configServer = this.configServerOptions; + } + return retValue; + } + // set the configuration objects representing + // options specified through either the command line args or env vars + set config(value) { + if(value) { + if(value.proxy) { + this.proxyConfiguration = value.proxy; + } + if(value.web) { + this.webConfiguration = value.web; + } + if(value.auth) { + if(value.auth.hostname && value.auth.hostname !== undefined) { + this.authConfiguration.hostname = value.auth.hostname; + } + if(value.auth.port && value.auth.port !== undefined) { + this.authConfiguration.port = value.auth.port; + } + if(value.auth.virtualPath && value.auth.virtualPath !== undefined) { + this.authConfiguration.virtualPath = value.auth.virtualPath; + } + if(value.auth.admin) { + this.authConfiguration.admin = {}; + if(value.auth.admin.mode === 'JWT') { + this.authConfiguration.admin = { + "mode": value.auth.admin.mode, + "checkUrl": value.auth.admin.checkUrl ? value.auth.admin.checkUrl : "/admin/auth/jwt/status", + "redirectOnFailure": value.auth.admin.redirectOnFailure !== undefined ? value.auth.admin.redirectOnFailure : true, + "loginUrl": value.auth.admin.loginUrl ? value.auth.admin.loginUrl : "/admin/login" + } + } else if(value.auth.admin.mode === 'SSO') { + this.authConfiguration.admin = value.auth.admin; + } else { + // no auth configuraton + this.authConfiguration.admin = { + "mode": false, + "redirectOnFailure": false + } + } + if(value.auth.admin.token) { + this.authConfiguration.admin.token = value.auth.admin.token; + } + if(value.auth.admin.secret) { + this.authConfiguration.admin.secret = value.auth.admin.secret; + } + } else { + this.authConfiguration.admin = undefined; + delete this.authConfiguration.admin; + } + + if(value.auth.operator) { + this.authConfiguration.operator = {}; + if(value.auth.operator.mode === 'JWT') { + this.authConfiguration.operator = { + "mode": value.auth.operator.mode, + "checkUrl": value.auth.operator.checkUrl ? value.auth.operator.checkUrl : "/admin/auth/jwt/status", + "redirectOnFailure": value.auth.operator.redirectOnFailure !== undefined ? value.auth.operator.redirectOnFailure : true, + "loginUrl": value.auth.operator.loginUrl ? value.auth.operator.loginUrl : "/admin/login" + } + } else if(value.auth.operator.mode === 'SSO') { + this.authConfiguration.operator = value.auth.operator; + } else { + // no auth configuraton + this.authConfiguration.operator = { + "mode": false, + "redirectOnFailure": false + } + } + if(value.auth.operator.token) { + this.authConfiguration.operator.token = value.auth.operator.token; + } + if(value.auth.operator.secret) { + this.authConfiguration.operator.secret = value.auth.operator.secret; + } + } else { + this.authConfiguration.operator = undefined; + delete this.authConfiguration.operator; + } + + //this.authConfiguration = value.auth; + } + if(value.console) { + this.consoleConfiguration = value.console; + } + if(value.cors) { + this.authConfiguration = value.cors; + } + if(value.csp) { + this.cspConfiguration = value.csp; + } else { + // default to this so there is at least a base policy enabled + this.cspConfiguration = { + directives: { + 'default-src': [`'self'`], + 'connect-src': [`'self'`], + 'script-src': [`'self'`, `'unsafe-eval'`,`'unsafe-inline'`], + 'style-src': [`'self'`, `'unsafe-inline'`, 'https://fonts.googleapis.com'], + 'font-src': [`'self'`, `https://fonts.gstatic.com`, `https://fonts.googleapis.com`] + }, + reportOnly: false + }; + } + if(value.stream) { + this.streamServerConfiguration = value.stream; + } + if(value.testing) { + this.testOptionsConfiguration = value.testing; + } + if(value.configServerOptions) { + this.configServerOptions = value.configServerOptions; + } + } + } + + writeProxyConfigToFile(filepath, filename) { + let fileExists = false; + let dirExists = false; + filename = filename && filename !== undefined ? filename : 'proxy.conf.json'; + + let fullDir = path.resolve(__dirname, filepath); + let fullPath = path.resolve(fullDir, filename); + + try{ + dirExists = fs.existsSync(fullDir); + fileExists = fs.existsSync(fullPath); + } catch(err){ + fileExists = false; + } + + if(dirExists) { + // now write proxy template to file + try{ + fs.writeFileSync(fullPath, JSON.stringify(this.config.proxy, undefined, 2)); + //file written on disk + //console.log('wrote ', fullPath ,'\n'); + }catch(err){ + console.log('could not write ', fullPath, '\n', err); + } + } + /* + console.log("\nwriteProxyConfigToFile: "); + console.log(filepath, '? ', dirExists); + console.log(filename, '? ', fileExists); + console.log('\n'); + console.log(fullDir, '? ', dirExists); + console.log(fullPath, '? ', fileExists); + */ + + } + + checkIfApiServerInitialized() { + let reqUrl = this.webConfiguration.apiServerUrl+'/server-info'; + let req = http.get(reqUrl, (res => { + //console.log('checkIfApiServerInitialized.response: ', res.statusCode); + let data = []; + res.on('data', ((d) => { + data.push(d); + }).bind(this)) + + res.on('end',(() => { + if(res.statusCode === 200) { + const _dataRes = JSON.parse(Buffer.concat(data).toString()); + //console.log('Response ended: \n', _dataRes); + if(_dataRes) { + //this.onApiServerReady(_dataRes); + this.emit('apiServerReady', _dataRes); + } + } + }).bind(this)); + + }).bind(this)).on('error', error => { + console.log('checking if api server up yet: '+ error.code +' | ['+ reqUrl +']'); + //console.log(error) + }) + } + /** + * When we get a response back from the API_SERVER or POC_SERVER + * we do some extra parameter updates and emit the 'initialized' event + * + * @param {*} serverInfo + */ + onApiServerReady( serverInfo ) { + if(this.apiServerInitializedTimer) { + clearInterval(this.apiServerInitializedTimer) + } + //console.log('------- API SERVER INITIALIZED -------\n', serverInfo); + + // are we using a POC Server or an API Server ? + if(serverInfo.meta) { + if(serverInfo.meta.pocServerVersion || serverInfo.meta.pocApiVersion) { + // poc server + this.webConfiguration.streamLoading = true; + if(serverInfo.data && !serverInfo.data.adminEnabled) { + // poc server supports adding datasources and importing data + this.streamServerConfiguration = undefined; + this.webConfiguration.streamLoading = false; + this.emit('streamLoadingChanged',this.webConfiguration.streamLoading); + } + if(!serverInfo.data.loadQueueConfigured) { + // poc server does not support loading through stream socket + this.streamServerConfiguration = undefined; + this.webConfiguration.streamLoading = false; + this.emit('streamLoadingChanged',this.webConfiguration.streamLoading); + } + } else if(serverInfo.data && !serverInfo.data.adminEnabled) { + // standard rest server that supports loading data + this.streamServerConfiguration = undefined; + this.webConfiguration.streamLoading = false; + this.emit('streamLoadingChanged',this.webConfiguration.streamLoading); + } + } else { + this.webConfiguration.streamLoading = false; + this.emit('streamLoadingChanged',this.webConfiguration.streamLoading); + } + // now notify any listeners that we fully have the data we need + this._apiServerIsReady = true; + this._initialized = true; + this.emit('initialized'); + } + +} + + +module.exports = inMemoryConfig; diff --git a/run/utils.js b/run/utils.js new file mode 100644 index 0000000..171e890 --- /dev/null +++ b/run/utils.js @@ -0,0 +1,147 @@ + +let getHostnameFromUrl = function(url) { + if(!url) return; + if(url) { + var hostname = url; + if(hostname.indexOf && hostname.indexOf('://') > -1) { + // strip protocol off + let urlTokened = hostname.split('://'); + hostname = urlTokened[1]; + } + if(hostname.indexOf && hostname.indexOf(':') > -1) { + // strip port off + _ntemp = hostname.split(':')[0]; + hostname = _ntemp; + } + if(hostname.indexOf && hostname.indexOf('/') > -1){ + // strip off anything in path + _ntemp = hostname.split('/')[0]; + hostname = _ntemp; + } + //console.log(`set hostname to: "${hostname}"`); + + return hostname; + } + return; +} + +let getPortFromUrl = function(url) { + if(!url) return; + if(url) { + var hostname = url; + var portnumber = 8250; + if(hostname.indexOf('://') > -1) { + // strip protocol off + let urlTokened = hostname.split('://'); + hostname = urlTokened[1]; + } + if(hostname.indexOf(':') > -1) { + // keep port + let _ntemp = hostname.split(':'); + if(_ntemp.length > 1 && _ntemp[1]) { + portnumber = parseInt(_ntemp[1]); + } + } + return portnumber; + } +} + +let getProtocolFromUrl = function(url) { + if(!url) return; + if( url ) { + let protocol = 'http'; + if(url.indexOf && url.indexOf('://') > -1) { + let urlTokened = url.split('://'); + protocol = urlTokened[0]; + } + return protocol; + } +} + +let replaceProtocol = function(protoStr, url) { + if(!url) return; + if( url ) { + if(url.indexOf && url.indexOf('://') > -1) { + let urlTokened = url.split('://'); + urlTokened[0] = protoStr; + url = urlTokened.join('://'); + } + } + return url; +} + +let replacePortNumber = function(portNumber, url) { + if(!url) return; + if( url ) { + if(url.indexOf && url.indexOf(':') > -1) { + let replInd = 1; + let urlTokened = url.split(':'); + // does it start with (https|http|ws|wss) + if(url.indexOf && url.indexOf('://') > -1) { + // has protocol + replInd = 2; + } else { + // no protocol + replInd = 1; + } + let replToken = urlTokened[replInd]; + if(replToken.indexOf('/') > -1){ + // just replace the part before teh "/" + replToken = portNumber + replToken.substring(replToken.indexOf('/')); + } else { + replToken = portNumber; + } + console.log('replacePortNumber: ',urlTokened[replInd], replInd, urlTokened); + + urlTokened[( replInd )] = replToken; + url = urlTokened.join(':'); + } + } + return url +} + +let getRootFromUrl = function(url) { + if(!url) return; + if( url ) { + if(url.indexOf && url.indexOf('://') > -1) { + let urlTokened = url.split('://'); + let urlBase = urlTokened[1]; + if(urlBase && urlBase.indexOf('/') > -1) { + urlBase = urlBase.substring(0, urlBase.indexOf('/')); + urlTokened[1] = urlBase; + } + url = urlTokened.join('://'); + } + } + return url; +} + +let getPathFromUrl = function(url) { + if(!url) return; + if( url ) { + let path = ''; + if(url.indexOf && url.indexOf('://') > -1) { + let urlTokened = url.split('://'); + let strPath = urlTokened[1]; + if(strPath.indexOf && strPath.indexOf('/') > -1) { + path = strPath.substring(strPath.indexOf('/')); + } else { + // no "/" in url + } + } else if(url.indexOf && url.indexOf('/') > -1) { + // no protocol, assume first "/" to end + path = url.substring(url.indexOf('/')); + } + return path; + } +} + +module.exports = { + "getHostnameFromUrl": getHostnameFromUrl, + "getPathFromUrl": getPathFromUrl, + "getPortFromUrl": getPortFromUrl, + "getProtocolFromUrl": getProtocolFromUrl, + "getRootFromUrl": getRootFromUrl, + "replaceProtocol": replaceProtocol, + "replacePortNumber": replacePortNumber +} \ No newline at end of file diff --git a/run/webserver/index.html b/run/webserver/index.html new file mode 100644 index 0000000..43e978a --- /dev/null +++ b/run/webserver/index.html @@ -0,0 +1,6 @@ + + + + embedded console goes here.. + + \ No newline at end of file diff --git a/run/webserver/index.js b/run/webserver/index.js new file mode 100644 index 0000000..e69de29 diff --git a/run/xterm/PTYService.js b/run/xterm/PTYService.js new file mode 100644 index 0000000..89ca677 --- /dev/null +++ b/run/xterm/PTYService.js @@ -0,0 +1,48 @@ +// PTYService.js +const os = require("os"); +const pty = require("node-pty"); + +class PTY { + constructor(socket) { + // Setting default terminals based on user os + this.shell = os.platform() === "win32" ? "powershell.exe" : "bash"; + this.ptyProcess = null; + this.socket = socket; + + // Initialize PTY process. + this.startPtyProcess(); + } + + /** + * Spawn an instance of pty with a selected shell. + */ + startPtyProcess() { + this.ptyProcess = pty.spawn(this.shell, [], { + name: "xterm-color", + cwd: process.env.HOME, // Which path should terminal start + env: process.env // Pass environment variables + }); + + // Add a "data" event listener. + this.ptyProcess.onData(data => { + // Whenever terminal generates any data, send that output to socket.io client + this.sendToClient(data); + }); + } + + /** + * Use this function to send in the input to Pseudo Terminal process. + * @param {*} data Input from user like a command sent from terminal UI + */ + + write(data) { + this.ptyProcess.write(data); + } + + sendToClient(data) { + // Emit data to socket.io client in an event "output" + this.socket.emit("output", data); + } +} + +module.exports = PTY; \ No newline at end of file diff --git a/run/xterm/SocketService.js b/run/xterm/SocketService.js new file mode 100644 index 0000000..5cb4e84 --- /dev/null +++ b/run/xterm/SocketService.js @@ -0,0 +1,48 @@ +//SocketService.js + +const socketIO = require("socket.io"); +const PTYService = require("./PTYService"); + +class SocketService { + constructor() { + this.socket = null; + this.pty = null; + } + + attachServer(server, options) { + if (!server) { + throw new Error("[error] no server to attach socket.io listener to.."); + } + let io; + if(options) { + io = socketIO(server, options); + console.log("[listening on "+ options.path +"] socket.io server waiting for client connections.."); + } else { + io = socketIO(server); + console.log("[listening] socket.io server waiting for client connections.."); + } + + // "connection" event happens when any client connects to this io instance. + io.on("connection", socket => { + console.log("[connected] client connected to socket.io server", socket.id); + + this.socket = socket; + + this.socket.on("disconnect", () => { + console.log("[disconnected] client disconnected from socket.io server", socket.id); + }); + + // Create a new pty service when client connects. + this.pty = new PTYService(this.socket); + + // Attach event listener for socket.io + this.socket.on("input", input => { + // Runs this listener when socket receives "input" events from socket.io client. + // input event is emitted on client side when user types in terminal UI + this.pty.write(input); + }); + }); + } +} + +module.exports = SocketService; \ No newline at end of file diff --git a/run/xterm/index.js b/run/xterm/index.js new file mode 100644 index 0000000..bee37e5 --- /dev/null +++ b/run/xterm/index.js @@ -0,0 +1,44 @@ +//index.js +const http = require("http"); +const SocketService = require("./SocketService"); +const express = require('express'); +const path = require("path") + +// utils +const { getPathFromUrl } = require("../utils"); +const inMemoryConfig = require("../runtime.datastore"); +const inMemoryConfigFromInputs = require('../runtime.datastore.config'); +const runtimeOptions = new inMemoryConfig(inMemoryConfigFromInputs, true); + +// grab env/cmdline vars +// web server config +let serverOptions = runtimeOptions.config.web; +// console +var consoleOptions = runtimeOptions.config.console; + +//let STARTUP_MSG = "\t RUNTIME OPTIONS: "+ JSON.stringify(inMemoryConfigFromInputs, undefined, 2); +//console.log(STARTUP_MSG); + +// server(s) +const app = express(); +const server = http.Server(app); + +if(consoleOptions && consoleOptions.enabled) { + server.listen(consoleOptions.port, function() { + console.log("[started ] Xterm Socket Server on port ", consoleOptions.port); + const socketService = new SocketService(); + let serverOptions; + if(consoleOptions && consoleOptions.url) { + let _pathFromUrl = getPathFromUrl(consoleOptions.url) + if(_pathFromUrl) { + serverOptions = { + path: _pathFromUrl + } + } + } + // We are going to pass server to socket.io in SocketService.js + socketService.attachServer(server, serverOptions); + }); +} else { + console.log("console is not enabled: \n"+ JSON.stringify(consoleOptions)); +} \ No newline at end of file