diff --git a/.env.example b/.env.example index de18412..4dc9a45 100644 --- a/.env.example +++ b/.env.example @@ -59,3 +59,17 @@ SB_catalogUrl=/stac SB_catalogTitle=eoAPI STAC Browser SB_useTileLayerAsFallback=false EOAPI_TILE_SERVER_BASE_URL=http://127.0.0.1:8080/raster + +# STAC Map (build-time Vite config; rebuild the image after changing these) +EOAPI_STAC_MAP_PATH_PREFIX=/stac-map/ +EOAPI_STAC_MAP_DEFAULT_HREF=http://localhost:8080/stac +EOAPI_STAC_MAP_BROWSER_URL=http://localhost:8080/browser/external/ + +# STAC Manager (runtime config via ghcr.io/developmentseed/stac-manager) +# Local dev: docker-compose.mock-oidc.yml sets OIDC automatically. +EOAPI_STAC_MANAGER_PUBLIC_URL=http://localhost:8080/stac-manager +EOAPI_STAC_MANAGER_STAC_API=http://localhost:8080/stac +EOAPI_STAC_MANAGER_BROWSER_URL=http://localhost:8080/browser/external/ +# EOAPI_STAC_MANAGER_OIDC_AUTHORITY=https://cognito-idp.us-east-1.amazonaws.com/ +# EOAPI_STAC_MANAGER_OIDC_CLIENT_ID=stac-manager +EOAPI_STAC_MANAGER_TITLE=eoAPI STAC Manager diff --git a/.github/workflows/publish-containers.yml b/.github/workflows/publish-containers.yml index 33506d6..b86aa90 100644 --- a/.github/workflows/publish-containers.yml +++ b/.github/workflows/publish-containers.yml @@ -52,6 +52,10 @@ jobs: context: dockerfiles dockerfile: dockerfiles/Dockerfile.browser platforms: linux/amd64,linux/arm64 + - name: stac-map + context: dockerfiles + dockerfile: dockerfiles/Dockerfile.stac-map + platforms: linux/amd64,linux/arm64 steps: - name: Resolve inputs @@ -109,6 +113,17 @@ jobs: echo "repo=$(echo "$CONFIG" | jq -r '.services["stac-browser"].build.args.STAC_BROWSER_REPO')" >> "$GITHUB_OUTPUT" echo "ref=$(echo "$CONFIG" | jq -r '.services["stac-browser"].build.args.STAC_BROWSER_REF')" >> "$GITHUB_OUTPUT" + - name: Read stac-map build args + if: steps.check.outputs.skip == 'false' && matrix.name == 'stac-map' + id: stac-map + run: | + CONFIG=$(docker compose config --format json) + echo "repo=$(echo "$CONFIG" | jq -r '.services["stac-map"].build.args.STAC_MAP_REPO')" >> "$GITHUB_OUTPUT" + echo "ref=$(echo "$CONFIG" | jq -r '.services["stac-map"].build.args.STAC_MAP_REF')" >> "$GITHUB_OUTPUT" + echo "base_path=$(echo "$CONFIG" | jq -r '.services["stac-map"].build.args.VITE_BASE_PATH')" >> "$GITHUB_OUTPUT" + echo "default_href=$(echo "$CONFIG" | jq -r '.services["stac-map"].build.args.VITE_DEFAULT_HREF')" >> "$GITHUB_OUTPUT" + echo "browser_url=$(echo "$CONFIG" | jq -r '.services["stac-map"].build.args.VITE_STAC_BROWSER_URL')" >> "$GITHUB_OUTPUT" + - name: Set up QEMU if: steps.check.outputs.skip == 'false' uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4 @@ -128,7 +143,7 @@ jobs: type=raw,value=latest,enable=${{ steps.inputs.outputs.tag_latest == 'true' }} - name: Build and push Docker image - if: steps.check.outputs.skip == 'false' && matrix.name != 'stac-browser' + if: steps.check.outputs.skip == 'false' && matrix.name != 'stac-browser' && matrix.name != 'stac-map' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 with: context: ${{ matrix.context }} @@ -159,3 +174,24 @@ jobs: type=gha,scope=${{ matrix.name }} type=registry,ref=${{ env.IMAGE_NAME }}:latest cache-to: type=gha,mode=max,scope=${{ matrix.name }} + + - name: Build and push stac-map image + if: steps.check.outputs.skip == 'false' && matrix.name == 'stac-map' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 + with: + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} + platforms: ${{ matrix.platforms }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + STAC_MAP_REPO=${{ steps.stac-map.outputs.repo }} + STAC_MAP_REF=${{ steps.stac-map.outputs.ref }} + VITE_BASE_PATH=${{ steps.stac-map.outputs.base_path }} + VITE_DEFAULT_HREF=${{ steps.stac-map.outputs.default_href }} + VITE_STAC_BROWSER_URL=${{ steps.stac-map.outputs.browser_url }} + cache-from: | + type=gha,scope=${{ matrix.name }} + type=registry,ref=${{ env.IMAGE_NAME }}:latest + cache-to: type=gha,mode=max,scope=${{ matrix.name }} diff --git a/README.md b/README.md index b2651ff..158ecd9 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ Then you can start exploring your dataset with: - the Raster service [http://localhost:8080/raster](http://localhost:8080/raster) - the Raster mosaic builder [http://localhost:8080/raster/searches/builder](http://localhost:8080/raster/searches/builder) - the browser UI [http://localhost:8080/browser](http://localhost:8080/browser) +- the map UI [http://localhost:8080/stac-map](http://localhost:8080/stac-map) +- the manager UI [http://localhost:8080/stac-manager](http://localhost:8080/stac-manager) Item map viewers are available at `/raster/collections/{collection_id}/items/{item_id}/viewer`, or via a redirect from the STAC API at `/stac/collections/{collection_id}/items/{item_id}/viewer`. diff --git a/RELEASING.md b/RELEASING.md index a084a40..964b3e7 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -16,7 +16,7 @@ Releases are automated with [release-please](https://github.com/googleapis/relea 3. Merge the release PR. Release Please tags the release (for example `0.4.0`) and publishes a GitHub release. -4. The tag push triggers [CI](.github/workflows/ci.yml). After tests pass and version consistency checks succeed, the `publish-containers` job builds and pushes the local Docker Compose images to `ghcr.io/developmentseed/eoapi-devseed/{stac,raster,vector,stac-browser}` tagged with the release version and `latest`. Images that already exist in the registry are skipped. +4. The tag push triggers [CI](.github/workflows/ci.yml). After tests pass and version consistency checks succeed, the `publish-containers` job builds and pushes the local Docker Compose images to `ghcr.io/developmentseed/eoapi-devseed/{stac,raster,vector,stac-browser,stac-map}` tagged with the release version and `latest`. Images that already exist in the registry are skipped. 5. The same CI run also triggers the `deploy` job for the AWS dev stack. diff --git a/docker-compose.mock-oidc.yml b/docker-compose.mock-oidc.yml index 04bdc30..2c3bd87 100644 --- a/docker-compose.mock-oidc.yml +++ b/docker-compose.mock-oidc.yml @@ -27,3 +27,8 @@ services: stac-browser: environment: SB_authConfig: '{"type":"openIdConnect","openIdConnectUrl":"http://localhost:8085/.well-known/openid-configuration","oidcConfig":{"client_id":"stac-browser"}}' + + stac-manager: + environment: + REACT_APP_OIDC_AUTHORITY: http://localhost:8085 + REACT_APP_OIDC_CLIENT_ID: stac-manager diff --git a/docker-compose.yml b/docker-compose.yml index 961d6f9..75a88fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ services: - raster - vector - stac-browser + - stac-map + - stac-manager doc-server: image: nginxinc/nginx-unprivileged:alpine @@ -56,6 +58,52 @@ services: - traefik.http.routers.stac-browser.entrypoints=web - traefik.http.services.stac-browser.loadbalancer.server.port=8080 + stac-map: + build: + context: dockerfiles + dockerfile: Dockerfile.stac-map + args: + STAC_MAP_REPO: https://github.com/developmentseed/stac-map.git + STAC_MAP_REF: 18e91620889b8553ca78103cc7d5a25c1c5a82dd + VITE_BASE_PATH: "${EOAPI_STAC_MAP_PATH_PREFIX:-/stac-map/}" + VITE_DEFAULT_HREF: "${EOAPI_STAC_MAP_DEFAULT_HREF:-http://localhost:8080/stac}" + VITE_STAC_BROWSER_URL: "${EOAPI_STAC_MAP_BROWSER_URL:-http://localhost:8080/browser/external/}" + depends_on: + - stac-auth-proxy + - stac-browser + labels: + - traefik.enable=true + - traefik.http.routers.stac-map.rule=PathPrefix(`/stac-map`) + - traefik.http.routers.stac-map.entrypoints=web + - traefik.http.routers.stac-map.middlewares=stac-map-stripprefix + - traefik.http.middlewares.stac-map-stripprefix.stripprefix.prefixes=/stac-map + - traefik.http.services.stac-map.loadbalancer.server.port=8080 + + stac-manager: + image: ghcr.io/developmentseed/stac-manager:1.0.3@sha256:8f9d26ed884d8a8b7e42d272f58d543bb941e5f423bba542c43bd6d317917bb2 + environment: + PUBLIC_URL: "${EOAPI_STAC_MANAGER_PUBLIC_URL:-http://localhost:8080/stac-manager}" + REACT_APP_STAC_API: "${EOAPI_STAC_MANAGER_STAC_API:-http://localhost:8080/stac}" + REACT_APP_STAC_BROWSER: "${EOAPI_STAC_MANAGER_BROWSER_URL:-http://localhost:8080/browser/external/}" + REACT_APP_OIDC_AUTHORITY: "${EOAPI_STAC_MANAGER_OIDC_AUTHORITY:-}" + REACT_APP_OIDC_CLIENT_ID: "${EOAPI_STAC_MANAGER_OIDC_CLIENT_ID:-}" + APP_TITLE: "${EOAPI_STAC_MANAGER_TITLE:-eoAPI STAC Manager}" + env_file: + - path: .env + required: false + - path: .stac-manager.env + required: false + depends_on: + - stac-auth-proxy + - stac-browser + labels: + - traefik.enable=true + - traefik.http.routers.stac-manager.rule=PathPrefix(`/stac-manager`) + - traefik.http.routers.stac-manager.entrypoints=web + - traefik.http.routers.stac-manager.middlewares=stac-manager-stripprefix + - traefik.http.middlewares.stac-manager-stripprefix.stripprefix.prefixes=/stac-manager + - traefik.http.services.stac-manager.loadbalancer.server.port=8080 + stac: build: context: . diff --git a/dockerfiles/Dockerfile.stac-map b/dockerfiles/Dockerfile.stac-map new file mode 100644 index 0000000..fe56e2f --- /dev/null +++ b/dockerfiles/Dockerfile.stac-map @@ -0,0 +1,42 @@ +# Static build of https://github.com/developmentseed/stac-map +ARG STAC_MAP_REPO=https://github.com/developmentseed/stac-map.git +ARG STAC_MAP_REF=18e91620889b8553ca78103cc7d5a25c1c5a82dd + +FROM node:22-alpine AS build-step +ARG STAC_MAP_REPO +ARG STAC_MAP_REF +ARG VITE_BASE_PATH=/stac-map/ +ARG VITE_DEFAULT_HREF=http://localhost:8080/stac +ARG VITE_STAC_BROWSER_URL=http://localhost:8080/browser/external/ +ARG VITE_AUTH_AUTHORITY= +ARG VITE_AUTH_CLIENT_ID= + +ENV VITE_BASE_PATH="${VITE_BASE_PATH}" +ENV VITE_DEFAULT_HREF="${VITE_DEFAULT_HREF}" +ENV VITE_STAC_BROWSER_URL="${VITE_STAC_BROWSER_URL}" +ENV VITE_AUTH_AUTHORITY="${VITE_AUTH_AUTHORITY}" +ENV VITE_AUTH_CLIENT_ID="${VITE_AUTH_CLIENT_ID}" + +WORKDIR /app + +RUN apk add --no-cache git +RUN git clone "${STAC_MAP_REPO}" . && \ + git checkout "${STAC_MAP_REF}" + +RUN yarn install --frozen-lockfile +RUN yarn build + +FROM nginxinc/nginx-unprivileged:1-alpine + +USER root + +COPY --from=build-step /app/dist /usr/share/nginx/html +COPY stac-map/default.conf /etc/nginx/conf.d/default.conf + +RUN chown -R nginx:nginx /usr/share/nginx/html + +EXPOSE 8080 + +STOPSIGNAL SIGTERM + +USER nginx diff --git a/dockerfiles/landing/index.html b/dockerfiles/landing/index.html index 031f746..19e7e50 100644 --- a/dockerfiles/landing/index.html +++ b/dockerfiles/landing/index.html @@ -33,11 +33,13 @@

Components

STAC logo
-

STAC Browser

-
Catalog explorer UI
-

Browse and search the cloud-native catalog of geospatial data.

+

STAC UI

+
Browse, map & edit
+

Explore and visualize the catalog.

diff --git a/dockerfiles/stac-map/default.conf b/dockerfiles/stac-map/default.conf new file mode 100644 index 0000000..b305898 --- /dev/null +++ b/dockerfiles/stac-map/default.conf @@ -0,0 +1,10 @@ +server { + listen 8080; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/infrastructure/docker-compose.hetzner.yml b/infrastructure/docker-compose.hetzner.yml index fff4129..b18213f 100644 --- a/infrastructure/docker-compose.hetzner.yml +++ b/infrastructure/docker-compose.hetzner.yml @@ -50,6 +50,36 @@ services: SB_catalogUrl: https://${EOAPI_DOMAIN}/stac EOAPI_TILE_SERVER_BASE_URL: https://${EOAPI_DOMAIN}/raster + stac-map: + labels: !override + - traefik.enable=true + - traefik.http.routers.stac-map.rule=Host(`${EOAPI_DOMAIN}`) && PathPrefix(`/stac-map`) + - traefik.http.routers.stac-map.entrypoints=websecure + - traefik.http.routers.stac-map.middlewares=stac-map-stripprefix + - traefik.http.routers.stac-map.tls.certresolver=letsencrypt + - traefik.http.middlewares.stac-map-stripprefix.stripprefix.prefixes=/stac-map + - traefik.http.services.stac-map.loadbalancer.server.port=8080 + build: + args: + VITE_DEFAULT_HREF: https://${EOAPI_DOMAIN}/stac + VITE_STAC_BROWSER_URL: https://${EOAPI_DOMAIN}/browser/external/ + + stac-manager: + labels: !override + - traefik.enable=true + - traefik.http.routers.stac-manager.rule=Host(`${EOAPI_DOMAIN}`) && PathPrefix(`/stac-manager`) + - traefik.http.routers.stac-manager.entrypoints=websecure + - traefik.http.routers.stac-manager.middlewares=stac-manager-stripprefix + - traefik.http.routers.stac-manager.tls.certresolver=letsencrypt + - traefik.http.middlewares.stac-manager-stripprefix.stripprefix.prefixes=/stac-manager + - traefik.http.services.stac-manager.loadbalancer.server.port=8080 + environment: + PUBLIC_URL: https://${EOAPI_DOMAIN}/stac-manager + REACT_APP_STAC_API: https://${EOAPI_DOMAIN}/stac + REACT_APP_STAC_BROWSER: https://${EOAPI_DOMAIN}/browser/external/ + REACT_APP_OIDC_AUTHORITY: https://auth.${EOAPI_DOMAIN} + REACT_APP_OIDC_CLIENT_ID: stac-manager + stac: command: !override - bash