From a196ba2c08bd16479134ab542f2560b75f19424f Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 15 Jul 2020 15:45:02 +0200 Subject: Make frontend independent of API This change makes the frontend independent of the API by removing the static file serving logic from the API server. Instead, we can serve the frontend as static HTML over CDNs. --- .editorconfig | 4 +++ api/main.py | 36 +++++---------------------- docker-compose.yml | 61 ++++++++++++++++++++++++++-------------------- frontend/Dockerfile | 19 +++++++++++++++ frontend/nginx.conf | 32 ++++++++++++++++++++++++ frontend/src/api/socket.js | 8 +++--- frontend/yarn.lock | 2 +- 7 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 frontend/Dockerfile create mode 100644 frontend/nginx.conf diff --git a/.editorconfig b/.editorconfig index 508cb765..b0ec2ebd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,7 @@ insert_final_newline = true [*.md] trim_trailing_whitespace = false + +# Ensure YAML formatting is according to standard +[*.{yml,yaml}] +indent_size = 2 diff --git a/api/main.py b/api/main.py index a2481269..7544333a 100755 --- a/api/main.py +++ b/api/main.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 -import flask_socketio import json import os import sys import traceback import urllib.request -from flask import Flask, request, send_from_directory, jsonify + +import flask_socketio +from dotenv import load_dotenv +from flask import Flask, request, jsonify from flask_compress import Compress -from oauth2client import client, crypt from flask_cors import CORS -from dotenv import load_dotenv +from oauth2client import client, crypt from opendc.models.user import User from opendc.util import rest, path_parser, database @@ -19,12 +20,6 @@ load_dotenv() TEST_MODE = "OPENDC_FLASK_TESTING" in os.environ -# Specify the directory of static assets -if TEST_MODE: - STATIC_ROOT = os.curdir -else: - STATIC_ROOT = os.path.join(os.environ['OPENDC_ROOT_DIR'], 'frontend', 'build') - # Set up database if not testing if not TEST_MODE: database.DB.initialize_database( @@ -34,7 +29,7 @@ if not TEST_MODE: host=os.environ['OPENDC_DB_HOST'] if 'OPENDC_DB_HOST' in os.environ else 'localhost') # Set up the core app -FLASK_CORE_APP = Flask(__name__, static_url_path='', static_folder=STATIC_ROOT) +FLASK_CORE_APP = Flask(__name__) FLASK_CORE_APP.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET'] # Set up CORS support for local setups @@ -50,11 +45,6 @@ else: SOCKET_IO_CORE = flask_socketio.SocketIO(FLASK_CORE_APP) -@FLASK_CORE_APP.errorhandler(404) -def page_not_found(e): - return send_from_directory(STATIC_ROOT, 'index.html') - - @FLASK_CORE_APP.route('/tokensignin', methods=['POST']) def sign_in(): """Authenticate a user with Google sign in""" @@ -132,20 +122,6 @@ def api_call(version, endpoint_path): return flask_response -@FLASK_CORE_APP.route('/my-auth-token') -def serve_web_server_test(): - """Serve the web server test.""" - return send_from_directory(STATIC_ROOT, 'index.html') - - -@FLASK_CORE_APP.route('/') -@FLASK_CORE_APP.route('/projects') -@FLASK_CORE_APP.route('/projects/') -@FLASK_CORE_APP.route('/profile') -def serve_index(project_id=None): - return send_from_directory(STATIC_ROOT, 'index.html') - - @SOCKET_IO_CORE.on('request') def receive_message(message): """"Receive a SocketIO request""" diff --git a/docker-compose.yml b/docker-compose.yml index 6dc01f67..837f9019 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,21 @@ version: "3" services: + frontend: + build: + context: ./frontend + args: + - REACT_APP_OAUTH_CLIENT_ID=${OPENDC_OAUTH_CLIENT_ID} + image: frontend + restart: on-failure + ports: + - "8081:80" + networks: + - backend + api: build: ./api image: api restart: on-failure - ports: - - "8081:8081" networks: - backend depends_on: @@ -20,34 +30,33 @@ services: - OPENDC_DB_HOST=mongo - OPENDC_FLASK_SECRET - OPENDC_OAUTH_CLIENT_ID - - REACT_APP_OAUTH_CLIENT_ID=${OPENDC_OAUTH_CLIENT_ID} - OPENDC_ROOT_DIR - OPENDC_SERVER_BASE_URL -# TODO: Implement new database interaction on the simulator side -# simulator: -# build: -# context: ./opendc-simulator -# dockerfile: opendc-model-odc/setup/Dockerfile -# image: simulator -# restart: on-failure -# links: -# - mongo -# depends_on: -# - mongo -# environment: -# - PERSISTENCE_URL=jdbc:mysql://mariadb:3306/opendc -# - PERSISTENCE_USER=opendc -# - PERSISTENCE_PASSWORD=opendcpassword -# - COLLECT_MACHINE_STATES=ON -# - COLLECT_TASK_STATES=ON -# - COLLECT_STAGE_MEASUREMENTS=OFF -# - COLLECT_TASK_METRICS=OFF -# - COLLECT_JOB_METRICS=OFF + # TODO: Implement new database interaction on the simulator side + # simulator: + # build: + # context: ./opendc-simulator + # dockerfile: opendc-model-odc/setup/Dockerfile + # image: simulator + # restart: on-failure + # links: + # - mongo + # depends_on: + # - mongo + # environment: + # - PERSISTENCE_URL=jdbc:mysql://mariadb:3306/opendc + # - PERSISTENCE_USER=opendc + # - PERSISTENCE_PASSWORD=opendcpassword + # - COLLECT_MACHINE_STATES=ON + # - COLLECT_TASK_STATES=ON + # - COLLECT_STAGE_MEASUREMENTS=OFF + # - COLLECT_TASK_METRICS=OFF + # - COLLECT_JOB_METRICS=OFF mongo: build: - context: database + context: database restart: on-failure environment: - MONGO_INITDB_ROOT_USERNAME @@ -79,8 +88,8 @@ services: ME_CONFIG_MONGODB_ADMINPASSWORD: "${MONGO_INITDB_ROOT_PASSWORD}" volumes: - mongo-volume: - external: false + mongo-volume: + external: false networks: backend: {} diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..36e3c20b --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,19 @@ +FROM node:14 +MAINTAINER OpenDC Maintainers + +ARG REACT_APP_OAUTH_CLIENT_ID + +# Copy OpenDC directory +COPY ./ /opendc + +# Build frontend +RUN cd /opendc/ \ + && rm -rf ./build \ + && yarn \ + && export REACT_APP_OAUTH_CLIENT_ID=$REACT_APP_OAUTH_CLIENT_ID \ + && yarn build + +# Setup nginx to serve the frontend +FROM nginx:1.19 +COPY --from=0 /opendc/build /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 00000000..ed7e5cfe --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name opendc.org; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + location /socket.io { + proxy_http_version 1.1; + + proxy_buffering off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass http://api:8081/socket.io; + } + + location /tokensignin { + proxy_pass http://api:8081/tokensignin; + } + + location /api { + proxy_pass http://api:8081/api; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/frontend/src/api/socket.js b/frontend/src/api/socket.js index 93ce8fa8..759c119e 100644 --- a/frontend/src/api/socket.js +++ b/frontend/src/api/socket.js @@ -6,11 +6,9 @@ let requestIdCounter = 0 const callbacks = {} export function setupSocketConnection(onConnect) { - let port = window.location.port - if (process.env.NODE_ENV !== 'production') { - port = 8081 - } - socket = io.connect(window.location.protocol + '//' + window.location.hostname + ':' + port) + const apiUrl = process.env.REACT_APP_API_URL || window.location.hostname + ':' + window.location.port; + + socket = io.connect(window.location.protocol + '//' + apiUrl); socket.on('connect', onConnect) socket.on('response', onSocketResponse) } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 00c6e441..2859e4e0 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -11527,7 +11527,7 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuidv4@^6.1.1: +uuidv4@~6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.1.1.tgz#6565b4f2be7d6f841c14106f420fdb701eae5c81" integrity sha512-ZplGb1SHFMVH3l7PUQl2Uwo+FpJQV6IPOoU+MjjbqrNYQolqbGwv+/sn9F+AGMsMOgGz3r9JN3ztGUi0VzMxmw== -- cgit v1.2.3