diff --git a/.env.development b/.env.development new file mode 100644 index 0000000000000000000000000000000000000000..32339887e466a22e73caa750a43e12627439dac5 --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +VITE_PORT=8080 +VITE_AUTH_URL= +VITE_BASE_PATH=http://localhost:8080 +VITE_API_PATH=http://localhost:8081 diff --git a/.env.example b/.env.example deleted file mode 100644 index f82ce1f811aa31fdd17bfedd37870f4621a4887f..0000000000000000000000000000000000000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -VITE_AUTH_URL= -VITE_BASE_PATH= -VITE_API_PATH= -DOCKER_REGISTRY= -K8S_NAMESPACE= diff --git a/.gitignore b/.gitignore index 5ae8b5aff86b682fa7af2930d864f27022c4f34c..48f405745ad9cd20ee4c1e78d7ca1d8e790d1744 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ dist .env .env.* -!.env.example +!.env.development diff --git a/Dockerfile b/Dockerfile index 52392fab936c0878365222555098eda5523c56ab..48b3a9b8aa42b1466a121600051224be7d953cc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,26 @@ -FROM node:20.11.1 AS build +# check=skip=SecretsUsedInArgOrEnv +# Ignore false-positive warning about VITE_AUTH_URL +FROM node:20.19.5 AS build + +ARG VITE_PORT="8080" +ENV VITE_PORT=$VITE_PORT +ARG VITE_AUTH_URL="" +ENV VITE_AUTH_URL=$VITE_AUTH_URL +ARG VITE_BASE_PATH="http://localhost:8080" +ENV VITE_BASE_PATH=$VITE_BASE_PATH +ARG VITE_API_PATH="http://localhost:8081" +ENV VITE_API_PATH=$VITE_API_PATH + WORKDIR /app COPY package.json package-lock.json /app/ RUN npm ci COPY . /app/ RUN npm run build-prod -FROM bitnami/nginx:1.25.4 +FROM nginx:1.29.1 EXPOSE 8080 -COPY ./docker/server.conf /opt/bitnami/nginx/conf/server_blocks/server.conf +COPY docker/nginx.conf /etc/nginx/nginx.conf COPY --from=build /app/dist /app +# Resolve permissions issues on unprivileged setups +RUN touch /run/nginx.pid +RUN chmod -R g+rw /var/cache/nginx/ /run/nginx.pid \ No newline at end of file diff --git a/README.md b/README.md index 04c7ff9ea1e27d55f8fa63d7e6ed1648abf486de..f4a7f9636fef2117e16b3e105ad726534a49f039 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,22 @@ User Interface for the ExaDigiT project that allows for running simulations and [Tanstack](https://tanstack.com/) -## Getting Started +## Running Locally -To get the project up and running locally, you will want to start by cloning the project and running `npm install` in the projects directory. Then run `npm run dev`. +To get the project up and running locally, clone and deploy the SimulationServer from [GitHub](https://github.com/ExaDigiT/SimulationServer) +or [GitLab](https://code.ornl.gov/exadigit/simulationserver) and follow the instructions to launch a local instance of the full server and +dashboard stack. The dashboard will be hosted on http://localhost:8080 by default. -After doing so you can navigate to `localhost:8080` to login to the application. +You can also run the ExaDigiT dashboard directly, e.g. for faster development builds or to run it against an existing ExaDigiT Simulation Server deployment. +Run the simulation server locally without the dashboard by running this in `simulation-server`: +```bash +docker compose up simulation-server --wait +``` + +Then in `simulation-dashboard` run: +```bash +npm install +npm run dev +``` + +After doing so you can navigate to http://localhost:8080 to use the application. diff --git a/deploy.sh b/deploy.sh index c35cf9f3299a6f90a3736fd0c15aa2241fc11c6d..11d78971ac2799f84ffddec3d2d4855bac81812c 100755 --- a/deploy.sh +++ b/deploy.sh @@ -8,7 +8,12 @@ set -o allexport source .env.production set +o allexport -docker build -t $DOCKER_REGISTRY:latest . +docker build -t $DOCKER_REGISTRY:latest \ + --build-arg VITE_PORT="$VITE_PORT" \ + --build-arg VITE_AUTH_URL="$VITE_AUTH_URL" \ + --build-arg VITE_BASE_PATH="$VITE_BASE_PATH" \ + --build-arg VITE_API_PATH="$VITE_API_PATH" \ + . docker push $DOCKER_REGISTRY:latest # Get fully qualified image name DOCKER_IMAGE=$(docker inspect --format='{{index .RepoDigests 0}}' $DOCKER_REGISTRY:latest) diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..2de4c27fa1dd298c1230bf6c92c8713e2e5b7f39 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,36 @@ +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + gzip on; + + server { + listen 8080; + root /app; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + } +} diff --git a/docker/server.conf b/docker/server.conf deleted file mode 100644 index 4c559fcdade4cc7681ba35ff9de8acf6a9a0d924..0000000000000000000000000000000000000000 --- a/docker/server.conf +++ /dev/null @@ -1,9 +0,0 @@ -server { - listen 8080; - root /app; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } -} diff --git a/package-lock.json b/package-lock.json index 6f1232dd5d6722e20510d90de3cba5500b0fdd9f..8b9d129d611129f1a5966139b64deeddf57fd387 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,8 @@ "axios": "^1.6.7", "date-fns": "^3.3.1", "framer-motion": "^11.0.20", + "iso8601-duration": "^2.1.3", + "js-yaml": "^4.1.0", "keycloak-js": "^23.0.7", "lodash": "^4.17.21", "plotly.js": "^2.29.1", @@ -36,6 +38,7 @@ "@tanstack/eslint-plugin-query": "^5.20.1", "@tanstack/router-cli": "^1.69.1", "@tanstack/router-vite-plugin": "^1.16.3", + "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.14.202", "@types/react": "^18.2.55", "@types/react-datepicker": "^6.0.3", @@ -2251,6 +2254,13 @@ "@types/geojson": "*" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3101,8 +3111,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-bounds": { "version": "1.0.1", @@ -3575,9 +3584,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001669", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", - "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "version": "1.0.30001747", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001747.tgz", + "integrity": "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==", "funding": [ { "type": "opencollective", @@ -3591,7 +3600,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/canvas-fit": { "version": "1.5.0", @@ -7397,6 +7407,12 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/iso8601-duration": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-2.1.3.tgz", + "integrity": "sha512-OwkROKDXYhqKTl9uyB+/+lQ/Tx+L9LVb9tNRcsO4LtCSBDrmYbzyJLg9rGjYKAPDabD6IVdjMyUnnULHpejrCg==", + "license": "MIT" + }, "node_modules/iterator.prototype": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", @@ -7489,7 +7505,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, diff --git a/package.json b/package.json index 6cb8883ac0f4a96f22b8e4ac6c865a1090fb4884..e844f988365774837185d4f0a8029064b2259be9 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "axios": "^1.6.7", "date-fns": "^3.3.1", "framer-motion": "^11.0.20", + "iso8601-duration": "^2.1.3", + "js-yaml": "^4.1.0", "keycloak-js": "^23.0.7", "lodash": "^4.17.21", "plotly.js": "^2.29.1", @@ -39,6 +41,7 @@ "@tanstack/eslint-plugin-query": "^5.20.1", "@tanstack/router-cli": "^1.69.1", "@tanstack/router-vite-plugin": "^1.16.3", + "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.14.202", "@types/react": "^18.2.55", "@types/react-datepicker": "^6.0.3", diff --git a/src/App.tsx b/src/App.tsx index 6cb0e247a75e9b4da786a8a30a00cc5f43f5d529..c1bb075b8e2bdc992f11110edc1de78ef1deb5a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,37 +13,8 @@ import "react-tooltip/dist/react-tooltip.css"; const basepath = import.meta.env.VITE_BASE_PATH ? new URL(import.meta.env.VITE_BASE_PATH).pathname : "/"; - -const initOptions: KeycloakConfig = { - url: import.meta.env.VITE_AUTH_URL, - realm: "obsidian", - clientId: "obsidian-public", -}; - -const kc = new Keycloak(initOptions); - axios.defaults.baseURL = import.meta.env.VITE_API_PATH; -kc.init({ - onLoad: "login-required", - redirectUri: window.location.toString(), -}).then( - (auth) => { - if (auth) { - if (kc.token) { - localStorage.setItem("exadigitAuthToken", kc.token); - axios.defaults.headers.common = { - Authorization: `Bearer ${kc.token}`, - }; - axios.defaults.withCredentials = true; - } - } - }, - () => { - console.error("Authenticated Failed"); - }, -); - export { axios }; export interface RouterContext { @@ -64,15 +35,13 @@ declare module "@tanstack/react-router" { } export const AppContext = createContext<{ - AuthToken?: string; theme: string | null; setTheme: (value: "dark" | "light") => void; -}>({ AuthToken: kc.token, theme: null, setTheme: () => {} }); +}>({ theme: null, setTheme: () => {} }); function App() { - const authToken = localStorage.getItem("exadigitAuthToken") || undefined; - const theme = localStorage.getItem("theme"); - const [_theme, setTheme] = useState(theme); + const [apiToken, setApiToken] = useState(); + const [theme, setTheme] = useState(localStorage.getItem("theme")); const onThemeSwitch = (theme: "dark" | "light") => { if (theme === "dark") { @@ -97,14 +66,48 @@ function App() { } }, []); - if (!authToken || axios.defaults.baseURL?.includes("localhost")) { + useEffect(() => { + if (!apiToken && import.meta.env.VITE_AUTH_URL) { + const initOptions: KeycloakConfig = { + url: import.meta.env.VITE_AUTH_URL, + realm: "obsidian", + clientId: "obsidian-public", + }; + + const kc = new Keycloak(initOptions); + + kc.init({ + onLoad: "login-required", + redirectUri: window.location.toString(), + }).then( + (auth) => { + if (auth) { + if (kc.token) { + axios.defaults.headers.common = { + Authorization: `Bearer ${kc.token}`, + }; + axios.defaults.withCredentials = true; + setApiToken(kc.token) + } + } + }, + () => { + console.error("Authenticated Failed"); + }, + ); + + setApiToken(apiToken) + } else { + setApiToken("no-auth") + } + }, [apiToken]) + + if (!apiToken) { return

Please Authenticate

; } return ( - + diff --git a/src/components/shared/dropdown.tsx b/src/components/shared/dropdown.tsx index f9a9e1d7c30a5857fa2802457801a1b201eb1b01..5b82a5c8bdbfce966b0c0c8c8d3d716a1010ebde 100644 --- a/src/components/shared/dropdown.tsx +++ b/src/components/shared/dropdown.tsx @@ -7,7 +7,7 @@ export interface SelectProps extends HTMLProps { onChange: (e: ChangeEvent) => void; } -export function Select(props: SelectProps) { +export function Select({onChange, value, choices, ...props}: SelectProps) { return (
{props.label && ( @@ -15,8 +15,8 @@ export function Select(props: SelectProps) { )}