Refactor frontend build and deployment scripts; consolidate services into a single app

- Updated package.json in StatusPage to correct dependency path for Common.
- Enhanced main package.json with new scripts for building and watching frontend applications.
- Modified tsconfig.json to exclude frontend directories from compilation.
- Simplified Nginx configuration by removing individual upstreams for each service and routing all to a single app upstream.
- Refactored configure.sh to streamline Dockerfile generation.
- Cleaned up docker-compose files by consolidating services and removing unnecessary definitions.
- Introduced new frontend handling logic in Index.ts for rendering different frontend applications.
- Added utility functions for managing status page data and RSS feeds in StatusPage.ts.
- Created dev.sh and frontend-run.sh scripts to facilitate development and build processes for frontend applications.
This commit is contained in:
Nawaz Dhandala
2026-03-03 17:51:47 +00:00
parent 4e8fde3b23
commit 3e5cd57082
35 changed files with 1466 additions and 41575 deletions

View File

@@ -31,7 +31,7 @@ jobs:
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./Accounts/Dockerfile .
command: sudo docker build --no-cache -f ./App/Accounts/Dockerfile .
docker-build-home:
runs-on: ubuntu-latest
@@ -219,7 +219,7 @@ jobs:
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./AdminDashboard/Dockerfile .
command: sudo docker build --no-cache -f ./App/AdminDashboard/Dockerfile .
docker-build-dashboard:
runs-on: ubuntu-latest
@@ -242,7 +242,7 @@ jobs:
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./Dashboard/Dockerfile .
command: sudo docker build --no-cache -f ./App/Dashboard/Dockerfile .
docker-build-probe:
runs-on: ubuntu-latest
@@ -311,7 +311,7 @@ jobs:
with:
timeout_minutes: 45
max_attempts: 3
command: sudo docker build --no-cache -f ./StatusPage/Dockerfile .
command: sudo docker build --no-cache -f ./App/StatusPage/Dockerfile .
docker-build-test-server:
runs-on: ubuntu-latest

View File

@@ -25,7 +25,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: cd Accounts && npm install && npm run compile && npm run dep-check
command: cd App/Accounts && npm install && npm run compile && npm run dep-check
compile-common:
runs-on: ubuntu-latest
@@ -178,7 +178,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: cd AdminDashboard && npm install && npm run compile && npm run dep-check
command: cd App/AdminDashboard && npm install && npm run compile && npm run dep-check
compile-dashboard:
runs-on: ubuntu-latest
@@ -196,7 +196,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: cd Dashboard && npm install && npm run compile && npm run dep-check
command: cd App/Dashboard && npm install && npm run compile && npm run dep-check
compile-e2e:
@@ -268,7 +268,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: cd StatusPage && npm install && npm run compile && npm run dep-check
command: cd App/StatusPage && npm install && npm run compile && npm run dep-check
compile-test-server:
runs-on: ubuntu-latest

View File

@@ -556,7 +556,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image status-page \
--version "${{needs.read-version.outputs.major_minor}}" \
--dockerfile ./StatusPage/Dockerfile \
--dockerfile ./App/StatusPage/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}"
@@ -829,7 +829,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image admin-dashboard \
--version "${{needs.read-version.outputs.major_minor}}" \
--dockerfile ./AdminDashboard/Dockerfile \
--dockerfile ./App/AdminDashboard/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}"
@@ -898,7 +898,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image dashboard \
--version "${{needs.read-version.outputs.major_minor}}" \
--dockerfile ./Dashboard/Dockerfile \
--dockerfile ./App/Dashboard/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}"
@@ -1035,7 +1035,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image accounts \
--version "${{needs.read-version.outputs.major_minor}}" \
--dockerfile ./Accounts/Dockerfile \
--dockerfile ./App/Accounts/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}"

View File

@@ -502,7 +502,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image status-page \
--version "${{needs.read-version.outputs.major_minor}}-test" \
--dockerfile ./StatusPage/Dockerfile \
--dockerfile ./App/StatusPage/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}" \
@@ -785,7 +785,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image dashboard \
--version "${{needs.read-version.outputs.major_minor}}-test" \
--dockerfile ./Dashboard/Dockerfile \
--dockerfile ./App/Dashboard/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}" \
@@ -855,7 +855,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image admin-dashboard \
--version "${{needs.read-version.outputs.major_minor}}-test" \
--dockerfile ./AdminDashboard/Dockerfile \
--dockerfile ./App/AdminDashboard/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}" \
@@ -998,7 +998,7 @@ jobs:
bash ./Scripts/GHA/build_docker_images.sh \
--image accounts \
--version "${{needs.read-version.outputs.major_minor}}-test" \
--dockerfile ./Accounts/Dockerfile \
--dockerfile ./App/Accounts/Dockerfile \
--context . \
--platforms linux/amd64,linux/arm64 \
--git-sha "${{ github.sha }}" \

1
.gitignore vendored
View File

@@ -127,6 +127,7 @@ MCP/build/
MCP/.env
MCP/node_modules
Dashboard/public/sw.js
App/Dashboard/public/sw.js
.claude/settings.local.json
Common/.claude/settings.local.json
E2E/Terraform/e2e-tests/test-env.sh

View File

@@ -65,7 +65,7 @@ ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
WORKDIR /usr/src/app
# Install app dependencies
COPY ./Accounts/package*.json /usr/src/app/
COPY ./App/Accounts/package*.json /usr/src/app/
RUN npm install
# Expose ports.
@@ -82,7 +82,7 @@ EXPOSE 3003
CMD [ "npm", "run", "dev" ]
{{ else }}
# Copy app source
COPY ./Accounts /usr/src/app
COPY ./App/Accounts /usr/src/app
# Bundle app source
RUN npm run build

View File

@@ -1,5 +1,5 @@
{
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
"watch": ["./","../../Common/UI", "../../Common/Types", "../../Common/Utils", "../../Common/Models"],
"ext": "ts,tsx",
"ignore": [
"./node_modules/**",
@@ -11,7 +11,7 @@
"./build/**",
"./build/dist/*",
"./build/dist/**",
"../Common/Server/**"
"../../Common/Server/**"
],
"exec": "npm run dev-build && npm run start"
}

View File

@@ -8,7 +8,7 @@
"name": "@oneuptime/accounts",
"version": "0.1.0",
"dependencies": {
"Common": "file:../Common",
"Common": "file:../../Common",
"ejs": "^3.1.10",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -24,9 +24,150 @@
"ts-node": "^10.9.1"
}
},
"../../Common": {
"name": "@oneuptime/common",
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.2",
"@bull-board/express": "^5.21.4",
"@clickhouse/client": "^1.10.1",
"@elastic/elasticsearch": "^8.12.1",
"@hcaptcha/react-hcaptcha": "^1.14.0",
"@monaco-editor/react": "^4.4.6",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.206.0",
"@opentelemetry/context-zone": "^1.25.1",
"@opentelemetry/exporter-logs-otlp-http": "^0.207.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.207.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.207.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.207.0",
"@opentelemetry/id-generator-aws-xray": "^1.2.2",
"@opentelemetry/instrumentation": "^0.207.0",
"@opentelemetry/instrumentation-fetch": "^0.207.0",
"@opentelemetry/instrumentation-xml-http-request": "^0.207.0",
"@opentelemetry/resources": "^1.25.1",
"@opentelemetry/sdk-logs": "^0.207.0",
"@opentelemetry/sdk-metrics": "^1.25.1",
"@opentelemetry/sdk-node": "^0.207.0",
"@opentelemetry/sdk-trace-web": "^1.25.1",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@remixicon/react": "^4.2.0",
"@simplewebauthn/server": "^13.2.2",
"@tippyjs/react": "^4.2.6",
"@types/archiver": "^6.0.3",
"@types/crypto-js": "^4.2.2",
"@types/multer": "^2.0.0",
"@types/qrcode": "^1.5.5",
"@types/react-highlight": "^0.12.8",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^8.3.4",
"@types/web-push": "^3.6.4",
"acme-client": "^5.3.0",
"airtable": "^0.12.2",
"archiver": "^7.0.1",
"axios": "^1.12.0",
"botbuilder": "^4.23.3",
"bullmq": "^5.61.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"cron-parser": "^4.8.1",
"crypto-js": "^4.2.0",
"dotenv": "^16.4.4",
"ejs": "^3.1.10",
"elkjs": "^0.10.0",
"esbuild": "^0.25.5",
"expo-server-sdk": "^3.15.0",
"express": "^4.21.1",
"formik": "^2.4.6",
"history": "^5.3.0",
"ioredis": "^5.3.2",
"isolated-vm": "^6.0.2",
"json2csv": "^5.0.7",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.0",
"jwt-decode": "^4.0.0",
"marked": "^12.0.2",
"mermaid": "^11.12.2",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"multer": "^2.0.2",
"node-cron": "^3.0.3",
"nodemailer": "^7.0.7",
"otpauth": "^9.3.1",
"pg": "^8.16.3",
"playwright": "^1.56.0",
"posthog-js": "^1.275.3",
"prop-types": "^15.8.1",
"qrcode": "^1.5.3",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-big-calendar": "^1.19.4",
"react-color": "^2.19.3",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.2",
"react-error-boundary": "^4.0.13",
"react-highlight": "^0.15.0",
"react-markdown": "^9.0.0",
"react-router-dom": "^6.30.1",
"react-select": "^5.4.0",
"react-spinners": "^0.14.1",
"react-syntax-highlighter": "^16.0.0",
"react-toggle": "^4.1.3",
"reactflow": "^11.11.4",
"recharts": "^2.12.7",
"redis-semaphore": "^5.5.1",
"reflect-metadata": "^0.2.2",
"remark-gfm": "^4.0.0",
"slackify-markdown": "^4.4.0",
"slugify": "^1.6.5",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.5",
"stripe": "^10.17.0",
"tailwind-merge": "^2.6.0",
"tippy.js": "^6.3.7",
"twilio": "^4.22.0",
"typeorm": "^0.3.26",
"typeorm-extension": "^2.2.13",
"universal-cookie": "^7.2.1",
"use-async-effect": "^2.2.6",
"uuid": "^8.3.2",
"web-push": "^3.6.7",
"zod": "^3.25.76"
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.4.3",
"@types/cookie-parser": "^1.4.4",
"@types/cors": "^2.8.12",
"@types/ejs": "^3.1.1",
"@types/express": "^4.17.13",
"@types/jest": "^28.1.4",
"@types/json2csv": "^5.0.3",
"@types/jsonwebtoken": "^8.5.9",
"@types/node": "^17.0.45",
"@types/node-cron": "^3.0.7",
"@types/nodemailer": "^6.4.7",
"@types/react": "^18.2.38",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-big-calendar": "^1.8.5",
"@types/react-color": "^3.0.6",
"@types/react-test-renderer": "^18.0.0",
"@types/react-toggle": "^4.0.3",
"jest": "^28.1.1",
"jest-environment-jsdom": "^29.7.0",
"jest-mock-extended": "^3.0.5",
"react-test-renderer": "^18.2.0",
"sass": "^1.89.2",
"ts-jest": "^28.0.5"
}
},
"../Common": {
"name": "@oneuptime/common",
"version": "1.0.0",
"extraneous": true,
"license": "Apache-2.0",
"dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.2",
@@ -569,7 +710,7 @@
}
},
"node_modules/Common": {
"resolved": "../Common",
"resolved": "../../Common",
"link": true
},
"node_modules/concat-map": {

View File

@@ -32,7 +32,7 @@
]
},
"dependencies": {
"Common": "file:../Common",
"Common": "file:../../Common",
"ejs": "^3.1.10",
"react": "^18.3.1",

View File

@@ -63,7 +63,7 @@ ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
WORKDIR /usr/src/app
# Install app dependencies
COPY ./AdminDashboard/package*.json /usr/src/app/
COPY ./App/AdminDashboard/package*.json /usr/src/app/
RUN npm install
# Expose ports.
@@ -79,7 +79,7 @@ EXPOSE 3158
CMD [ "npm", "run", "dev" ]
{{ else }}
# Copy app source
COPY ./AdminDashboard /usr/src/app
COPY ./App/AdminDashboard /usr/src/app
# Bundle app source
RUN npm run build
# Set permission to write logs and cache in case container run as non root

View File

@@ -1,5 +1,5 @@
{
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
"watch": ["./","../../Common/UI", "../../Common/Types", "../../Common/Utils", "../../Common/Models"],
"ext": "ts,tsx",
"ignore": [
"./node_modules/**",
@@ -11,7 +11,7 @@
"./build/**",
"./build/dist/*",
"./build/dist/**",
"../Common/Server/**"
"../../Common/Server/**"
],
"exec": " npm run dev-build && npm run start"
}

View File

@@ -8,7 +8,7 @@
"name": "@oneuptime/admin-dashboard",
"version": "0.1.0",
"dependencies": {
"Common": "file:../Common",
"Common": "file:../../Common",
"ejs": "^3.1.10",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -23,9 +23,150 @@
"ts-node": "^10.9.1"
}
},
"../../Common": {
"name": "@oneuptime/common",
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.2",
"@bull-board/express": "^5.21.4",
"@clickhouse/client": "^1.10.1",
"@elastic/elasticsearch": "^8.12.1",
"@hcaptcha/react-hcaptcha": "^1.14.0",
"@monaco-editor/react": "^4.4.6",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.206.0",
"@opentelemetry/context-zone": "^1.25.1",
"@opentelemetry/exporter-logs-otlp-http": "^0.207.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.207.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.207.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.207.0",
"@opentelemetry/id-generator-aws-xray": "^1.2.2",
"@opentelemetry/instrumentation": "^0.207.0",
"@opentelemetry/instrumentation-fetch": "^0.207.0",
"@opentelemetry/instrumentation-xml-http-request": "^0.207.0",
"@opentelemetry/resources": "^1.25.1",
"@opentelemetry/sdk-logs": "^0.207.0",
"@opentelemetry/sdk-metrics": "^1.25.1",
"@opentelemetry/sdk-node": "^0.207.0",
"@opentelemetry/sdk-trace-web": "^1.25.1",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@remixicon/react": "^4.2.0",
"@simplewebauthn/server": "^13.2.2",
"@tippyjs/react": "^4.2.6",
"@types/archiver": "^6.0.3",
"@types/crypto-js": "^4.2.2",
"@types/multer": "^2.0.0",
"@types/qrcode": "^1.5.5",
"@types/react-highlight": "^0.12.8",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^8.3.4",
"@types/web-push": "^3.6.4",
"acme-client": "^5.3.0",
"airtable": "^0.12.2",
"archiver": "^7.0.1",
"axios": "^1.12.0",
"botbuilder": "^4.23.3",
"bullmq": "^5.61.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"cron-parser": "^4.8.1",
"crypto-js": "^4.2.0",
"dotenv": "^16.4.4",
"ejs": "^3.1.10",
"elkjs": "^0.10.0",
"esbuild": "^0.25.5",
"expo-server-sdk": "^3.15.0",
"express": "^4.21.1",
"formik": "^2.4.6",
"history": "^5.3.0",
"ioredis": "^5.3.2",
"isolated-vm": "^6.0.2",
"json2csv": "^5.0.7",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.0",
"jwt-decode": "^4.0.0",
"marked": "^12.0.2",
"mermaid": "^11.12.2",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"multer": "^2.0.2",
"node-cron": "^3.0.3",
"nodemailer": "^7.0.7",
"otpauth": "^9.3.1",
"pg": "^8.16.3",
"playwright": "^1.56.0",
"posthog-js": "^1.275.3",
"prop-types": "^15.8.1",
"qrcode": "^1.5.3",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-big-calendar": "^1.19.4",
"react-color": "^2.19.3",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.2",
"react-error-boundary": "^4.0.13",
"react-highlight": "^0.15.0",
"react-markdown": "^9.0.0",
"react-router-dom": "^6.30.1",
"react-select": "^5.4.0",
"react-spinners": "^0.14.1",
"react-syntax-highlighter": "^16.0.0",
"react-toggle": "^4.1.3",
"reactflow": "^11.11.4",
"recharts": "^2.12.7",
"redis-semaphore": "^5.5.1",
"reflect-metadata": "^0.2.2",
"remark-gfm": "^4.0.0",
"slackify-markdown": "^4.4.0",
"slugify": "^1.6.5",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.5",
"stripe": "^10.17.0",
"tailwind-merge": "^2.6.0",
"tippy.js": "^6.3.7",
"twilio": "^4.22.0",
"typeorm": "^0.3.26",
"typeorm-extension": "^2.2.13",
"universal-cookie": "^7.2.1",
"use-async-effect": "^2.2.6",
"uuid": "^8.3.2",
"web-push": "^3.6.7",
"zod": "^3.25.76"
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.4.3",
"@types/cookie-parser": "^1.4.4",
"@types/cors": "^2.8.12",
"@types/ejs": "^3.1.1",
"@types/express": "^4.17.13",
"@types/jest": "^28.1.4",
"@types/json2csv": "^5.0.3",
"@types/jsonwebtoken": "^8.5.9",
"@types/node": "^17.0.45",
"@types/node-cron": "^3.0.7",
"@types/nodemailer": "^6.4.7",
"@types/react": "^18.2.38",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-big-calendar": "^1.8.5",
"@types/react-color": "^3.0.6",
"@types/react-test-renderer": "^18.0.0",
"@types/react-toggle": "^4.0.3",
"jest": "^28.1.1",
"jest-environment-jsdom": "^29.7.0",
"jest-mock-extended": "^3.0.5",
"react-test-renderer": "^18.2.0",
"sass": "^1.89.2",
"ts-jest": "^28.0.5"
}
},
"../Common": {
"name": "@oneuptime/common",
"version": "1.0.0",
"extraneous": true,
"license": "Apache-2.0",
"dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.2",
@@ -553,7 +694,7 @@
}
},
"node_modules/Common": {
"resolved": "../Common",
"resolved": "../../Common",
"link": true
},
"node_modules/concat-map": {

View File

@@ -7,7 +7,7 @@
"url": "https://github.com/OneUptime/oneuptime"
},
"dependencies": {
"Common": "file:../Common",
"Common": "file:../../Common",
"ejs": "^3.1.10",
"react": "^18.3.1",

View File

@@ -57,7 +57,7 @@ ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
WORKDIR /usr/src/app
# Install app dependencies
COPY ./Dashboard/package*.json /usr/src/app/
COPY ./App/Dashboard/package*.json /usr/src/app/
RUN npm install
# Expose ports.
@@ -73,7 +73,7 @@ EXPOSE 3009
CMD [ "npm", "run", "dev" ]
{{ else }}
# Copy app source
COPY ./Dashboard /usr/src/app
COPY ./App/Dashboard /usr/src/app
# Bundle app source
RUN npm run build
# Set permission to write logs and cache in case container run as non root

View File

@@ -1,5 +1,5 @@
{
"watch": ["./","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
"watch": ["./","../../Common/UI", "../../Common/Types", "../../Common/Utils", "../../Common/Models"],
"ext": "ts,tsx",
"ignore": [
"./node_modules/**",
@@ -11,7 +11,7 @@
"./build/**",
"./build/dist/*",
"./build/dist/**",
"../Common/Server/**"
"../../Common/Server/**"
],
"exec": " npm run dev-build && npm run start"
}

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@
"dependencies": {
"@stripe/react-stripe-js": "^1.15.0",
"@stripe/stripe-js": "^1.44.1",
"Common": "file:../Common",
"Common": "file:../../Common",
"ejs": "^3.1.10",
"react": "^18.3.1",

View File

@@ -8,7 +8,7 @@
*/
const path = require('path');
const { generateServiceWorker } = require('../../Common/Scripts/generate-service-worker');
const { generateServiceWorker } = require('../../../Common/Scripts/generate-service-worker');
// Generate Dashboard service worker
const templatePath = path.join(__dirname, '..', 'sw.js.template');

View File

@@ -56,6 +56,24 @@ COPY ./App/package*.json /usr/src/app/
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/app/package.json
RUN npm install
WORKDIR /usr/src/app/Accounts
COPY ./App/Accounts/package*.json /usr/src/app/Accounts/
RUN npm install
WORKDIR /usr/src/app/Dashboard
COPY ./App/Dashboard/package*.json /usr/src/app/Dashboard/
RUN npm install
WORKDIR /usr/src/app/AdminDashboard
COPY ./App/AdminDashboard/package*.json /usr/src/app/AdminDashboard/
RUN npm install
WORKDIR /usr/src/app/StatusPage
COPY ./App/StatusPage/package*.json /usr/src/app/StatusPage/
RUN npm install
WORKDIR /usr/src/app
# Expose ports.
# - 3002: OneUptime-backend
EXPOSE 3002
@@ -66,6 +84,13 @@ CMD [ "npm", "run", "dev" ]
{{ else }}
# Copy app source
COPY ./App /usr/src/app
# Copy frontend sources
COPY ./App/Accounts /usr/src/app/Accounts
COPY ./App/Dashboard /usr/src/app/Dashboard
COPY ./App/AdminDashboard /usr/src/app/AdminDashboard
COPY ./App/StatusPage /usr/src/app/StatusPage
# Bundle frontend source
RUN npm run build-frontends:prod
# Bundle app source
RUN npm run compile
# Set permission to write logs and cache in case container run as non root

View File

@@ -0,0 +1,476 @@
import UserMiddleware from "Common/Server/Middleware/UserAuthorization";
import {
IsBillingEnabled,
getFrontendEnvVars,
} from "Common/Server/EnvironmentConfig";
import Express, {
ExpressApplication,
ExpressRequest,
ExpressResponse,
ExpressStatic,
NextFunction,
} from "Common/Server/Utils/Express";
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
import logger from "Common/Server/Utils/Logger";
import Response from "Common/Server/Utils/Response";
import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import { JSONObject } from "Common/Types/JSON";
import ObjectID from "Common/Types/ObjectID";
import { handleRSS, StatusPageData, getStatusPageData } from "./Utils/StatusPage";
const app: ExpressApplication = Express.getExpressApp();
const AccountsPublicPath: string = "/usr/src/app/Accounts/public";
const AccountsViewPath: string = "/usr/src/app/Accounts/views/index.ejs";
const DashboardPublicPath: string = "/usr/src/app/Dashboard/public";
const DashboardViewPath: string = "/usr/src/app/Dashboard/views/index.ejs";
const AdminPublicPath: string = "/usr/src/app/AdminDashboard/public";
const AdminViewPath: string = "/usr/src/app/AdminDashboard/views/index.ejs";
const StatusPagePublicPath: string = "/usr/src/app/StatusPage/public";
const StatusPageViewPath: string = "/usr/src/app/StatusPage/views/index.ejs";
interface FrontendConfig {
routePrefix: string;
publicPath: string;
indexViewPath: string;
primaryHostOnly?: boolean;
getVariablesToRenderIndexPage?: (
req: ExpressRequest,
res: ExpressResponse,
) => Promise<JSONObject>;
}
interface RenderFrontendOptions {
req: ExpressRequest;
res: ExpressResponse;
next: NextFunction;
frontendConfig: FrontendConfig;
}
const DashboardFallbackRoutePrefixesToSkip: Array<string> = [
"/status-page",
"/status-page-api",
"/status-page-sso-api",
"/status-page-identity-api",
"/api",
"/identity",
"/notification",
"/telemetry",
"/incoming-request-ingest",
"/otlp",
"/opentelemetry.proto.collector",
"/probe-ingest",
"/ingestor",
"/server-monitor",
"/realtime",
"/workflow",
"/workers",
"/mcp",
"/analytics-api",
"/heartbeat",
"/incoming-email",
"/file",
"/docs",
"/reference",
"/worker",
"/.well-known",
"/l",
"/manifest.json",
"/service-worker.js",
"/sw.js",
"/browserconfig.xml",
"/rss",
];
const StatusPageDomainFallbackRoutePrefixesToSkip: Array<string> = [
"/status-page-api",
"/status-page-sso-api",
"/status-page-identity-api",
"/.well-known",
"/rss",
];
const StatusPageFrontendConfig: FrontendConfig = {
routePrefix: "/status-page",
publicPath: StatusPagePublicPath,
indexViewPath: StatusPageViewPath,
getVariablesToRenderIndexPage: async (
req: ExpressRequest,
_res: ExpressResponse,
): Promise<JSONObject> => {
const statusPageData: StatusPageData | null = await getStatusPageData(req);
if (statusPageData) {
return {
title: statusPageData.title,
description: statusPageData.description,
faviconUrl: statusPageData.faviconUrl,
};
}
return {
title: "Status Page",
description:
"Status Page lets you see real-time information about the status of our services.",
faviconUrl:
"/status-page-api/favicon/" + ObjectID.getZeroObjectID().toString(),
};
},
};
const DashboardFrontendConfig: FrontendConfig = {
routePrefix: "/dashboard",
publicPath: DashboardPublicPath,
indexViewPath: DashboardViewPath,
primaryHostOnly: true,
};
const DashboardRootPwaFileMap: Array<{ route: string; file: string }> = [
{ route: "/manifest.json", file: "manifest.json" },
{ route: "/sw.js", file: "sw.js" },
{ route: "/service-worker.js", file: "sw.js" },
{ route: "/browserconfig.xml", file: "browserconfig.xml" },
];
const normalizeHostname: (host: string) => string = (host: string): string => {
const hostParts: Array<string> = host.split(":");
const hostPart: string | undefined = hostParts[0];
if (!hostPart) {
return "";
}
return hostPart.trim().toLowerCase();
};
const getPrimaryHosts: () => Set<string> = (): Set<string> => {
const hostSet: Set<string> = new Set<string>();
const hostCandidates: Array<string> = [
process.env["HOST"] || "",
"localhost",
"ingress",
];
for (const hostCandidate of hostCandidates) {
const normalizedHost: string = normalizeHostname(hostCandidate);
if (normalizedHost) {
hostSet.add(normalizedHost);
}
}
return hostSet;
};
const PrimaryHosts: Set<string> = getPrimaryHosts();
const getRequestHostname: (req: ExpressRequest) => string = (
req: ExpressRequest,
): string => {
if (req.hostname) {
return normalizeHostname(req.hostname.toString());
}
const hostHeader: string | Array<string> | undefined = req.headers["host"];
if (typeof hostHeader === "string") {
return normalizeHostname(hostHeader);
}
if (Array.isArray(hostHeader)) {
const firstHostHeader: string | undefined = hostHeader[0];
if (!firstHostHeader) {
return "";
}
return normalizeHostname(firstHostHeader);
}
return "";
};
const isPrimaryHostRequest: (req: ExpressRequest) => boolean = (
req: ExpressRequest,
): boolean => {
const requestHost: string = getRequestHostname(req);
if (!requestHost) {
return true;
}
return PrimaryHosts.has(requestHost);
};
const shouldSkipDashboardFallbackRoute: (path: string) => boolean = (
path: string,
): boolean => {
return DashboardFallbackRoutePrefixesToSkip.some((prefix: string) => {
if (path === prefix) {
return true;
}
return path.startsWith(`${prefix}/`);
});
};
const shouldSkipStatusPageDomainFallbackRoute: (path: string) => boolean = (
path: string,
): boolean => {
return StatusPageDomainFallbackRoutePrefixesToSkip.some((prefix: string) => {
if (path === prefix) {
return true;
}
return path.startsWith(`${prefix}/`);
});
};
const sendFrontendEnvScript: (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => Promise<void> = async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
const env: JSONObject = getFrontendEnvVars();
const script: string = `
if(!window.process){
window.process = {}
}
if(!window.process.env){
window.process.env = {}
}
window.process.env = ${JSON.stringify(env)};
`;
Response.sendJavaScriptResponse(req, res, script);
} catch (err) {
next(err);
}
};
const renderFrontendIndexPage: (
options: RenderFrontendOptions,
) => Promise<void> = async (
options: RenderFrontendOptions,
): Promise<void> => {
const { req, res, next, frontendConfig } = options;
try {
let variables: JSONObject = {};
if (frontendConfig.getVariablesToRenderIndexPage) {
try {
const variablesToRenderIndexPage: JSONObject =
await frontendConfig.getVariablesToRenderIndexPage(req, res);
variables = {
...variables,
...variablesToRenderIndexPage,
};
} catch (err) {
logger.error(err);
}
}
if (res.headersSent) {
return;
}
res.render(frontendConfig.indexViewPath, {
enableGoogleTagManager: IsBillingEnabled || false,
...variables,
});
} catch (err) {
next(err);
}
};
const ensureMasterAdminAccess: (
req: ExpressRequest,
res: ExpressResponse,
) => Promise<JSONObject> = async (
req: ExpressRequest,
res: ExpressResponse,
): Promise<JSONObject> => {
try {
const accessToken: string | undefined =
UserMiddleware.getAccessTokenFromExpressRequest(req);
if (!accessToken) {
Response.sendErrorResponse(
req,
res,
new NotAuthorizedException(
"Unauthorized: Only master admins can access the admin dashboard.",
),
);
return {};
}
const authData = JSONWebToken.decode(accessToken);
if (!authData.isMasterAdmin) {
Response.sendErrorResponse(
req,
res,
new NotAuthorizedException(
"Unauthorized: Only master admins can access the admin dashboard.",
),
);
return {};
}
return {};
} catch (error) {
logger.error(error);
Response.sendErrorResponse(
req,
res,
new NotAuthorizedException(
"Unauthorized: Only master admins can access the admin dashboard.",
),
);
return {};
}
};
const registerFrontendApp: (frontendConfig: FrontendConfig) => void = (
frontendConfig: FrontendConfig,
): void => {
const staticHandler = ExpressStatic(frontendConfig.publicPath);
app.use(
frontendConfig.routePrefix,
(req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
if (frontendConfig.primaryHostOnly && !isPrimaryHostRequest(req)) {
return next();
}
return staticHandler(req, res, next);
},
);
app.get(
`${frontendConfig.routePrefix}/env.js`,
(req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
if (frontendConfig.primaryHostOnly && !isPrimaryHostRequest(req)) {
return next();
}
return sendFrontendEnvScript(req, res, next);
},
);
app.get(
[frontendConfig.routePrefix, `${frontendConfig.routePrefix}/*`],
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
if (frontendConfig.primaryHostOnly && !isPrimaryHostRequest(req)) {
return next();
}
return renderFrontendIndexPage({
req,
res,
next,
frontendConfig,
});
},
);
};
const registerStatusPageCustomDomainFallback: () => void = (): void => {
app.get("*", async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
if (isPrimaryHostRequest(req)) {
return next();
}
if (shouldSkipStatusPageDomainFallbackRoute(req.path)) {
return next();
}
return renderFrontendIndexPage({
req,
res,
next,
frontendConfig: StatusPageFrontendConfig,
});
});
};
const registerDashboardFallbackForPrimaryHost: () => void = (): void => {
app.get("*", async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
if (IsBillingEnabled) {
return next();
}
if (!isPrimaryHostRequest(req)) {
return next();
}
if (shouldSkipDashboardFallbackRoute(req.path)) {
return next();
}
return renderFrontendIndexPage({
req,
res,
next,
frontendConfig: DashboardFrontendConfig,
});
});
};
const registerDashboardRootPwaFiles: () => void = (): void => {
for (const pwaFileRoute of DashboardRootPwaFileMap) {
app.get(
pwaFileRoute.route,
(req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
if (IsBillingEnabled || !isPrimaryHostRequest(req)) {
return next();
}
return res.sendFile(`${DashboardPublicPath}/${pwaFileRoute.file}`);
},
);
}
};
const init: PromiseVoidFunction = async (): Promise<void> => {
app.get("/rss", handleRSS);
app.get("/status-page/:statusPageId/rss", handleRSS);
registerFrontendApp({
routePrefix: "/accounts",
publicPath: AccountsPublicPath,
indexViewPath: AccountsViewPath,
primaryHostOnly: true,
});
registerFrontendApp(DashboardFrontendConfig);
registerFrontendApp({
routePrefix: "/admin",
publicPath: AdminPublicPath,
indexViewPath: AdminViewPath,
primaryHostOnly: true,
getVariablesToRenderIndexPage: ensureMasterAdminAccess,
});
registerFrontendApp(StatusPageFrontendConfig);
registerDashboardRootPwaFiles();
registerStatusPageCustomDomainFallback();
registerDashboardFallbackForPrimaryHost();
};
export default {
init,
};

View File

@@ -0,0 +1,252 @@
import { StatusPageApiInternalUrl } from "Common/Server/EnvironmentConfig";
import { ExpressRequest, ExpressResponse } from "Common/Server/Utils/Express";
import logger from "Common/Server/Utils/Logger";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import URL from "Common/Types/API/URL";
import { JSONArray, JSONObject } from "Common/Types/JSON";
import API from "Common/Utils/API";
export interface StatusPageData {
id: string;
title: string;
description: string;
faviconUrl: string;
}
export const getStatusPageData: (
req: ExpressRequest,
) => Promise<StatusPageData | null> = async (
req: ExpressRequest,
): Promise<StatusPageData | null> => {
try {
logger.debug("Getting status page data");
let statusPageIdOrDomain: string = "";
let isPreview: boolean = false;
const path: string = req.path;
logger.debug(`Request path: ${path}`);
if (path && path.includes("/status-page/")) {
statusPageIdOrDomain =
path.split("/status-page/")[1]?.split("/")[0] || "";
isPreview = true;
logger.debug(`Found status page ID in URL: ${statusPageIdOrDomain}`);
} else {
const host: string =
req.hostname?.toString() || req.headers["host"]?.toString() || "";
if (host) {
statusPageIdOrDomain = host;
logger.debug(
`Found domain in request headers: ${statusPageIdOrDomain}`,
);
}
}
if (!statusPageIdOrDomain) {
logger.debug("No status page ID or domain found");
return null;
}
let statusPageId: string;
let title: string = "Status Page";
let description: string =
"Status Page lets you see real-time information about the status of our services.";
if (isPreview) {
// For preview pages, use the extracted ID directly.
statusPageId = statusPageIdOrDomain;
} else {
logger.debug(
`Pinging the API with statusPageIdOrDomain: ${statusPageIdOrDomain}`,
);
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.get({
url: URL.fromString(StatusPageApiInternalUrl.toString()).addRoute(
`/seo/${statusPageIdOrDomain}`,
),
});
if (response instanceof HTTPErrorResponse) {
logger.debug(`Received error response from API: ${response}`);
return null;
}
logger.debug("Successfully received response from API");
statusPageId = response.data?.["_id"] as string;
if (!statusPageId) {
logger.debug("No status page ID in response");
return null;
}
title = (response.data?.["title"] as string) || title;
description = (response.data?.["description"] as string) || description;
}
return {
id: statusPageId,
title,
description,
faviconUrl: `/status-page-api/favicon/${statusPageIdOrDomain}`,
};
} catch (err) {
logger.error("Error getting status page data:");
logger.error(err);
return null;
}
};
type RSSItem = {
title: string;
description: string;
link: string;
pubDate: string;
};
export const handleRSS: (
req: ExpressRequest,
res: ExpressResponse,
) => Promise<void> = async (
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> => {
try {
const statusPageData: StatusPageData | null = await getStatusPageData(req);
if (!statusPageData) {
res.status(404).send("Status page not found");
return;
}
const { id: statusPageId, title, description } = statusPageData;
const incidentsResponse: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post({
url: URL.fromString(StatusPageApiInternalUrl.toString()).addRoute(
`/incidents/${statusPageId}`,
),
data: {},
headers: {
"status-page-id": statusPageId,
},
});
const announcementsResponse: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post({
url: URL.fromString(StatusPageApiInternalUrl.toString()).addRoute(
`/announcements/${statusPageId}`,
),
data: {},
headers: {
"status-page-id": statusPageId,
},
});
const scheduledResponse: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post({
url: URL.fromString(StatusPageApiInternalUrl.toString()).addRoute(
`/scheduled-maintenance-events/${statusPageId}`,
),
data: {},
headers: {
"status-page-id": statusPageId,
},
});
const items: RSSItem[] = [];
const isPreview: boolean = req.path.includes("/status-page/");
const baseUrl: string = isPreview
? `${req.protocol}://${req.get("host")}/status-page/${statusPageId}`
: `${req.protocol}://${req.get("host")}`;
if (
incidentsResponse instanceof HTTPResponse &&
incidentsResponse.data?.["incidents"]
) {
const incidents: JSONArray = incidentsResponse.data[
"incidents"
] as JSONArray;
incidents.forEach((incident: JSONObject) => {
items.push({
title: `Incident: ${incident["title"]}`,
description: (incident["description"] as string) || "",
link: `${baseUrl}/incidents/${incident["_id"]}`,
pubDate: new Date(incident["createdAt"] as string).toUTCString(),
});
});
}
if (
announcementsResponse instanceof HTTPResponse &&
announcementsResponse.data?.["announcements"]
) {
const announcements: JSONArray = announcementsResponse.data[
"announcements"
] as JSONArray;
announcements.forEach((announcement: JSONObject) => {
items.push({
title: `Announcement: ${announcement["title"]}`,
description: (announcement["description"] as string) || "",
link: `${baseUrl}/announcements/${announcement["_id"]}`,
pubDate: new Date(announcement["createdAt"] as string).toUTCString(),
});
});
}
if (
scheduledResponse instanceof HTTPResponse &&
scheduledResponse.data?.["scheduledMaintenanceEvents"]
) {
const scheduled: JSONArray = scheduledResponse.data[
"scheduledMaintenanceEvents"
] as JSONArray;
scheduled.forEach((event: JSONObject) => {
items.push({
title: `Scheduled Maintenance: ${event["title"]}`,
description: (event["description"] as string) || "",
link: `${baseUrl}/scheduled-events/${event["_id"]}`,
pubDate: new Date(event["createdAt"] as string).toUTCString(),
});
});
}
items.sort((a: RSSItem, b: RSSItem) => {
return new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime();
});
const feedUrl: string = `${req.protocol}://${req.get("host")}${req.path}`;
let rssXml: string = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${title} Updates</title>
<description>${description}</description>
<link>${baseUrl}</link>
<atom:link href="${feedUrl}" rel="self" type="application/rss+xml" />
`;
items.forEach((item: RSSItem) => {
rssXml += `
<item>
<title><![CDATA[${item.title}]]></title>
<description><![CDATA[${item.description}]]></description>
<link>${item.link}</link>
<guid>${item.link}</guid>
<pubDate>${item.pubDate}</pubDate>
</item>`;
});
rssXml += `
</channel>
</rss>`;
res.set("Content-Type", "application/rss+xml");
res.send(rssXml);
} catch (err) {
logger.error(err);
res.status(500).send("Internal Server Error");
}
};

View File

@@ -1,4 +1,5 @@
import BaseAPIRoutes from "./FeatureSet/BaseAPI/Index";
import FrontendRoutes from "./FeatureSet/Frontend/Index";
// import FeatureSets.
import IdentityRoutes from "./FeatureSet/Identity/Index";
import MCPRoutes from "./FeatureSet/MCP/Index";
@@ -96,6 +97,7 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
await NotificationRoutes.init();
await BaseAPIRoutes.init();
await MCPRoutes.init();
await FrontendRoutes.init();
// Add default routes to the app
await App.addDefaultRoutes();

View File

@@ -62,7 +62,7 @@ ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
WORKDIR /usr/src/app
# Install app dependencies
COPY ./StatusPage/package*.json /usr/src/app/
COPY ./App/StatusPage/package*.json /usr/src/app/
RUN npm install
# Expose ports.
@@ -80,7 +80,7 @@ EXPOSE 3106
CMD [ "npm", "run", "dev" ]
{{ else }}
# Copy app source
COPY ./StatusPage /usr/src/app
COPY ./App/StatusPage /usr/src/app
# Bundle app source
RUN npm run build
# Set permission to write logs and cache in case container run as non root

View File

@@ -1,5 +1,5 @@
{
"watch": ["./*","../Common/UI", "../Common/Types", "../Common/Utils", "../Common/Models"],
"watch": ["./*","../../Common/UI", "../../Common/Types", "../../Common/Utils", "../../Common/Models"],
"ext": "ts,tsx",
"ignore": [
"./node_modules/**",
@@ -11,7 +11,7 @@
"./build/**",
"./build/dist/*",
"./build/dist/**",
"../Common/Server/**"
"../../Common/Server/**"
],
"exec": " npm run dev-build && npm run start"
}

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@
]
},
"dependencies": {
"Common": "file:../Common",
"Common": "file:../../Common",
"ejs": "^3.1.10",
"react": "^18.3.1",

View File

@@ -8,10 +8,25 @@
},
"main": "Index.ts",
"scripts": {
"build-frontend:accounts": "bash ./scripts/frontend-run.sh Accounts dev-build",
"build-frontend:dashboard": "bash ./scripts/frontend-run.sh Dashboard dev-build",
"build-frontend:admin-dashboard": "bash ./scripts/frontend-run.sh AdminDashboard dev-build",
"build-frontend:status-page": "bash ./scripts/frontend-run.sh StatusPage dev-build",
"build-frontends": "npm run build-frontend:accounts && npm run build-frontend:dashboard && npm run build-frontend:admin-dashboard && npm run build-frontend:status-page",
"build-frontend:accounts:prod": "bash ./scripts/frontend-run.sh Accounts build",
"build-frontend:dashboard:prod": "bash ./scripts/frontend-run.sh Dashboard build",
"build-frontend:admin-dashboard:prod": "bash ./scripts/frontend-run.sh AdminDashboard build",
"build-frontend:status-page:prod": "bash ./scripts/frontend-run.sh StatusPage build",
"build-frontends:prod": "npm run build-frontend:accounts:prod && npm run build-frontend:dashboard:prod && npm run build-frontend:admin-dashboard:prod && npm run build-frontend:status-page:prod",
"watch-frontend:accounts": "bash ./scripts/frontend-run.sh Accounts dev-build --watch",
"watch-frontend:dashboard": "bash ./scripts/frontend-run.sh Dashboard dev-build --watch",
"watch-frontend:admin-dashboard": "bash ./scripts/frontend-run.sh AdminDashboard dev-build --watch",
"watch-frontend:status-page": "bash ./scripts/frontend-run.sh StatusPage dev-build --watch",
"dev:api": "npx nodemon",
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
"compile": "tsc",
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
"dev": "npx nodemon",
"dev": "bash ./scripts/dev.sh",
"audit": "npm audit --audit-level=low",
"dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true",
"test": "rm -rf build && jest --detectOpenHandles --passWithNoTests",

34
App/scripts/dev.sh Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
pids=()
cleanup() {
for pid in "${pids[@]}"; do
if kill -0 "$pid" >/dev/null 2>&1; then
kill "$pid" >/dev/null 2>&1 || true
fi
done
}
trap cleanup EXIT INT TERM
npm run build-frontends
npm run watch-frontend:accounts &
pids+=($!)
npm run watch-frontend:dashboard &
pids+=($!)
npm run watch-frontend:admin-dashboard &
pids+=($!)
npm run watch-frontend:status-page &
pids+=($!)
npm run dev:api &
pids+=($!)
wait -n "${pids[@]}"

32
App/scripts/frontend-run.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <FrontendDirName> <NpmScript> [script args...]" >&2
exit 1
fi
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
app_dir="$(cd "${script_dir}/.." && pwd)"
frontend_dir_name="$1"
shift
frontend_script="$1"
shift
if [ -d "${app_dir}/${frontend_dir_name}" ]; then
frontend_dir="${app_dir}/${frontend_dir_name}"
elif [ -d "${app_dir}/../${frontend_dir_name}" ]; then
frontend_dir="${app_dir}/../${frontend_dir_name}"
else
echo "Frontend directory not found for ${frontend_dir_name}" >&2
exit 1
fi
if [ "$#" -gt 0 ]; then
npm --prefix "$frontend_dir" run "$frontend_script" -- "$@"
else
npm --prefix "$frontend_dir" run "$frontend_script"
fi

View File

@@ -108,5 +108,11 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"resolveJsonModule": true
}
},
"exclude": [
"Accounts",
"Dashboard",
"AdminDashboard",
"StatusPage"
]
}

View File

@@ -3,10 +3,6 @@
server_names_hash_bucket_size ${SERVER_NAMES_HASH_BUCKET_SIZE};
server_names_hash_max_size ${SERVER_NAMES_HASH_MAX_SIZE};
upstream accounts {
server ${SERVER_ACCOUNTS_HOSTNAME}:${ACCOUNTS_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream app {
server ${SERVER_APP_HOSTNAME}:${APP_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
@@ -15,15 +11,6 @@ upstream telemetry {
server ${SERVER_TELEMETRY_HOSTNAME}:${TELEMETRY_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream dashboard {
server ${SERVER_DASHBOARD_HOSTNAME}:${DASHBOARD_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream admin-dashboard {
server ${SERVER_ADMIN_DASHBOARD_HOSTNAME}:${ADMIN_DASHBOARD_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream worker {
server ${SERVER_WORKER_HOSTNAME}:${WORKER_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
@@ -40,10 +27,6 @@ upstream home {
server ${SERVER_HOME_HOSTNAME}:${HOME_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream status-page {
server ${SERVER_STATUS_PAGE_HOSTNAME}:${STATUS_PAGE_PORT} weight=10 max_fails=3 fail_timeout=30s;
}
upstream opentelemetry-collector-http {
server ${SERVER_OTEL_COLLECTOR_HOSTNAME}:4318;
}
@@ -92,7 +75,7 @@ server {
}
if ($billing_enabled != true) {
proxy_pass http://status-page;
proxy_pass http://app;
}
}
@@ -111,7 +94,7 @@ server {
}
if ($billing_enabled != true) {
proxy_pass http://status-page;
proxy_pass http://app;
}
}
@@ -223,7 +206,7 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://status-page;
proxy_pass http://app;
}
@@ -289,7 +272,7 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://status-page;
proxy_pass http://app;
}
}
@@ -339,7 +322,7 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
}
if ($billing_enabled != true) {
proxy_pass http://dashboard;
proxy_pass http://app;
}
}
@@ -401,7 +384,7 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
}
if ($billing_enabled != true) {
proxy_pass http://dashboard;
proxy_pass http://app;
}
}
@@ -467,7 +450,7 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://accounts;
proxy_pass http://app;
}
@@ -644,7 +627,7 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
proxy_pass http://dashboard;
proxy_pass http://app;
}
# PWA manifest and service worker with proper headers
@@ -670,7 +653,7 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
add_header Cache-Control "public, max-age=86400" always;
}
proxy_pass http://dashboard;
proxy_pass http://app;
}
@@ -686,7 +669,7 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://admin-dashboard;
proxy_pass http://app;
}
location /worker {
@@ -717,7 +700,7 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://status-page;
proxy_pass http://app;
}
location /identity {

View File

@@ -196,16 +196,12 @@ main() {
export $(grep -v '^#' config.env | xargs)
print_info "Generating Dockerfile configurations..."
find . -maxdepth 1 -type d -exec sh -c '
for dir do
if [ -f "$dir/Dockerfile.tpl" ]; then
cat "$dir/Dockerfile.tpl" | gomplate > "$dir/Dockerfile"
fi
done
' sh {} +
while IFS= read -r dockerfile_template; do
cat "$dockerfile_template" | gomplate > "${dockerfile_template%.tpl}"
done < <(find . -type f -name "Dockerfile.tpl" -not -path "*/node_modules/*")
print_success "OneUptime installation completed successfully! 🚀"
}
# Run main function
main
main

View File

@@ -28,14 +28,14 @@ x-common-variables: &common-variables
ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN: ${ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN}
SERVER_ACCOUNTS_HOSTNAME: accounts
SERVER_ACCOUNTS_HOSTNAME: app
SERVER_APP_HOSTNAME: app
SERVER_ALERT_HOSTNAME: alert
SERVER_TELEMETRY_HOSTNAME: telemetry
SERVER_TEST_SERVER_HOSTNAME: test-server
SERVER_STATUS_PAGE_HOSTNAME: status-page
SERVER_DASHBOARD_HOSTNAME: dashboard
SERVER_ADMIN_DASHBOARD_HOSTNAME: admin-dashboard
SERVER_STATUS_PAGE_HOSTNAME: app
SERVER_DASHBOARD_HOSTNAME: app
SERVER_ADMIN_DASHBOARD_HOSTNAME: app
SERVER_OTEL_COLLECTOR_HOSTNAME: otel-collector
SERVER_WORKER_HOSTNAME: worker
SERVER_HOME_HOSTNAME: home
@@ -216,20 +216,6 @@ services:
options:
max-size: "1000m"
accounts:
networks:
- oneuptime
restart: always
environment:
<<: *common-runtime-variables
PORT: ${ACCOUNTS_PORT}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_ACCOUNTS}
logging:
driver: "local"
options:
max-size: "1000m"
llm:
networks:
- oneuptime
@@ -246,49 +232,6 @@ services:
options:
max-size: "1000m"
admin-dashboard:
networks:
- oneuptime
restart: always
environment:
<<: *common-runtime-variables
PORT: ${ADMIN_DASHBOARD_PORT}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_ADMIN_DASHBOARD}
logging:
driver: "local"
options:
max-size: "1000m"
dashboard:
networks:
- oneuptime
restart: always
environment:
<<: *common-runtime-variables
PORT: ${DASHBOARD_PORT}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_DASHBOARD}
logging:
driver: "local"
options:
max-size: "1000m"
status-page:
networks:
- oneuptime
restart: always
environment:
<<: *common-runtime-variables
PORT: ${STATUS_PAGE_PORT}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_STATUS_PAGE}
logging:
driver: "local"
options:
max-size: "1000m"
test-server:
networks:
- oneuptime
@@ -524,4 +467,4 @@ volumes:
networks:
oneuptime:
driver: bridge
driver: bridge

View File

@@ -45,107 +45,6 @@ services:
context: .
dockerfile: ./OTelCollector/Dockerfile
accounts:
ports:
- '${ACCOUNTS_PORT}:${ACCOUNTS_PORT}'
extends:
file: ./docker-compose.base.yml
service: accounts
depends_on:
<<: *common-depends-on
volumes:
- ./Accounts:/usr/src/app:cached
# Use node modules of the container and not host system.
# https://stackoverflow.com/questions/29181032/add-a-volume-to-docker-but-exclude-a-sub-folder
- /usr/src/app/dev-env
- /usr/src/app/node_modules/
- ./Common:/usr/src/Common:cached
- /usr/src/Common/node_modules/
build:
network: host
context: .
dockerfile: ./Accounts/Dockerfile
dashboard:
ports:
- '${DASHBOARD_PORT}:${DASHBOARD_PORT}'
extends:
file: ./docker-compose.base.yml
service: dashboard
depends_on:
<<: *common-depends-on
volumes:
- ./Dashboard:/usr/src/app:cached
# Use node modules of the container and not host system.
# https://stackoverflow.com/questions/29181032/add-a-volume-to-docker-but-exclude-a-sub-folder
- /usr/src/app/dev-env
- /usr/src/app/node_modules/
- ./Common:/usr/src/Common:cached
- /usr/src/Common/node_modules/
build:
network: host
context: .
dockerfile: ./Dashboard/Dockerfile
admin-dashboard:
ports:
- '${ADMIN_DASHBOARD_PORT}:${ADMIN_DASHBOARD_PORT}'
extends:
file: ./docker-compose.base.yml
service: admin-dashboard
depends_on:
<<: *common-depends-on
volumes:
- ./AdminDashboard:/usr/src/app:cached
# Use node modules of the container and not host system.
# https://stackoverflow.com/questions/29181032/add-a-volume-to-docker-but-exclude-a-sub-folder
- /usr/src/app/dev-env
- /usr/src/app/node_modules/
- ./Common:/usr/src/Common:cached
- /usr/src/Common/node_modules/
build:
network: host
context: .
dockerfile: ./AdminDashboard/Dockerfile
status-page:
ports:
- '${STATUS_PAGE_PORT}:${STATUS_PAGE_PORT}'
extends:
file: ./docker-compose.base.yml
service: status-page
depends_on:
<<: *common-depends-on
volumes:
- ./StatusPage:/usr/src/app:cached
# Use node modules of the container and not host system.
# https://stackoverflow.com/questions/29181032/add-a-volume-to-docker-but-exclude-a-sub-folder
- /usr/src/app/dev-env
- /usr/src/app/node_modules/
- ./Common:/usr/src/Common:cached
- /usr/src/Common/node_modules/
build:
network: host
context: .
dockerfile: ./StatusPage/Dockerfile
test-server:
volumes:
- ./TestServer:/usr/src/app:cached
@@ -255,9 +154,17 @@ services:
app:
volumes:
- ./App:/usr/src/app:cached
- ./App/Accounts:/usr/src/app/Accounts:cached
- ./App/Dashboard:/usr/src/app/Dashboard:cached
- ./App/AdminDashboard:/usr/src/app/AdminDashboard:cached
- ./App/StatusPage:/usr/src/app/StatusPage:cached
# Use node modules of the container and not host system.
# https://stackoverflow.com/questions/29181032/add-a-volume-to-docker-but-exclude-a-sub-folder
- /usr/src/app/node_modules/
- /usr/src/app/Accounts/node_modules/
- /usr/src/app/Dashboard/node_modules/
- /usr/src/app/AdminDashboard/node_modules/
- /usr/src/app/StatusPage/node_modules/
- ./Common:/usr/src/Common:cached
- /usr/src/Common/node_modules/
extends:

View File

@@ -35,38 +35,6 @@ services:
file: ./docker-compose.base.yml
service: otel-collector
accounts:
image: oneuptime/accounts:${APP_TAG}
extends:
file: ./docker-compose.base.yml
service: accounts
depends_on:
<<: *common-depends-on
dashboard:
image: oneuptime/dashboard:${APP_TAG}
extends:
file: ./docker-compose.base.yml
service: dashboard
depends_on:
<<: *common-depends-on
admin-dashboard:
image: oneuptime/admin-dashboard:${APP_TAG}
extends:
file: ./docker-compose.base.yml
service: admin-dashboard
depends_on:
<<: *common-depends-on
status-page:
image: oneuptime/status-page:${APP_TAG}
extends:
file: ./docker-compose.base.yml
service: status-page
depends_on:
<<: *common-depends-on
app:
image: oneuptime/app:${APP_TAG}
extends: