Compare commits
129
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e81fdb9810 | ||
|
|
9712579c4d | ||
|
|
98e0ffc983 | ||
|
|
ac251c4d3f | ||
|
|
08f167c18e | ||
|
|
8a608961e6 | ||
|
|
093553f9ba | ||
|
|
e5282b9099 | ||
|
|
6b4444038c | ||
|
|
89464e9be4 | ||
|
|
6856914d41 | ||
|
|
c102cd8d26 | ||
|
|
ff28e74aca | ||
|
|
23b420d072 | ||
|
|
0172272dc3 | ||
|
|
3f088c70f8 | ||
|
|
ed54fa4668 | ||
|
|
0e0cace676 | ||
|
|
58f1ae7fe9 | ||
|
|
e3075318b6 | ||
|
|
81aab35961 | ||
|
|
4e495eed33 | ||
|
|
061da5e7ef | ||
|
|
c7662727e7 | ||
|
|
13c921531c | ||
|
|
0cf8cfa5dc | ||
|
|
4182a964a6 | ||
|
|
04f7ef252a | ||
|
|
3201653f19 | ||
|
|
0988a61c1d | ||
|
|
80ea09b864 | ||
|
|
e0228918fb | ||
|
|
8369db5ca2 | ||
|
|
49e8d26bfc | ||
|
|
c28be97fb0 | ||
|
|
ed36db172a | ||
|
|
c021d04f53 | ||
|
|
86eeda3f0e | ||
|
|
25d81f4238 | ||
|
|
af8ee643e1 | ||
|
|
61e442493a | ||
|
|
9540084425 | ||
|
|
f50ba1bf23 | ||
|
|
b4e42bfd68 | ||
|
|
3af00281c4 | ||
|
|
4a90ab8e78 | ||
|
|
7598cf3007 | ||
|
|
e1c89cd093 | ||
|
|
80d4e818f1 | ||
|
|
4949acac1c | ||
|
|
586e79efb2 | ||
|
|
c961f5292d | ||
|
|
d53785b768 | ||
|
|
b9cbf199e6 | ||
|
|
ac6678a986 | ||
|
|
ed1ef56184 | ||
|
|
ac16126e2e | ||
|
|
8e600e9a87 | ||
|
|
790d83b7ed | ||
|
|
c567e4be5e | ||
|
|
936d9cd9a0 | ||
|
|
6e047d2cdb | ||
|
|
aad03b787f | ||
|
|
bb374db463 | ||
|
|
57e008c7ba | ||
|
|
5d3dc03c89 | ||
|
|
bf255a0178 | ||
|
|
36434c1160 | ||
|
|
63cecf3447 | ||
|
|
4d9e5821a8 | ||
|
|
5433a0cf5f | ||
|
|
d485adbb54 | ||
|
|
3eadb99c5e | ||
|
|
9c04061a04 | ||
|
|
897ffd5736 | ||
|
|
368595178d | ||
|
|
924c862244 | ||
|
|
cf451416b1 | ||
|
|
6e2a64fc9f | ||
|
|
0e52cb6231 | ||
|
|
507b96164f | ||
|
|
887521c376 | ||
|
|
caee9510eb | ||
|
|
825a81d326 | ||
|
|
bd13e9d9c8 | ||
|
|
92696d3685 | ||
|
|
5120390923 | ||
|
|
0810fcbb88 | ||
|
|
15453c2d8d | ||
|
|
70f10a73d8 | ||
|
|
930c06f5ff | ||
|
|
b088a8c878 | ||
|
|
067a186765 | ||
|
|
107b5cbd96 | ||
|
|
7fdd51c072 | ||
|
|
2331035da0 | ||
|
|
fd4edb7227 | ||
|
|
538b0df7e9 | ||
|
|
9f79feec8e | ||
|
|
fe703199f5 | ||
|
|
800080fd06 | ||
|
|
c1bcd9b206 | ||
|
|
5d55750b7b | ||
|
|
4f398e98d3 | ||
|
|
242e853bf0 | ||
|
|
523bf2253c | ||
|
|
f92dc835b7 | ||
|
|
0b0d412187 | ||
|
|
77b817778b | ||
|
|
eddb9a82a8 | ||
|
|
9459edce00 | ||
|
|
eaf1064413 | ||
|
|
dae6f9a570 | ||
|
|
9fa45c5871 | ||
|
|
2810cd3562 | ||
|
|
f4177baed5 | ||
|
|
5feb666bdb | ||
|
|
ceacaa5499 | ||
|
|
91343f996b | ||
|
|
312961e8c9 | ||
|
|
0475f194e9 | ||
|
|
bbb051406f | ||
|
|
40a7599388 | ||
|
|
dffce2709e | ||
|
|
62031a0ab1 | ||
|
|
ef175950b2 | ||
|
|
6d91efecad | ||
|
|
f7d3bcb696 | ||
|
|
4a093e2b97 |
No files matched your search
@@ -0,0 +1,6 @@
|
||||
**/node_modules
|
||||
**/dist
|
||||
**/.env
|
||||
**/*.env
|
||||
.git
|
||||
docker/dev/mongo/data
|
||||
@@ -0,0 +1,103 @@
|
||||
name: Build image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["develop", "master"]
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# checkmate-backend-mono is a transitional alias so existing deployments
|
||||
# keep receiving updates; drop it after one release cycle.
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ github.repository_owner }}/checkmate
|
||||
ghcr.io/${{ github.repository_owner }}/checkmate-backend-mono
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=semver,pattern={{version}}
|
||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# The hosted staging/prod instances share one host and one compose file; each
|
||||
# deploy pulls and recreates only its own service.
|
||||
deploy-staging:
|
||||
needs: build-and-push
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy staging.checkmate.so
|
||||
uses: appleboy/ssh-action@v1.2.2
|
||||
with:
|
||||
host: ${{ secrets.DEMO_SERVER_HOST }}
|
||||
username: ${{ secrets.DEMO_SERVER_USER }}
|
||||
key: ${{ secrets.DEMO_SERVER_SSH_KEY }}
|
||||
script: |
|
||||
cd checkmate
|
||||
git pull
|
||||
cd docs/hosting/checkmate-so
|
||||
docker compose pull checkmate-staging
|
||||
docker compose up -d checkmate-staging
|
||||
docker system prune -af
|
||||
|
||||
deploy-prod:
|
||||
needs: build-and-push
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy demo.checkmate.so
|
||||
uses: appleboy/ssh-action@v1.2.2
|
||||
with:
|
||||
host: ${{ secrets.DEMO_SERVER_HOST }}
|
||||
username: ${{ secrets.DEMO_SERVER_USER }}
|
||||
key: ${{ secrets.DEMO_SERVER_SSH_KEY }}
|
||||
script: |
|
||||
cd checkmate
|
||||
git pull
|
||||
cd docs/hosting/checkmate-so
|
||||
docker compose pull checkmate-demo
|
||||
docker compose up -d checkmate-demo
|
||||
docker system prune -af
|
||||
@@ -1,148 +0,0 @@
|
||||
name: Deploy images on release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
docker-build-and-push-client:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Extract version from tag
|
||||
id: extract_tag
|
||||
run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Client Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-client:${{ steps.extract_tag.outputs.version }} \
|
||||
-f ./docker/dist/client.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push Client Docker image
|
||||
run: |
|
||||
docker push ghcr.io/bluewave-labs/checkmate-client:${{ steps.extract_tag.outputs.version }}
|
||||
|
||||
docker-build-and-push-server:
|
||||
needs: docker-build-and-push-client
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Extract version
|
||||
id: extract_tag
|
||||
run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Server Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-backend:${{ steps.extract_tag.outputs.version }} \
|
||||
-f ./docker/dist/server.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push Server Docker image
|
||||
run: |
|
||||
docker push ghcr.io/bluewave-labs/checkmate-backend:${{ steps.extract_tag.outputs.version }}
|
||||
|
||||
- name: Build Mongo Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-mongo:${{ steps.extract_tag.outputs.version }} \
|
||||
-f ./docker/dist/mongoDB.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push MongoDB Docker image
|
||||
run: |
|
||||
docker push ghcr.io/bluewave-labs/checkmate-mongo:${{ steps.extract_tag.outputs.version }}
|
||||
|
||||
docker-build-and-push-server-mono-multiarch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract version
|
||||
id: extract_tag
|
||||
run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push multi-arch Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/dist-arm/server.Dockerfile
|
||||
push: true
|
||||
no-cache: true
|
||||
tags: |
|
||||
ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:${{ steps.extract_tag.outputs.version }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
labels: |
|
||||
org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate
|
||||
build-args: |
|
||||
VITE_APP_VERSION=${{ steps.extract_tag.outputs.version }}
|
||||
|
||||
docker-build-and-push-server-mono:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Extract version
|
||||
id: extract_tag
|
||||
run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Server Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-backend-mono:${{ steps.extract_tag.outputs.version }} \
|
||||
-f ./docker/dist-mono/server.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
--build-arg VITE_APP_VERSION=${{ steps.extract_tag.outputs.version }} \
|
||||
.
|
||||
|
||||
- name: Push Server Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate-backend-mono:${{ steps.extract_tag.outputs.version }}
|
||||
@@ -1,143 +0,0 @@
|
||||
name: Deploy images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
docker-build-and-push-client:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: vars
|
||||
run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
- name: Build Client Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-client:latest \
|
||||
-f ./docker/dist/client.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
--build-arg VITE_APP_VERSION=${{ env.VERSION }} \
|
||||
.
|
||||
|
||||
- name: Push Client Docker image
|
||||
run: |
|
||||
docker push ghcr.io/bluewave-labs/checkmate-client:latest
|
||||
|
||||
docker-build-and-push-server:
|
||||
needs: docker-build-and-push-client
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Server Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-backend:latest \
|
||||
-f ./docker/dist/server.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push Server Docker image
|
||||
run: |
|
||||
docker push ghcr.io/bluewave-labs/checkmate-backend:latest
|
||||
|
||||
- name: Build Mongo Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-mongo:latest \
|
||||
-f ./docker/dist/mongoDB.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push MongoDB Docker image
|
||||
run: |
|
||||
docker push ghcr.io/bluewave-labs/checkmate-mongo:latest
|
||||
|
||||
docker-build-and-push-server-mono-multiarch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: vars
|
||||
run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push multi-arch Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/dist-arm/server.Dockerfile
|
||||
push: true
|
||||
no-cache: true
|
||||
tags: |
|
||||
ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
labels: |
|
||||
org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate
|
||||
build-args: |
|
||||
VITE_APP_VERSION=${{ env.VERSION }}
|
||||
|
||||
docker-build-and-push-server-mono:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: vars
|
||||
run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Server Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate-backend-mono:latest \
|
||||
-f ./docker/dist-mono/server.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
--build-arg VITE_APP_VERSION=${{ env.VERSION }} \
|
||||
.
|
||||
|
||||
- name: Push Server Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate-backend-mono:latest
|
||||
@@ -1,91 +0,0 @@
|
||||
name: Demo deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "demo" ]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
docker-build-and-push-client:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: vars
|
||||
run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
- name: Build Client Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate:frontend-demo \
|
||||
-f ./docker/prod/client.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
--build-arg VITE_APP_VERSION=${{ env.VERSION }} \
|
||||
.
|
||||
|
||||
- name: Push Client Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate:frontend-demo
|
||||
|
||||
docker-build-and-push-server:
|
||||
needs: docker-build-and-push-client
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Server Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate:backend-demo \
|
||||
-f ./docker/prod/server.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push Server Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate:backend-demo
|
||||
|
||||
- name: Build Mongo Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate:mongo-demo \
|
||||
-f ./docker/prod/mongoDB.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push MongoDB Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate:mongo-demo
|
||||
|
||||
deploy-to-demo:
|
||||
needs: docker-build-and-push-server
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: SSH into server and restart container using Docker Compose
|
||||
uses: appleboy/ssh-action@v1.2.2
|
||||
with:
|
||||
host: ${{ secrets.DEMO_SERVER_HOST }}
|
||||
username: ${{ secrets.DEMO_SERVER_USER }}
|
||||
key: ${{ secrets.DEMO_SERVER_SSH_KEY }}
|
||||
script: |
|
||||
cd checkmate
|
||||
git pull
|
||||
cd docker/unified
|
||||
docker compose down
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker system prune -af
|
||||
@@ -1,91 +0,0 @@
|
||||
name: Staging deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "develop" ]
|
||||
|
||||
jobs:
|
||||
docker-build-and-push-client:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: vars
|
||||
run: echo "VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
- name: Build Client Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate:frontend-staging \
|
||||
-f ./docker/staging/client.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
--build-arg VITE_APP_VERSION=${{ env.VERSION }} \
|
||||
.
|
||||
|
||||
- name: Push Client Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate:frontend-staging
|
||||
|
||||
docker-build-and-push-server:
|
||||
needs: docker-build-and-push-client
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Server Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate:backend-staging \
|
||||
-f ./docker/staging/server.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push Server Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate:backend-staging
|
||||
|
||||
- name: Build Mongo Docker image
|
||||
run: |
|
||||
docker build \
|
||||
-t ghcr.io/bluewave-labs/checkmate:mongo-staging \
|
||||
-f ./docker/staging/mongoDB.Dockerfile \
|
||||
--label org.opencontainers.image.source=https://github.com/bluewave-labs/checkmate \
|
||||
.
|
||||
|
||||
- name: Push MongoDB Docker image
|
||||
run: docker push ghcr.io/bluewave-labs/checkmate:mongo-staging
|
||||
|
||||
deploy-to-staging:
|
||||
needs: docker-build-and-push-server
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: SSH into server and restart container using Docker Compose
|
||||
uses: appleboy/ssh-action@v1.2.2
|
||||
with:
|
||||
host: ${{ secrets.DEMO_SERVER_HOST }}
|
||||
username: ${{ secrets.DEMO_SERVER_USER }}
|
||||
key: ${{ secrets.DEMO_SERVER_SSH_KEY }}
|
||||
script: |
|
||||
cd checkmate
|
||||
git pull
|
||||
cd docker/unified
|
||||
docker compose down
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker system prune -af
|
||||
@@ -35,9 +35,9 @@ npm run format # Prettier formatting
|
||||
|
||||
### Docker Development
|
||||
```bash
|
||||
cd docker/dev
|
||||
./build_images.sh
|
||||
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
|
||||
docker build -f docker/Dockerfile -t checkmate . # build the mono image (server + built client)
|
||||
docker compose -f docker/dev/docker-compose.yaml up # local full stack (builds image, runs mongo + worker)
|
||||
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:8.0 # just a dev database
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
+30
-25
@@ -88,34 +88,10 @@ cd Checkmate
|
||||
|
||||
#### Step 2: Set Up Docker Containers (MongoDB)
|
||||
|
||||
Navigate to the Docker dev directory:
|
||||
|
||||
```bash
|
||||
cd docker/dev
|
||||
```
|
||||
|
||||
Build the Docker images:
|
||||
|
||||
Linux:
|
||||
```bash
|
||||
./build_images.sh
|
||||
```
|
||||
|
||||
Windows:
|
||||
```bash
|
||||
./build_images.bat
|
||||
```
|
||||
|
||||
Run MongoDB container:
|
||||
|
||||
```bash
|
||||
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
|
||||
```
|
||||
|
||||
Navigate back to the root directory:
|
||||
|
||||
```bash
|
||||
cd ../..
|
||||
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:8.0
|
||||
```
|
||||
|
||||
#### Step 3: Set Up the Backend (Server)
|
||||
@@ -256,6 +232,35 @@ docker rm uptime_database_mongo
|
||||
|
||||
Start with [good first issues](https://github.com/bluewave-labs/checkmate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
|
||||
|
||||
### Improve the documentation?
|
||||
|
||||
Documentation contributions are very welcome — fixing a typo, clarifying a step, or adding a missing example is a valuable PR.
|
||||
|
||||
- Small edits (typos, wording, formatting): open a PR directly against `develop`.
|
||||
- Larger changes (restructuring a section, adding a new guide): open an issue first so we can align on scope, then a PR.
|
||||
- Screenshots are helpful when documenting UI behaviour.
|
||||
- README translations live in `docs/translations/`. If you're adding a new language, please also add a flag + link to the language bar in the root `README.md` and every existing translated README so they stay in sync.
|
||||
|
||||
### Help with translations?
|
||||
|
||||
The application uses `i18next` with one JSON file per language under `client/src/locales/` (`en.json` is the source of truth). English source strings are the definitive copy; other languages are generated as one-time machine translations that anyone is welcome to refine.
|
||||
|
||||
**To improve an existing language:**
|
||||
|
||||
1. Edit the relevant `client/src/locales/<code>.json` file directly.
|
||||
2. Keep the JSON key structure identical to `en.json` — only translate the string values.
|
||||
3. Open a PR against `develop`. No language-selector code changes are needed.
|
||||
|
||||
**To add a new language:**
|
||||
|
||||
1. Add `client/src/locales/<code>.json` with the same key structure as `en.json`, translated. If you can't translate every key, leave unfamiliar strings in English rather than removing keys — i18next falls back to the key path when a value is missing.
|
||||
2. Add the display name to `languageNames` in `client/src/Components/inputs/LanguageSelector.tsx` (e.g. `it: "Italiano"`). The selector picks up any language present in `resources` automatically.
|
||||
3. Open a PR against `develop`.
|
||||
|
||||
If translating a whole file is more than you want to take on, open an issue asking us to add the language and we'll generate an initial machine-translated file that you (or others) can refine over time.
|
||||
|
||||
**Note:** an older workflow that synced translations with PoEditor is still in the repo but no longer authoritative. Please treat the `.json` files in `client/src/locales/` as the source of truth.
|
||||
|
||||
### Submit a pull request?
|
||||
|
||||
Follow the [pull request checklist](#pull-request-checklist). Your PR should:
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
🇪🇸 <a href="docs/translations/README.es.md">Español</a> |
|
||||
🇫🇮 <a href="docs/translations/README.fi.md">Suomi</a> |
|
||||
🇫🇷 <a href="docs/translations/README.fr.md">Français</a> |
|
||||
🇮🇹 <a href="docs/translations/README.it.md">Italiano</a> |
|
||||
🇯🇵 <a href="docs/translations/README.ja.md">日本語</a> |
|
||||
🇧🇷 <a href="docs/translations/README.pt-BR.md">Português (Brasil)</a> |
|
||||
🇷🇺 <a href="docs/translations/README.ru.md">Русский</a> |
|
||||
@@ -79,7 +80,37 @@ Usage instructions can be found [here](https://checkmate.so/docs).
|
||||
<a id="installation"></a>
|
||||
## Installation
|
||||
|
||||
See installation instructions in [Checkmate documentation portal](https://checkmate.so/docs).
|
||||
The quickest way to run Checkmate is the reference Docker Compose file, which runs the all-in-one image (`ghcr.io/bluewave-labs/checkmate`) plus MongoDB:
|
||||
|
||||
```bash
|
||||
curl -O https://raw.githubusercontent.com/bluewave-labs/checkmate/master/docker/docker-compose.yaml
|
||||
JWT_SECRET="$(openssl rand -hex 32)" docker compose up -d
|
||||
```
|
||||
|
||||
Then open http://localhost:52345. If the app is reached at another origin (domain or LAN IP), set `CLIENT_HOST` accordingly. To build the image yourself, run `docker build -f docker/Dockerfile -t checkmate .` from a checkout. For TLS, put any reverse proxy (Caddy, Traefik, nginx) in front of port 52345.
|
||||
|
||||
### Configuration
|
||||
|
||||
The image is configured entirely through environment variables on the server container:
|
||||
|
||||
| Variable | Required | Description |
|
||||
|---|---|---|
|
||||
| `DB_CONNECTION_STRING` | Yes | MongoDB connection string, e.g. `mongodb://mongodb:27017/uptime_db` |
|
||||
| `JWT_SECRET` | Yes | Secret used to sign auth tokens; generate one with `openssl rand -hex 32` |
|
||||
| `CLIENT_HOST` | Yes | The URL users reach the app at, e.g. `https://checkmate.example.com`; used for CORS and for links in notifications and emails |
|
||||
| `LOG_LEVEL` | No | Server log level: `error`, `warn`, `info`, or `debug` (default `debug`) |
|
||||
|
||||
The web client needs no configuration by default: it calls the API on the same origin it was served from (`/api/v1`). For setups where the defaults don't apply — for example, the API is reached through a different origin than the page — the server renders overrides into the client at runtime via these optional variables:
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `CLIENT_CONFIG_API_BASE_URL` | Full base URL the client calls the API at, e.g. `https://api.example.com/api/v1`; defaults to same-origin `/api/v1` |
|
||||
| `CLIENT_CONFIG_CLIENT_HOST` | Origin used when the client builds absolute links (invites, status pages); defaults to the browser's current origin |
|
||||
| `CLIENT_CONFIG_LOG_LEVEL` | Browser console log level: `error`, `warn`, `info`, or `debug` (default `error`) |
|
||||
|
||||
> **Upgrading from an older image?** The `UPTIME_APP_*` variables (`UPTIME_APP_API_BASE_URL`, `UPTIME_APP_CLIENT_HOST`, `UPTIME_APP_LOG_LEVEL`) are no longer read. In most setups no replacement is needed — the same-origin defaults cover them; if you pointed the client at a different origin, use the `CLIENT_CONFIG_*` equivalents above. The `checkmate-client`, `checkmate-backend`, `checkmate-mongo`, and `checkmate-backend-mono-multiarch` images are no longer updated — switch to `ghcr.io/bluewave-labs/checkmate`, keeping your existing MongoDB service and data volume.
|
||||
|
||||
See full installation instructions in the [Checkmate documentation portal](https://checkmate.so/docs).
|
||||
|
||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa), [Cloudzy](https://cloudzy.com/marketplace/checkmate) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
|
||||
|
||||
@@ -3,4 +3,8 @@ name: checkmate-chart
|
||||
description: A Helm chart for Checkmate App
|
||||
type: application
|
||||
version: 0.2.0
|
||||
# DO NOT bump appVersion past v3.8.1 until this chart is migrated to the all-in-one
|
||||
# image (ghcr.io/bluewave-labs/checkmate). The checkmate-client / checkmate-backend /
|
||||
# checkmate-mongo images this chart references are frozen at v3.8.1 — later versions
|
||||
# are never published, so a bumped chart will fail to pull.
|
||||
appVersion: "v3.8.1" # "v" prefix: published image tags are v-prefixed (ghcr.io/.../checkmate-*:v3.8.1)
|
||||
@@ -1,4 +1,6 @@
|
||||
VITE_APP_API_BASE_URL=UPTIME_APP_API_BASE_URL
|
||||
VITE_APP_API_V2_BASE_URL=UPTIME_APP_API_V2_BASE_URL
|
||||
VITE_APP_CLIENT_HOST=UPTIME_APP_CLIENT_HOST
|
||||
VITE_APP_LOG_LEVEL=UPTIME_APP_LOG_LEVEL
|
||||
# Production builds use same-origin defaults: API at /api/v1, client host from
|
||||
# window.location.origin. The empty values below override any local .env so a
|
||||
# production build never bakes in a developer's localhost URLs.
|
||||
VITE_APP_API_BASE_URL=
|
||||
VITE_APP_CLIENT_HOST=
|
||||
VITE_APP_LOG_LEVEL=
|
||||
@@ -28,5 +28,4 @@ dist-ssr
|
||||
|
||||
# Scripts
|
||||
*.sh
|
||||
!env.sh
|
||||
.env.development
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
for i in $(env | grep UPTIME_APP_)
|
||||
do
|
||||
key=$(echo $i | cut -d '=' -f 1)
|
||||
value=$(echo $i | cut -d '=' -f 2-)
|
||||
echo $key=$value
|
||||
find /usr/share/nginx/html -type f \( -name '*.js' -o -name '*.css' \) -exec sed -i "s|${key}|${value}|g" '{}' +
|
||||
done
|
||||
@@ -12,6 +12,7 @@
|
||||
/>
|
||||
|
||||
<title>Checkmate</title>
|
||||
<script src="/config.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Generated
+4
-24
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "client",
|
||||
"version": "3.9.2",
|
||||
"version": "3.10.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "client",
|
||||
"version": "3.9.2",
|
||||
"version": "3.10.0",
|
||||
"dependencies": {
|
||||
"@csstools/css-tokenizer": "^3.0.4",
|
||||
"@emotion/react": "^11.13.3",
|
||||
@@ -92,7 +92,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
@@ -408,7 +407,6 @@
|
||||
"node_modules/@emotion/react": {
|
||||
"version": "11.14.0",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -446,7 +444,6 @@
|
||||
"node_modules/@emotion/styled": {
|
||||
"version": "11.14.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -1226,7 +1223,6 @@
|
||||
"node_modules/@mui/material": {
|
||||
"version": "7.3.7",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/core-downloads-tracker": "^7.3.7",
|
||||
@@ -1331,7 +1327,6 @@
|
||||
"node_modules/@mui/system": {
|
||||
"version": "7.3.7",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/private-theming": "^7.3.7",
|
||||
@@ -2376,7 +2371,6 @@
|
||||
"version": "24.5.2",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.12.0"
|
||||
}
|
||||
@@ -2392,7 +2386,6 @@
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.27",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -2483,7 +2476,6 @@
|
||||
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "7.18.0",
|
||||
"@typescript-eslint/types": "7.18.0",
|
||||
@@ -2764,7 +2756,6 @@
|
||||
"version": "8.15.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -3121,7 +3112,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.42",
|
||||
"caniuse-lite": "^1.0.30001800",
|
||||
@@ -3565,8 +3555,7 @@
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
@@ -3965,7 +3954,6 @@
|
||||
"version": "8.57.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -4778,7 +4766,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -5590,7 +5577,6 @@
|
||||
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.19.0.tgz",
|
||||
"integrity": "sha512-REhYUN8gNP3HlcIZS6QU2uy8iovl31cXsrNDkCcqWSQbCkcpdYLczqDz5PVIwNH42UQNyvukjes/RoHPDrOUmQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@mapbox/geojson-rewind": "^0.5.2",
|
||||
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
|
||||
@@ -6372,7 +6358,6 @@
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -6384,7 +6369,6 @@
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.71.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -6588,8 +6572,7 @@
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux-persist": {
|
||||
"version": "6.0.0",
|
||||
@@ -6739,7 +6722,6 @@
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
||||
"integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.9"
|
||||
},
|
||||
@@ -7581,7 +7563,6 @@
|
||||
"version": "5.9.3",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -7719,7 +7700,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
|
||||
"integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "client",
|
||||
"private": true,
|
||||
"version": "3.9.2",
|
||||
"version": "3.10.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Fallback so /config.js always resolves (e.g. under the Vite dev server). In
|
||||
// the Docker image the server shadows this path and renders the real config
|
||||
// from its CLIENT_CONFIG_* environment variables.
|
||||
window.__CHECKMATE_CONFIG__ = {};
|
||||
@@ -7,6 +7,7 @@ import { useTheme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/Types/state";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import prettyMilliseconds from "pretty-ms";
|
||||
|
||||
type HeatmapCheck =
|
||||
| CheckSnapshot
|
||||
@@ -58,7 +59,9 @@ export const HeatmapResponseTimeTooltip = ({
|
||||
</Typography>
|
||||
<Typography>
|
||||
{t("common.labels.responseTime")}:{" "}
|
||||
{check?.originalResponseTime?.toFixed() ?? "N/A"} ms
|
||||
{check?.responseTime
|
||||
? prettyMilliseconds(check.responseTime, { compact: true })
|
||||
: "N/A"}
|
||||
</Typography>
|
||||
<Typography textTransform={"capitalize"}>
|
||||
Status:{" "}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MAX_RECENT_CHECKS } from "@/Types/Monitor";
|
||||
import { computeBarHeights } from "@/Utils/DataUtils";
|
||||
|
||||
interface HistogramResponseTimeProps {
|
||||
checks: CheckSnapshot[];
|
||||
@@ -29,12 +30,12 @@ const calculateResponseTimeStats = (checks: CheckSnapshot[]): ResponseTimeStats
|
||||
return { max: "-", avg: "-" };
|
||||
}
|
||||
|
||||
const validChecks = checks.filter((check) => check.originalResponseTime != null);
|
||||
const validChecks = checks.filter((check) => check.responseTime != null);
|
||||
if (validChecks.length === 0) {
|
||||
return { max: "-", avg: "-" };
|
||||
}
|
||||
|
||||
const responseTimes = validChecks.map((check) => check.originalResponseTime);
|
||||
const responseTimes = validChecks.map((check) => check.responseTime);
|
||||
const max = Math.round(Math.max(...responseTimes));
|
||||
const avg = Math.round(
|
||||
responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length
|
||||
@@ -54,7 +55,7 @@ export const HistogramResponseTime = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const stats = useMemo(() => calculateResponseTimeStats(checks), [checks]);
|
||||
|
||||
const barHeights = useMemo(() => computeBarHeights(checks), [checks]);
|
||||
if (!Array.isArray(checks) || checks.length === 0) return null;
|
||||
|
||||
const data =
|
||||
@@ -111,7 +112,7 @@ export const HistogramResponseTime = ({
|
||||
>
|
||||
{data.map((check, index) => {
|
||||
const isPlaceholder = (check as any).status === "placeholder";
|
||||
const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`;
|
||||
const heightPct = `${barHeights[index] ?? 0}%`;
|
||||
const barColor =
|
||||
check.status === true
|
||||
? theme.palette.success.light
|
||||
|
||||
@@ -13,6 +13,7 @@ type BaseChartProps = React.PropsWithChildren<{
|
||||
width?: number | string;
|
||||
maxWidth?: number | string;
|
||||
padding?: number | string | ResponsiveStyleValue<number | string>;
|
||||
onClick?: () => void;
|
||||
}>;
|
||||
|
||||
export const BaseChart = ({
|
||||
@@ -21,16 +22,22 @@ export const BaseChart = ({
|
||||
width = "100%",
|
||||
maxWidth = "100%",
|
||||
padding,
|
||||
onClick,
|
||||
}: BaseChartProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<BaseBox
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
padding: padding ?? theme.spacing(LAYOUT.MD),
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
width: width,
|
||||
maxWidth: maxWidth,
|
||||
...(onClick && {
|
||||
cursor: "pointer",
|
||||
"& .recharts-wrapper": { cursor: "pointer !important" },
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
@@ -40,6 +47,7 @@ export const BaseChart = ({
|
||||
<Typography
|
||||
variant="eyebrow"
|
||||
color="text.secondary"
|
||||
sx={{ overflowWrap: "anywhere" }}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
export const DotSizes = ["xs", "sm", "md", "lg"] as const;
|
||||
export type DotSize = (typeof DotSizes)[number];
|
||||
|
||||
const DOT_SIZE_PX: Record<DotSize, string> = {
|
||||
xs: "2px",
|
||||
sm: "4px",
|
||||
md: "8px",
|
||||
lg: "12px",
|
||||
};
|
||||
|
||||
interface DotProps {
|
||||
color?: string;
|
||||
size?: string;
|
||||
size?: DotSize;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const Dot = ({ color = "gray", size = "4px", style }: DotProps) => {
|
||||
export const Dot = ({ color = "gray", size = "sm", style }: DotProps) => {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
content: '""',
|
||||
width: size,
|
||||
height: size,
|
||||
width: DOT_SIZE_PX[size],
|
||||
height: DOT_SIZE_PX[size],
|
||||
borderRadius: "50%",
|
||||
backgroundColor: color,
|
||||
opacity: 0.8,
|
||||
|
||||
@@ -13,7 +13,9 @@ export const languageNames: Record<string, string> = {
|
||||
ca: "Català",
|
||||
es: "Español",
|
||||
fr: "Français",
|
||||
it: "Italiano",
|
||||
"pt-BR": "Português (BR)",
|
||||
pl: "Polski",
|
||||
cs: "Čeština",
|
||||
fi: "Suomi",
|
||||
tr: "Türkçe",
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
export const computeYAxisCap = (values: number[]): number | undefined => {
|
||||
const sorted = values.slice().sort((a, b) => a - b);
|
||||
if (sorted.length < 2) return undefined;
|
||||
const p95 = sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))];
|
||||
return p95 > 0 ? Math.ceil(p95 * 2) : undefined;
|
||||
};
|
||||
|
||||
export const PHASE_KEYS = [
|
||||
"avgDns",
|
||||
"avgTcp",
|
||||
"avgTls",
|
||||
"avgRequest",
|
||||
"avgFirstByte",
|
||||
"avgDownload",
|
||||
] as const;
|
||||
|
||||
export const PHASE_COLOR_KEYS = {
|
||||
avgDns: "dns",
|
||||
avgTcp: "tcp",
|
||||
avgTls: "tls",
|
||||
avgRequest: "request",
|
||||
avgFirstByte: "firstByte",
|
||||
avgDownload: "download",
|
||||
} as const;
|
||||
|
||||
// i18n keys, resolved with t() at render
|
||||
export const PHASE_LABEL_KEYS: Record<(typeof PHASE_KEYS)[number], string> = {
|
||||
avgDns: "common.charts.phases.dns",
|
||||
avgTcp: "common.charts.phases.tcp",
|
||||
avgTls: "common.charts.phases.tls",
|
||||
avgRequest: "common.charts.phases.request",
|
||||
avgFirstByte: "common.charts.phases.firstByte",
|
||||
avgDownload: "common.charts.phases.download",
|
||||
};
|
||||
@@ -1,53 +1,40 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { BaseChart, BaseBox } from "@/Components/design-elements";
|
||||
import { Clock } from "lucide-react";
|
||||
import { Dot } from "@/Components/design-elements";
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Text,
|
||||
} from "recharts";
|
||||
import type { TooltipProps } from "recharts";
|
||||
import type { NameType, ValueType } from "recharts/types/component/DefaultTooltipContent";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import prettyMilliseconds from "pretty-ms";
|
||||
import { XTick } from "@/Components/monitors/charts/XTick";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
formatDateWithTz,
|
||||
tickDateFormatLookup,
|
||||
tooltipDateFormatLookup,
|
||||
} from "@/Utils/TimeUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatDateWithTz, tooltipDateFormatLookup } from "@/Utils/TimeUtils";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Theme } from "@mui/material/styles";
|
||||
import type { GroupedCheck } from "@/Types/Check";
|
||||
import type { GroupedCheck, GroupedUptimeCheck } from "@/Types/Check";
|
||||
import type { RootState } from "@/Types/state";
|
||||
import { computeYAxisCap } from "@/Components/monitors/charts/ChartUtils";
|
||||
|
||||
type XTickProps = {
|
||||
x: number;
|
||||
y: number;
|
||||
payload: { value: string };
|
||||
range: string;
|
||||
};
|
||||
import { useMemo, useState } from "react";
|
||||
import { LAYOUT, SPACING } from "@/Utils/Theme/constants";
|
||||
import {
|
||||
PHASE_COLOR_KEYS,
|
||||
PHASE_KEYS,
|
||||
PHASE_LABEL_KEYS,
|
||||
} from "@/Components/monitors/charts/ChartUtils";
|
||||
|
||||
export const XTick = ({ x, y, payload, range }: XTickProps) => {
|
||||
const format = tickDateFormatLookup(range);
|
||||
const theme = useTheme();
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
return (
|
||||
<Text
|
||||
x={x}
|
||||
y={y + 10}
|
||||
textAnchor="middle"
|
||||
fill={theme.palette.text.secondary}
|
||||
fontSize={11}
|
||||
fontWeight={400}
|
||||
>
|
||||
{formatDateWithTz(payload?.value, format, uiTimezone)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
type DetailsCheck = GroupedCheck &
|
||||
Partial<Pick<GroupedUptimeCheck, (typeof PHASE_KEYS)[number]>>;
|
||||
|
||||
type ResponseTimeToolTipProps = TooltipProps<ValueType, NameType> & {
|
||||
range: string;
|
||||
@@ -63,17 +50,72 @@ const ResponseTimeToolTip = ({
|
||||
theme,
|
||||
uiTimezone,
|
||||
}: ResponseTimeToolTipProps) => {
|
||||
const { t } = useTranslation();
|
||||
if (!label) return null;
|
||||
if (!payload) return null;
|
||||
if (!active) return null;
|
||||
|
||||
const format = tooltipDateFormatLookup(range);
|
||||
console.log(payload);
|
||||
const responseTime = Math.floor(payload[0]?.payload?.originalAvgResponseTime || 0);
|
||||
const responseTime = Math.floor(payload[0]?.payload?.avgResponseTime || 0);
|
||||
return (
|
||||
<BaseBox sx={{ py: theme.spacing(2), px: theme.spacing(4) }}>
|
||||
<Typography>{formatDateWithTz(String(label), format, uiTimezone)}</Typography>
|
||||
<Typography>Response time: {responseTime} ms</Typography>
|
||||
<Typography>
|
||||
{t("common.charts.histogram.responseTime", {
|
||||
value: prettyMilliseconds(responseTime, {
|
||||
formatSubMilliseconds: true,
|
||||
compact: true,
|
||||
}),
|
||||
})}
|
||||
</Typography>
|
||||
</BaseBox>
|
||||
);
|
||||
};
|
||||
|
||||
type TimingPhaseToolTipProps = TooltipProps<ValueType, NameType> & {
|
||||
range: string;
|
||||
theme: Theme;
|
||||
uiTimezone: string;
|
||||
};
|
||||
|
||||
const TimingPhasesToolTip = ({
|
||||
active,
|
||||
payload,
|
||||
range,
|
||||
theme,
|
||||
uiTimezone,
|
||||
}: TimingPhaseToolTipProps) => {
|
||||
const { t } = useTranslation();
|
||||
if (!active || !payload?.length) return null;
|
||||
const bucket = payload[0].payload as GroupedUptimeCheck;
|
||||
const total = PHASE_KEYS.reduce((sum, key) => sum + bucket[key], 0);
|
||||
const format = tooltipDateFormatLookup(range);
|
||||
return (
|
||||
<BaseBox sx={{ py: theme.spacing(2), px: theme.spacing(4) }}>
|
||||
<Typography>{formatDateWithTz(bucket.bucketDate, format, uiTimezone)}</Typography>
|
||||
<Typography>
|
||||
{t("common.charts.histogram.total", { value: prettyMilliseconds(total) })}
|
||||
</Typography>
|
||||
{PHASE_KEYS.map((key) => (
|
||||
<Stack
|
||||
alignItems={"center"}
|
||||
direction="row"
|
||||
key={key}
|
||||
gap={SPACING.LG}
|
||||
>
|
||||
<Dot
|
||||
size="lg"
|
||||
color={theme.palette.chart.phases[PHASE_COLOR_KEYS[key]]}
|
||||
/>
|
||||
<Typography>
|
||||
{t(PHASE_LABEL_KEYS[key])}:{" "}
|
||||
{prettyMilliseconds(bucket[key], {
|
||||
formatSubMilliseconds: true,
|
||||
compact: true,
|
||||
})}
|
||||
</Typography>
|
||||
</Stack>
|
||||
))}
|
||||
</BaseBox>
|
||||
);
|
||||
};
|
||||
@@ -82,11 +124,28 @@ export const HistogramDetails = ({
|
||||
checks,
|
||||
range,
|
||||
}: {
|
||||
checks: GroupedCheck[];
|
||||
checks: DetailsCheck[];
|
||||
range: string;
|
||||
}) => {
|
||||
const [showTimingPhases, setShowTimingPhases] = useState(false);
|
||||
const [hoveredPhase, setHoveredPhase] = useState<string | null>(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
const yMax = useMemo(() => {
|
||||
const totals = checks.map((check) => {
|
||||
const phaseSum = PHASE_KEYS.reduce((sum, key) => sum + (check[key] ?? 0), 0);
|
||||
return Math.max(check.avgResponseTime ?? 0, phaseSum);
|
||||
});
|
||||
return computeYAxisCap(totals);
|
||||
}, [checks]);
|
||||
|
||||
const hasTimingPhases = useMemo(
|
||||
() => (checks ?? []).some((check) => PHASE_KEYS.some((key) => (check[key] ?? 0) > 0)),
|
||||
[checks]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseChart
|
||||
icon={
|
||||
@@ -95,7 +154,15 @@ export const HistogramDetails = ({
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
title="Response times"
|
||||
title={
|
||||
showTimingPhases
|
||||
? t("common.charts.labels.timingPhases")
|
||||
: t("common.charts.labels.responseTime")
|
||||
}
|
||||
onClick={() => {
|
||||
if (!hasTimingPhases) return;
|
||||
setShowTimingPhases((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
@@ -128,7 +195,34 @@ export const HistogramDetails = ({
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
{PHASE_KEYS.map((key) => (
|
||||
<linearGradient
|
||||
key={key}
|
||||
id={`phaseGradient-${key}`}
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme.palette.chart.phases[PHASE_COLOR_KEYS[key]]}
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={theme.palette.chart.phases[PHASE_COLOR_KEYS[key]]}
|
||||
stopOpacity={0.2}
|
||||
/>
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
<YAxis
|
||||
hide
|
||||
// scale="sqrt"
|
||||
domain={[0, yMax ?? "auto"]}
|
||||
allowDataOverflow
|
||||
/>
|
||||
<XAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
@@ -141,24 +235,99 @@ export const HistogramDetails = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<Tooltip
|
||||
content={(props) => (
|
||||
<ResponseTimeToolTip
|
||||
{...props}
|
||||
range={range}
|
||||
theme={theme}
|
||||
uiTimezone={uiTimezone}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="avgResponseTime"
|
||||
stroke={theme.palette.primary.main}
|
||||
fill="url(#colorUv)"
|
||||
/>
|
||||
{showTimingPhases ? (
|
||||
<Tooltip
|
||||
content={(props) => (
|
||||
<TimingPhasesToolTip
|
||||
{...props}
|
||||
range={range}
|
||||
theme={theme}
|
||||
uiTimezone={uiTimezone}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip
|
||||
content={(props) => (
|
||||
<ResponseTimeToolTip
|
||||
{...props}
|
||||
range={range}
|
||||
theme={theme}
|
||||
uiTimezone={uiTimezone}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{!showTimingPhases && (
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="avgResponseTime"
|
||||
stroke={theme.palette.primary.main}
|
||||
fill="url(#colorUv)"
|
||||
/>
|
||||
)}
|
||||
{hasTimingPhases &&
|
||||
showTimingPhases &&
|
||||
PHASE_KEYS.map((key) => {
|
||||
const dimmed = hoveredPhase !== null && hoveredPhase !== key;
|
||||
return (
|
||||
<Area
|
||||
key={key}
|
||||
type="monotone"
|
||||
stackId={1}
|
||||
dataKey={key}
|
||||
stroke={theme.palette.chart.phases[PHASE_COLOR_KEYS[key]]}
|
||||
fill={`url(#phaseGradient-${key})`}
|
||||
fillOpacity={dimmed ? 0.2 : undefined}
|
||||
strokeOpacity={dimmed ? 0.2 : undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
<Stack
|
||||
justifyContent={"center"}
|
||||
gap={LAYOUT.XS}
|
||||
direction={"row"}
|
||||
flexWrap={"wrap"}
|
||||
>
|
||||
{showTimingPhases ? (
|
||||
PHASE_KEYS.map((key) => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={SPACING.LG}
|
||||
key={key}
|
||||
onMouseEnter={() => setHoveredPhase(key)}
|
||||
onMouseLeave={() => setHoveredPhase(null)}
|
||||
>
|
||||
<Dot
|
||||
color={theme.palette.chart.phases[PHASE_COLOR_KEYS[key]]}
|
||||
size="lg"
|
||||
/>
|
||||
<Typography lineHeight={1}>{t(PHASE_LABEL_KEYS[key])}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={SPACING.LG}
|
||||
onMouseEnter={() => setHoveredPhase("avgResponseTime")}
|
||||
onMouseLeave={() => setHoveredPhase(null)}
|
||||
>
|
||||
<Dot
|
||||
color={theme.palette.primary.main}
|
||||
size="lg"
|
||||
/>
|
||||
<Typography lineHeight={1}>
|
||||
{t("common.charts.labels.totalResponseTime")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</BaseChart>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import { Fragment, useId } from "react";
|
||||
import { XTick } from "@/Components/monitors";
|
||||
import { XTick } from "@/Components/monitors/charts/XTick";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { HardwareCheckStats } from "@/Types/Monitor";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BaseChart } from "@/Components/design-elements";
|
||||
import { TrendingUp } from "lucide-react";
|
||||
import { XTick } from "@/Components/monitors";
|
||||
import { XTick } from "@/Components/monitors/charts/XTick";
|
||||
import {
|
||||
XAxis,
|
||||
AreaChart,
|
||||
|
||||
@@ -20,7 +20,7 @@ export const RadialAvgResponse = ({ avg, max }: { avg: number; max: number }) =>
|
||||
const msg: Record<string, string> = {
|
||||
success: "Excellent",
|
||||
warning: "Average",
|
||||
danger: "Poor",
|
||||
error: "Poor",
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { formatDateWithTz, tickDateFormatLookup } from "@/Utils/TimeUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Text } from "recharts";
|
||||
import { useTheme } from "@mui/material";
|
||||
import type { RootState } from "@/Types/state";
|
||||
|
||||
type XTickProps = {
|
||||
x: number;
|
||||
y: number;
|
||||
payload: { value: string };
|
||||
range: string;
|
||||
};
|
||||
|
||||
export const XTick = ({ x, y, payload, range }: XTickProps) => {
|
||||
const format = tickDateFormatLookup(range);
|
||||
const theme = useTheme();
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
return (
|
||||
<Text
|
||||
x={x}
|
||||
y={y + 10}
|
||||
textAnchor="middle"
|
||||
fill={theme.palette.text.secondary}
|
||||
fontSize={11}
|
||||
fontWeight={400}
|
||||
>
|
||||
{formatDateWithTz(payload?.value, format, uiTimezone)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -10,8 +10,9 @@ import { useInviteForm } from "@/Hooks/useInviteForm";
|
||||
import type { InviteFormData } from "@/Validation/invite";
|
||||
import { usePost } from "@/Hooks/UseApi";
|
||||
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||
import { runtimeConfig } from "@/Utils/runtimeConfig";
|
||||
|
||||
const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST;
|
||||
const CLIENT_HOST = runtimeConfig.clientHost || import.meta.env.VITE_APP_CLIENT_HOST;
|
||||
|
||||
interface InviteResponse {
|
||||
token: string;
|
||||
@@ -56,7 +57,7 @@ export const InviteTeamMemberDialog = ({
|
||||
const result = await generateToken("/invite", data);
|
||||
if (result?.data?.token) {
|
||||
const token = result.data.token;
|
||||
const link = CLIENT_HOST ? `${CLIENT_HOST}/register/${token}` : token;
|
||||
const link = `${CLIENT_HOST || window.location.origin}/register/${token}`;
|
||||
setInviteLink(link);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,10 +63,20 @@ const getChartConfigs = (
|
||||
return configs;
|
||||
};
|
||||
|
||||
const hasAnyNetworkTraffic = (checks: HardwareCheckStats[]): boolean =>
|
||||
checks.some((c) =>
|
||||
c.net?.some((iface) => iface.bytesSentPerSecond > 0 || iface.deltaBytesRecv > 0)
|
||||
);
|
||||
// Loopback / bridge names Capture sees when it can only observe the container's
|
||||
// own network namespace. When ALL reported interfaces match this set we assume
|
||||
// the "no real interfaces visible" case and surface the notice.
|
||||
const CONTAINER_ONLY_IFACE_NAMES = new Set(["lo", "lo0", "docker0"]);
|
||||
|
||||
const onlyContainerVisibleInterfaces = (checks: HardwareCheckStats[]): boolean => {
|
||||
const named = new Set<string>();
|
||||
checks.forEach((c) => c.net?.forEach((iface) => named.add(iface.name)));
|
||||
if (named.size === 0) return false;
|
||||
for (const name of named) {
|
||||
if (!CONTAINER_ONLY_IFACE_NAMES.has(name)) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const InfraNetworkCharts = ({
|
||||
checks,
|
||||
@@ -82,13 +92,14 @@ export const InfraNetworkCharts = ({
|
||||
() => getChartConfigs(theme, checks, t),
|
||||
[theme, checks, t]
|
||||
);
|
||||
const showNoTrafficNotice = chartConfigs.length > 0 && !hasAnyNetworkTraffic(checks);
|
||||
const showLoopbackOnlyNotice =
|
||||
chartConfigs.length > 0 && onlyContainerVisibleInterfaces(checks);
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(LAYOUT.XS)}>
|
||||
{showNoTrafficNotice && (
|
||||
{showLoopbackOnlyNotice && (
|
||||
<NoticeBanner severity="warning">
|
||||
{t("pages.infrastructure.charts.labels.netNoTraffic")}
|
||||
{t("pages.infrastructure.charts.labels.loopbackOnlyNotice")}
|
||||
</NoticeBanner>
|
||||
)}
|
||||
<Grid
|
||||
|
||||
@@ -25,10 +25,11 @@ export const InfraDetailsGauges = ({
|
||||
<Stack
|
||||
direction={isSmall ? "column" : "row"}
|
||||
spacing={theme.spacing(8)}
|
||||
alignItems={"center"}
|
||||
alignItems={"stretch"}
|
||||
>
|
||||
<DetailGauge
|
||||
title={t("pages.infrastructure.gauges.memory.title")}
|
||||
maxWidth={260}
|
||||
progress={(snapshot?.memory?.usage_percent || 0) * 100}
|
||||
upperLabel={t("pages.infrastructure.gauges.memory.upperLabel")}
|
||||
upperValue={prettyBytes(snapshot?.memory?.used_bytes || 0)}
|
||||
@@ -37,6 +38,7 @@ export const InfraDetailsGauges = ({
|
||||
/>
|
||||
<DetailGauge
|
||||
title={t("pages.infrastructure.gauges.cpu.title")}
|
||||
maxWidth={260}
|
||||
progress={(snapshot?.cpu?.usage_percent || 0) * 100}
|
||||
upperLabel={t("pages.infrastructure.gauges.cpu.upperLabel")}
|
||||
upperValue={getFrequency(snapshot?.cpu?.current_frequency || 0)}
|
||||
@@ -49,6 +51,7 @@ export const InfraDetailsGauges = ({
|
||||
key={disk?.device || 0 + idx}
|
||||
// title={`Disk ${idx} usage`}
|
||||
title={t("pages.infrastructure.gauges.disk.title", { idx })}
|
||||
maxWidth={260}
|
||||
progress={(disk.usage_percent || 0) * 100}
|
||||
upperLabel={t("pages.infrastructure.gauges.disk.upperLabel")}
|
||||
upperValue={prettyBytes(disk?.used_bytes || 0)}
|
||||
|
||||
@@ -13,13 +13,12 @@ const generateDummyChecks = (): CheckSnapshot[] => {
|
||||
const checks: CheckSnapshot[] = [];
|
||||
for (let i = 0; i < MAX_RECENT_CHECKS; i++) {
|
||||
const isUp = Math.random() > 0.1;
|
||||
const responseTime = Math.floor(Math.random() * 80) + 20;
|
||||
const responseTime = Math.floor(Math.random() * 800) + 20;
|
||||
checks.push({
|
||||
id: `dummy-${i}`,
|
||||
status: isUp,
|
||||
statusCode: isUp ? 200 : 500,
|
||||
responseTime: responseTime,
|
||||
originalResponseTime: responseTime,
|
||||
message: "",
|
||||
createdAt: new Date(Date.now() - i * 60000).toISOString(),
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getStatusPagePublicUrl,
|
||||
isCustomDomainHost,
|
||||
} from "@/Utils/statusPageUrl";
|
||||
import { cssReferencesExternalResource } from "@/Utils/customCss";
|
||||
import { HeaderStatusPageControls } from "@/Pages/StatusPage/Status/Components/HeaderStatusPageControls";
|
||||
import { StatusPageThemeProvider } from "@/Pages/StatusPage/Status/themes/StatusPageThemeProvider";
|
||||
import {
|
||||
@@ -132,13 +133,19 @@ const StatusPageView = () => {
|
||||
// Public route: render directly on the viewport, themed background covers everything.
|
||||
if (isPublic) {
|
||||
const themeConfig = THEME_CONFIGS[resolveStatusPageTheme(statusPage.theme)];
|
||||
const customCss =
|
||||
statusPage.customCSS && !cssReferencesExternalResource(statusPage.customCSS)
|
||||
? statusPage.customCSS
|
||||
: "";
|
||||
return (
|
||||
<StatusPageThemeProvider
|
||||
theme={statusPage.theme}
|
||||
themeMode={statusPage.themeMode}
|
||||
timezone={statusPage.timezone}
|
||||
brandColor={statusPage.color}
|
||||
paintBody
|
||||
>
|
||||
{customCss && <style>{customCss}</style>}
|
||||
<BaseStatusPage
|
||||
statusPage={statusPage}
|
||||
monitors={monitors}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { alpha, darken } from "@mui/material/styles";
|
||||
import {
|
||||
DEFAULT_STATUS_PAGE_THEME,
|
||||
resolveStatusPageTheme,
|
||||
@@ -36,6 +37,15 @@ interface Props {
|
||||
theme?: StatusPageTheme;
|
||||
themeMode?: StatusPageThemeMode;
|
||||
timezone?: string;
|
||||
/**
|
||||
* Brand color picked in the status-page appearance settings. When
|
||||
* provided, it feeds `tokens.brand` / `brandStrong` / `brandSoft`,
|
||||
* which theme styles use for accent surfaces (hero, active tabs,
|
||||
* links). It does NOT recolor the semantic `up`/OK status color, so
|
||||
* a red or orange brand cannot make the "all operational" indicator
|
||||
* signal downtime.
|
||||
*/
|
||||
brandColor?: string;
|
||||
/**
|
||||
* When true, paint the document body with the theme background so it
|
||||
* covers beyond the provider's wrapper (useful on the public route
|
||||
@@ -54,10 +64,32 @@ interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const HEX_COLOR = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
||||
|
||||
// Resolve the brand triple that theme styles read from. A customer's
|
||||
// picked color (when valid) overrides the theme's default; otherwise
|
||||
// the theme's own `brand*` values win, and finally we fall back to the
|
||||
// `up*` triple so unmigrated theme styles have something to consume.
|
||||
const resolveBrandTokens = (
|
||||
tokens: StatusPageThemeTokens,
|
||||
brandColor: string | undefined
|
||||
): StatusPageThemeTokens => {
|
||||
const validOverride = brandColor && HEX_COLOR.test(brandColor) ? brandColor : undefined;
|
||||
const brand = validOverride ?? tokens.brand ?? tokens.up;
|
||||
const brandStrong = validOverride
|
||||
? darken(validOverride, 0.2)
|
||||
: (tokens.brandStrong ?? tokens.upStrong);
|
||||
const brandSoft = validOverride
|
||||
? alpha(validOverride, 0.15)
|
||||
: (tokens.brandSoft ?? tokens.upSoft);
|
||||
return { ...tokens, brand, brandStrong, brandSoft };
|
||||
};
|
||||
|
||||
export const StatusPageThemeProvider = ({
|
||||
theme,
|
||||
themeMode,
|
||||
timezone,
|
||||
brandColor,
|
||||
paintBody = false,
|
||||
transparent = false,
|
||||
children,
|
||||
@@ -109,7 +141,11 @@ export const StatusPageThemeProvider = ({
|
||||
|
||||
const resolvedMode: ResolvedMode =
|
||||
resolvedThemeMode === "auto" ? systemMode : resolvedThemeMode;
|
||||
const tokens = themeTokens[resolvedTheme][resolvedMode];
|
||||
const baseTokens = themeTokens[resolvedTheme][resolvedMode];
|
||||
const tokens = useMemo(
|
||||
() => resolveBrandTokens(baseTokens, brandColor),
|
||||
[baseTokens, brandColor]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -119,7 +119,7 @@ export const refinedStyles = (
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: "8px",
|
||||
background: tokens.up,
|
||||
background: tokens.brand,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
color: "#fff",
|
||||
@@ -191,16 +191,16 @@ export const refinedStyles = (
|
||||
whiteSpace: "nowrap",
|
||||
textAlign: "center",
|
||||
border: 0,
|
||||
background: active ? tokens.upSoft : "transparent",
|
||||
background: active ? tokens.brandSoft : "transparent",
|
||||
fontFamily: "inherit",
|
||||
fontSize: 11,
|
||||
padding: { xs: "5px 6px", md: "5px 14px" },
|
||||
cursor: "pointer",
|
||||
color: active ? tokens.up : tokens.textMuted,
|
||||
color: active ? tokens.brand : tokens.textMuted,
|
||||
borderRadius: "5px",
|
||||
transition: "background 0.15s ease, color 0.15s ease",
|
||||
fontWeight: active ? 600 : 500,
|
||||
"&:hover": { color: active ? tokens.up : tokens.text },
|
||||
"&:hover": { color: active ? tokens.brand : tokens.text },
|
||||
}),
|
||||
|
||||
monitorList: {
|
||||
@@ -259,9 +259,9 @@ export const refinedStyles = (
|
||||
},
|
||||
pillHardware: {
|
||||
...pillBase,
|
||||
color: tokens.up,
|
||||
color: tokens.brand,
|
||||
border: `1px solid ${tokens.border}`,
|
||||
background: tokens.upSoft,
|
||||
background: tokens.brandSoft,
|
||||
},
|
||||
monitorUrl: {
|
||||
fontSize: 12,
|
||||
@@ -382,11 +382,11 @@ export const refinedStyles = (
|
||||
fontSize: 12,
|
||||
mt: "40px",
|
||||
"& a": {
|
||||
color: tokens.up,
|
||||
color: tokens.brand,
|
||||
textDecoration: "underline",
|
||||
textUnderlineOffset: "3px",
|
||||
fontWeight: 600,
|
||||
"&:hover": { color: tokens.upStrong || tokens.up },
|
||||
"&:hover": { color: tokens.brandStrong },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import { useStatusPageTheme } from "@/Pages/StatusPage/Status/themes/StatusPageThemeProvider";
|
||||
import { formatPercentage } from "@/Utils/FormatUtils";
|
||||
|
||||
type StatusPageMonitor = Monitor & { checks?: Monitor["recentChecks"] };
|
||||
type StatusPageMonitor = Monitor;
|
||||
|
||||
export interface BaseStyles {
|
||||
page: SxProps<Theme>;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import prettyMilliseconds from "pretty-ms";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { useStatusPageTheme } from "@/Pages/StatusPage/Status/themes/StatusPageThemeProvider";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const ThemedChartTooltip = ({ check }: { check: CheckSnapshot }) => {
|
||||
const { t } = useTranslation();
|
||||
const { timezone } = useStatusPageTheme();
|
||||
return (
|
||||
<Stack gap={0.25}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontWeight={600}
|
||||
>
|
||||
{check.status
|
||||
? `${prettyMilliseconds(check.responseTime, { compact: true })}`
|
||||
: t("pages.statusPages.monitorsList.chart.downTooltip")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{formatDateWithTz(check.createdAt, "ddd, MMMM D, YYYY, HH:mm A", timezone)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,10 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import type { SxProps, Theme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { MAX_RECENT_CHECKS } from "@/Types/Monitor";
|
||||
import { useStatusPageTheme } from "../StatusPageThemeProvider";
|
||||
import { ThemedChartTooltip } from "@/Pages/StatusPage/Status/themes/shared/ThemedChartTooltip";
|
||||
|
||||
const CELLS = MAX_RECENT_CHECKS;
|
||||
|
||||
@@ -29,7 +26,6 @@ const classify = (check: CheckSnapshot): Exclude<HeatCellKind, "empty"> => {
|
||||
|
||||
export const ThemedHeatmap = ({ checks, containerSx, cellSx }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { timezone } = useStatusPageTheme();
|
||||
|
||||
const source = checks.slice(-CELLS);
|
||||
const padded: (CheckSnapshot | null)[] = [
|
||||
@@ -53,24 +49,7 @@ export const ThemedHeatmap = ({ checks, containerSx, cellSx }: Props) => {
|
||||
);
|
||||
}
|
||||
const kind = classify(check);
|
||||
const tooltipContent = (
|
||||
<Stack gap={0.25}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontWeight={600}
|
||||
>
|
||||
{check.status
|
||||
? `${check.responseTime} ms`
|
||||
: t("pages.statusPages.monitorsList.chart.downTooltip")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{formatDateWithTz(check.createdAt, "ddd, MMMM D, YYYY, HH:mm A", timezone)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
const tooltipContent = <ThemedChartTooltip check={check} />;
|
||||
return (
|
||||
<Tooltip
|
||||
key={check.id ?? i}
|
||||
|
||||
@@ -2,13 +2,12 @@ import { useMemo } from "react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import type { SxProps, Theme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { MAX_RECENT_CHECKS } from "@/Types/Monitor";
|
||||
import { useStatusPageTheme } from "../StatusPageThemeProvider";
|
||||
import { computeBarHeights } from "@/Utils/DataUtils";
|
||||
import { ThemedChartTooltip } from "@/Pages/StatusPage/Status/themes/shared/ThemedChartTooltip";
|
||||
const CELLS = MAX_RECENT_CHECKS;
|
||||
const MIN_HEIGHT_PCT = 6;
|
||||
|
||||
@@ -33,20 +32,20 @@ export const ThemedHistogram = ({
|
||||
statsGap = 1,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { timezone } = useStatusPageTheme();
|
||||
|
||||
const { padded, max, avg, peak } = useMemo(() => {
|
||||
const { padded, heights, avg, peak } = useMemo(() => {
|
||||
const source = checks.slice(-CELLS);
|
||||
const out: (CheckSnapshot | null)[] = [
|
||||
...source,
|
||||
...Array.from({ length: Math.max(0, CELLS - source.length) }, () => null),
|
||||
];
|
||||
const heights = computeBarHeights(source);
|
||||
const valid = out.filter((c): c is CheckSnapshot => c !== null && c.responseTime > 0);
|
||||
const maxRt = valid.length ? Math.max(...valid.map((c) => c.responseTime)) : 1;
|
||||
const avgRt = valid.length
|
||||
? Math.round(valid.reduce((s, c) => s + c.responseTime, 0) / valid.length)
|
||||
: 0;
|
||||
return { padded: out, max: maxRt, avg: avgRt, peak: valid.length ? maxRt : 0 };
|
||||
return { padded: out, heights, avg: avgRt, peak: maxRt };
|
||||
}, [checks]);
|
||||
|
||||
return (
|
||||
@@ -61,32 +60,8 @@ export const ThemedHistogram = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
const height = Math.max(
|
||||
MIN_HEIGHT_PCT,
|
||||
Math.round((check.responseTime / max) * 100)
|
||||
);
|
||||
const tooltipContent = (
|
||||
<Stack gap={0.25}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontWeight={600}
|
||||
>
|
||||
{check.status
|
||||
? `${check.responseTime} ms`
|
||||
: t("pages.statusPages.monitorsList.chart.downTooltip")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
check.createdAt,
|
||||
"ddd, MMMM D, YYYY, HH:mm A",
|
||||
timezone
|
||||
)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
const height = heights[i] ?? MIN_HEIGHT_PCT;
|
||||
const tooltipContent = <ThemedChartTooltip check={check} />;
|
||||
return (
|
||||
<Tooltip
|
||||
key={check.id ?? i}
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface InfraSx {
|
||||
}
|
||||
|
||||
interface Props {
|
||||
monitor: Monitor & { checks?: Monitor["recentChecks"] };
|
||||
monitor: Monitor;
|
||||
sxApi: InfraSx;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const heatLevel = (value: number): GaugeFillLevel =>
|
||||
|
||||
export const ThemedInfrastructure = ({ monitor, sxApi }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const latest = monitor.recentChecks?.[0] ?? monitor.checks?.[0];
|
||||
const latest = monitor.recentChecks?.[0];
|
||||
|
||||
const renderEmpty = () => (
|
||||
<Box sx={sxApi.emptySx}>{t("pages.statusPages.monitorsList.noData")}</Box>
|
||||
|
||||
@@ -9,6 +9,14 @@ export interface StatusPageThemeTokens {
|
||||
up: string;
|
||||
upStrong: string;
|
||||
upSoft: string;
|
||||
// Accent used for brand-tinted surfaces (hero background, active tab
|
||||
// underline, "view all" links). Distinct from `up` so a red/orange
|
||||
// brand color cannot recolor the semantic OK/up status indicators.
|
||||
// Themes leave these undefined by default; the provider falls back
|
||||
// to `up`/`upStrong`/`upSoft` so styles have a value to consume.
|
||||
brand?: string;
|
||||
brandStrong?: string;
|
||||
brandSoft?: string;
|
||||
degraded: string;
|
||||
degradedSoft: string;
|
||||
down: string;
|
||||
|
||||
@@ -57,7 +57,6 @@ const UptimeDetailsPage = () => {
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
params.append("dateRange", dateRange);
|
||||
params.append("normalize", "true");
|
||||
return `/monitors/uptime/details/${monitorId}?${params.toString()}`;
|
||||
}, [monitorId, dateRange]);
|
||||
|
||||
@@ -72,6 +71,7 @@ const UptimeDetailsPage = () => {
|
||||
);
|
||||
|
||||
const monitorData = monitorDetailsData?.monitorData;
|
||||
|
||||
const monitor = monitorData?.monitor;
|
||||
const monitorStats = monitorDetailsData?.monitorStats ?? null;
|
||||
|
||||
@@ -165,10 +165,18 @@ const UptimeDetailsPage = () => {
|
||||
{ keepPreviousData: true, revalidateOnFocus: false }
|
||||
);
|
||||
|
||||
if (!monitorData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!monitor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const geoChecksForTable = geoChecksTableData?.geoChecks ?? [];
|
||||
const geoChecksCount = geoChecksTableData?.geoChecksCount ?? 0;
|
||||
|
||||
const geoLocations = monitor?.geoCheckLocations;
|
||||
const geoLocations = monitor.geoCheckLocations;
|
||||
|
||||
const checks = checksData?.checks ?? [];
|
||||
const checksCount = checksData?.checksCount ?? 0;
|
||||
@@ -215,7 +223,7 @@ const UptimeDetailsPage = () => {
|
||||
/>
|
||||
</Stack>
|
||||
<HistogramDetails
|
||||
checks={monitorData?.groupedChecks || []}
|
||||
checks={monitorData.groupedChecks}
|
||||
range={dateRange}
|
||||
/>
|
||||
<ChecksTable
|
||||
|
||||
+10
-12
@@ -149,6 +149,15 @@ export interface GroupedCheck {
|
||||
totalChecks: number;
|
||||
}
|
||||
|
||||
export interface GroupedUptimeCheck extends GroupedCheck {
|
||||
avgDns: number;
|
||||
avgTcp: number;
|
||||
avgTls: number;
|
||||
avgRequest: number;
|
||||
avgFirstByte: number;
|
||||
avgDownload: number;
|
||||
}
|
||||
|
||||
export interface PageSpeedGroupedCheck {
|
||||
bucketDate: string;
|
||||
performance: number;
|
||||
@@ -241,18 +250,7 @@ export interface ChecksSummary {
|
||||
downChecks: number;
|
||||
}
|
||||
|
||||
export type CheckSnapshot = Omit<Check, "metadata" | "__v" | "updatedAt"> & {
|
||||
originalResponseTime: number;
|
||||
};
|
||||
|
||||
export type CheckSnapshot = Omit<Check, "metadata" | "__v" | "updatedAt">;
|
||||
export interface HasResponseTime {
|
||||
responseTime: number;
|
||||
}
|
||||
|
||||
export type NormalizedCheck<T extends HasResponseTime = Check> = T & {
|
||||
originalResponseTime: number;
|
||||
};
|
||||
|
||||
export type NormalizedUptimeCheck<T extends GroupedCheck = GroupedCheck> = T & {
|
||||
originalAvgResponseTime: number;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GroupedCheck, CheckSnapshot } from "@/Types/Check";
|
||||
import type { GroupedCheck, CheckSnapshot, GroupedUptimeCheck } from "@/Types/Check";
|
||||
import type { PageSpeedGroupedCheck } from "@/Types/Check";
|
||||
import type { GeoContinent } from "@/Types/GeoCheck";
|
||||
export type { GeoContinent } from "@/Types/GeoCheck";
|
||||
@@ -137,7 +137,7 @@ export interface MonitorStats {
|
||||
|
||||
export interface MonitorData {
|
||||
monitor: Monitor;
|
||||
groupedChecks: GroupedCheck[];
|
||||
groupedChecks: GroupedUptimeCheck[];
|
||||
groupedUpChecks: GroupedCheck[];
|
||||
groupedDownChecks: GroupedCheck[];
|
||||
groupedAvgResponseTime: number;
|
||||
|
||||
Vendored
+2
-1
@@ -3,7 +3,8 @@
|
||||
declare const __APP_VERSION__: string;
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_API_V2_BASE_URL?: string;
|
||||
readonly VITE_APP_API_BASE_URL?: string;
|
||||
readonly VITE_APP_CLIENT_HOST?: string;
|
||||
readonly VITE_APP_LOG_LEVEL?: "debug" | "info" | "warn" | "error";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import axios from "axios";
|
||||
import type { AxiosError } from "axios";
|
||||
import type { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import { runtimeConfig } from "@/Utils/runtimeConfig";
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
|
||||
const BASE_URL =
|
||||
runtimeConfig.apiBaseUrl || import.meta.env.VITE_APP_API_BASE_URL || "/api/v1";
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
|
||||
@@ -1,47 +1,3 @@
|
||||
const MIN_OUT = 10;
|
||||
const MAX_OUT = 100;
|
||||
|
||||
export const normalizeResponseTimes = <T, K extends keyof T>(
|
||||
checks: T[],
|
||||
key: K
|
||||
): (T & { normalResponseTime: number })[] => {
|
||||
if (!Array.isArray(checks) || checks.length === 0)
|
||||
return checks as (T & {
|
||||
normalResponseTime: number;
|
||||
})[];
|
||||
|
||||
if (checks.length === 1) {
|
||||
return [
|
||||
{
|
||||
...checks[0],
|
||||
normalResponseTime: 50,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const getVal = (check: T): number => {
|
||||
const v = check[key] as unknown;
|
||||
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
||||
};
|
||||
|
||||
const { min, max } = checks.reduce(
|
||||
(acc, check) => {
|
||||
const v = getVal(check);
|
||||
if (v > acc.max) acc.max = v;
|
||||
if (v < acc.min) acc.min = v;
|
||||
return acc;
|
||||
},
|
||||
{ max: -Infinity, min: Infinity }
|
||||
);
|
||||
|
||||
const range = max - min || 1;
|
||||
|
||||
return checks.map((check) => ({
|
||||
...check,
|
||||
normalResponseTime: MIN_OUT + ((getVal(check) - min) * (MAX_OUT - MIN_OUT)) / range,
|
||||
}));
|
||||
};
|
||||
|
||||
// Interpolate color between three theme colors over 0-100 range.
|
||||
// 0-50 => start (success.light), 50-75 => mid (warning.light), 75-100 => end (error.light)
|
||||
export const getResponseColor = (
|
||||
@@ -129,3 +85,32 @@ export const getResponseColor = (
|
||||
}
|
||||
return normalizeToHex(safe.end);
|
||||
};
|
||||
|
||||
const MIN_HEIGHT_PCT = 8;
|
||||
const CAP_PERCENTILE = 95;
|
||||
|
||||
const percentile = (sortedValues: number[], p: number): number => {
|
||||
const index = (p / 100) * (sortedValues.length - 1);
|
||||
const lower = Math.floor(index);
|
||||
const upper = lower + 1;
|
||||
const weight = index % 1;
|
||||
if (upper >= sortedValues.length) return sortedValues[lower];
|
||||
return sortedValues[lower] * (1 - weight) + sortedValues[upper] * weight;
|
||||
};
|
||||
|
||||
export const computeBarHeights = (
|
||||
checks: { responseTime?: number | null }[]
|
||||
): number[] => {
|
||||
const values = checks.map((check) => {
|
||||
return typeof check.responseTime === "number" && Number.isFinite(check.responseTime)
|
||||
? Math.max(0, check.responseTime)
|
||||
: 0;
|
||||
});
|
||||
|
||||
const sorted = values.slice().sort((a, b) => a - b);
|
||||
const cap = percentile(sorted, CAP_PERCENTILE);
|
||||
if (cap <= 0) return values.map(() => MIN_HEIGHT_PCT);
|
||||
return values.map((value) => {
|
||||
return Math.max(MIN_HEIGHT_PCT, Math.min(100, (value / cap) * 100));
|
||||
});
|
||||
};
|
||||
@@ -20,8 +20,19 @@ export const colors = {
|
||||
brandGreen: "#13715B",
|
||||
brandGreenLight: "#4DAF94",
|
||||
green200: "#ECF7F2",
|
||||
green700: "#008300",
|
||||
yellow200: "#FFF4E5",
|
||||
yellow500: "#EDA100",
|
||||
yellow600: "#C98500",
|
||||
orange500: "#FFA500",
|
||||
orange600: "#EB6834",
|
||||
orange700: "#D95926",
|
||||
blue400: "#3987E5",
|
||||
blue500: "#2A78D6",
|
||||
aqua500: "#1BAF7A",
|
||||
aqua600: "#199E70",
|
||||
magenta300: "#E87BA4",
|
||||
magenta400: "#D55181",
|
||||
};
|
||||
|
||||
export const lightPalette = {
|
||||
@@ -38,6 +49,16 @@ export const lightPalette = {
|
||||
running: colors.green200,
|
||||
paused: colors.yellow200,
|
||||
},
|
||||
chart: {
|
||||
phases: {
|
||||
dns: colors.magenta300,
|
||||
tcp: colors.orange600,
|
||||
tls: colors.aqua500,
|
||||
request: colors.yellow500,
|
||||
firstByte: colors.blue500,
|
||||
download: colors.green700,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const darkPalette = {
|
||||
@@ -58,4 +79,14 @@ export const darkPalette = {
|
||||
running: alpha(colors.brandGreen, 0.18),
|
||||
paused: alpha(colors.orange500, 0.18),
|
||||
},
|
||||
chart: {
|
||||
phases: {
|
||||
dns: colors.magenta400,
|
||||
tcp: colors.orange700,
|
||||
tls: colors.aqua600,
|
||||
request: colors.yellow600,
|
||||
firstByte: colors.blue500,
|
||||
download: colors.green700,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -16,10 +16,30 @@ declare module "@mui/material/styles" {
|
||||
interface Palette {
|
||||
sidebar: { accent: string };
|
||||
rowStatus: { running: string; paused: string };
|
||||
chart: {
|
||||
phases: {
|
||||
dns: string;
|
||||
tcp: string;
|
||||
tls: string;
|
||||
request: string;
|
||||
firstByte: string;
|
||||
download: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
interface PaletteOptions {
|
||||
sidebar?: { accent: string };
|
||||
rowStatus?: { running: string; paused: string };
|
||||
chart?: {
|
||||
phases: {
|
||||
dns: string;
|
||||
tcp: string;
|
||||
tls: string;
|
||||
request: string;
|
||||
firstByte: string;
|
||||
download: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LogLevel } from "@/Types/Log";
|
||||
import { runtimeConfig } from "@/Utils/runtimeConfig";
|
||||
|
||||
interface LogContext {
|
||||
[key: string]: any;
|
||||
@@ -25,7 +26,8 @@ interface ErrorLogData {
|
||||
userAgent: string;
|
||||
}
|
||||
|
||||
const configuredLevel: LogLevel = import.meta.env.VITE_APP_LOG_LEVEL || "error";
|
||||
const configuredLevel: LogLevel =
|
||||
runtimeConfig.logLevel || import.meta.env.VITE_APP_LOG_LEVEL || "error";
|
||||
const configuredPriority = LOG_LEVEL_PRIORITY[configuredLevel];
|
||||
|
||||
const shouldLog = (level: LogLevel): boolean => {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { LogLevel } from "@/Types/Log";
|
||||
|
||||
// Served by GET /config.js (rendered from server env) or a static config.js
|
||||
// next to index.html; loaded before the bundle in index.html.
|
||||
export interface RuntimeConfig {
|
||||
apiBaseUrl?: string;
|
||||
clientHost?: string;
|
||||
logLevel?: LogLevel;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__CHECKMATE_CONFIG__?: RuntimeConfig;
|
||||
}
|
||||
}
|
||||
|
||||
export const runtimeConfig: RuntimeConfig =
|
||||
(typeof window !== "undefined" && window.__CHECKMATE_CONFIG__) || {};
|
||||
@@ -1,6 +1,9 @@
|
||||
import { PUBLIC_STATUS_PAGE_PREFIX, type StatusPage } from "@/Types/StatusPage";
|
||||
import { runtimeConfig } from "@/Utils/runtimeConfig";
|
||||
|
||||
const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST as string | undefined;
|
||||
const CLIENT_HOST =
|
||||
runtimeConfig.clientHost ||
|
||||
(import.meta.env.VITE_APP_CLIENT_HOST as string | undefined);
|
||||
|
||||
const getClientOriginProtocol = (): string => {
|
||||
const source =
|
||||
|
||||
@@ -825,7 +825,7 @@
|
||||
"netBytesRecv": "{{name}} - Empfangene Bytes",
|
||||
"netBytesSent": "{{name}} - Gesendete Bytes",
|
||||
"netNoTraffic": "Capture meldet null Datenverkehr auf allen Schnittstellen. Das ist normal, wenn Capture in einem Container läuft, der nur Loopback-Schnittstellen sieht — führe Capture direkt auf dem Host aus, um echten Netzwerkverkehr zu sehen.",
|
||||
"temp": "Temp."
|
||||
"temp": "CPU-Temperatur"
|
||||
}
|
||||
},
|
||||
"gauges": {
|
||||
@@ -841,13 +841,13 @@
|
||||
},
|
||||
"memory": {
|
||||
"lowerLabel": "Frei",
|
||||
"title": "Arbeitsspeicherauslastung",
|
||||
"title": "Speicherauslastung",
|
||||
"upperLabel": "Belegt"
|
||||
}
|
||||
},
|
||||
"statBoxes": {
|
||||
"avgCpuTemperature": "Durchschn. CPU Termperatur",
|
||||
"cpuFrequency": "CPU Frequenz",
|
||||
"avgCpuTemperature": "Durchschn. CPU-Temperatur",
|
||||
"cpuFrequency": "CPU-Frequenz",
|
||||
"cpuLogical": "CPU (logisch)",
|
||||
"cpuPhysical": "CPU (physisch)",
|
||||
"disk": "Festplatte",
|
||||
|
||||
@@ -68,11 +68,24 @@
|
||||
"downtime": "Downtime",
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"responseTime": "Response time",
|
||||
"timingPhases": "Timing phases",
|
||||
"totalResponseTime": "Total response time",
|
||||
"uptime": "Uptime"
|
||||
},
|
||||
"histogram": {
|
||||
"avg": "Avg: {{value}} ms",
|
||||
"max": "Max: {{value}} ms"
|
||||
"max": "Max: {{value}} ms",
|
||||
"responseTime": "Response time: {{value}}",
|
||||
"total": "Total: {{value}}"
|
||||
},
|
||||
"phases": {
|
||||
"dns": "DNS",
|
||||
"tcp": "TCP",
|
||||
"tls": "TLS",
|
||||
"request": "Request",
|
||||
"firstByte": "First byte",
|
||||
"download": "Download"
|
||||
}
|
||||
},
|
||||
"dialogs": {
|
||||
@@ -844,8 +857,8 @@
|
||||
"memory": "Memory usage",
|
||||
"netBytesRecv": "{{name}} - Bytes Received",
|
||||
"netBytesSent": "{{name}} - Bytes Sent",
|
||||
"netNoTraffic": "Capture is reporting zero traffic on every interface. This is normal when Capture runs inside a container that can only see loopback interfaces — run Capture directly on the host to see real network activity.",
|
||||
"temp": "Temp"
|
||||
"loopbackOnlyNotice": "Capture is only reporting loopback or container-bridge interfaces (lo, lo0, docker0). This is normal when Capture runs inside a container that can only see its own network namespace — run Capture directly on the host to see real network activity.",
|
||||
"temp": "CPU temperature"
|
||||
}
|
||||
},
|
||||
"gauges": {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -4,7 +4,7 @@
|
||||
# Build on the host arch (BUILDPLATFORM) rather than under QEMU — the output is
|
||||
# pure JS so it's arch-independent, and this avoids the esbuild host/native
|
||||
# binary version mismatch under emulation.
|
||||
FROM --platform=$BUILDPLATFORM node:20-slim AS frontend-build
|
||||
FROM --platform=$BUILDPLATFORM node:22-slim AS frontend-build
|
||||
|
||||
WORKDIR /app/client
|
||||
|
||||
@@ -19,7 +19,7 @@ RUN npm run build
|
||||
# ---------------------
|
||||
# Backend stage
|
||||
# ---------------------
|
||||
FROM node:22-slim AS backend
|
||||
FROM node:22-slim AS app
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
@@ -28,23 +28,18 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /app/server
|
||||
|
||||
COPY server/package.json ./
|
||||
COPY server/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
RUN npm ci
|
||||
|
||||
COPY server ./
|
||||
|
||||
RUN chmod +x ./scripts/inject-vars.sh
|
||||
|
||||
RUN npm run build
|
||||
|
||||
COPY --from=frontend-build /app/client/dist ./public
|
||||
|
||||
RUN chown -R node:node ./public \
|
||||
&& chown -R node:node ./dist \
|
||||
&& chown -R node:node ./scripts
|
||||
USER node
|
||||
|
||||
EXPOSE 52345 52346
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./dist/index.js"]
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -1,33 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Change directory to root Server directory for correct Docker Context
|
||||
cd /d "%~dp0"
|
||||
cd ../..
|
||||
|
||||
REM Define service names and their corresponding Dockerfiles
|
||||
set services[0]=bluewaveuptime/uptime_client
|
||||
set dockerfiles[0]=.\docker\coolify\client.Dockerfile
|
||||
|
||||
set services[1]=bluewaveuptime/uptime_database_mongo
|
||||
set dockerfiles[1]=.\docker\coolify\mongoDB.Dockerfile
|
||||
|
||||
set services[2]=bluewaveuptime/uptime_server
|
||||
set dockerfiles[2]=.\docker\coolify\server.Dockerfile
|
||||
|
||||
REM Loop through each service and build the corresponding image
|
||||
for /L %%i in (0,1,2) do (
|
||||
set service=!services[%%i]!
|
||||
set dockerfile=!dockerfiles[%%i]!
|
||||
|
||||
docker build -f "!dockerfile!" -t "!service!" .
|
||||
|
||||
REM Check if the build succeeded
|
||||
if errorlevel 1 (
|
||||
echo Error building !service! image. Exiting...
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo All images built successfully
|
||||
exit /b 0
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Change directory to root Server directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../../
|
||||
|
||||
# Define an array of services and their Dockerfiles
|
||||
declare -A services=(
|
||||
["bluewaveuptime/uptime_client"]="./docker/coolify/client.Dockerfile"
|
||||
["bluewaveuptime/uptime_database_mongo"]="./docker/coolify/mongoDB.Dockerfile"
|
||||
["bluewaveuptime/uptime_server"]="./docker/coolify/server.Dockerfile"
|
||||
)
|
||||
|
||||
# Loop through each service and build the corresponding image
|
||||
for service in "${!services[@]}"; do
|
||||
docker build -f "${services[$service]}" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All images built successfully"
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM node:20-slim AS build
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./client/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./client .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27.1-alpine
|
||||
|
||||
COPY ./server/docker/dist/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh
|
||||
RUN chmod +x /docker-entrypoint.d/env.sh
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM mongo
|
||||
EXPOSE 27017
|
||||
CMD ["mongod"]
|
||||
@@ -1,35 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name checkmate-demo.bluewavelabs.ca;
|
||||
server_tokens off;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://server:5000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api-docs/ {
|
||||
proxy_pass http://server:5000/api-docs/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM node:22-slim
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ../../package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ../../ ./
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -1,33 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Change directory to root Server directory for correct Docker Context
|
||||
cd /d "%~dp0"
|
||||
cd ../..
|
||||
|
||||
REM Define service names and their corresponding Dockerfiles
|
||||
set services[0]=uptime_client
|
||||
set dockerfiles[0]=.\docker\dev\client.Dockerfile
|
||||
|
||||
set services[1]=uptime_mongo
|
||||
set dockerfiles[1]=.\docker\dev\mongoDB.Dockerfile
|
||||
|
||||
set services[2]=uptime_server
|
||||
set dockerfiles[2]=.\docker\dev\server.Dockerfile
|
||||
|
||||
REM Loop through each service and build the corresponding image
|
||||
for /L %%i in (0,1,2) do (
|
||||
set service=!services[%%i]!
|
||||
set dockerfile=!dockerfiles[%%i]!
|
||||
|
||||
docker build -f "!dockerfile!" -t "!service!" .
|
||||
|
||||
REM Check if the build succeeded
|
||||
if errorlevel 1 (
|
||||
echo Error building !service! image. Exiting...
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo All images built successfully
|
||||
exit /b 0
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Change directory to root Server directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../..
|
||||
|
||||
# Define service names and their corresponding Dockerfiles in parallel arrays
|
||||
services=("uptime_client" "uptime_mongo" "uptime_server")
|
||||
dockerfiles=(
|
||||
"./docker/dev/client.Dockerfile"
|
||||
"./docker/dev/mongoDB.Dockerfile"
|
||||
"./docker/dev/server.Dockerfile"
|
||||
)
|
||||
|
||||
# Loop through each service and build the corresponding image
|
||||
for i in "${!services[@]}"; do
|
||||
service="${services[$i]}"
|
||||
dockerfile="${dockerfiles[$i]}"
|
||||
|
||||
docker build -f "$dockerfile" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All images built successfully"
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM node:20-slim AS build
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./client/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./client ./
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27.1-alpine
|
||||
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh
|
||||
RUN chmod +x /docker-entrypoint.d/env.sh
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,29 +1,14 @@
|
||||
# Dev/Test only: Not required in production
|
||||
# Example Docker Compose configuration for custom CA trust
|
||||
# This file demonstrates how to configure Checkmate to trust custom CAs
|
||||
#
|
||||
# Usage: docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up
|
||||
#
|
||||
# Usage: docker compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up
|
||||
|
||||
services:
|
||||
server:
|
||||
image: uptime_server:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
env_file:
|
||||
- server.env
|
||||
checkmate:
|
||||
environment:
|
||||
# Mount your custom CA certificate
|
||||
NODE_EXTRA_CA_CERTS: /certs/custom-ca.pem
|
||||
volumes:
|
||||
# Mount the certs directory (read-only for security)
|
||||
- ./certs:/certs:ro
|
||||
depends_on:
|
||||
- mongodb
|
||||
# Optional: Add healthcheck to ensure the server starts properly
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:52345/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
@@ -1,27 +1,20 @@
|
||||
services:
|
||||
client:
|
||||
image: uptime_client:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
environment:
|
||||
UPTIME_APP_API_BASE_URL: "http://localhost:52345/api/v1"
|
||||
UPTIME_APP_CLIENT_HOST: "http://localhost"
|
||||
volumes:
|
||||
- ./nginx/conf.d:/etc/nginx/conf.d/
|
||||
depends_on:
|
||||
- server
|
||||
server:
|
||||
image: uptime_server:latest
|
||||
checkmate:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/Dockerfile
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
env_file:
|
||||
- server.env
|
||||
depends_on:
|
||||
- mongodb
|
||||
mongodb:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: uptime_server:latest
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/Dockerfile
|
||||
restart: always
|
||||
ports:
|
||||
- "52346:52346"
|
||||
@@ -43,9 +36,10 @@ services:
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
depends_on:
|
||||
- mongodb
|
||||
mongodb:
|
||||
condition: service_healthy
|
||||
mongodb:
|
||||
image: uptime_mongo:latest
|
||||
image: mongo:8.0
|
||||
restart: always
|
||||
ports:
|
||||
- "27017:27017"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM mongo
|
||||
EXPOSE 27017
|
||||
CMD ["mongod"]
|
||||
@@ -1,35 +0,0 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
server_name localhost; # Set server name to localhost
|
||||
server_tokens off;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# location /api/ {
|
||||
# proxy_pass http://server:52345/api/;
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header Connection '';
|
||||
# chunked_transfer_encoding off;
|
||||
# proxy_buffering off;
|
||||
# proxy_cache off;
|
||||
# }
|
||||
|
||||
# location /api-docs/ {
|
||||
# proxy_pass http://server:52345/api-docs/;
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# }
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM node:22-slim
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./server/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./server/ ./
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Change directory to root Server directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../..
|
||||
|
||||
# Ensure buildx builder exists and is used
|
||||
if ! docker buildx inspect mybuilder &>/dev/null; then
|
||||
docker buildx create --name mybuilder --use
|
||||
else
|
||||
docker buildx use mybuilder
|
||||
fi
|
||||
|
||||
# Define service names and Dockerfiles
|
||||
services=("mono_server_arm")
|
||||
dockerfiles=(
|
||||
"./docker/dist-arm/server.Dockerfile"
|
||||
)
|
||||
|
||||
# Static image name for GitHub Container Registry (GHCR)
|
||||
image="ghcr.io/bluewave-labs/checkmate:backend-dist-multi-arch"
|
||||
platforms="linux/amd64,linux/arm64"
|
||||
repo_url="https://github.com/bluewave-labs/checkmate"
|
||||
|
||||
# Loop through each service and build
|
||||
for i in "${!services[@]}"; do
|
||||
service="${services[$i]}"
|
||||
dockerfile="${dockerfiles[$i]}"
|
||||
|
||||
echo "🚀 Building multi-arch image for $service..."
|
||||
docker buildx build \
|
||||
--platform "$platforms" \
|
||||
-f "$dockerfile" \
|
||||
-t "$image" \
|
||||
--label "org.opencontainers.image.source=$repo_url" \
|
||||
--push \
|
||||
.
|
||||
|
||||
echo "✅ $image pushed for platforms: $platforms"
|
||||
done
|
||||
|
||||
echo "🎉 All multi-arch images built and pushed successfully"
|
||||
@@ -1,28 +0,0 @@
|
||||
services:
|
||||
server:
|
||||
image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
|
||||
restart: always
|
||||
pull_policy: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
environment:
|
||||
- UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1
|
||||
- UPTIME_APP_CLIENT_HOST=http://localhost
|
||||
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
|
||||
- CLIENT_HOST=http://localhost
|
||||
- JWT_SECRET=my_secret
|
||||
depends_on:
|
||||
- mongodb
|
||||
mongodb:
|
||||
image: mongo:6.0
|
||||
restart: always
|
||||
command: ["mongod", "--quiet", "--bind_ip_all"]
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.runCommand({ ping: 1 })", "--quiet"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
start_interval: 1s
|
||||
retries: 30
|
||||
@@ -1,30 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Change directory to root Server directory for correct Docker Context
|
||||
cd /d "%~dp0"
|
||||
cd ../..
|
||||
|
||||
REM Define service names and their corresponding Dockerfiles
|
||||
set services[0]=uptime_database_mongo
|
||||
set dockerfiles[0]=.\docker\dist-mono\mongoDB.Dockerfile
|
||||
|
||||
set services[1]=uptime_server
|
||||
set dockerfiles[1]=.\docker\dist-mono\server.Dockerfile
|
||||
|
||||
REM Loop through each service and build the corresponding image
|
||||
for /L %%i in (0,1) do (
|
||||
set service=!services[%%i]!
|
||||
set dockerfile=!dockerfiles[%%i]!
|
||||
|
||||
docker build -f "!dockerfile!" -t "!service!" .
|
||||
|
||||
REM Check if the build succeeded
|
||||
if errorlevel 1 (
|
||||
echo Error building !service! image. Exiting...
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo All images built successfully
|
||||
exit /b 0
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Change directory to root Server directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../..
|
||||
|
||||
# Define service names and their corresponding Dockerfiles in parallel arrays
|
||||
services=("mono_mongo" "mono_server")
|
||||
dockerfiles=(
|
||||
"./docker/dist-mono/mongoDB.Dockerfile"
|
||||
"./docker/dist-mono/server.Dockerfile"
|
||||
)
|
||||
|
||||
# Loop through each service and build the corresponding image
|
||||
for i in "${!services[@]}"; do
|
||||
service="${services[$i]}"
|
||||
dockerfile="${dockerfiles[$i]}"
|
||||
|
||||
docker build -f "$dockerfile" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All images built successfully"
|
||||
@@ -1,27 +0,0 @@
|
||||
services:
|
||||
server:
|
||||
image: mono_server
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
environment:
|
||||
- UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1
|
||||
- UPTIME_APP_CLIENT_HOST=http://localhost
|
||||
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
|
||||
- CLIENT_HOST=http://localhost
|
||||
- JWT_SECRET=my_secret
|
||||
depends_on:
|
||||
- mongodb
|
||||
mongodb:
|
||||
image: mono_mongo
|
||||
restart: always
|
||||
command: ["mongod", "--quiet", "--bind_ip_all"]
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
start_interval: 1s
|
||||
retries: 30
|
||||
@@ -1,28 +0,0 @@
|
||||
services:
|
||||
server:
|
||||
image: ghcr.io/bluewave-labs/checkmate-backend-mono:latest
|
||||
pull_policy: always
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
environment:
|
||||
- UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1
|
||||
- UPTIME_APP_CLIENT_HOST=http://localhost
|
||||
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
|
||||
- CLIENT_HOST=http://localhost
|
||||
- JWT_SECRET=my_secret
|
||||
depends_on:
|
||||
- mongodb
|
||||
mongodb:
|
||||
image: ghcr.io/bluewave-labs/checkmate-mongo:latest
|
||||
restart: always
|
||||
command: ["mongod", "--quiet", "--bind_ip_all"]
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
start_interval: 1s
|
||||
retries: 30
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM mongo
|
||||
EXPOSE 27017
|
||||
CMD ["mongod"]
|
||||
@@ -1,37 +0,0 @@
|
||||
FROM node:22-slim AS frontend-build
|
||||
|
||||
WORKDIR /app/client
|
||||
|
||||
COPY client/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY client ./
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-slim AS app
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app/server
|
||||
|
||||
COPY server ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
RUN npm run build
|
||||
RUN cp -r src/templates dist/templates
|
||||
|
||||
COPY --from=frontend-build /app/client/dist ./public
|
||||
|
||||
RUN chmod +x ./scripts/inject-vars.sh \
|
||||
&& chown -R node:node ./public \
|
||||
&& chown -R node:node ./dist \
|
||||
&& chown -R node:node ./scripts
|
||||
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./dist/index.js"]
|
||||
Vendored
-33
@@ -1,33 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Change directory to root Server directory for correct Docker Context
|
||||
cd /d "%~dp0"
|
||||
cd ../..
|
||||
|
||||
REM Define service names and their corresponding Dockerfiles
|
||||
set services[0]=bluewaveuptime/uptime_client
|
||||
set dockerfiles[0]=.\docker\dist\client.Dockerfile
|
||||
|
||||
set services[1]=bluewaveuptime/uptime_database_mongo
|
||||
set dockerfiles[1]=.\docker\dist\mongoDB.Dockerfile
|
||||
|
||||
set services[2]=bluewaveuptime/uptime_server
|
||||
set dockerfiles[2]=.\docker\dist\server.Dockerfile
|
||||
|
||||
REM Loop through each service and build the corresponding image
|
||||
for /L %%i in (0,1,2) do (
|
||||
set service=!services[%%i]!
|
||||
set dockerfile=!dockerfiles[%%i]!
|
||||
|
||||
docker build -f "!dockerfile!" -t "!service!" .
|
||||
|
||||
REM Check if the build succeeded
|
||||
if errorlevel 1 (
|
||||
echo Error building !service! image. Exiting...
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo All images built successfully
|
||||
exit /b 0
|
||||
Vendored
-25
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Change directory to root Server directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../../
|
||||
|
||||
# Define an array of services and their Dockerfiles
|
||||
declare -A services=(
|
||||
["bluewaveuptime/uptime_client"]="./docker/dist/client.Dockerfile"
|
||||
["bluewaveuptime/uptime_database_mongo"]="./docker/dist/mongoDB.Dockerfile"
|
||||
["bluewaveuptime/uptime_server"]="./docker/dist/server.Dockerfile"
|
||||
)
|
||||
|
||||
for service in "${!services[@]}"; do
|
||||
docker build \
|
||||
-f "${services[$service]}" \
|
||||
-t "$service" \
|
||||
.
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All images built successfully"
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
FROM node:20-slim AS build
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./client/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./client .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27.1-alpine
|
||||
|
||||
COPY ./docker/dist/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh
|
||||
RUN chmod +x /docker-entrypoint.d/env.sh
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
Vendored
-38
@@ -1,38 +0,0 @@
|
||||
services:
|
||||
client:
|
||||
image: ghcr.io/bluewave-labs/checkmate-client:latest
|
||||
restart: always
|
||||
environment:
|
||||
UPTIME_APP_API_BASE_URL: "http://localhost:52345/api/v1"
|
||||
UPTIME_APP_CLIENT_HOST: "http://localhost"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
depends_on:
|
||||
- server
|
||||
server:
|
||||
image: ghcr.io/bluewave-labs/checkmate-backend:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
depends_on:
|
||||
- mongodb
|
||||
environment:
|
||||
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
|
||||
- CLIENT_HOST=http://localhost
|
||||
- JWT_SECRET=my_secret
|
||||
# volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
mongodb:
|
||||
image: ghcr.io/bluewave-labs/checkmate-mongo:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
command: ["mongod", "--quiet", "--bind_ip_all"]
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
start_interval: 1s
|
||||
retries: 30
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
FROM mongo
|
||||
EXPOSE 27017
|
||||
CMD ["mongod"]
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name checkmate-demo.bluewavelabs.ca;
|
||||
server_tokens off;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# location /api/ {
|
||||
# proxy_pass http://server:5000/api/;
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# }
|
||||
|
||||
location /api-docs/ {
|
||||
proxy_pass http://server:5000/api-docs/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
Vendored
-20
@@ -1,20 +0,0 @@
|
||||
FROM node:22-slim
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./server/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./server/ ./
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -0,0 +1,45 @@
|
||||
services:
|
||||
checkmate:
|
||||
image: ghcr.io/bluewave-labs/checkmate:latest
|
||||
pull_policy: always
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
environment:
|
||||
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
|
||||
- CLIENT_HOST=${CLIENT_HOST:-http://localhost:52345}
|
||||
- JWT_SECRET=${JWT_SECRET:?set JWT_SECRET in your environment}
|
||||
stop_grace_period: 60s
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"require('http').get('http://localhost:52346/livez',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))",
|
||||
]
|
||||
interval: 15s
|
||||
timeout: 3s
|
||||
start_period: 60s
|
||||
start_interval: 2s
|
||||
retries: 3
|
||||
depends_on:
|
||||
mongodb:
|
||||
condition: service_healthy
|
||||
|
||||
mongodb:
|
||||
image: mongo:8.0
|
||||
restart: always
|
||||
command: ["mongod", "--quiet", "--bind_ip_all"]
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
start_interval: 1s
|
||||
retries: 30
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
@@ -1,33 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Change directory to root Server directory for correct Docker Context
|
||||
cd /d "%~dp0"
|
||||
cd ../..
|
||||
|
||||
REM Define service names and their corresponding Dockerfiles
|
||||
set services[0]=uptime_client
|
||||
set dockerfiles[0]=.\docker\prod\client.Dockerfile
|
||||
|
||||
set services[1]=uptime_database_mongo
|
||||
set dockerfiles[1]=.\docker\prod\mongoDB.Dockerfile
|
||||
|
||||
set services[2]=uptime_server
|
||||
set dockerfiles[2]=.\docker\prod\server.Dockerfile
|
||||
|
||||
REM Loop through each service and build the corresponding image
|
||||
for /L %%i in (0,1,2) do (
|
||||
set service=!services[%%i]!
|
||||
set dockerfile=!dockerfiles[%%i]!
|
||||
|
||||
docker build -f "!dockerfile!" -t "!service!" .
|
||||
|
||||
REM Check if the build succeeded
|
||||
if errorlevel 1 (
|
||||
echo Error building !service! image. Exiting...
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo All images built successfully
|
||||
exit /b 0
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Change directory to root directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../../
|
||||
|
||||
# Define an array of services and their Dockerfiles
|
||||
declare -A services=(
|
||||
["uptime_client"]="./docker/prod/client.Dockerfile"
|
||||
["uptime_database_mongo"]="./docker/prod/mongoDB.Dockerfile"
|
||||
["uptime_server"]="./docker/prod/server.Dockerfile"
|
||||
)
|
||||
|
||||
# Loop through each service and build the corresponding image
|
||||
for service in "${!services[@]}"; do
|
||||
docker build -f "${services[$service]}" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All images built successfully"
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM node:20-slim AS build
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./client/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./client .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27.1-alpine
|
||||
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh
|
||||
RUN chmod +x /docker-entrypoint.d/env.sh
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,53 +0,0 @@
|
||||
services:
|
||||
client:
|
||||
image: ghcr.io/bluewave-labs/checkmate:frontend-demo
|
||||
restart: always
|
||||
environment:
|
||||
UPTIME_APP_API_BASE_URL: "https://demo.checkmate.so/api/v1"
|
||||
UPTIME_APP_CLIENT_HOST: "https://demo.checkmate.so"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
depends_on:
|
||||
- server
|
||||
volumes:
|
||||
- ./nginx/conf.d:/etc/nginx/conf.d/:ro
|
||||
- /etc/letsencrypt:/etc/nginx/ssl:ro
|
||||
|
||||
server:
|
||||
image: ghcr.io/bluewave-labs/checkmate:backend-demo
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
env_file:
|
||||
- server.env
|
||||
depends_on:
|
||||
- mongodb
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
mongodb:
|
||||
image: ghcr.io/bluewave-labs/checkmate:mongo-demo
|
||||
restart: always
|
||||
command: ["mongod", "--quiet", "--bind_ip_all"]
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
env_file:
|
||||
- mongo.env
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
start_interval: 1s
|
||||
retries: 30
|
||||
capture:
|
||||
image: ghcr.io/bluewave-labs/capture:latest
|
||||
container_name: capture
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "59232:59232"
|
||||
environment:
|
||||
- API_SECRET=my_secret
|
||||
- GIN_MODE=release
|
||||
volumes:
|
||||
- /etc/os-release:/etc/os-release:ro
|
||||
@@ -1,41 +0,0 @@
|
||||
function initiateReplicaSet() {
|
||||
try {
|
||||
rs.initiate({
|
||||
_id: "rs0",
|
||||
members: [{ _id: 0, host: "localhost:27017" }],
|
||||
});
|
||||
} catch (e) {
|
||||
print("Replica set already initiated or error occurred: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
function waitForPrimary() {
|
||||
while (true) {
|
||||
const isMaster = db.isMaster();
|
||||
if (isMaster.ismaster) {
|
||||
print("This node is now PRIMARY.");
|
||||
break;
|
||||
}
|
||||
print("Waiting to become PRIMARY...");
|
||||
sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
function createUser() {
|
||||
db = db.getSiblingDB("uptime_db");
|
||||
|
||||
var username = process.env.USERNAME_ENV_VAR;
|
||||
var password = process.env.PASSWORD_ENV_VAR;
|
||||
|
||||
db.createUser({
|
||||
user: username,
|
||||
pwd: password,
|
||||
roles: [{ role: "readWrite", db: "uptime_db" }],
|
||||
});
|
||||
|
||||
print("User '" + username + "' created successfully");
|
||||
}
|
||||
|
||||
initiateReplicaSet();
|
||||
waitForPrimary();
|
||||
createUser();
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM mongo
|
||||
EXPOSE 27017
|
||||
CMD ["mongod"]
|
||||
@@ -1,15 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name checkmate-demo.bluewavelabs.ca www.checkmate-demo.bluewavelabs.ca;
|
||||
server_tokens off;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://[domain-name]$request_uri;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name checkmate-demo.bluewavelabs.ca;
|
||||
server_tokens off;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://server:52345/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api-docs/ {
|
||||
proxy_pass http://server:52345/api-docs/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 default_server ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
server_name checkmate-demo.bluewavelabs.ca;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/live/demo.checkmate.so/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/live/demo.checkmate.so/privkey.pem;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://server:52345/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api-docs/ {
|
||||
proxy_pass http://server:52345/api-docs/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
FROM node:22-slim
|
||||
|
||||
# Install ping
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./server/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./server ./
|
||||
|
||||
RUN npm run build
|
||||
|
||||
RUN cp -r src/templates dist/templates
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -1,33 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Change directory to root Server directory for correct Docker Context
|
||||
cd /d "%~dp0"
|
||||
cd ../..
|
||||
|
||||
REM Define service names and their corresponding Dockerfiles
|
||||
set services[0]=uptime_client
|
||||
set dockerfiles[0]=.\docker\staging\client.Dockerfile
|
||||
|
||||
set services[1]=uptime_database_mongo
|
||||
set dockerfiles[1]=.\docker\staging\mongoDB.Dockerfile
|
||||
|
||||
set services[2]=uptime_server
|
||||
set dockerfiles[2]=.\docker\staging\server.Dockerfile
|
||||
|
||||
REM Loop through each service and build the corresponding image
|
||||
for /L %%i in (0,1,2) do (
|
||||
set service=!services[%%i]!
|
||||
set dockerfile=!dockerfiles[%%i]!
|
||||
|
||||
docker build -f "!dockerfile!" -t "!service!" .
|
||||
|
||||
REM Check if the build succeeded
|
||||
if errorlevel 1 (
|
||||
echo Error building !service! image. Exiting...
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo All images built successfully
|
||||
exit /b 0
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Change directory to root directory for correct Docker Context
|
||||
cd "$(dirname "$0")"
|
||||
cd ../../
|
||||
|
||||
# Define an array of services and their Dockerfiles
|
||||
declare -A services=(
|
||||
["uptime_client"]="./docker/staging/client.Dockerfile"
|
||||
["uptime_database_mongo"]="./docker/staging/mongoDB.Dockerfile"
|
||||
["uptime_server"]="./docker/staging/server.Dockerfile"
|
||||
)
|
||||
|
||||
# Loop through each service and build the corresponding image
|
||||
for service in "${!services[@]}"; do
|
||||
docker build -f "${services[$service]}" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All images built successfully"
|
||||
@@ -1,19 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
webserver:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
restart: always
|
||||
volumes:
|
||||
- ./nginx/conf.d/:/etc/nginx/conf.d/:ro
|
||||
- ./certbot/www/:/var/www/certbot/:ro
|
||||
certbot:
|
||||
image: certbot/certbot:latest
|
||||
volumes:
|
||||
- ./certbot/www/:/var/www/certbot/:rw
|
||||
- ./certbot/conf/:/etc/letsencrypt/:rw
|
||||
depends_on:
|
||||
- webserver
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM node:20-slim AS build
|
||||
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./client/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./client ./
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27.1-alpine
|
||||
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh
|
||||
RUN chmod +x /docker-entrypoint.d/env.sh
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
Loaded 100 of 319 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user