From 62a3707c100006b35896a797a9fe8e35f9c8d5f8 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 29 Jan 2025 18:49:27 +0530 Subject: [PATCH 01/19] feat(template): frappe HR, open source HR & Payroll software --- apps/dokploy/public/templates/frappe-hr.svg | 5 + .../templates/frappe-hr/docker-compose.yml | 346 ++++++++++++++++++ apps/dokploy/templates/frappe-hr/index.ts | 37 ++ apps/dokploy/templates/templates.ts | 15 + 4 files changed, 403 insertions(+) create mode 100644 apps/dokploy/public/templates/frappe-hr.svg create mode 100644 apps/dokploy/templates/frappe-hr/docker-compose.yml create mode 100644 apps/dokploy/templates/frappe-hr/index.ts diff --git a/apps/dokploy/public/templates/frappe-hr.svg b/apps/dokploy/public/templates/frappe-hr.svg new file mode 100644 index 000000000..4cbf51641 --- /dev/null +++ b/apps/dokploy/public/templates/frappe-hr.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/dokploy/templates/frappe-hr/docker-compose.yml b/apps/dokploy/templates/frappe-hr/docker-compose.yml new file mode 100644 index 000000000..3ed923ea0 --- /dev/null +++ b/apps/dokploy/templates/frappe-hr/docker-compose.yml @@ -0,0 +1,346 @@ +x-custom-image: &custom_image + image: ${IMAGE_NAME:-ghcr.io/frappe/hrms}:${VERSION:-version-15} + pull_policy: ${PULL_POLICY:-always} + deploy: + restart_policy: + condition: always + +services: + backend: + <<: *custom_image + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + healthcheck: + test: + - CMD + - wait-for-it + - '0.0.0.0:8000' + interval: 2s + timeout: 10s + retries: 30 + + frontend: + <<: *custom_image + command: + - nginx-entrypoint.sh + depends_on: + backend: + condition: service_started + required: true + websocket: + condition: service_started + required: true + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host} + SOCKETIO: websocket:9000 + UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1 + UPSTREAM_REAL_IP_HEADER: X-Forwarded-For + UPSTREAM_REAL_IP_RECURSIVE: "off" + volumes: + - sites:/home/frappe/frappe-bench/sites + + networks: + - bench-network + + healthcheck: + test: + - CMD + - wait-for-it + - '0.0.0.0:8080' + interval: 2s + timeout: 30s + retries: 30 + + queue-default: + <<: *custom_image + command: + - bench + - worker + - --queue + - default + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + healthcheck: + test: + - CMD + - wait-for-it + - 'redis-queue:6379' + interval: 2s + timeout: 10s + retries: 30 + depends_on: + configurator: + condition: service_completed_successfully + required: true + + queue-long: + <<: *custom_image + command: + - bench + - worker + - --queue + - long + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + healthcheck: + test: + - CMD + - wait-for-it + - 'redis-queue:6379' + interval: 2s + timeout: 10s + retries: 30 + depends_on: + configurator: + condition: service_completed_successfully + required: true + + queue-short: + <<: *custom_image + command: + - bench + - worker + - --queue + - short + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + healthcheck: + test: + - CMD + - wait-for-it + - 'redis-queue:6379' + interval: 2s + timeout: 10s + retries: 30 + depends_on: + configurator: + condition: service_completed_successfully + required: true + + scheduler: + <<: *custom_image + healthcheck: + test: + - CMD + - wait-for-it + - 'redis-queue:6379' + interval: 2s + timeout: 10s + retries: 30 + command: + - bench + - schedule + depends_on: + configurator: + condition: service_completed_successfully + required: true + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + + websocket: + <<: *custom_image + healthcheck: + test: + - CMD + - wait-for-it + - '0.0.0.0:9000' + interval: 2s + timeout: 10s + retries: 30 + command: + - node + - /home/frappe/frappe-bench/apps/frappe/socketio.js + depends_on: + configurator: + condition: service_completed_successfully + required: true + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + + configurator: + <<: *custom_image + deploy: + mode: replicated + replicas: ${CONFIGURE:-0} + restart_policy: + condition: none + entrypoint: ["bash", "-c"] + command: + - > + [[ $${REGENERATE_APPS_TXT} == "1" ]] && ls -1 apps > sites/apps.txt; + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && exit 0; + bench set-config -g db_host $$DB_HOST; + bench set-config -gp db_port $$DB_PORT; + bench set-config -g redis_cache "redis://$$REDIS_CACHE"; + bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; + bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; + bench set-config -gp socketio_port $$SOCKETIO_PORT; + environment: + DB_HOST: db + DB_PORT: "3306" + REDIS_CACHE: redis-cache:6379 + REDIS_QUEUE: redis-queue:6379 + SOCKETIO_PORT: "9000" + REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}" + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + + create-site: + <<: *custom_image + deploy: + mode: replicated + replicas: ${CREATE_SITE:-0} + restart_policy: + condition: none + entrypoint: ["bash", "-c"] + command: + - > + wait-for-it -t 120 db:3306; + wait-for-it -t 120 redis-cache:6379; + wait-for-it -t 120 redis-queue:6379; + export start=`date +%s`; + until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; + do + echo "Waiting for sites/common_site_config.json to be created"; + sleep 5; + if (( `date +%s`-start > 120 )); then + echo "could not find sites/common_site_config.json with required keys"; + exit 1 + fi + done; + echo "sites/common_site_config.json found"; + [[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0; + bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME}; + volumes: + - sites:/home/frappe/frappe-bench/sites + environment: + ADMIN_PASSWORD: ${ADMIN_PASSWORD} + DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + INSTALL_APP_ARGS: ${INSTALL_APP_ARGS} + SITE_NAME: ${SITE_NAME} + networks: + - bench-network + + migration: + <<: *custom_image + deploy: + mode: replicated + replicas: ${MIGRATE:-0} + restart_policy: + condition: none + entrypoint: ["bash", "-c"] + command: + - > + curl -f http://${SITE_NAME}:8080/api/method/ping || echo "Site busy" && exit 0; + bench --site all set-config -p maintenance_mode 1; + bench --site all set-config -p pause_scheduler 1; + bench --site all migrate; + bench --site all set-config -p maintenance_mode 0; + bench --site all set-config -p pause_scheduler 0; + volumes: + - sites:/home/frappe/frappe-bench/sites + networks: + - bench-network + + db: + image: mariadb:10.6 + deploy: + restart_policy: + condition: always + healthcheck: + test: mysqladmin ping -h localhost --password=${DB_ROOT_PASSWORD} + interval: 1s + retries: 20 + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed + environment: + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD} + - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD} + volumes: + - db-data:/var/lib/mysql + networks: + - bench-network + + redis-cache: + deploy: + restart_policy: + condition: always + image: redis:6.2-alpine + volumes: + - redis-cache-data:/data + networks: + - bench-network + healthcheck: + test: + - CMD + - redis-cli + - ping + interval: 5s + timeout: 5s + retries: 3 + + redis-queue: + deploy: + restart_policy: + condition: always + image: redis:6.2-alpine + volumes: + - redis-queue-data:/data + networks: + - bench-network + healthcheck: + test: + - CMD + - redis-cli + - ping + interval: 5s + timeout: 5s + retries: 3 + + redis-socketio: + deploy: + restart_policy: + condition: always + image: redis:6.2-alpine + volumes: + - redis-socketio-data:/data + networks: + - bench-network + healthcheck: + test: + - CMD + - redis-cli + - ping + interval: 5s + timeout: 5s + retries: 3 + +volumes: + db-data: + redis-cache-data: + redis-queue-data: + redis-socketio-data: + sites: + +networks: + bench-network: \ No newline at end of file diff --git a/apps/dokploy/templates/frappe-hr/index.ts b/apps/dokploy/templates/frappe-hr/index.ts new file mode 100644 index 000000000..7a2f501f5 --- /dev/null +++ b/apps/dokploy/templates/frappe-hr/index.ts @@ -0,0 +1,37 @@ +import { + type DomainSchema, + type Schema, + type Template, + generatePassword, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const dbRootPassword = generatePassword(32); + const adminPassword = generatePassword(32); + const mainDomain = generateRandomDomain(schema); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 8080, + serviceName: "frontend", + }, + ]; + + const envs = [ + `SITE_NAME=${mainDomain}`, + `ADMIN_PASSWORD=${adminPassword}`, + `DB_ROOT_PASSWORD=${dbRootPassword}`, + "MIGRATE=1", + "CREATE_SITE=1", + "CONFIGURE=1", + "REGENERATE_APPS_TXT=1", + "INSTALL_APP_ARGS=--install-app hrms", + "IMAGE_NAME=ghcr.io/frappe/hrms", + "VERSION=version-15", + "FRAPPE_SITE_NAME_HEADER=", + ]; + + return { envs, domains }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 98bbdbc5f..78cb39a28 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -1438,4 +1438,19 @@ export const templates: TemplateData[] = [ tags: ["sharing", "shortener", "url"], load: () => import("./shlink/index").then((m) => m.generate), }, + { + id: "frappe-hr", + name: "Frappe HR", + version: "version-15", + description: + "Feature rich HR & Payroll software. 100% FOSS and customizable.", + logo: "frappe-hr.svg", + links: { + github: "https://github.com/frappe/hrms", + docs: "https://docs.frappe.io/hr", + website: "https://frappe.io/hr", + }, + tags: ["hrms", "payroll", "leaves", "expenses", "attendance", "performace"], + load: () => import("./frappe-hr/index").then((m) => m.generate), + }, ]; From d8c5244d19e166f0d622bc4c804929bc7b5b37bc Mon Sep 17 00:00:00 2001 From: Tam Nguyen Date: Mon, 3 Feb 2025 11:47:57 +1100 Subject: [PATCH 02/19] fix(template): superset SQLALCHEMY_EXAMPLES_URI env + warning --- apps/dokploy/templates/superset/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/templates/superset/index.ts b/apps/dokploy/templates/superset/index.ts index 6132f978f..81538ac35 100644 --- a/apps/dokploy/templates/superset/index.ts +++ b/apps/dokploy/templates/superset/index.ts @@ -35,6 +35,11 @@ export function generate(schema: Schema): Template { { filePath: "./superset/superset_config.py", content: ` +""" +For more configuration options, see: +- https://superset.apache.org/docs/configuration/configuring-superset +""" + import os SECRET_KEY = os.getenv("SECRET_KEY") @@ -53,8 +58,12 @@ CACHE_CONFIG = { FILTER_STATE_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_filter_"} EXPLORE_FORM_DATA_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_explore_form_"} -SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@db:5432/{os.getenv('POSTGRES_DB')}" SQLALCHEMY_TRACK_MODIFICATIONS = True +SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@db:5432/{os.getenv('POSTGRES_DB')}" +# Uncomment if you want to load example data (using "superset load_examples") at the +# same location as your metadata postgresql instance. Otherwise, the default sqlite +# will be used, which will not persist in volume when restarting superset by default. +#SQLALCHEMY_EXAMPLES_URI = SQLALCHEMY_DATABASE_URI `.trim(), }, ]; From 0c8c0844b1c1cc1e29998220549272e71d693667 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:32:37 -0600 Subject: [PATCH 03/19] refactor: make request to dokploy server to proxy requests --- .../show-paid-container-monitoring.tsx | 115 +++++----------- .../paid/servers/show-paid-monitoring.tsx | 127 +++++++----------- .../services/application/[applicationId].tsx | 2 + .../services/compose/[composeId].tsx | 2 +- .../services/mariadb/[mariadbId].tsx | 2 +- .../[projectId]/services/mongo/[mongoId].tsx | 2 +- .../[projectId]/services/mysql/[mysqlId].tsx | 2 +- .../services/postgres/[postgresId].tsx | 2 +- .../[projectId]/services/redis/[redisId].tsx | 2 +- apps/dokploy/server/api/routers/admin.ts | 118 ++++++++++++++++ 10 files changed, 209 insertions(+), 165 deletions(-) diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx index c49be0c47..194bf7204 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx @@ -12,6 +12,7 @@ import { ContainerBlockChart } from "./container-block-chart"; import { ContainerCPUChart } from "./container-cpu-chart"; import { ContainerMemoryChart } from "./container-memory-chart"; import { ContainerNetworkChart } from "./container-network-chart"; +import { api } from "@/utils/api"; const REFRESH_INTERVALS = { "5000": "5 Seconds", @@ -70,84 +71,36 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { const [metrics, setMetrics] = useState( {} as ContainerMetric, ); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); const [dataPoints, setDataPoints] = useState("50"); const [refreshInterval, setRefreshInterval] = useState("5000"); - const fetchMetrics = async () => { - try { - const url = new URL(`${baseUrl}/metrics/containers`); - - // if (dataPoints !== "all") { - url.searchParams.append("limit", dataPoints); - // } - - if (!appName) { - throw new Error( - [ - "No Application Selected:", - "", - "Make Sure to select an application to monitor.", - ].join("\n"), - ); - } - - url.searchParams.append("appName", appName); - - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - throw new Error( - `Error ${response.status}: ${response.statusText}. Please verify that the application "${appName}" is running and this service is included in the monitoring configuration.`, - ); - } - - const data = await response.json(); - if (!Array.isArray(data) || data.length === 0) { - throw new Error( - [ - `No monitoring data available for "${appName}". This could be because:`, - "", - "1. The container was recently started - wait a few minutes for data to be collected", - "2. The container is not running - verify its status", - "3. The service is not included in your monitoring configuration", - ].join("\n"), - ); - } - - setHistoricalData(data); - setMetrics(data[data.length - 1]); - setError(null); - } catch (err) { - setError( - err instanceof Error - ? err.message - : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly.", - ); - } finally { - setIsLoading(false); - } - }; + const { + data, + isLoading, + error: queryError, + } = api.admin.getContainerMetrics.useQuery( + { + url: baseUrl, + token, + dataPoints, + appName, + }, + { + refetchInterval: + dataPoints === "all" ? undefined : Number.parseInt(refreshInterval), + enabled: !!appName, + }, + ); useEffect(() => { - fetchMetrics(); + if (!data) return; - if (dataPoints === "all") { - return; - } - - const interval = setInterval(() => { - fetchMetrics(); - }, Number(refreshInterval)); - - return () => clearInterval(interval); - }, [dataPoints, appName, token, refreshInterval]); + // @ts-ignore + setHistoricalData(data); + // @ts-ignore + setMetrics(data[data.length - 1]); + }, [data]); if (isLoading) { return ( @@ -157,7 +110,7 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { ); } - if (error) { + if (queryError) { return (
@@ -166,7 +119,9 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { {appName}

- {error} + {queryError instanceof Error + ? queryError.message + : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}

URL: {baseUrl}

@@ -238,11 +193,11 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Memory Usage

- {metrics.Memory.percentage}% + {metrics?.Memory?.percentage}%

- {metrics.Memory.used} {metrics.Memory.unit} / {metrics.Memory.total}{" "} - {metrics.Memory.unit} + {metrics?.Memory?.used} {metrics?.Memory?.unit} /{" "} + {metrics?.Memory?.total} {metrics?.Memory?.unit}

@@ -252,8 +207,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Network I/O

- {metrics.Network.input} {metrics.Network.inputUnit} /{" "} - {metrics.Network.output} {metrics.Network.outputUnit} + {metrics?.Network?.input} {metrics?.Network?.inputUnit} /{" "} + {metrics?.Network?.output} {metrics?.Network?.outputUnit}

@@ -263,8 +218,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Block I/O

- {metrics.BlockIO.read} {metrics.BlockIO.readUnit} /{" "} - {metrics.BlockIO.write} {metrics.BlockIO.writeUnit} + {metrics?.BlockIO?.read} {metrics?.BlockIO?.readUnit} /{" "} + {metrics?.BlockIO?.write} {metrics?.BlockIO?.writeUnit}

diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx index 90148dfa3..096df998a 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx @@ -12,6 +12,7 @@ import { CPUChart } from "./cpu-chart"; import { DiskChart } from "./disk-chart"; import { MemoryChart } from "./memory-chart"; import { NetworkChart } from "./network-chart"; +import { api } from "@/utils/api"; const REFRESH_INTERVALS = { "5000": "5 Seconds", @@ -64,76 +65,56 @@ export const ShowPaidMonitoring = ({ }: Props) => { const [historicalData, setHistoricalData] = useState([]); const [metrics, setMetrics] = useState({} as SystemMetrics); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); const [dataPoints, setDataPoints] = useState("50"); const [refreshInterval, setRefreshInterval] = useState("5000"); - const fetchMetrics = async () => { - try { - const url = new URL(BASE_URL); - url.searchParams.append("limit", dataPoints); - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + const { + data, + isLoading, + error: queryError, + } = api.admin.getServerMetrics.useQuery( + { + url: BASE_URL, + token, + dataPoints, + }, + { + refetchInterval: + dataPoints === "all" ? undefined : Number.parseInt(refreshInterval), + enabled: true, + }, + ); - if (!response.ok) { - throw new Error( - `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`, - ); - } + useEffect(() => { + if (!data) return; - const data = await response.json(); - if (!Array.isArray(data) || data.length === 0) { - throw new Error( - [ - "No monitoring data available. This could be because:", - "", - "1. You don't have setup the monitoring service, you can do in web server section.", - "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.", - ].join("\n"), - ); - } + const formattedData = data.map((metric: SystemMetrics) => ({ + timestamp: metric.timestamp, + cpu: Number.parseFloat(metric.cpu), + cpuModel: metric.cpuModel, + cpuCores: metric.cpuCores, + cpuPhysicalCores: metric.cpuPhysicalCores, + cpuSpeed: metric.cpuSpeed, + os: metric.os, + distro: metric.distro, + kernel: metric.kernel, + arch: metric.arch, + memUsed: Number.parseFloat(metric.memUsed), + memUsedGB: Number.parseFloat(metric.memUsedGB), + memTotal: Number.parseFloat(metric.memTotal), + networkIn: Number.parseFloat(metric.networkIn), + networkOut: Number.parseFloat(metric.networkOut), + diskUsed: Number.parseFloat(metric.diskUsed), + totalDisk: Number.parseFloat(metric.totalDisk), + uptime: metric.uptime, + })); - const formattedData = data.map((metric: SystemMetrics) => ({ - timestamp: metric.timestamp, - cpu: Number.parseFloat(metric.cpu), - cpuModel: metric.cpuModel, - cpuCores: metric.cpuCores, - cpuPhysicalCores: metric.cpuPhysicalCores, - cpuSpeed: metric.cpuSpeed, - os: metric.os, - distro: metric.distro, - kernel: metric.kernel, - arch: metric.arch, - memUsed: Number.parseFloat(metric.memUsed), - memUsedGB: Number.parseFloat(metric.memUsedGB), - memTotal: Number.parseFloat(metric.memTotal), - networkIn: Number.parseFloat(metric.networkIn), - networkOut: Number.parseFloat(metric.networkOut), - diskUsed: Number.parseFloat(metric.diskUsed), - totalDisk: Number.parseFloat(metric.totalDisk), - uptime: metric.uptime, - })); - - // @ts-ignore - setHistoricalData(formattedData); - // @ts-ignore - setMetrics(formattedData[formattedData.length - 1] || {}); - setError(null); - } catch (err) { - setError( - err instanceof Error - ? err.message - : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly.", - ); - } finally { - setIsLoading(false); - } - }; + // @ts-ignore + setHistoricalData(formattedData); + // @ts-ignore + setMetrics(formattedData[formattedData.length - 1] || {}); + }, [data]); const formatUptime = (seconds: number): string => { const days = Math.floor(seconds / (24 * 60 * 60)); @@ -143,20 +124,6 @@ export const ShowPaidMonitoring = ({ return `${days}d ${hours}h ${minutes}m`; }; - useEffect(() => { - fetchMetrics(); - - if (dataPoints === "all") { - return; - } - - const interval = setInterval(() => { - fetchMetrics(); - }, Number(refreshInterval)); - - return () => clearInterval(interval); - }, [dataPoints, token, refreshInterval]); - if (isLoading) { return (
@@ -165,7 +132,7 @@ export const ShowPaidMonitoring = ({ ); } - if (error) { + if (queryError) { return (
@@ -173,7 +140,9 @@ export const ShowPaidMonitoring = ({ Error fetching metrics{" "}

- {error} + {queryError instanceof Error + ? queryError.message + : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}

URL: {BASE_URL}

diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx index 781854f8d..9d3ffe4fb 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx @@ -399,6 +399,8 @@ export async function getServerSideProps( applicationId: params?.applicationId, }); + await helpers.settings.isCloud.prefetch(); + return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx index b9ed1d8c7..c68402142 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -394,7 +394,7 @@ export async function getServerSideProps( await helpers.compose.one.fetch({ composeId: params?.composeId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx index d441b08c3..a2ee90512 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx @@ -343,7 +343,7 @@ export async function getServerSideProps( await helpers.mariadb.one.fetch({ mariadbId: params?.mariadbId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx index 7fdb1e773..4f3947c2a 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx @@ -345,7 +345,7 @@ export async function getServerSideProps( await helpers.mongo.one.fetch({ mongoId: params?.mongoId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx index ba4efecc3..baf7e6f8b 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx @@ -350,7 +350,7 @@ export async function getServerSideProps( await helpers.mysql.one.fetch({ mysqlId: params?.mysqlId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx index 047d1c571..e3fd8b444 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx @@ -346,7 +346,7 @@ export async function getServerSideProps( await helpers.postgres.one.fetch({ postgresId: params?.postgresId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx index 1b0416400..3421e759c 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx @@ -337,7 +337,7 @@ export async function getServerSideProps( await helpers.redis.one.fetch({ redisId: params?.redisId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index f10bc759a..ab1cd8e82 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -28,6 +28,7 @@ import { protectedProcedure, publicProcedure, } from "../trpc"; +import { z } from "zod"; export const adminRouter = createTRPCRouter({ one: adminProcedure.query(async ({ ctx }) => { @@ -169,4 +170,121 @@ export const adminRouter = createTRPCRouter({ metricsConfig: admin?.metricsConfig, }; }), + + getServerMetrics: protectedProcedure + .input( + z.object({ + url: z.string(), + token: z.string(), + dataPoints: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + try { + const url = new URL(input.url); + url.searchParams.append("limit", input.dataPoints); + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${input.token}`, + }, + }); + if (!response.ok) { + throw new Error( + `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`, + ); + } + + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) { + throw new Error( + [ + "No monitoring data available. This could be because:", + "", + "1. You don't have setup the monitoring service, you can do in web server section.", + "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.", + ].join("\n"), + ); + } + return data as { + cpu: string; + cpuModel: string; + cpuCores: number; + cpuPhysicalCores: number; + cpuSpeed: number; + os: string; + distro: string; + kernel: string; + arch: string; + memUsed: string; + memUsedGB: string; + memTotal: string; + uptime: number; + diskUsed: string; + totalDisk: string; + networkIn: string; + networkOut: string; + timestamp: string; + }[]; + } catch (error) { + throw error; + } + }), + getContainerMetrics: protectedProcedure + .input( + z.object({ + url: z.string(), + token: z.string(), + appName: z.string(), + dataPoints: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + try { + if (!input.appName) { + throw new Error( + [ + "No Application Selected:", + "", + "Make Sure to select an application to monitor.", + ].join("\n"), + ); + } + const url = new URL(`${input.url}/metrics/containers`); + url.searchParams.append("limit", input.dataPoints); + url.searchParams.append("appName", input.appName); + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${input.token}`, + }, + }); + if (!response.ok) { + throw new Error( + `Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`, + ); + } + + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) { + throw new Error( + [ + `No monitoring data available for "${input.appName}". This could be because:`, + "", + "1. The container was recently started - wait a few minutes for data to be collected", + "2. The container is not running - verify its status", + "3. The service is not included in your monitoring configuration", + ].join("\n"), + ); + } + return data as { + containerId: string; + containerName: string; + containerImage: string; + containerLabels: string; + containerCommand: string; + containerCreated: string; + }[]; + } catch (error) { + throw error; + } + }), }); From d1610855bbd878a73768c2b03a15f760eaed410a Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:32:45 -0600 Subject: [PATCH 04/19] refactor: lint --- .../paid/container/show-paid-container-monitoring.tsx | 2 +- .../dashboard/monitoring/paid/servers/show-paid-monitoring.tsx | 2 +- apps/dokploy/server/api/routers/admin.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx index 194bf7204..f45c73827 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx @@ -6,13 +6,13 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { api } from "@/utils/api"; import { Cpu, HardDrive, Loader2, MemoryStick, Network } from "lucide-react"; import { useEffect, useState } from "react"; import { ContainerBlockChart } from "./container-block-chart"; import { ContainerCPUChart } from "./container-cpu-chart"; import { ContainerMemoryChart } from "./container-memory-chart"; import { ContainerNetworkChart } from "./container-network-chart"; -import { api } from "@/utils/api"; const REFRESH_INTERVALS = { "5000": "5 Seconds", diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx index 096df998a..8f09ab121 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx @@ -5,6 +5,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { api } from "@/utils/api"; import { Clock, Cpu, HardDrive, Loader2, MemoryStick } from "lucide-react"; import type React from "react"; import { useEffect, useState } from "react"; @@ -12,7 +13,6 @@ import { CPUChart } from "./cpu-chart"; import { DiskChart } from "./disk-chart"; import { MemoryChart } from "./memory-chart"; import { NetworkChart } from "./network-chart"; -import { api } from "@/utils/api"; const REFRESH_INTERVALS = { "5000": "5 Seconds", diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index ab1cd8e82..31612fe06 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -22,13 +22,13 @@ import { } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; +import { z } from "zod"; import { adminProcedure, createTRPCRouter, protectedProcedure, publicProcedure, } from "../trpc"; -import { z } from "zod"; export const adminRouter = createTRPCRouter({ one: adminProcedure.query(async ({ ctx }) => { From f638f49ab6240db1658f18f16c9a76f177e9f6fe Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:33:52 -0600 Subject: [PATCH 05/19] refactor: use dokploy/monitoring tag image --- .github/workflows/deploy.yml | 2 +- packages/server/src/setup/monitoring-setup.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f5826da4f..86bf06286 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -98,5 +98,5 @@ jobs: file: ./Dockerfile.monitoring push: true tags: | - siumauricio/monitoring:${{ github.ref_name == 'main' && 'latest' || 'canary' }} + dokploy/monitoring:${{ github.ref_name == 'main' && 'latest' || 'canary' }} platforms: linux/amd64 diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts index e7cd941d2..bf50cdb9b 100644 --- a/packages/server/src/setup/monitoring-setup.ts +++ b/packages/server/src/setup/monitoring-setup.ts @@ -9,7 +9,7 @@ export const setupMonitoring = async (serverId: string) => { const server = await findServerById(serverId); const containerName = "mauricio-monitoring"; - const imageName = "siumauricio/monitoring:canary"; + const imageName = "dokploy/monitoring:canary"; const settings: ContainerCreateOptions = { name: containerName, @@ -74,7 +74,7 @@ export const setupWebMonitoring = async (adminId: string) => { const admin = await findAdminById(adminId); const containerName = "mauricio-monitoring"; - const imageName = "siumauricio/monitoring:canary"; + const imageName = "dokploy/monitoring:canary"; const settings: ContainerCreateOptions = { name: containerName, From d492ff87f261062ed608ac6243a11965e6537cfd Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:35:36 -0600 Subject: [PATCH 06/19] Fix/monitoring (#1270) * refactor: make request to dokploy server to proxy requests * refactor: lint * refactor: use dokploy/monitoring tag image --- .github/workflows/deploy.yml | 2 +- .../show-paid-container-monitoring.tsx | 115 +++++----------- .../paid/servers/show-paid-monitoring.tsx | 127 +++++++----------- .../services/application/[applicationId].tsx | 2 + .../services/compose/[composeId].tsx | 2 +- .../services/mariadb/[mariadbId].tsx | 2 +- .../[projectId]/services/mongo/[mongoId].tsx | 2 +- .../[projectId]/services/mysql/[mysqlId].tsx | 2 +- .../services/postgres/[postgresId].tsx | 2 +- .../[projectId]/services/redis/[redisId].tsx | 2 +- apps/dokploy/server/api/routers/admin.ts | 118 ++++++++++++++++ packages/server/src/setup/monitoring-setup.ts | 4 +- 12 files changed, 212 insertions(+), 168 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f5826da4f..86bf06286 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -98,5 +98,5 @@ jobs: file: ./Dockerfile.monitoring push: true tags: | - siumauricio/monitoring:${{ github.ref_name == 'main' && 'latest' || 'canary' }} + dokploy/monitoring:${{ github.ref_name == 'main' && 'latest' || 'canary' }} platforms: linux/amd64 diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx index c49be0c47..f45c73827 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx @@ -6,6 +6,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { api } from "@/utils/api"; import { Cpu, HardDrive, Loader2, MemoryStick, Network } from "lucide-react"; import { useEffect, useState } from "react"; import { ContainerBlockChart } from "./container-block-chart"; @@ -70,84 +71,36 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { const [metrics, setMetrics] = useState( {} as ContainerMetric, ); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); const [dataPoints, setDataPoints] = useState("50"); const [refreshInterval, setRefreshInterval] = useState("5000"); - const fetchMetrics = async () => { - try { - const url = new URL(`${baseUrl}/metrics/containers`); - - // if (dataPoints !== "all") { - url.searchParams.append("limit", dataPoints); - // } - - if (!appName) { - throw new Error( - [ - "No Application Selected:", - "", - "Make Sure to select an application to monitor.", - ].join("\n"), - ); - } - - url.searchParams.append("appName", appName); - - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - throw new Error( - `Error ${response.status}: ${response.statusText}. Please verify that the application "${appName}" is running and this service is included in the monitoring configuration.`, - ); - } - - const data = await response.json(); - if (!Array.isArray(data) || data.length === 0) { - throw new Error( - [ - `No monitoring data available for "${appName}". This could be because:`, - "", - "1. The container was recently started - wait a few minutes for data to be collected", - "2. The container is not running - verify its status", - "3. The service is not included in your monitoring configuration", - ].join("\n"), - ); - } - - setHistoricalData(data); - setMetrics(data[data.length - 1]); - setError(null); - } catch (err) { - setError( - err instanceof Error - ? err.message - : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly.", - ); - } finally { - setIsLoading(false); - } - }; + const { + data, + isLoading, + error: queryError, + } = api.admin.getContainerMetrics.useQuery( + { + url: baseUrl, + token, + dataPoints, + appName, + }, + { + refetchInterval: + dataPoints === "all" ? undefined : Number.parseInt(refreshInterval), + enabled: !!appName, + }, + ); useEffect(() => { - fetchMetrics(); + if (!data) return; - if (dataPoints === "all") { - return; - } - - const interval = setInterval(() => { - fetchMetrics(); - }, Number(refreshInterval)); - - return () => clearInterval(interval); - }, [dataPoints, appName, token, refreshInterval]); + // @ts-ignore + setHistoricalData(data); + // @ts-ignore + setMetrics(data[data.length - 1]); + }, [data]); if (isLoading) { return ( @@ -157,7 +110,7 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { ); } - if (error) { + if (queryError) { return (
@@ -166,7 +119,9 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { {appName}

- {error} + {queryError instanceof Error + ? queryError.message + : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}

URL: {baseUrl}

@@ -238,11 +193,11 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Memory Usage

- {metrics.Memory.percentage}% + {metrics?.Memory?.percentage}%

- {metrics.Memory.used} {metrics.Memory.unit} / {metrics.Memory.total}{" "} - {metrics.Memory.unit} + {metrics?.Memory?.used} {metrics?.Memory?.unit} /{" "} + {metrics?.Memory?.total} {metrics?.Memory?.unit}

@@ -252,8 +207,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Network I/O

- {metrics.Network.input} {metrics.Network.inputUnit} /{" "} - {metrics.Network.output} {metrics.Network.outputUnit} + {metrics?.Network?.input} {metrics?.Network?.inputUnit} /{" "} + {metrics?.Network?.output} {metrics?.Network?.outputUnit}

@@ -263,8 +218,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Block I/O

- {metrics.BlockIO.read} {metrics.BlockIO.readUnit} /{" "} - {metrics.BlockIO.write} {metrics.BlockIO.writeUnit} + {metrics?.BlockIO?.read} {metrics?.BlockIO?.readUnit} /{" "} + {metrics?.BlockIO?.write} {metrics?.BlockIO?.writeUnit}

diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx index 90148dfa3..8f09ab121 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx @@ -5,6 +5,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { api } from "@/utils/api"; import { Clock, Cpu, HardDrive, Loader2, MemoryStick } from "lucide-react"; import type React from "react"; import { useEffect, useState } from "react"; @@ -64,76 +65,56 @@ export const ShowPaidMonitoring = ({ }: Props) => { const [historicalData, setHistoricalData] = useState([]); const [metrics, setMetrics] = useState({} as SystemMetrics); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); const [dataPoints, setDataPoints] = useState("50"); const [refreshInterval, setRefreshInterval] = useState("5000"); - const fetchMetrics = async () => { - try { - const url = new URL(BASE_URL); - url.searchParams.append("limit", dataPoints); - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + const { + data, + isLoading, + error: queryError, + } = api.admin.getServerMetrics.useQuery( + { + url: BASE_URL, + token, + dataPoints, + }, + { + refetchInterval: + dataPoints === "all" ? undefined : Number.parseInt(refreshInterval), + enabled: true, + }, + ); - if (!response.ok) { - throw new Error( - `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`, - ); - } + useEffect(() => { + if (!data) return; - const data = await response.json(); - if (!Array.isArray(data) || data.length === 0) { - throw new Error( - [ - "No monitoring data available. This could be because:", - "", - "1. You don't have setup the monitoring service, you can do in web server section.", - "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.", - ].join("\n"), - ); - } + const formattedData = data.map((metric: SystemMetrics) => ({ + timestamp: metric.timestamp, + cpu: Number.parseFloat(metric.cpu), + cpuModel: metric.cpuModel, + cpuCores: metric.cpuCores, + cpuPhysicalCores: metric.cpuPhysicalCores, + cpuSpeed: metric.cpuSpeed, + os: metric.os, + distro: metric.distro, + kernel: metric.kernel, + arch: metric.arch, + memUsed: Number.parseFloat(metric.memUsed), + memUsedGB: Number.parseFloat(metric.memUsedGB), + memTotal: Number.parseFloat(metric.memTotal), + networkIn: Number.parseFloat(metric.networkIn), + networkOut: Number.parseFloat(metric.networkOut), + diskUsed: Number.parseFloat(metric.diskUsed), + totalDisk: Number.parseFloat(metric.totalDisk), + uptime: metric.uptime, + })); - const formattedData = data.map((metric: SystemMetrics) => ({ - timestamp: metric.timestamp, - cpu: Number.parseFloat(metric.cpu), - cpuModel: metric.cpuModel, - cpuCores: metric.cpuCores, - cpuPhysicalCores: metric.cpuPhysicalCores, - cpuSpeed: metric.cpuSpeed, - os: metric.os, - distro: metric.distro, - kernel: metric.kernel, - arch: metric.arch, - memUsed: Number.parseFloat(metric.memUsed), - memUsedGB: Number.parseFloat(metric.memUsedGB), - memTotal: Number.parseFloat(metric.memTotal), - networkIn: Number.parseFloat(metric.networkIn), - networkOut: Number.parseFloat(metric.networkOut), - diskUsed: Number.parseFloat(metric.diskUsed), - totalDisk: Number.parseFloat(metric.totalDisk), - uptime: metric.uptime, - })); - - // @ts-ignore - setHistoricalData(formattedData); - // @ts-ignore - setMetrics(formattedData[formattedData.length - 1] || {}); - setError(null); - } catch (err) { - setError( - err instanceof Error - ? err.message - : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly.", - ); - } finally { - setIsLoading(false); - } - }; + // @ts-ignore + setHistoricalData(formattedData); + // @ts-ignore + setMetrics(formattedData[formattedData.length - 1] || {}); + }, [data]); const formatUptime = (seconds: number): string => { const days = Math.floor(seconds / (24 * 60 * 60)); @@ -143,20 +124,6 @@ export const ShowPaidMonitoring = ({ return `${days}d ${hours}h ${minutes}m`; }; - useEffect(() => { - fetchMetrics(); - - if (dataPoints === "all") { - return; - } - - const interval = setInterval(() => { - fetchMetrics(); - }, Number(refreshInterval)); - - return () => clearInterval(interval); - }, [dataPoints, token, refreshInterval]); - if (isLoading) { return (
@@ -165,7 +132,7 @@ export const ShowPaidMonitoring = ({ ); } - if (error) { + if (queryError) { return (
@@ -173,7 +140,9 @@ export const ShowPaidMonitoring = ({ Error fetching metrics{" "}

- {error} + {queryError instanceof Error + ? queryError.message + : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}

URL: {BASE_URL}

diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx index 781854f8d..9d3ffe4fb 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx @@ -399,6 +399,8 @@ export async function getServerSideProps( applicationId: params?.applicationId, }); + await helpers.settings.isCloud.prefetch(); + return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx index b9ed1d8c7..c68402142 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -394,7 +394,7 @@ export async function getServerSideProps( await helpers.compose.one.fetch({ composeId: params?.composeId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx index d441b08c3..a2ee90512 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx @@ -343,7 +343,7 @@ export async function getServerSideProps( await helpers.mariadb.one.fetch({ mariadbId: params?.mariadbId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx index 7fdb1e773..4f3947c2a 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx @@ -345,7 +345,7 @@ export async function getServerSideProps( await helpers.mongo.one.fetch({ mongoId: params?.mongoId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx index ba4efecc3..baf7e6f8b 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx @@ -350,7 +350,7 @@ export async function getServerSideProps( await helpers.mysql.one.fetch({ mysqlId: params?.mysqlId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx index 047d1c571..e3fd8b444 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx @@ -346,7 +346,7 @@ export async function getServerSideProps( await helpers.postgres.one.fetch({ postgresId: params?.postgresId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx index 1b0416400..3421e759c 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx @@ -337,7 +337,7 @@ export async function getServerSideProps( await helpers.redis.one.fetch({ redisId: params?.redisId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index f10bc759a..31612fe06 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -22,6 +22,7 @@ import { } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; +import { z } from "zod"; import { adminProcedure, createTRPCRouter, @@ -169,4 +170,121 @@ export const adminRouter = createTRPCRouter({ metricsConfig: admin?.metricsConfig, }; }), + + getServerMetrics: protectedProcedure + .input( + z.object({ + url: z.string(), + token: z.string(), + dataPoints: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + try { + const url = new URL(input.url); + url.searchParams.append("limit", input.dataPoints); + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${input.token}`, + }, + }); + if (!response.ok) { + throw new Error( + `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`, + ); + } + + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) { + throw new Error( + [ + "No monitoring data available. This could be because:", + "", + "1. You don't have setup the monitoring service, you can do in web server section.", + "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.", + ].join("\n"), + ); + } + return data as { + cpu: string; + cpuModel: string; + cpuCores: number; + cpuPhysicalCores: number; + cpuSpeed: number; + os: string; + distro: string; + kernel: string; + arch: string; + memUsed: string; + memUsedGB: string; + memTotal: string; + uptime: number; + diskUsed: string; + totalDisk: string; + networkIn: string; + networkOut: string; + timestamp: string; + }[]; + } catch (error) { + throw error; + } + }), + getContainerMetrics: protectedProcedure + .input( + z.object({ + url: z.string(), + token: z.string(), + appName: z.string(), + dataPoints: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + try { + if (!input.appName) { + throw new Error( + [ + "No Application Selected:", + "", + "Make Sure to select an application to monitor.", + ].join("\n"), + ); + } + const url = new URL(`${input.url}/metrics/containers`); + url.searchParams.append("limit", input.dataPoints); + url.searchParams.append("appName", input.appName); + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${input.token}`, + }, + }); + if (!response.ok) { + throw new Error( + `Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`, + ); + } + + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) { + throw new Error( + [ + `No monitoring data available for "${input.appName}". This could be because:`, + "", + "1. The container was recently started - wait a few minutes for data to be collected", + "2. The container is not running - verify its status", + "3. The service is not included in your monitoring configuration", + ].join("\n"), + ); + } + return data as { + containerId: string; + containerName: string; + containerImage: string; + containerLabels: string; + containerCommand: string; + containerCreated: string; + }[]; + } catch (error) { + throw error; + } + }), }); diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts index e7cd941d2..bf50cdb9b 100644 --- a/packages/server/src/setup/monitoring-setup.ts +++ b/packages/server/src/setup/monitoring-setup.ts @@ -9,7 +9,7 @@ export const setupMonitoring = async (serverId: string) => { const server = await findServerById(serverId); const containerName = "mauricio-monitoring"; - const imageName = "siumauricio/monitoring:canary"; + const imageName = "dokploy/monitoring:canary"; const settings: ContainerCreateOptions = { name: containerName, @@ -74,7 +74,7 @@ export const setupWebMonitoring = async (adminId: string) => { const admin = await findAdminById(adminId); const containerName = "mauricio-monitoring"; - const imageName = "siumauricio/monitoring:canary"; + const imageName = "dokploy/monitoring:canary"; const settings: ContainerCreateOptions = { name: containerName, From f9b4035c203e2d288b315e6bc63b2b694bc34af5 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:41:07 -0600 Subject: [PATCH 07/19] refactor: use canary in development or canary tags --- packages/server/src/services/docker.ts | 6 ++++- packages/server/src/setup/monitoring-setup.ts | 23 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index b7a5c4403..597c03fa1 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -58,7 +58,11 @@ export const getContainers = async (serverId?: string | null) => { serverId, }; }) - .filter((container) => !container.name.includes("dokploy")); + .filter( + (container) => + !container.name.includes("dokploy") || + container.name.includes("dokploy-monitoring"), + ); return containers; } catch (error) { diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts index bf50cdb9b..1a22142f3 100644 --- a/packages/server/src/setup/monitoring-setup.ts +++ b/packages/server/src/setup/monitoring-setup.ts @@ -4,12 +4,20 @@ import { findAdminById } from "../services/admin"; import { pullImage, pullRemoteImage } from "../utils/docker/utils"; import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; import { getRemoteDocker } from "../utils/servers/remote-docker"; +import { getDokployImageTag } from "../services/settings"; export const setupMonitoring = async (serverId: string) => { const server = await findServerById(serverId); - const containerName = "mauricio-monitoring"; - const imageName = "dokploy/monitoring:canary"; + const containerName = "dokploy-monitoring"; + let imageName = "dokploy/monitoring:latest"; + + if ( + getDokployImageTag() !== "latest" || + process.env.NODE_ENV === "development" + ) { + imageName = "dokploy/monitoring:canary"; + } const settings: ContainerCreateOptions = { name: containerName, @@ -73,8 +81,15 @@ export const setupMonitoring = async (serverId: string) => { export const setupWebMonitoring = async (adminId: string) => { const admin = await findAdminById(adminId); - const containerName = "mauricio-monitoring"; - const imageName = "dokploy/monitoring:canary"; + const containerName = "dokploy-monitoring"; + let imageName = "dokploy/monitoring:latest"; + + if ( + getDokployImageTag() !== "latest" || + process.env.NODE_ENV === "development" + ) { + imageName = "dokploy/monitoring:canary"; + } const settings: ContainerCreateOptions = { name: containerName, From d1aaeb9a7b6f77379c243319444ef62200f85912 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:43:28 -0600 Subject: [PATCH 08/19] refactor: adjust logic --- packages/server/src/setup/monitoring-setup.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts index 1a22142f3..e7b39b0b3 100644 --- a/packages/server/src/setup/monitoring-setup.ts +++ b/packages/server/src/setup/monitoring-setup.ts @@ -1,10 +1,11 @@ import { findServerById } from "@dokploy/server/services/server"; import type { ContainerCreateOptions } from "dockerode"; import { findAdminById } from "../services/admin"; +import { getDokployImageTag } from "../services/settings"; import { pullImage, pullRemoteImage } from "../utils/docker/utils"; import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; import { getRemoteDocker } from "../utils/servers/remote-docker"; -import { getDokployImageTag } from "../services/settings"; +import { IS_CLOUD } from "../constants"; export const setupMonitoring = async (serverId: string) => { const server = await findServerById(serverId); @@ -13,8 +14,9 @@ export const setupMonitoring = async (serverId: string) => { let imageName = "dokploy/monitoring:latest"; if ( - getDokployImageTag() !== "latest" || - process.env.NODE_ENV === "development" + (getDokployImageTag() !== "latest" || + process.env.NODE_ENV === "development") && + !IS_CLOUD ) { imageName = "dokploy/monitoring:canary"; } @@ -85,8 +87,9 @@ export const setupWebMonitoring = async (adminId: string) => { let imageName = "dokploy/monitoring:latest"; if ( - getDokployImageTag() !== "latest" || - process.env.NODE_ENV === "development" + (getDokployImageTag() !== "latest" || + process.env.NODE_ENV === "development") && + !IS_CLOUD ) { imageName = "dokploy/monitoring:canary"; } From 51851567db3c5d952153046fece28250a0ec6034 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:49:30 -0600 Subject: [PATCH 09/19] refactor: update name --- .../show-paid-container-monitoring.tsx | 91 ++++++++++--------- .../paid/servers/show-paid-monitoring.tsx | 91 ++++++++++--------- packages/server/src/setup/monitoring-setup.ts | 2 +- 3 files changed, 97 insertions(+), 87 deletions(-) diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx index f45c73827..3636a391a 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx @@ -131,54 +131,59 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { return ( <> -
+

Container Monitoring

-
- Data points: - - - Refresh interval: - - +
+
+ Data points: + +
+ +
+ + Refresh interval: + + +
{/* Stats Cards */} -
+
@@ -236,13 +241,13 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Name

-

{metrics.Name}

+

{metrics.Name}

{/* Charts Grid */} -
+
diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx index 8f09ab121..043b5c625 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx @@ -151,53 +151,58 @@ export const ShowPaidMonitoring = ({ } return ( -
-
+
+

System Monitoring

-
- Data points: - - - Refresh interval: - - +
+
+ Data points: + +
+ +
+ + Refresh interval: + + +
{/* Stats Cards */} -
+
@@ -260,7 +265,7 @@ export const ShowPaidMonitoring = ({
{/* Charts Grid */} -
+
diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts index e7b39b0b3..f72b22447 100644 --- a/packages/server/src/setup/monitoring-setup.ts +++ b/packages/server/src/setup/monitoring-setup.ts @@ -1,11 +1,11 @@ import { findServerById } from "@dokploy/server/services/server"; import type { ContainerCreateOptions } from "dockerode"; +import { IS_CLOUD } from "../constants"; import { findAdminById } from "../services/admin"; import { getDokployImageTag } from "../services/settings"; import { pullImage, pullRemoteImage } from "../utils/docker/utils"; import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; import { getRemoteDocker } from "../utils/servers/remote-docker"; -import { IS_CLOUD } from "../constants"; export const setupMonitoring = async (serverId: string) => { const server = await findServerById(serverId); From d84a22fa726420e1be7fdb8765bf980480e3a618 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:23:49 -0600 Subject: [PATCH 10/19] feat: add monitoring arm-amd version --- .github/workflows/deploy.yml | 23 ------ .github/workflows/monitoring.yml | 117 +++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/monitoring.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 86bf06286..bb7721468 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -77,26 +77,3 @@ jobs: tags: | siumauricio/server:${{ github.ref_name == 'main' && 'latest' || 'canary' }} platforms: linux/amd64 - - build-and-push-monitoring-image: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile.monitoring - push: true - tags: | - dokploy/monitoring:${{ github.ref_name == 'main' && 'latest' || 'canary' }} - platforms: linux/amd64 diff --git a/.github/workflows/monitoring.yml b/.github/workflows/monitoring.yml new file mode 100644 index 000000000..47325cdd7 --- /dev/null +++ b/.github/workflows/monitoring.yml @@ -0,0 +1,117 @@ +name: Dokploy Monitoring Build + +on: + push: + branches: [main, canary] + +env: + IMAGE_NAME: dokploy/monitoring + +jobs: + docker-amd: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set tag + id: meta + run: | + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + TAG="latest" + elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then + TAG="canary" + else + TAG="feature" + fi + echo "tags=${IMAGE_NAME}:${TAG}-amd64" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + docker-arm: + runs-on: ubuntu-24.04-arm + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set tag and version + id: meta + run: | + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + TAG="latest" + elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then + TAG="canary" + else + TAG="feature" + fi + echo "tags=${IMAGE_NAME}:${TAG}-arm64" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + + combine-manifests: + needs: [docker-amd, docker-arm] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create and push manifests + run: | + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + VERSION=$(node -p "require('./apps/dokploy/package.json').version") + TAG="latest" + + docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \ + ${IMAGE_NAME}:${TAG}-amd64 \ + ${IMAGE_NAME}:${TAG}-arm64 + + elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then + TAG="canary" + docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \ + ${IMAGE_NAME}:${TAG}-amd64 \ + ${IMAGE_NAME}:${TAG}-arm64 + + else + TAG="feature" + docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \ + ${IMAGE_NAME}:${TAG}-amd64 \ + ${IMAGE_NAME}:${TAG}-arm64 + fi From 48a8c6021d0e1d4ae07e6ef97399828a94027a0c Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:25:45 -0600 Subject: [PATCH 11/19] chore: bump version --- .github/workflows/monitoring.yml | 5 +++-- apps/dokploy/package.json | 2 +- packages/server/src/setup/monitoring-setup.ts | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/monitoring.yml b/.github/workflows/monitoring.yml index 47325cdd7..378b019d4 100644 --- a/.github/workflows/monitoring.yml +++ b/.github/workflows/monitoring.yml @@ -39,6 +39,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . + file: ./Dockerfile.monitoring platforms: linux/amd64 push: true tags: ${{ steps.meta.outputs.tags }} @@ -57,7 +58,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set tag and version + - name: Set id: meta run: | if [ "${{ github.ref }}" = "refs/heads/main" ]; then @@ -73,6 +74,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . + file: ./Dockerfile.monitoring platforms: linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} @@ -96,7 +98,6 @@ jobs: - name: Create and push manifests run: | if [ "${{ github.ref }}" = "refs/heads/main" ]; then - VERSION=$(node -p "require('./apps/dokploy/package.json').version") TAG="latest" docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \ diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index ff93e575b..b35953ebe 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.17.9", + "version": "v0.18.0", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts index 1093d9ca2..f72b22447 100644 --- a/packages/server/src/setup/monitoring-setup.ts +++ b/packages/server/src/setup/monitoring-setup.ts @@ -10,7 +10,6 @@ import { getRemoteDocker } from "../utils/servers/remote-docker"; export const setupMonitoring = async (serverId: string) => { const server = await findServerById(serverId); - const containerName = "dokploy-monitoring"; let imageName = "dokploy/monitoring:latest"; From 5d6847b97090079f32d588b93b471cbdad8d05a8 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:18:21 -0600 Subject: [PATCH 12/19] fix: add missing command --- packages/server/src/utils/builders/compose.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index af5607282..5e155271c 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -154,7 +154,6 @@ const sanitizeCommand = (command: string) => { export const createCommand = (compose: ComposeNested) => { const { composeType, appName, sourceType } = compose; - if (compose.command) { return `${sanitizeCommand(compose.command)}`; } @@ -163,7 +162,9 @@ export const createCommand = (compose: ComposeNested) => { sourceType === "raw" ? "docker-compose.yml" : compose.composePath; let command = ""; - if (composeType === "stack") { + if (composeType === "docker-compose") { + command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`; + } else if (composeType === "stack") { command = `stack deploy -c ${path} ${appName} --prune`; } From 077f47f2d8414f14baf91255aefa3a0d82ca3aee Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:18:47 -0600 Subject: [PATCH 13/19] chore: bump version --- apps/dokploy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index b35953ebe..14f7d27c5 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.18.0", + "version": "v0.18.1", "private": true, "license": "Apache-2.0", "type": "module", From 20eb6d7985188d39d40ab6d732e9df2287910bb6 Mon Sep 17 00:00:00 2001 From: Franco Gamonal Date: Mon, 3 Feb 2025 10:27:35 -0300 Subject: [PATCH 14/19] Revert "feat: Updated PocketBase version to 0.25.0" --- apps/dokploy/templates/pocketbase/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/templates/pocketbase/docker-compose.yml b/apps/dokploy/templates/pocketbase/docker-compose.yml index 485c0f85b..cf5e94f1b 100644 --- a/apps/dokploy/templates/pocketbase/docker-compose.yml +++ b/apps/dokploy/templates/pocketbase/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: pocketbase: - image: spectado/pocketbase:0.25.0 + image: spectado/pocketbase:0.23.3 restart: unless-stopped volumes: - /etc/dokploy/templates/${HASH}/data:/pb_data From 9d988c9a9bc1bd22db1a2c722bf8d7f70a305e21 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:49:20 -0600 Subject: [PATCH 15/19] Update package.json --- apps/dokploy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 14f7d27c5..213c90739 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.18.1", + "version": "v0.18.2", "private": true, "license": "Apache-2.0", "type": "module", From 65780ee8528cfb78ba8d124884c83de52c7c24a7 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 4 Feb 2025 13:57:49 +0530 Subject: [PATCH 16/19] feat: make db configurable --- apps/dokploy/templates/frappe-hr/docker-compose.yml | 4 +++- apps/dokploy/templates/frappe-hr/index.ts | 2 ++ apps/dokploy/templates/listmonk/index.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/templates/frappe-hr/docker-compose.yml b/apps/dokploy/templates/frappe-hr/docker-compose.yml index 3ed923ea0..29ba0069f 100644 --- a/apps/dokploy/templates/frappe-hr/docker-compose.yml +++ b/apps/dokploy/templates/frappe-hr/docker-compose.yml @@ -189,7 +189,7 @@ services: bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; bench set-config -gp socketio_port $$SOCKETIO_PORT; environment: - DB_HOST: db + DB_HOST: "${DB_HOST:-db}" DB_PORT: "3306" REDIS_CACHE: redis-cache:6379 REDIS_QUEUE: redis-queue:6379 @@ -262,6 +262,8 @@ services: db: image: mariadb:10.6 deploy: + mode: replicated + replicas: ${ENABLE_DB:-0} restart_policy: condition: always healthcheck: diff --git a/apps/dokploy/templates/frappe-hr/index.ts b/apps/dokploy/templates/frappe-hr/index.ts index 7a2f501f5..1e6b94745 100644 --- a/apps/dokploy/templates/frappe-hr/index.ts +++ b/apps/dokploy/templates/frappe-hr/index.ts @@ -24,6 +24,8 @@ export function generate(schema: Schema): Template { `ADMIN_PASSWORD=${adminPassword}`, `DB_ROOT_PASSWORD=${dbRootPassword}`, "MIGRATE=1", + "ENABLE_DB=1", + "DB_HOST=db", "CREATE_SITE=1", "CONFIGURE=1", "REGENERATE_APPS_TXT=1", diff --git a/apps/dokploy/templates/listmonk/index.ts b/apps/dokploy/templates/listmonk/index.ts index 113b3862f..2a25efcaa 100644 --- a/apps/dokploy/templates/listmonk/index.ts +++ b/apps/dokploy/templates/listmonk/index.ts @@ -17,7 +17,7 @@ export function generate(schema: Schema): Template { ]; const envs = [ - `# visit the page to setup your super admin user`, + "# visit the page to setup your super admin user", "# check config.toml in Advanced / Volumes for more options", ]; From 82cf0643d72b376b9432f3c600df67335dde0440 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 4 Feb 2025 14:15:47 +0530 Subject: [PATCH 17/19] fix: site volume configurable --- apps/dokploy/templates/frappe-hr/docker-compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/dokploy/templates/frappe-hr/docker-compose.yml b/apps/dokploy/templates/frappe-hr/docker-compose.yml index 29ba0069f..f4d13b5f6 100644 --- a/apps/dokploy/templates/frappe-hr/docker-compose.yml +++ b/apps/dokploy/templates/frappe-hr/docker-compose.yml @@ -343,6 +343,10 @@ volumes: redis-queue-data: redis-socketio-data: sites: + driver_opts: + type: "${SITE_VOLUME_TYPE}" + o: "${SITE_VOLUME_OPTS}" + device: "${SITE_VOLUME_DEV}" networks: bench-network: \ No newline at end of file From 46411a5f4e28c12f2a6ed8f63329f461fa11cacd Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 4 Feb 2025 14:30:55 +0530 Subject: [PATCH 18/19] fix: create site should use configured db --- apps/dokploy/templates/frappe-hr/docker-compose.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/templates/frappe-hr/docker-compose.yml b/apps/dokploy/templates/frappe-hr/docker-compose.yml index f4d13b5f6..a7ce9b262 100644 --- a/apps/dokploy/templates/frappe-hr/docker-compose.yml +++ b/apps/dokploy/templates/frappe-hr/docker-compose.yml @@ -210,7 +210,7 @@ services: entrypoint: ["bash", "-c"] command: - > - wait-for-it -t 120 db:3306; + wait-for-it -t 120 $$DB_HOST:$$DB_PORT; wait-for-it -t 120 redis-cache:6379; wait-for-it -t 120 redis-queue:6379; export start=`date +%s`; @@ -231,10 +231,12 @@ services: volumes: - sites:/home/frappe/frappe-bench/sites environment: + SITE_NAME: ${SITE_NAME} ADMIN_PASSWORD: ${ADMIN_PASSWORD} + DB_HOST: ${DB_HOST:-db} + DB_PORT: "${DB_PORT:-3306}" DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} INSTALL_APP_ARGS: ${INSTALL_APP_ARGS} - SITE_NAME: ${SITE_NAME} networks: - bench-network From 48642979c56a0ee7a79352301839457f9973bcda Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 4 Feb 2025 17:17:43 +0530 Subject: [PATCH 19/19] chore: make erpnext template more configurable --- apps/dokploy/templates/erpnext/docker-compose.yml | 14 +++++++++++--- apps/dokploy/templates/erpnext/index.ts | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/templates/erpnext/docker-compose.yml b/apps/dokploy/templates/erpnext/docker-compose.yml index def916be1..28cd8f6ab 100644 --- a/apps/dokploy/templates/erpnext/docker-compose.yml +++ b/apps/dokploy/templates/erpnext/docker-compose.yml @@ -189,7 +189,7 @@ services: bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; bench set-config -gp socketio_port $$SOCKETIO_PORT; environment: - DB_HOST: db + DB_HOST: "${DB_HOST:-db}" DB_PORT: "3306" REDIS_CACHE: redis-cache:6379 REDIS_QUEUE: redis-queue:6379 @@ -210,7 +210,7 @@ services: entrypoint: ["bash", "-c"] command: - > - wait-for-it -t 120 db:3306; + wait-for-it -t 120 $$DB_HOST:$$DB_PORT; wait-for-it -t 120 redis-cache:6379; wait-for-it -t 120 redis-queue:6379; export start=`date +%s`; @@ -231,10 +231,12 @@ services: volumes: - sites:/home/frappe/frappe-bench/sites environment: + SITE_NAME: ${SITE_NAME} ADMIN_PASSWORD: ${ADMIN_PASSWORD} + DB_HOST: ${DB_HOST:-db} + DB_PORT: "${DB_PORT:-3306}" DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} INSTALL_APP_ARGS: ${INSTALL_APP_ARGS} - SITE_NAME: ${SITE_NAME} networks: - bench-network @@ -262,6 +264,8 @@ services: db: image: mariadb:10.6 deploy: + mode: replicated + replicas: ${ENABLE_DB:-0} restart_policy: condition: always healthcheck: @@ -341,6 +345,10 @@ volumes: redis-queue-data: redis-socketio-data: sites: + driver_opts: + type: "${SITE_VOLUME_TYPE}" + o: "${SITE_VOLUME_OPTS}" + device: "${SITE_VOLUME_DEV}" networks: bench-network: \ No newline at end of file diff --git a/apps/dokploy/templates/erpnext/index.ts b/apps/dokploy/templates/erpnext/index.ts index 0be46c449..5b7543b91 100644 --- a/apps/dokploy/templates/erpnext/index.ts +++ b/apps/dokploy/templates/erpnext/index.ts @@ -24,6 +24,8 @@ export function generate(schema: Schema): Template { `ADMIN_PASSWORD=${adminPassword}`, `DB_ROOT_PASSWORD=${dbRootPassword}`, "MIGRATE=1", + "ENABLE_DB=1", + "DB_HOST=db", "CREATE_SITE=1", "CONFIGURE=1", "REGENERATE_APPS_TXT=1",