diff --git a/.circleci/config.yml b/.circleci/config.yml index c8c7afabe..f8fced437 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -229,6 +229,36 @@ jobs: docker push ohif/app:latest fi + DOCKER_RELEASE_PUBLISH_ARM: + <<: *defaults + resource_class: arm.large + steps: + - attach_workspace: + at: ~/repo + - setup_remote_docker: + docker_layer_caching: false + - run: + name: Build and push ARM64 Docker image from the release branch + command: | + # This file will exist if a new version was published by + # our command in the previous job. + if [[ ! -e version.txt ]]; then + exit 0 + else + # Remove npm config + rm -f ./.npmrc + # Set our version number using vars + export IMAGE_VERSION=$(cat version.txt) + export IMAGE_VERSION_FULL=v$IMAGE_VERSION + echo $IMAGE_VERSION + echo $IMAGE_VERSION_FULL + # Build our ARM64 image, auth, and push + docker build --tag ohif/app:$IMAGE_VERSION_FULL-arm64 --tag ohif/app:latest-arm64 . + echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin + docker push ohif/app:$IMAGE_VERSION_FULL-arm64 + docker push ohif/app:latest-arm64 + fi + DOCKER_BETA_PUBLISH: <<: *defaults resource_class: large @@ -265,6 +295,41 @@ jobs: docker push ohif/app:$IMAGE_VERSION_FULL fi + DOCKER_BETA_PUBLISH_ARM: + <<: *defaults + resource_class: arm.large + steps: + - attach_workspace: + at: ~/repo + - setup_remote_docker: + docker_layer_caching: false + - run: + name: Build and push ARM64 Docker image from the master branch (beta releases) + command: | + echo $(ls -l) + + # This file will exist if a new version was published by + # our command in the previous job. + if [[ ! -e version.txt ]]; then + echo "don't have version txt" + exit 0 + else + echo "Building and pushing ARM64 Docker image from the master branch (beta releases)" + rm -f ./.npmrc + # Set our version number using vars + export IMAGE_VERSION=$(cat version.txt) + export IMAGE_VERSION_FULL=v$IMAGE_VERSION + echo $IMAGE_VERSION + echo $IMAGE_VERSION_FULL + # Build our ARM64 image, auth, and push + echo "starting docker build" + docker build --tag ohif/app:$IMAGE_VERSION_FULL-arm64 . + echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin + + echo "starting docker push" + docker push ohif/app:$IMAGE_VERSION_FULL-arm64 + fi + CYPRESS: <<: *defaults resource_class: large @@ -316,6 +381,9 @@ workflows: - DOCKER_BETA_PUBLISH: requires: - NPM_PUBLISH + - DOCKER_BETA_PUBLISH_ARM: + requires: + - NPM_PUBLISH # viewer.ohif.org DEPLOY_RELEASE: @@ -334,3 +402,6 @@ workflows: - DOCKER_RELEASE_PUBLISH: requires: - NPM_PUBLISH + - DOCKER_RELEASE_PUBLISH_ARM: + requires: + - NPM_PUBLISH diff --git a/.docker/Viewer-v3.x/default.conf.template b/.docker/Viewer-v3.x/default.conf.template index 11206da2a..bbee32449 100644 --- a/.docker/Viewer-v3.x/default.conf.template +++ b/.docker/Viewer-v3.x/default.conf.template @@ -1,4 +1,7 @@ server { + gzip_static always; + gzip_proxied expired no-cache no-store private auth; + gunzip on; listen ${PORT} default_server; listen [::]:${PORT} default_server; location / { diff --git a/.docker/Viewer-v3.x/entrypoint.sh b/.docker/Viewer-v3.x/entrypoint.sh index 988e087e8..75e27a8cb 100644 --- a/.docker/Viewer-v3.x/entrypoint.sh +++ b/.docker/Viewer-v3.x/entrypoint.sh @@ -7,9 +7,18 @@ if [ -n "$SSL_PORT" ] envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf fi -if [ -n "$APP_CONFIG" ] +if [ -n "$APP_CONFIG" ] ; then + echo "$APP_CONFIG" > /usr/share/nginx/html${PUBLIC_URL}app-config.js + # Removes the old compressed app-config file, then compresses the replacement + # and finally creates a new empty file so that gunzip works correctly. + # This code is correct despite the AI warning otherwise about order of create/delete + rm /usr/share/nginx/html${PUBLIC_URL}app-config.js.gz + gzip /usr/share/nginx/html${PUBLIC_URL}app-config.js + touch /usr/share/nginx/html${PUBLIC_URL}app-config.js +fi +if [ ! -n "$APP_CONFIG" ] then - echo "$APP_CONFIG" > /usr/share/nginx/html/app-config.js + echo "Not using custom app config" fi if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ] @@ -40,6 +49,6 @@ if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ] cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js fi -echo "Starting Nginx to serve the OHIF Viewer..." +echo "Starting Nginx to serve the OHIF Viewer on ${PUBLIC_URL}" exec "$@" diff --git a/.docker/compressDist.sh b/.docker/compressDist.sh new file mode 100644 index 000000000..4aaf932a3 --- /dev/null +++ b/.docker/compressDist.sh @@ -0,0 +1,4 @@ +find platform/app/dist -name "*.js" -exec gzip -9 "{}" \; -exec touch "{}" \; +find platform/app/dist -name "*.map" -exec gzip -9 "{}" \; -exec touch "{}" \; +find platform/app/dist -name "*.css" -exec gzip -9 "{}" \; -exec touch "{}" \; +find platform/app/dist -name "*.svg" -exec gzip -9 "{}" \; -exec touch "{}" \; diff --git a/.dockerignore b/.dockerignore index ca74c5398..f906eff86 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,11 +8,11 @@ # have this file as their .dockerignore. # Output -dist/ -build/ +**/dist/ +**/build/ # Dependencies -node_modules/ +**/node_modules/ # Root README.md @@ -33,5 +33,5 @@ dockerfile .scripts/ .vscode/ coverage/ -docs/ +platform/docs/ testdata/ diff --git a/Dockerfile b/Dockerfile index 141b139a4..29fce9964 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1.7-labs # This dockerfile is used to publish the `ohif/app` image on dockerhub. # # It's a good example of how to build our static application and package it @@ -19,61 +20,79 @@ # +# syntax=docker/dockerfile:1.7-labs +# This dockerfile is used to publish the `ohif/app` image on dockerhub. +# +# It's a good example of how to build our static application and package it +# with a web server capable of hosting it as static content. +# +# docker build +# -------------- +# If you would like to use this dockerfile to build and tag an image, make sure +# you set the context to the project's root directory: +# https://docs.docker.com/engine/reference/commandline/build/ +# +# +# SUMMARY +# -------------- +# This dockerfile is used as an input for a second stage to make things run faster. +# + + # Stage 1: Build the application # docker build -t ohif/viewer:latest . -FROM node:20.18.1-slim as json-copier - -RUN mkdir /usr/src/app -WORKDIR /usr/src/app - -COPY ["package.json", "yarn.lock", "preinstall.js", "./"] -COPY extensions /usr/src/app/extensions -COPY modes /usr/src/app/modes -COPY platform /usr/src/app/platform - -# Find and remove non-package.json files -#RUN find extensions \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf -#RUN find modes \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf -#RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf - # Copy Files FROM node:20.18.1-slim as builder RUN apt-get update && apt-get install -y build-essential python3 RUN mkdir /usr/src/app WORKDIR /usr/src/app +RUN npm install -g bun +# RUN npm install -g lerna@7.4.2 +ENV PATH=/usr/src/app/node_modules/.bin:$PATH -COPY --from=json-copier /usr/src/app . - +# Do an initial install and then a final install +COPY package.json yarn.lock preinstall.js lerna.json ./ +COPY --parents ./addOns/package.json ./addOns/*/*/package.json ./extensions/*/package.json ./modes/*/package.json ./platform/*/package.json ./ # Run the install before copying the rest of the files -RUN yarn config set workspaces-experimental true -RUN yarn install --frozen-lockfile --verbose +RUN bun install +# Copy the local directory +COPY --link --exclude=yarn.lock --exclude=package.json --exclude=Dockerfile . . +# Do a second install to finalize things after the copy +RUN bun run show:config +RUN bun install -COPY . . - -# To restore workspaces symlinks -RUN yarn install --frozen-lockfile --verbose - -ENV PATH /usr/src/app/node_modules/.bin:$PATH +# Build here +# After install it should hopefully be stable until the local directory changes ENV QUICK_BUILD true # ENV GENERATE_SOURCEMAP=false -# ENV REACT_APP_CONFIG=config/default.js +ARG APP_CONFIG=config/default.js +ARG PUBLIC_URL=/ -RUN yarn run build +RUN bun run show:config +RUN bun run build + +# Precompress files +RUN chmod u+x .docker/compressDist.sh +RUN ./.docker/compressDist.sh # Stage 3: Bundle the built application into a Docker container # which runs Nginx using Alpine Linux -FROM nginxinc/nginx-unprivileged:1.25-alpine as final +FROM nginxinc/nginx-unprivileged:1.27-alpine as final #RUN apk add --no-cache bash -ENV PORT=80 +ARG PORT=80 +ENV PORT=${PORT} +ARG PUBLIC_URL=/ +ENV PUBLIC_URL=${PUBLIC_URL} RUN rm /etc/nginx/conf.d/default.conf USER nginx COPY --chown=nginx:nginx .docker/Viewer-v3.x /usr/src RUN chmod 777 /usr/src/entrypoint.sh -COPY --from=builder /usr/src/app/platform/app/dist /usr/share/nginx/html +COPY --from=builder /usr/src/app/platform/app/dist /usr/share/nginx/html${PUBLIC_URL} +COPY --from=builder /usr/src/app/platform/app/dist/index.html /usr/share/nginx/html # In entrypoint.sh, app-config.js might be overwritten, so chmod it to be writeable. # The nginx user cannot chmod it, so change to root. USER root -RUN chmod 666 /usr/share/nginx/html/app-config.js +RUN chown -R nginx:nginx /usr/share/nginx/html USER nginx ENTRYPOINT ["/usr/src/entrypoint.sh"] CMD ["nginx", "-g", "daemon off;"] diff --git a/extensions/default/src/ViewerLayout/ViewerHeader.tsx b/extensions/default/src/ViewerLayout/ViewerHeader.tsx index ced72eb6f..470528fd3 100644 --- a/extensions/default/src/ViewerLayout/ViewerHeader.tsx +++ b/extensions/default/src/ViewerLayout/ViewerHeader.tsx @@ -10,6 +10,7 @@ import { hotkeys } from '@ohif/core'; import { Toolbar } from '../Toolbar/Toolbar'; import HeaderPatientInfo from './HeaderPatientInfo'; import { PatientInfoVisibility } from './HeaderPatientInfo/HeaderPatientInfo'; +import { preserveQueryParameters, publicUrl } from '@ohif/app'; const { availableLanguages, defaultLanguage, currentLanguage } = i18n; @@ -41,7 +42,7 @@ function ViewerHeader({ } navigate({ - pathname: '/', + pathname: publicUrl, search: decodeURIComponent(searchQuery.toString()), }); }; diff --git a/package.json b/package.json index e69f2ccf3..29deca024 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "build:demo": "lerna run build:viewer:demo --stream", "build:package-all": "lerna run build:package --parallel --stream", "build:package-all-1": "lerna run build:package-1 --parallel --stream", + "show:config": "echo Config is $APP_CONFIG on $PUBLIC_URL", "dev": "lerna run dev:viewer --stream", "dev:no:cache": "lerna run dev:no:cache --stream", "dev:project": ".scripts/dev.sh", diff --git a/platform/app/public/config/default.js b/platform/app/public/config/default.js index 0e5443099..1b118b5b0 100644 --- a/platform/app/public/config/default.js +++ b/platform/app/public/config/default.js @@ -1,6 +1,7 @@ /** @type {AppTypes.Config} */ window.config = { + name: 'config/default.js', routerBasename: '/', // whiteLabeling: {}, extensions: [], diff --git a/platform/app/public/config/kheops.js b/platform/app/public/config/kheops.js index 15b739efd..3e7bc7926 100644 --- a/platform/app/public/config/kheops.js +++ b/platform/app/public/config/kheops.js @@ -1,8 +1,8 @@ /** @type {AppTypes.Config} */ window.config = { + name: 'config/kheops.js', routerBasename: '/', - // whiteLabeling: {}, extensions: [], modes: [], customizationService: {}, @@ -24,8 +24,9 @@ window.config = { prefetch: 25, }, // filterQueryParam: false, - // Uses the dicomweb proxy as the default URL - defaultDataSourceName: 'dicomwebproxy', + // Uses the ohif datasource as the default - this requires that KHEOPS be + // configured with an OHIF path to .../viewer/dicomwebproxy + defaultDataSourceName: 'ohif3', /* Dynamic config allows user to pass "configUrl" query string this allows to load config without recompiling application. The regex will ensure valid configuration source */ // dangerouslyUseDynamicConfig: { // enabled: true, diff --git a/platform/app/public/config/local_orthanc.js b/platform/app/public/config/local_orthanc.js index a3bdc8ace..b17dc3fbe 100644 --- a/platform/app/public/config/local_orthanc.js +++ b/platform/app/public/config/local_orthanc.js @@ -29,7 +29,6 @@ window.config = { enableStudyLazyLoad: true, supportsFuzzyMatching: true, supportsWildcard: true, - dicomUploadEnabled: true, omitQuotationForMultipartRequest: true, bulkDataURI: { enabled: true, diff --git a/platform/app/public/html-templates/index.html b/platform/app/public/html-templates/index.html index 25e718e79..225a00697 100644 --- a/platform/app/public/html-templates/index.html +++ b/platform/app/public/html-templates/index.html @@ -214,8 +214,9 @@ function browserImportFunction(moduleId) { return import(moduleId); } - + window.PUBLIC_URL = '<%= PUBLIC_URL %>'; +