Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6edd2a81e5 | ||
|
|
fe5b8782e9 | ||
|
|
71f28fae70 | ||
|
|
c44618aa95 | ||
|
|
c7d86dd430 | ||
|
|
e50bbd1a6a | ||
|
|
d5ff91563a | ||
|
|
210fe0759c | ||
|
|
edff66900e | ||
|
|
4cf2177928 | ||
|
|
4a8306b015 | ||
|
|
f92d6693c3 | ||
|
|
81248ed03f | ||
|
|
c7d5900e11 | ||
|
|
d0608f43a9 | ||
|
|
adaf12a9a4 | ||
|
|
baa2ca20f4 | ||
|
|
537caf02e5 | ||
|
|
02ff507094 | ||
|
|
53df7d969e | ||
|
|
9e6e68558a | ||
|
|
a2e9ea2986 | ||
|
|
026e1bece6 | ||
|
|
51f6e08e16 | ||
|
|
c0f8218ad9 | ||
|
|
35dd6bff7a | ||
|
|
8dad8fd008 | ||
|
|
52dbc0d8f1 | ||
|
|
692f883064 | ||
|
|
6d90e268f7 | ||
|
|
1d86f1a0fc | ||
|
|
c7338983b8 | ||
|
|
ce06cd42b3 | ||
|
|
1a44a0ea5a | ||
|
|
444121f8d8 | ||
|
|
05a75edbec | ||
|
|
f5d81f434c | ||
|
|
10353d1f29 | ||
|
|
6226c75959 | ||
|
|
42c9cd5901 | ||
|
|
49edcdb99e | ||
|
|
ad71e8b36a | ||
|
|
b166cd5bfa | ||
|
|
0045608acc | ||
|
|
b140e81210 | ||
|
|
9298d6c693 | ||
|
|
23df3fba85 | ||
|
|
93c7f1ce76 | ||
|
|
1323394589 | ||
|
|
4f11fc2547 | ||
|
|
6d052ad455 | ||
|
|
60748da144 | ||
|
|
1956836cde | ||
|
|
7dca4fe430 | ||
|
|
84690c5f75 | ||
|
|
95b67ef2e9 | ||
|
|
3f8bc47ce5 | ||
|
|
498678c4ae | ||
|
|
3d602c232d | ||
|
|
94ffa7d578 | ||
|
|
64fc3c7677 | ||
|
|
f27830daf0 | ||
|
|
3ec2e2dd1a | ||
|
|
1e006cb094 | ||
|
|
8aa655af2c | ||
|
|
65659e27f1 | ||
|
|
4b6f9108d4 | ||
|
|
1e4a41a8e3 | ||
|
|
539aa7a85b | ||
|
|
b6ae502b92 | ||
|
|
e82db47ec4 | ||
|
|
f9b4f008c2 | ||
|
|
adb204ec1f | ||
|
|
5310a559b0 | ||
|
|
43b7db00f9 | ||
|
|
52c83fd6fc | ||
|
|
25a8df567e | ||
|
|
089274492d | ||
|
|
65c0ea829f | ||
|
|
d060eec465 | ||
|
|
2dca0d343e | ||
|
|
a8f63bb4d3 | ||
|
|
cecd371988 | ||
|
|
6c4d94cb4f | ||
|
|
c4e5c818f3 | ||
|
|
31a35d91e8 | ||
|
|
74a2f79a36 | ||
|
|
a8f8a727bd | ||
|
|
e8f2ab35c9 | ||
|
|
9806a5d607 | ||
|
|
e25d0c0c68 | ||
|
|
1f8a476264 | ||
|
|
cc473b3e87 | ||
|
|
10b3543d39 | ||
|
|
0893149db0 | ||
|
|
e257f86194 | ||
|
|
a9a0b4cb03 | ||
|
|
c5073c9f30 | ||
|
|
e69c602d1c | ||
|
|
0116d995d9 | ||
|
|
ad479c4be1 | ||
|
|
f70192a71c | ||
|
|
abff70f081 | ||
|
|
9f03faaec2 | ||
|
|
1d760bd25f | ||
|
|
26a67dd175 | ||
|
|
013ee89a56 | ||
|
|
c3d3c7b96a | ||
|
|
4e724d88aa | ||
|
|
351e4b6103 | ||
|
|
5b633ec6c5 | ||
|
|
ca9552d618 | ||
|
|
23b40c1aa7 | ||
|
|
5e0b7ba143 | ||
|
|
cb1203e302 | ||
|
|
373c5bc507 | ||
|
|
ee5afa4793 | ||
|
|
7b9426586d | ||
|
|
772b24a415 | ||
|
|
1922437115 | ||
|
|
c5eb8b087d | ||
|
|
94ee5391fb | ||
|
|
b3ff14f792 | ||
|
|
b68273c8ca | ||
|
|
8b203c48b4 | ||
|
|
fb33a5b6a5 | ||
|
|
b17ab6d8cb | ||
|
|
2de0e73284 | ||
|
|
537950dd9f | ||
|
|
d2094d6d76 | ||
|
|
c0b8a411bd | ||
|
|
1d8db07fa1 | ||
|
|
dd3618bfd9 | ||
|
|
4a9d7225c9 |
@@ -1,119 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp apps/dokploy/.env.production.example .env.production
|
||||
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
|
||||
|
||||
- run:
|
||||
name: Build and push AMD64 image
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
TAG="latest"
|
||||
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||
TAG="canary"
|
||||
else
|
||||
TAG="feature"
|
||||
fi
|
||||
docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 .
|
||||
docker push dokploy/dokploy:${TAG}-amd64
|
||||
|
||||
build-arm64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp apps/dokploy/.env.production.example .env.production
|
||||
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
|
||||
- run:
|
||||
name: Build and push ARM64 image
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
TAG="latest"
|
||||
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||
TAG="canary"
|
||||
else
|
||||
TAG="feature"
|
||||
fi
|
||||
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
|
||||
docker push dokploy/dokploy:${TAG}-arm64
|
||||
|
||||
combine-manifests:
|
||||
docker:
|
||||
- image: cimg/node:20.9.0
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Create and push multi-arch manifest
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
|
||||
echo $VERSION
|
||||
TAG="latest"
|
||||
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
|
||||
docker manifest create dokploy/dokploy:${VERSION} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${VERSION}
|
||||
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||
TAG="canary"
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
else
|
||||
TAG="feature"
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
fi
|
||||
|
||||
workflows:
|
||||
build-all:
|
||||
jobs:
|
||||
- build-amd64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- feat/add-sidebar
|
||||
- build-arm64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- feat/add-sidebar
|
||||
- combine-manifests:
|
||||
requires:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- feat/add-sidebar
|
||||
83
.github/workflows/create-pr.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: Auto PR to main when version changes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- canary
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
create-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get version from package.json
|
||||
id: package_version
|
||||
run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get latest GitHub tag
|
||||
id: latest_tag
|
||||
run: |
|
||||
LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1)
|
||||
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
||||
echo $LATEST_TAG
|
||||
- name: Compare versions
|
||||
id: compare_versions
|
||||
run: |
|
||||
if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then
|
||||
VERSION_CHANGED="true"
|
||||
else
|
||||
VERSION_CHANGED="false"
|
||||
fi
|
||||
echo "VERSION_CHANGED=$VERSION_CHANGED" >> $GITHUB_ENV
|
||||
echo "Comparing versions:"
|
||||
echo "Current version: ${{ env.VERSION }}"
|
||||
echo "Latest tag: ${{ env.LATEST_TAG }}"
|
||||
echo "Version changed: $VERSION_CHANGED"
|
||||
- name: Check if a PR already exists
|
||||
id: check_pr
|
||||
run: |
|
||||
PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length')
|
||||
echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
|
||||
- name: Create Pull Request
|
||||
if: env.VERSION_CHANGED == 'true' && env.PR_EXISTS == '0'
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git fetch origin main
|
||||
git checkout canary
|
||||
git push origin canary
|
||||
|
||||
gh pr create \
|
||||
--title "🚀 Release ${{ env.VERSION }}" \
|
||||
--body '
|
||||
This PR promotes changes from `canary` to `main` for version ${{ env.VERSION }}.
|
||||
|
||||
### 🔍 Changes Include:
|
||||
- Version bump to ${{ env.VERSION }}
|
||||
- All changes from canary branch
|
||||
|
||||
### ✅ Pre-merge Checklist:
|
||||
- [ ] All tests passing
|
||||
- [ ] Documentation updated
|
||||
- [ ] Docker images built and tested
|
||||
|
||||
> 🤖 This PR was automatically generated by [GitHub Actions](https://github.com/actions)' \
|
||||
--base main \
|
||||
--head canary \
|
||||
--label "release" --label "automated pr" || true \
|
||||
--reviewer siumauricio \
|
||||
--assignee siumauricio
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
161
.github/workflows/dokploy.yml
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
name: Dokploy Docker Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, canary, feat/github-runners]
|
||||
|
||||
env:
|
||||
IMAGE_NAME: dokploy/dokploy
|
||||
|
||||
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 and version
|
||||
id: meta
|
||||
run: |
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
TAG="latest"
|
||||
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
|
||||
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
|
||||
TAG="canary"
|
||||
else
|
||||
TAG="feature"
|
||||
fi
|
||||
echo "tags=${IMAGE_NAME}:${TAG}-amd64" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Prepare env file
|
||||
run: |
|
||||
cp apps/dokploy/.env.production.example .env.production
|
||||
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
|
||||
|
||||
- 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: |
|
||||
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
TAG="latest"
|
||||
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
|
||||
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
|
||||
TAG="canary"
|
||||
else
|
||||
TAG="feature"
|
||||
fi
|
||||
echo "tags=${IMAGE_NAME}:${TAG}-arm64" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Prepare env file
|
||||
run: |
|
||||
cp apps/dokploy/.env.production.example .env.production
|
||||
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
|
||||
|
||||
- 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
|
||||
|
||||
docker buildx imagetools create -t ${IMAGE_NAME}:${VERSION} \
|
||||
${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
|
||||
|
||||
generate-release:
|
||||
needs: [combine-manifests]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.get_version.outputs.version }}
|
||||
name: ${{ steps.get_version.outputs.version }}
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
98
apps/dokploy/__test__/deploy/github.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("GitHub Webhook Skip CI", () => {
|
||||
const mockGithubHeaders = {
|
||||
"x-github-event": "push",
|
||||
};
|
||||
|
||||
const createMockBody = (message: string) => ({
|
||||
head_commit: {
|
||||
message,
|
||||
},
|
||||
});
|
||||
|
||||
const skipKeywords = [
|
||||
"[skip ci]",
|
||||
"[ci skip]",
|
||||
"[no ci]",
|
||||
"[skip actions]",
|
||||
"[actions skip]",
|
||||
];
|
||||
|
||||
it("should detect skip keywords in commit message", () => {
|
||||
for (const keyword of skipKeywords) {
|
||||
const message = `feat: add new feature ${keyword}`;
|
||||
const commitMessage = extractCommitMessage(
|
||||
mockGithubHeaders,
|
||||
createMockBody(message),
|
||||
);
|
||||
expect(commitMessage.includes(keyword)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("should not detect skip keywords in normal commit message", () => {
|
||||
const message = "feat: add new feature";
|
||||
const commitMessage = extractCommitMessage(
|
||||
mockGithubHeaders,
|
||||
createMockBody(message),
|
||||
);
|
||||
for (const keyword of skipKeywords) {
|
||||
expect(commitMessage.includes(keyword)).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it("should handle different webhook sources", () => {
|
||||
// GitHub
|
||||
expect(
|
||||
extractCommitMessage(
|
||||
{ "x-github-event": "push" },
|
||||
{ head_commit: { message: "[skip ci] test" } },
|
||||
),
|
||||
).toBe("[skip ci] test");
|
||||
|
||||
// GitLab
|
||||
expect(
|
||||
extractCommitMessage(
|
||||
{ "x-gitlab-event": "push" },
|
||||
{ commits: [{ message: "[skip ci] test" }] },
|
||||
),
|
||||
).toBe("[skip ci] test");
|
||||
|
||||
// Bitbucket
|
||||
expect(
|
||||
extractCommitMessage(
|
||||
{ "x-event-key": "repo:push" },
|
||||
{
|
||||
push: {
|
||||
changes: [{ new: { target: { message: "[skip ci] test" } } }],
|
||||
},
|
||||
},
|
||||
),
|
||||
).toBe("[skip ci] test");
|
||||
|
||||
// Gitea
|
||||
expect(
|
||||
extractCommitMessage(
|
||||
{ "x-gitea-event": "push" },
|
||||
{ commits: [{ message: "[skip ci] test" }] },
|
||||
),
|
||||
).toBe("[skip ci] test");
|
||||
});
|
||||
|
||||
it("should handle missing commit message", () => {
|
||||
expect(extractCommitMessage(mockGithubHeaders, {})).toBe("NEW COMMIT");
|
||||
expect(extractCommitMessage({ "x-gitlab-event": "push" }, {})).toBe(
|
||||
"NEW COMMIT",
|
||||
);
|
||||
expect(
|
||||
extractCommitMessage(
|
||||
{ "x-event-key": "repo:push" },
|
||||
{ push: { changes: [] } },
|
||||
),
|
||||
).toBe("NEW COMMIT");
|
||||
expect(extractCommitMessage({ "x-gitea-event": "push" }, {})).toBe(
|
||||
"NEW COMMIT",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
import { beforeEach, expect, test, vi } from "vitest";
|
||||
|
||||
const baseAdmin: Admin = {
|
||||
cleanupCacheApplications: false,
|
||||
cleanupCacheOnCompose: false,
|
||||
cleanupCacheOnPreviews: false,
|
||||
createdAt: "",
|
||||
authId: "",
|
||||
adminId: "string",
|
||||
|
||||
@@ -13,6 +13,7 @@ export default defineConfig({
|
||||
NODE: "test",
|
||||
},
|
||||
},
|
||||
plugins: [tsconfigPaths()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@dokploy/server": path.resolve(
|
||||
|
||||
@@ -100,7 +100,7 @@ export const ShowVolumes = ({ id, type }: Props) => {
|
||||
{mount.type === "file" && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Content</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-muted-foreground line-clamp-[10] whitespace-break-spaces">
|
||||
{mount.content}
|
||||
</span>
|
||||
</div>
|
||||
@@ -113,12 +113,21 @@ export const ShowVolumes = ({ id, type }: Props) => {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Mount Path</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{mount.mountPath}
|
||||
</span>
|
||||
</div>
|
||||
{mount.type === "file" ? (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">File Path</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{mount.filePath}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Mount Path</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{mount.mountPath}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-1">
|
||||
<UpdateVolume
|
||||
|
||||
@@ -75,14 +75,14 @@ export const ShowBackups = ({ id, type }: Props) => {
|
||||
{data?.length === 0 ? (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<DatabaseBackup className="size-8 text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
<span className="text-base text-muted-foreground text-center">
|
||||
To create a backup it is required to set at least 1 provider.
|
||||
Please, go to{" "}
|
||||
<Link
|
||||
href="/dashboard/settings/server"
|
||||
href="/dashboard/settings/destinations"
|
||||
className="text-foreground"
|
||||
>
|
||||
Settings
|
||||
S3 Destinations
|
||||
</Link>{" "}
|
||||
to do so.
|
||||
</span>
|
||||
|
||||
@@ -43,7 +43,7 @@ const LOG_STYLES: Record<LogType, LogStyle> = {
|
||||
|
||||
export function parseLogs(logString: string): LogLine[] {
|
||||
// Regex to match the log line format
|
||||
// Exemple of return :
|
||||
// Example of return :
|
||||
// 1 2024-12-10T10:00:00.000Z The server is running on port 8080
|
||||
// Should return :
|
||||
// { timestamp: new Date("2024-12-10T10:00:00.000Z"),
|
||||
|
||||
@@ -80,7 +80,7 @@ export const ShowContainers = ({ serverId }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-8xl mx-auto">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<CardHeader className="">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
|
||||
@@ -35,7 +35,7 @@ export const ShowTraefikSystem = ({ serverId }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-8xl mx-auto">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<CardHeader className="">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
|
||||
@@ -200,7 +200,7 @@ export const DockerMonitoring = ({
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<Card className="bg-background">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">CPU Usage</CardTitle>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DockerMonitoring } from "../docker/show";
|
||||
|
||||
export const ShowMonitoring = () => {
|
||||
return (
|
||||
<div className="my-6 w-full ">
|
||||
<div className="w-full">
|
||||
<DockerMonitoring appName="dokploy" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -114,26 +114,28 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen sm:max-w-[90vw] p-0">
|
||||
<DialogHeader className="sticky top-0 z-10 bg-background p-6 border-b">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
|
||||
<div>
|
||||
<DialogTitle>Create from Template</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create an open source application from a template
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
|
||||
<Input
|
||||
placeholder="Search Template"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
className="w-[200px]"
|
||||
className="w-full sm:w-[200px]"
|
||||
value={query}
|
||||
/>
|
||||
<Popover modal={true}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn("w-[200px] justify-between !bg-input")}
|
||||
className={cn(
|
||||
"w-full sm:w-[200px] justify-between !bg-input",
|
||||
)}
|
||||
>
|
||||
{isLoadingTags
|
||||
? "Loading...."
|
||||
@@ -238,8 +240,8 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
className={cn(
|
||||
"grid gap-6",
|
||||
viewMode === "detailed"
|
||||
? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-6"
|
||||
: "grid-cols-2 sm:grid-cols-4 lg:grid-cols-8",
|
||||
? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
|
||||
: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6",
|
||||
)}
|
||||
>
|
||||
{templates?.map((template, index) => (
|
||||
@@ -340,6 +342,7 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"w-auto",
|
||||
|
||||
@@ -118,7 +118,7 @@ export const HandleProject = ({ projectId }: Props) => {
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:m:max-w-lg ">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a project</DialogTitle>
|
||||
<DialogTitle>{projectId ? "Update" : "Add a"} project</DialogTitle>
|
||||
<DialogDescription>The home of something big!</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
|
||||
@@ -75,10 +75,10 @@ export const ShowProjects = () => {
|
||||
list={[{ name: "Projects", href: "/dashboard/projects" }]}
|
||||
/>
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl ">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl ">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<div className="flex justify-between gap-4 w-full items-center">
|
||||
<CardHeader className="">
|
||||
<div className="flex justify-between gap-4 w-full items-center flex-wrap p-6">
|
||||
<CardHeader className="p-0">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<FolderInput className="size-6 text-muted-foreground self-center" />
|
||||
Projects
|
||||
@@ -87,9 +87,12 @@ export const ShowProjects = () => {
|
||||
Create and manage your projects
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<div className=" px-4 ">
|
||||
<HandleProject />
|
||||
</div>
|
||||
|
||||
{(auth?.rol === "admin" || user?.canCreateProjects) && (
|
||||
<div className="">
|
||||
<HandleProject />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<CardContent className="space-y-2 py-8 border-t gap-4 flex flex-col min-h-[60vh]">
|
||||
|
||||
@@ -50,7 +50,7 @@ export const ShowCertificates = () => {
|
||||
{data?.length === 0 ? (
|
||||
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
|
||||
<ShieldCheck className="size-8 self-center text-muted-foreground" />
|
||||
<span className="text-base text-muted-foreground">
|
||||
<span className="text-base text-muted-foreground text-center">
|
||||
You don't have any certificates created
|
||||
</span>
|
||||
<AddCertificate />
|
||||
|
||||
@@ -28,7 +28,13 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle, Mail, PenBoxIcon, PlusIcon } from "lucide-react";
|
||||
import {
|
||||
AlertTriangle,
|
||||
Mail,
|
||||
MessageCircleMore,
|
||||
PenBoxIcon,
|
||||
PlusIcon,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -84,6 +90,15 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
||||
.min(1, { message: "At least one email is required" }),
|
||||
})
|
||||
.merge(notificationBaseSchema),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("gotify"),
|
||||
serverUrl: z.string().min(1, { message: "Server URL is required" }),
|
||||
appToken: z.string().min(1, { message: "App Token is required" }),
|
||||
priority: z.number().min(1).max(10).default(5),
|
||||
decoration: z.boolean().default(true),
|
||||
})
|
||||
.merge(notificationBaseSchema),
|
||||
]);
|
||||
|
||||
export const notificationsMap = {
|
||||
@@ -103,6 +118,10 @@ export const notificationsMap = {
|
||||
icon: <Mail size={29} className="text-muted-foreground" />,
|
||||
label: "Email",
|
||||
},
|
||||
gotify: {
|
||||
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
||||
label: "Gotify",
|
||||
},
|
||||
};
|
||||
|
||||
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
||||
@@ -126,13 +145,14 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
);
|
||||
const { mutateAsync: testSlackConnection, isLoading: isLoadingSlack } =
|
||||
api.notification.testSlackConnection.useMutation();
|
||||
|
||||
const { mutateAsync: testTelegramConnection, isLoading: isLoadingTelegram } =
|
||||
api.notification.testTelegramConnection.useMutation();
|
||||
const { mutateAsync: testDiscordConnection, isLoading: isLoadingDiscord } =
|
||||
api.notification.testDiscordConnection.useMutation();
|
||||
const { mutateAsync: testEmailConnection, isLoading: isLoadingEmail } =
|
||||
api.notification.testEmailConnection.useMutation();
|
||||
const { mutateAsync: testGotifyConnection, isLoading: isLoadingGotify } =
|
||||
api.notification.testGotifyConnection.useMutation();
|
||||
const slackMutation = notificationId
|
||||
? api.notification.updateSlack.useMutation()
|
||||
: api.notification.createSlack.useMutation();
|
||||
@@ -145,6 +165,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
const emailMutation = notificationId
|
||||
? api.notification.updateEmail.useMutation()
|
||||
: api.notification.createEmail.useMutation();
|
||||
const gotifyMutation = notificationId
|
||||
? api.notification.updateGotify.useMutation()
|
||||
: api.notification.createGotify.useMutation();
|
||||
|
||||
const form = useForm<NotificationSchema>({
|
||||
defaultValues: {
|
||||
@@ -222,6 +245,20 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
name: notification.name,
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
});
|
||||
} else if (notification.notificationType === "gotify") {
|
||||
form.reset({
|
||||
appBuildError: notification.appBuildError,
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
type: notification.notificationType,
|
||||
appToken: notification.gotify?.appToken,
|
||||
decoration: notification.gotify?.decoration || undefined,
|
||||
priority: notification.gotify?.priority,
|
||||
serverUrl: notification.gotify?.serverUrl,
|
||||
name: notification.name,
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
form.reset();
|
||||
@@ -233,6 +270,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
telegram: telegramMutation,
|
||||
discord: discordMutation,
|
||||
email: emailMutation,
|
||||
gotify: gotifyMutation,
|
||||
};
|
||||
|
||||
const onSubmit = async (data: NotificationSchema) => {
|
||||
@@ -300,6 +338,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
notificationId: notificationId || "",
|
||||
emailId: notification?.emailId || "",
|
||||
});
|
||||
} else if (data.type === "gotify") {
|
||||
promise = gotifyMutation.mutateAsync({
|
||||
appBuildError: appBuildError,
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
serverUrl: data.serverUrl,
|
||||
appToken: data.appToken,
|
||||
priority: data.priority,
|
||||
name: data.name,
|
||||
dockerCleanup: dockerCleanup,
|
||||
decoration: data.decoration,
|
||||
notificationId: notificationId || "",
|
||||
gotifyId: notification?.gotifyId || "",
|
||||
});
|
||||
}
|
||||
|
||||
if (promise) {
|
||||
@@ -700,6 +753,94 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "gotify" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="serverUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="https://gotify.example.com"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appToken"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>App Token</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="AzxcvbnmKjhgfdsa..."
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="priority"
|
||||
defaultValue={5}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Priority</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="5"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value) {
|
||||
const port = Number.parseInt(value);
|
||||
if (port > 0 && port < 10) {
|
||||
field.onChange(port);
|
||||
}
|
||||
}
|
||||
}}
|
||||
type="number"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Message priority (1-10, default: 5)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="decoration"
|
||||
defaultValue={true}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Decoration</FormLabel>
|
||||
<FormDescription>
|
||||
Decorate the notification with emojis.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -824,7 +965,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
isLoadingSlack ||
|
||||
isLoadingTelegram ||
|
||||
isLoadingDiscord ||
|
||||
isLoadingEmail
|
||||
isLoadingEmail ||
|
||||
isLoadingGotify
|
||||
}
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
@@ -853,6 +995,13 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
toAddresses: form.getValues("toAddresses"),
|
||||
fromAddress: form.getValues("fromAddress"),
|
||||
});
|
||||
} else if (type === "gotify") {
|
||||
await testGotifyConnection({
|
||||
serverUrl: form.getValues("serverUrl"),
|
||||
appToken: form.getValues("appToken"),
|
||||
priority: form.getValues("priority"),
|
||||
decoration: form.getValues("decoration"),
|
||||
});
|
||||
}
|
||||
toast.success("Connection Success");
|
||||
} catch (err) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { Bell, Loader2, Mail, Trash2 } from "lucide-react";
|
||||
import { Bell, Loader2, Mail, MessageCircleMore, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { HandleNotifications } from "./handle-notifications";
|
||||
|
||||
@@ -47,7 +47,7 @@ export const ShowNotifications = () => {
|
||||
{data?.length === 0 ? (
|
||||
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
|
||||
<Bell />
|
||||
<span className="text-base text-muted-foreground">
|
||||
<span className="text-base text-muted-foreground text-center">
|
||||
To send notifications it is required to set at least 1
|
||||
provider.
|
||||
</span>
|
||||
@@ -83,6 +83,11 @@ export const ShowNotifications = () => {
|
||||
<Mail className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
{notification.notificationType === "gotify" && (
|
||||
<div className="flex items-center justify-center rounded-lg ">
|
||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{notification.name}
|
||||
</span>
|
||||
|
||||
@@ -30,8 +30,8 @@ import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const addPermissions = z.object({
|
||||
accesedProjects: z.array(z.string()).optional(),
|
||||
accesedServices: z.array(z.string()).optional(),
|
||||
accessedProjects: z.array(z.string()).optional(),
|
||||
accessedServices: z.array(z.string()).optional(),
|
||||
canCreateProjects: z.boolean().optional().default(false),
|
||||
canCreateServices: z.boolean().optional().default(false),
|
||||
canDeleteProjects: z.boolean().optional().default(false),
|
||||
@@ -66,8 +66,8 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
|
||||
const form = useForm<AddPermissions>({
|
||||
defaultValues: {
|
||||
accesedProjects: [],
|
||||
accesedServices: [],
|
||||
accessedProjects: [],
|
||||
accessedServices: [],
|
||||
},
|
||||
resolver: zodResolver(addPermissions),
|
||||
});
|
||||
@@ -75,8 +75,8 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
accesedProjects: data.accesedProjects || [],
|
||||
accesedServices: data.accesedServices || [],
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
canCreateProjects: data.canCreateProjects,
|
||||
canCreateServices: data.canCreateServices,
|
||||
canDeleteProjects: data.canDeleteProjects,
|
||||
@@ -98,8 +98,8 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
canDeleteServices: data.canDeleteServices,
|
||||
canDeleteProjects: data.canDeleteProjects,
|
||||
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
||||
accesedProjects: data.accesedProjects || [],
|
||||
accesedServices: data.accesedServices || [],
|
||||
accessedProjects: data.accessedProjects || [],
|
||||
accessedServices: data.accessedServices || [],
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||
@@ -318,7 +318,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accesedProjects"
|
||||
name="accessedProjects"
|
||||
render={() => (
|
||||
<FormItem className="md:col-span-2">
|
||||
<div className="mb-4">
|
||||
@@ -339,7 +339,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
<FormField
|
||||
key={`project-${index}`}
|
||||
control={form.control}
|
||||
name="accesedProjects"
|
||||
name="accessedProjects"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
@@ -380,7 +380,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
<FormField
|
||||
key={`project-${index}`}
|
||||
control={form.control}
|
||||
name="accesedServices"
|
||||
name="accessedServices"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
|
||||
@@ -60,7 +60,7 @@ export function NodeCard({ node, serverId }: Props) {
|
||||
<div className="font-medium">{node.Hostname}</div>
|
||||
<Badge variant="green">{node.ManagerStatus || "Worker"}</Badge>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center space-x-4">
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<Badge variant="green">TLS Status: {node.TLSStatus}</Badge>
|
||||
<Badge variant="blue">Availability: {node.Availability}</Badge>
|
||||
</div>
|
||||
|
||||
@@ -70,9 +70,9 @@ export default function SwarmMonitorCard({ serverId }: Props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-8xl mx-auto w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl mx-auto w-full">
|
||||
<div className="rounded-xl bg-background shadow-md p-6 flex flex-col gap-4">
|
||||
<header className="flex items-center justify-between">
|
||||
<header className="flex items-center flex-wrap gap-4 justify-between">
|
||||
<div className="space-y-1">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<WorkflowIcon className="size-6 text-muted-foreground self-center" />
|
||||
@@ -94,7 +94,7 @@ export default function SwarmMonitorCard({ serverId }: Props) {
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<div className="grid gap-6 lg:grid-cols-3">
|
||||
<Card className="bg-background">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Nodes</CardTitle>
|
||||
|
||||
@@ -5,9 +5,5 @@ interface Props {
|
||||
}
|
||||
|
||||
export const DashboardLayout = ({ children }: Props) => {
|
||||
return (
|
||||
<Page>
|
||||
<div>{children}</div>
|
||||
</Page>
|
||||
);
|
||||
return <Page>{children}</Page>;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Props {
|
||||
export const OnboardingLayout = ({ children }: Props) => {
|
||||
return (
|
||||
<div className="container relative min-h-svh flex-col items-center justify-center flex lg:max-w-none lg:grid lg:grid-cols-2 lg:px-0 w-full">
|
||||
<div className="relative hidden h-full flex-col p-10 text-white dark:border-r lg:flex">
|
||||
<div className="relative hidden h-full flex-col p-10 text-primary dark:border-r lg:flex">
|
||||
<div className="absolute inset-0 bg-muted" />
|
||||
<Link
|
||||
href="https://dokploy.com"
|
||||
@@ -22,35 +22,16 @@ export const OnboardingLayout = ({ children }: Props) => {
|
||||
</Link>
|
||||
<div className="relative z-20 mt-auto">
|
||||
<blockquote className="space-y-2">
|
||||
<p className="text-lg">
|
||||
<p className="text-lg text-primary">
|
||||
“The Open Source alternative to Netlify, Vercel,
|
||||
Heroku.”
|
||||
</p>
|
||||
{/* <footer className="text-sm">Sofia Davis</footer> */}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="flex w-full flex-col justify-center space-y-6 max-w-lg mx-auto">
|
||||
{children}
|
||||
|
||||
{/* <p className="px-8 text-center text-sm text-muted-foreground">
|
||||
By clicking continue, you agree to our{" "}
|
||||
<Link
|
||||
href="/terms"
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link
|
||||
href="/privacy"
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p> */}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 justify-center absolute bottom-4 right-4 text-muted-foreground">
|
||||
<Button variant="ghost" size="icon">
|
||||
|
||||
@@ -5,9 +5,5 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ProjectLayout = ({ children }: Props) => {
|
||||
return (
|
||||
<div>
|
||||
<Page>{children}</Page>
|
||||
</div>
|
||||
);
|
||||
return <Page>{children}</Page>;
|
||||
};
|
||||
|
||||
@@ -118,14 +118,16 @@ export const UserNav = () => {
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
router.push("/dashboard/settings/server");
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
{data?.rol === "admin" && (
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
router.push("/dashboard/settings");
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -147,6 +149,17 @@ export const UserNav = () => {
|
||||
Servers
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{data?.rol === "admin" && (
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
router.push("/dashboard/settings");
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
|
||||
@@ -28,7 +28,7 @@ export const BreadcrumbSidebar = ({ list }: Props) => {
|
||||
<BreadcrumbList>
|
||||
{list.map((item, index) => (
|
||||
<Fragment key={item.name}>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbItem className="block">
|
||||
<BreadcrumbLink href={item.href} asChild={!!item.href}>
|
||||
{item.href ? (
|
||||
<Link href={item.href}>{item.name}</Link>
|
||||
@@ -37,7 +37,7 @@ export const BreadcrumbSidebar = ({ list }: Props) => {
|
||||
)}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbSeparator className="block" />
|
||||
</Fragment>
|
||||
))}
|
||||
</BreadcrumbList>
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const SIDEBAR_COOKIE_NAME = "sidebar:state";
|
||||
export const SIDEBAR_COOKIE_NAME = "sidebar:state";
|
||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||
const SIDEBAR_WIDTH = "16rem";
|
||||
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
||||
@@ -231,7 +231,7 @@ const Sidebar = React.forwardRef<
|
||||
{/* This is what handles the sidebar gap on desktop */}
|
||||
<div
|
||||
className={cn(
|
||||
"duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
|
||||
"duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-out",
|
||||
"group-data-[collapsible=offcanvas]:w-0",
|
||||
"group-data-[side=right]:rotate-180",
|
||||
variant === "floating" || variant === "inset"
|
||||
@@ -241,7 +241,7 @@ const Sidebar = React.forwardRef<
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
|
||||
"duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-out md:flex",
|
||||
side === "left"
|
||||
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
@@ -329,7 +329,7 @@ const SidebarInset = React.forwardRef<
|
||||
<main
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex min-h-svh flex-1 flex-col bg-background",
|
||||
"relative flex min-h-svh overflow-auto w-full flex-col bg-background",
|
||||
"peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
|
||||
className,
|
||||
)}
|
||||
|
||||
15
apps/dokploy/drizzle/0056_majestic_skaar.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
ALTER TYPE "notificationType" ADD VALUE 'gotify';--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "gotify" (
|
||||
"gotifyId" text PRIMARY KEY NOT NULL,
|
||||
"serverUrl" text NOT NULL,
|
||||
"appToken" text NOT NULL,
|
||||
"priority" integer DEFAULT 5 NOT NULL,
|
||||
"decoration" boolean
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "notification" ADD COLUMN "gotifyId" text;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "notification" ADD CONSTRAINT "notification_gotifyId_gotify_gotifyId_fk" FOREIGN KEY ("gotifyId") REFERENCES "public"."gotify"("gotifyId") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
3
apps/dokploy/drizzle/0057_tricky_living_tribunal.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "admin" ADD COLUMN "cleanupCacheApplications" boolean DEFAULT true NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "admin" ADD COLUMN "cleanupCacheOnPreviews" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "admin" ADD COLUMN "cleanupCacheOnCompose" boolean DEFAULT false NOT NULL;
|
||||
4314
apps/dokploy/drizzle/meta/0056_snapshot.json
Normal file
4335
apps/dokploy/drizzle/meta/0057_snapshot.json
Normal file
@@ -393,6 +393,20 @@
|
||||
"when": 1736669623831,
|
||||
"tag": "0055_next_serpent_society",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 56,
|
||||
"version": "6",
|
||||
"when": 1736789918294,
|
||||
"tag": "0056_majestic_skaar",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 57,
|
||||
"version": "6",
|
||||
"when": 1737306063563,
|
||||
"tag": "0057_tricky_living_tribunal",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export const Languages = {
|
||||
english: { code: "en", name: "English" },
|
||||
polish: { code: "pl", name: "Polski" },
|
||||
ukrainian: { code: "uk", name: "Українська" },
|
||||
russian: { code: "ru", name: "Русский" },
|
||||
french: { code: "fr", name: "Français" },
|
||||
german: { code: "de", name: "Deutsch" },
|
||||
@@ -17,6 +18,7 @@ export const Languages = {
|
||||
norwegian: { code: "no", name: "Norsk" },
|
||||
azerbaijani: { code: "az", name: "Azərbaycan" },
|
||||
indonesian: { code: "id", name: "Bahasa Indonesia" },
|
||||
malayalam: { code: "ml", name: "മലയാളം" },
|
||||
};
|
||||
|
||||
export type Language = keyof typeof Languages;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.17.2",
|
||||
"version": "v0.17.7",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -71,6 +71,25 @@ export default async function handler(
|
||||
return;
|
||||
}
|
||||
|
||||
// skip workflow runs use keywords
|
||||
// @link https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/skipping-workflow-runs
|
||||
if (
|
||||
[
|
||||
"[skip ci]",
|
||||
"[ci skip]",
|
||||
"[no ci]",
|
||||
"[skip actions]",
|
||||
"[actions skip]",
|
||||
].find((keyword) =>
|
||||
extractCommitMessage(req.headers, req.body).includes(keyword),
|
||||
)
|
||||
) {
|
||||
res.status(200).json({
|
||||
message: "Deployment skipped: commit message contains skip keyword",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.headers["x-github-event"] === "push") {
|
||||
try {
|
||||
const branchName = githubBody?.ref?.replace("refs/heads/", "");
|
||||
@@ -102,7 +121,7 @@ export default async function handler(
|
||||
if (IS_CLOUD && app.serverId) {
|
||||
jobData.serverId = app.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
@@ -137,7 +156,7 @@ export default async function handler(
|
||||
if (IS_CLOUD && composeApp.serverId) {
|
||||
jobData.serverId = composeApp.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
await myQueue.add(
|
||||
|
||||
@@ -239,8 +239,8 @@ const Project = (
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl ">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<div className="flex justify-between gap-4 w-full items-center">
|
||||
<CardHeader className="">
|
||||
<div className="flex justify-between gap-4 w-full items-center flex-wrap p-6">
|
||||
<CardHeader className="p-0">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<FolderInput className="size-6 text-muted-foreground self-center" />
|
||||
{data?.name}
|
||||
@@ -248,7 +248,7 @@ const Project = (
|
||||
<CardDescription>{data?.description}</CardDescription>
|
||||
</CardHeader>
|
||||
{(auth?.rol === "admin" || user?.canCreateServices) && (
|
||||
<div className="flex flex-row gap-4 flex-wrap px-4">
|
||||
<div className="flex flex-row gap-4 flex-wrap">
|
||||
<ProjectEnvironment projectId={projectId}>
|
||||
<Button variant="outline">Project Environment</Button>
|
||||
</ProjectEnvironment>
|
||||
|
||||
@@ -165,10 +165,10 @@ const Mongo = (
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Postgres deleted successfully");
|
||||
toast.success("Mongo deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the postgres");
|
||||
toast.error("Error deleting the mongo");
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
220
apps/dokploy/pages/dashboard/settings/index.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { DialogFooter } from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { Settings } from "lucide-react";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { useEffect, type ReactElement } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { z } from "zod";
|
||||
|
||||
const settings = z.object({
|
||||
cleanCacheOnApplications: z.boolean(),
|
||||
cleanCacheOnCompose: z.boolean(),
|
||||
cleanCacheOnPreviews: z.boolean(),
|
||||
});
|
||||
|
||||
type SettingsType = z.infer<typeof settings>;
|
||||
|
||||
const Page = () => {
|
||||
const { data, refetch } = api.admin.one.useQuery();
|
||||
const { mutateAsync, isLoading, isError, error } =
|
||||
api.admin.update.useMutation();
|
||||
const form = useForm<SettingsType>({
|
||||
defaultValues: {
|
||||
cleanCacheOnApplications: false,
|
||||
cleanCacheOnCompose: false,
|
||||
cleanCacheOnPreviews: false,
|
||||
},
|
||||
resolver: zodResolver(settings),
|
||||
});
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
cleanCacheOnApplications: data?.cleanupCacheApplications || false,
|
||||
cleanCacheOnCompose: data?.cleanupCacheOnCompose || false,
|
||||
cleanCacheOnPreviews: data?.cleanupCacheOnPreviews || false,
|
||||
});
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||
|
||||
const onSubmit = async (values: SettingsType) => {
|
||||
await mutateAsync({
|
||||
cleanupCacheApplications: values.cleanCacheOnApplications,
|
||||
cleanupCacheOnCompose: values.cleanCacheOnCompose,
|
||||
cleanupCacheOnPreviews: values.cleanCacheOnPreviews,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Settings updated");
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Something went wrong");
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<CardHeader className="">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<Settings className="size-6 text-muted-foreground self-center" />
|
||||
Settings
|
||||
</CardTitle>
|
||||
<CardDescription>Manage your Dokploy settings</CardDescription>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 py-8 border-t">
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-security"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cleanCacheOnApplications"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Clean Cache on Applications</FormLabel>
|
||||
<FormDescription>
|
||||
Clean the cache after every application deployment
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cleanCacheOnPreviews"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Clean Cache on Previews</FormLabel>
|
||||
<FormDescription>
|
||||
Clean the cache after every preview deployment
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cleanCacheOnCompose"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Clean Cache on Compose</FormLabel>
|
||||
<FormDescription>
|
||||
Clean the cache after every compose deployment
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
form="hook-form-add-security"
|
||||
type="submit"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
Page.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout metaName="Server">{page}</DashboardLayout>;
|
||||
};
|
||||
export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
if (user.rol === "user") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/dashboard/settings/profile",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
ctx: {
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const Page = () => {
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="h-full p-2.5 rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||
<ProfileForm />
|
||||
{(user?.canAccessToAPI || data?.rol === "admin") && <GenerateToken />}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import superjson from "superjson";
|
||||
const Page = () => {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="h-full p-2.5 rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||
<WebDomain />
|
||||
<WebServer />
|
||||
</div>
|
||||
|
||||
@@ -8,11 +8,7 @@ import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<>
|
||||
<SwarmMonitorCard />
|
||||
</>
|
||||
);
|
||||
return <SwarmMonitorCard />;
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
1
apps/dokploy/public/locales/ml/common.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
58
apps/dokploy/public/locales/ml/settings.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"settings.common.save": "സേവ് ചെയ്യുക",
|
||||
"settings.common.enterTerminal": "ടർമിനലിൽ പ്രവേശിക്കുക",
|
||||
"settings.server.domain.title": "സർവർ ഡോമെയ്ൻ",
|
||||
"settings.server.domain.description": "നിങ്ങളുടെ സർവർ അപ്ലിക്കേഷനിൽ ഒരു ഡോമെയ്ൻ ചേർക്കുക.",
|
||||
"settings.server.domain.form.domain": "ഡോമെയ്ൻ",
|
||||
"settings.server.domain.form.letsEncryptEmail": "ലെറ്റ്സ് എൻക്രിപ്റ്റ് ഇമെയിൽ",
|
||||
"settings.server.domain.form.certificate.label": "സർട്ടിഫിക്കറ്റ് പ്രൊവൈഡർ",
|
||||
"settings.server.domain.form.certificate.placeholder": "ഒരു സർട്ടിഫിക്കറ്റ് തിരഞ്ഞെടുക്കുക",
|
||||
"settings.server.domain.form.certificateOptions.none": "ഒന്നുമില്ല",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "ലെറ്റ്സ് എൻക്രിപ്റ്റ്",
|
||||
|
||||
"settings.server.webServer.title": "വെബ് സർവർ",
|
||||
"settings.server.webServer.description": "വെബ് സർവർ റീലോഡ് ചെയ്യുക അല്ലെങ്കിൽ ശുചീകരിക്കുക.",
|
||||
"settings.server.webServer.actions": "നടപടികൾ",
|
||||
"settings.server.webServer.reload": "റീലോഡ് ചെയ്യുക",
|
||||
"settings.server.webServer.watchLogs": "ലോഗുകൾ കാണുക",
|
||||
"settings.server.webServer.updateServerIp": "സർവർ IP അപ്ഡേറ്റ് ചെയ്യുക",
|
||||
"settings.server.webServer.server.label": "സർവർ",
|
||||
"settings.server.webServer.traefik.label": "ട്രാഫിക്",
|
||||
"settings.server.webServer.traefik.modifyEnv": "ചുറ്റുപാടുകൾ മാറ്റുക",
|
||||
"settings.server.webServer.traefik.managePorts": "അധിക പോർട്ട് മാപ്പിംഗ്",
|
||||
"settings.server.webServer.traefik.managePortsDescription": "ട്രാഫിക്കിനായി അധിക പോർട്ടുകൾ ചേർക്കുക അല്ലെങ്കിൽ നീക്കം ചെയ്യുക",
|
||||
"settings.server.webServer.traefik.targetPort": "ടാർഗറ്റ് പോർട്ട്",
|
||||
"settings.server.webServer.traefik.publishedPort": "പ്രസിദ്ധീകരിച്ച പോർട്ട്",
|
||||
"settings.server.webServer.traefik.addPort": "പോർട്ട് ചേർക്കുക",
|
||||
"settings.server.webServer.traefik.portsUpdated": "പോർട്ടുകൾ വിജയകരമായി അപ്ഡേറ്റ് ചെയ്തു",
|
||||
"settings.server.webServer.traefik.portsUpdateError": "പോർട്ടുകൾ അപ്ഡേറ്റ് ചെയ്യാൻ പരാജയപ്പെട്ടു",
|
||||
"settings.server.webServer.traefik.publishMode": "പ്രസിദ്ധീകരണ മോഡ്",
|
||||
"settings.server.webServer.storage.label": "ഇടം",
|
||||
"settings.server.webServer.storage.cleanUnusedImages": "ഉപയോഗിക്കാത്ത ഇമേജുകൾ ശുചീകരിക്കുക",
|
||||
"settings.server.webServer.storage.cleanUnusedVolumes": "ഉപയോഗിക്കാത്ത വോള്യങ്ങൾ ശുചീകരിക്കുക",
|
||||
"settings.server.webServer.storage.cleanStoppedContainers": "നിർത്തിയ കണ്ടെയ്നറുകൾ ശുചീകരിക്കുക",
|
||||
"settings.server.webServer.storage.cleanDockerBuilder": "ഡോക്കർ ബിൽഡറും സിസ്റ്റവും ശുചീകരിക്കുക",
|
||||
"settings.server.webServer.storage.cleanMonitoring": "മോണിറ്ററിംഗ് ശുചീകരിക്കുക",
|
||||
"settings.server.webServer.storage.cleanAll": "എല്ലാം ശുചീകരിക്കുക",
|
||||
|
||||
"settings.profile.title": "അക്കൗണ്ട്",
|
||||
"settings.profile.description": "നിങ്ങളുടെ പ്രൊഫൈൽ വിശദാംശങ്ങൾ ഇവിടെ മാറ്റുക.",
|
||||
"settings.profile.email": "ഇമെയിൽ",
|
||||
"settings.profile.password": "പാസ്വേഡ്",
|
||||
"settings.profile.avatar": "അവതാർ",
|
||||
|
||||
"settings.appearance.title": "ദൃശ്യമാനം",
|
||||
"settings.appearance.description": "നിങ്ങളുടെ ഡാഷ്ബോർഡിന്റെ തീം ഇഷ്ടാനുസൃതമാക്കുക.",
|
||||
"settings.appearance.theme": "തീം",
|
||||
"settings.appearance.themeDescription": "നിങ്ങളുടെ ഡാഷ്ബോർഡിന് ഒരു തീം തിരഞ്ഞെടുക്കുക",
|
||||
"settings.appearance.themes.light": "ലൈറ്റ്",
|
||||
"settings.appearance.themes.dark": "ഡാർക്ക്",
|
||||
"settings.appearance.themes.system": "സിസ്റ്റം",
|
||||
"settings.appearance.language": "ഭാഷ",
|
||||
"settings.appearance.languageDescription": "നിങ്ങളുടെ ഡാഷ്ബോർഡിന് ഒരു ഭാഷ തിരഞ്ഞെടുക്കുക",
|
||||
|
||||
"settings.terminal.connectionSettings": "കണക്ഷൻ ക്രമീകരണങ്ങൾ",
|
||||
"settings.terminal.ipAddress": "IP വിലാസം",
|
||||
"settings.terminal.port": "പോർട്ട്",
|
||||
"settings.terminal.username": "ഉപയോക്തൃനാമം"
|
||||
}
|
||||
1
apps/dokploy/public/locales/uk/common.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
58
apps/dokploy/public/locales/uk/settings.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"settings.common.save": "Зберегти",
|
||||
"settings.common.enterTerminal": "Увійти в термінал",
|
||||
"settings.server.domain.title": "Домен сервера",
|
||||
"settings.server.domain.description": "Додайте домен до вашого серверного застосунку.",
|
||||
"settings.server.domain.form.domain": "Домен",
|
||||
"settings.server.domain.form.letsEncryptEmail": "Електронна пошта для Let's Encrypt",
|
||||
"settings.server.domain.form.certificate.label": "Постачальник сертифікатів",
|
||||
"settings.server.domain.form.certificate.placeholder": "Оберіть сертифікат",
|
||||
"settings.server.domain.form.certificateOptions.none": "Відсутній",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
|
||||
|
||||
"settings.server.webServer.title": "Веб-сервер",
|
||||
"settings.server.webServer.description": "Перезавантажте або очистьте веб-сервер.",
|
||||
"settings.server.webServer.actions": "Дії",
|
||||
"settings.server.webServer.reload": "Перезавантажити",
|
||||
"settings.server.webServer.watchLogs": "Перегляд логів",
|
||||
"settings.server.webServer.updateServerIp": "Оновити IP-адресу сервера",
|
||||
"settings.server.webServer.server.label": "Сервер",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.traefik.modifyEnv": "Змінити середовище",
|
||||
"settings.server.webServer.traefik.managePorts": "Додаткові порти",
|
||||
"settings.server.webServer.traefik.managePortsDescription": "Додайте або видаліть порти для Traefik",
|
||||
"settings.server.webServer.traefik.targetPort": "Цільовий порт",
|
||||
"settings.server.webServer.traefik.publishedPort": "Опублікований порт",
|
||||
"settings.server.webServer.traefik.addPort": "Додати порт",
|
||||
"settings.server.webServer.traefik.portsUpdated": "Порти успішно оновлено",
|
||||
"settings.server.webServer.traefik.portsUpdateError": "Не вдалося оновити порти",
|
||||
"settings.server.webServer.traefik.publishMode": "Режим публікації",
|
||||
"settings.server.webServer.storage.label": "Дисковий простір",
|
||||
"settings.server.webServer.storage.cleanUnusedImages": "Очистити невикористані образи",
|
||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Очистити невикористані томи",
|
||||
"settings.server.webServer.storage.cleanStoppedContainers": "Очистити зупинені контейнери",
|
||||
"settings.server.webServer.storage.cleanDockerBuilder": "Очистити Docker Builder і систему",
|
||||
"settings.server.webServer.storage.cleanMonitoring": "Очистити моніторинг",
|
||||
"settings.server.webServer.storage.cleanAll": "Очистити все",
|
||||
|
||||
"settings.profile.title": "Обліковий запис",
|
||||
"settings.profile.description": "Змініть дані вашого профілю.",
|
||||
"settings.profile.email": "Електронна пошта",
|
||||
"settings.profile.password": "Пароль",
|
||||
"settings.profile.avatar": "Аватар",
|
||||
|
||||
"settings.appearance.title": "Зовнішній вигляд",
|
||||
"settings.appearance.description": "Налаштуйте тему вашої панелі керування.",
|
||||
"settings.appearance.theme": "Тема",
|
||||
"settings.appearance.themeDescription": "Оберіть тему для вашої панелі керування",
|
||||
"settings.appearance.themes.light": "Світла",
|
||||
"settings.appearance.themes.dark": "Темна",
|
||||
"settings.appearance.themes.system": "Системна",
|
||||
"settings.appearance.language": "Мова",
|
||||
"settings.appearance.languageDescription": "Оберіть мову для вашої панелі керування",
|
||||
|
||||
"settings.terminal.connectionSettings": "Налаштування з'єднання",
|
||||
"settings.terminal.ipAddress": "IP-адреса",
|
||||
"settings.terminal.port": "Порт",
|
||||
"settings.terminal.username": "Ім'я користувача"
|
||||
}
|
||||
33
apps/dokploy/public/templates/cloudflared.svg
Normal file
@@ -0,0 +1,33 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 38 38" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M31.079694,0.645206c-1.0388,-0.247509-2.129,-0.177396-3.1268,0.201097
|
||||
-0.9978,0.378497-1.8564,1.047597-2.4626,1.919097-0.6061,0.8715-0.9314,1.90449
|
||||
-0.933,2.96277-0.0015,1.05829 0.3208,2.09219 0.9245,2.9654l-0.1325,0.16058
|
||||
-4.4471,5.23515c-0.2873,-0.1277-0.5906,-0.2311-0.9078,-0.3069-2.7726,-0.6632
|
||||
-5.563,1.0255-6.2325,3.7716-0.355,1.4559-0.0426,2.9167 0.7424,4.0668l-4.60483,
|
||||
5.4208c-0.16295,-0.0482-0.32858,-0.087-0.49607,-0.1162-1.21038,-0.2899-2.48522,
|
||||
-0.1472-3.59979,0.4028-1.11456,0.5501-1.99728,1.4722-2.492545,2.6038-0.495264,
|
||||
1.1317-0.571271,2.4002-0.214622,3.5819 0.356648,1.1817 1.123057,2.2007 2.164107,
|
||||
2.8775 1.04105,0.6768 2.2899,0.9678 3.5264,0.8218 1.23651,-0.1461 2.38126,
|
||||
-0.7198 3.23245,-1.62 0.8512,-0.9003 1.3542,-2.0693 1.4203,-3.3009 0.0661,
|
||||
-1.2316-0.3089,-2.4468-1.05894,-3.4314l4.29464,-5.1489 0.1792,-0.2109c0.2293,
|
||||
0.0911 0.468,0.1669 0.7152,0.226 2.7727,0.6632 5.5631,-1.0255 6.2326,-3.7716
|
||||
0.3373,-1.3835 0.072,-2.7714-0.6289,-3.893l4.6468,-5.4702c0.2538,0.0944 0.5136,
|
||||
0.1718 0.7778,0.2316 1.3628,0.326 2.8005,0.1023 3.9968,-0.6216 1.1963,-0.72398
|
||||
2.0533,-1.88899 2.3824,-3.23877 0.3291,-1.34978 0.1033,-2.77376-0.6276,-3.95867
|
||||
-0.731,-1.18492-1.9072,-2.033711-3.27,-2.359654zM7.630804,34.1959c-0.43554,
|
||||
-0.1042-0.83012,-0.334-1.13383,-0.6602-0.30371,-0.3263-0.50292,-0.7345
|
||||
-0.57243,-1.1729-0.0695,-0.4384-0.00619,-0.8874 0.18193,-1.2902 0.18813,
|
||||
-0.4028 0.49262,-0.7412 0.87497,-0.9726 0.38234,-0.2314 0.82538,-0.3453 1.27308,
|
||||
-0.3273 0.44769,0.018 0.87994,0.1671 1.24209,0.4285 0.36214,0.2613 0.63791,
|
||||
0.6231 0.79244,1.0397 0.15453,0.4165 0.18087,0.8691 0.07569,1.3005 -0.14103,
|
||||
0.5785-0.5083,1.0778-1.02102,1.3881-0.51271,0.3102-1.12887,0.4061-1.71292,
|
||||
0.2664zM29.307094,7.91571c-0.4355,-0.10417-0.8301,-0.33393-1.1338,-0.66021
|
||||
-0.3037,-0.32628-0.5029,-0.73444-0.5724,-1.17286-0.0695,-0.43842-0.0062,
|
||||
-0.88741 0.1819,-1.29018 0.1881,-0.40278 0.4926,-0.74126 0.875,-0.97264
|
||||
0.3823,-0.23138 0.8253,-0.34527 1.273,-0.32726 0.4477,0.01801 0.88,0.16711
|
||||
1.2421,0.42844 0.3622,0.26132 0.638,0.62315 0.7925,1.03971 0.1545,0.41656
|
||||
0.1808,0.86916 0.0757,1.30055 -0.1411,0.57848-0.5083,1.07777-1.0211,1.38804
|
||||
-0.5127,0.31027-1.1288,0.4061-1.7129,0.26641z"
|
||||
fill="#0057D9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
37
apps/dokploy/public/templates/conduwuit.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="100%"
|
||||
viewBox="0 0 864 864"
|
||||
enableBackground="new 0 0 864 864"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
fill="#EC008C"
|
||||
opacity="1.000000"
|
||||
stroke="none"
|
||||
d="M0.999997,649.000000 C1.000000,433.052795 1.000000,217.105591 1.000000,1.079198 C288.876801,1.079198 576.753601,1.079198 865.000000,1.079198 C865.000000,73.025414 865.000000,145.051453 864.634888,217.500671 C852.362488,223.837280 840.447632,229.735275 828.549438,235.666794 C782.143677,258.801056 735.743225,281.945923 688.998657,304.980469 C688.122009,304.476532 687.580750,304.087708 687.053894,303.680206 C639.556946,266.944733 573.006775,291.446869 560.804199,350.179443 C560.141357,353.369446 559.717590,356.609131 559.195374,359.748962 C474.522705,359.748962 390.283478,359.748962 306.088135,359.748962 C298.804138,318.894806 265.253357,295.206024 231.834442,293.306793 C201.003021,291.554596 169.912033,310.230042 156.935104,338.792725 C149.905151,354.265930 147.884064,370.379944 151.151794,387.034515 C155.204453,407.689667 166.300507,423.954224 183.344437,436.516663 C181.938263,437.607025 180.887405,438.409576 179.849426,439.228516 C147.141953,465.032562 139.918045,510.888947 163.388611,545.322632 C167.274551,551.023804 172.285187,555.958313 176.587341,561.495728 C125.846893,587.012817 75.302292,612.295532 24.735992,637.534790 C16.874903,641.458496 8.914484,645.183228 0.999997,649.000000 z"
|
||||
/>
|
||||
<path
|
||||
fill="#000000"
|
||||
opacity="1.000000"
|
||||
stroke="none"
|
||||
d="M689.340759,305.086823 C735.743225,281.945923 782.143677,258.801056 828.549438,235.666794 C840.447632,229.735275 852.362488,223.837280 864.634888,217.961929 C865.000000,433.613190 865.000000,649.226379 865.000000,864.919800 C577.000000,864.919800 289.000000,864.919800 1.000000,864.919800 C1.000000,793.225708 1.000000,721.576721 0.999997,649.463867 C8.914484,645.183228 16.874903,641.458496 24.735992,637.534790 C75.302292,612.295532 125.846893,587.012817 176.939667,561.513062 C178.543060,562.085083 179.606812,562.886414 180.667526,563.691833 C225.656799,597.853394 291.232574,574.487244 304.462524,519.579773 C304.989105,517.394409 305.501068,515.205505 305.984619,513.166748 C391.466370,513.166748 476.422729,513.166748 561.331177,513.166748 C573.857727,555.764343 608.978149,572.880920 638.519897,572.672791 C671.048340,572.443665 700.623230,551.730408 711.658752,520.910583 C722.546875,490.502106 715.037842,453.265564 682.776733,429.447052 C683.966064,428.506866 685.119507,427.602356 686.265320,426.688232 C712.934143,405.412262 723.011475,370.684631 711.897339,338.686676 C707.312805,325.487671 699.185303,314.725128 689.340759,305.086823 z"
|
||||
/>
|
||||
<path
|
||||
fill="#FEFBFC"
|
||||
opacity="1.000000"
|
||||
stroke="none"
|
||||
d="M688.998657,304.980469 C699.185303,314.725128 707.312805,325.487671 711.897339,338.686676 C723.011475,370.684631 712.934143,405.412262 686.265320,426.688232 C685.119507,427.602356 683.966064,428.506866 682.776733,429.447052 C715.037842,453.265564 722.546875,490.502106 711.658752,520.910583 C700.623230,551.730408 671.048340,572.443665 638.519897,572.672791 C608.978149,572.880920 573.857727,555.764343 561.331177,513.166748 C476.422729,513.166748 391.466370,513.166748 305.984619,513.166748 C305.501068,515.205505 304.989105,517.394409 304.462524,519.579773 C291.232574,574.487244 225.656799,597.853394 180.667526,563.691833 C179.606812,562.886414 178.543060,562.085083 177.128418,561.264465 C172.285187,555.958313 167.274551,551.023804 163.388611,545.322632 C139.918045,510.888947 147.141953,465.032562 179.849426,439.228516 C180.887405,438.409576 181.938263,437.607025 183.344437,436.516663 C166.300507,423.954224 155.204453,407.689667 151.151794,387.034515 C147.884064,370.379944 149.905151,354.265930 156.935104,338.792725 C169.912033,310.230042 201.003021,291.554596 231.834442,293.306793 C265.253357,295.206024 298.804138,318.894806 306.088135,359.748962 C390.283478,359.748962 474.522705,359.748962 559.195374,359.748962 C559.717590,356.609131 560.141357,353.369446 560.804199,350.179443 C573.006775,291.446869 639.556946,266.944733 687.053894,303.680206 C687.580750,304.087708 688.122009,304.476532 688.998657,304.980469 M703.311279,484.370789 C698.954468,457.053253 681.951416,440.229645 656.413696,429.482330 C673.953552,421.977875 688.014709,412.074219 696.456482,395.642365 C704.862061,379.280853 706.487793,362.316345 700.947998,344.809204 C691.688965,315.548492 664.183716,296.954437 633.103516,298.838257 C618.467957,299.725372 605.538086,305.139557 594.588501,314.780121 C577.473999,329.848511 570.185486,349.121399 571.838501,371.750854 C479.166595,371.750854 387.082886,371.750854 294.582672,371.750854 C293.993011,354.662048 288.485260,339.622314 276.940491,327.118439 C265.392609,314.611176 251.082092,307.205322 234.093262,305.960541 C203.355347,303.708374 176.337585,320.898438 166.089890,348.816620 C159.557541,366.613007 160.527206,384.117401 168.756042,401.172516 C177.054779,418.372589 191.471954,428.832886 207.526581,435.632172 C198.407059,442.272583 188.815598,448.302246 180.383728,455.660675 C171.685028,463.251984 166.849655,473.658661 163.940216,484.838684 C161.021744,496.053375 161.212982,507.259705 164.178833,518.426208 C171.577927,546.284302 197.338104,566.588867 226.001465,567.336853 C240.828415,567.723816 254.357819,563.819092 266.385468,555.199646 C284.811554,541.994751 293.631104,523.530579 294.687347,501.238312 C387.354828,501.238312 479.461304,501.238312 571.531799,501.238312 C577.616638,543.189026 615.312866,566.342102 651.310059,559.044739 C684.973938,552.220398 708.263306,519.393127 703.311279,484.370789 z"
|
||||
/>
|
||||
<path
|
||||
fill="#EC008C"
|
||||
opacity="1.000000"
|
||||
stroke="none"
|
||||
d="M703.401855,484.804718 C708.263306,519.393127 684.973938,552.220398 651.310059,559.044739 C615.312866,566.342102 577.616638,543.189026 571.531799,501.238312 C479.461304,501.238312 387.354828,501.238312 294.687347,501.238312 C293.631104,523.530579 284.811554,541.994751 266.385468,555.199646 C254.357819,563.819092 240.828415,567.723816 226.001465,567.336853 C197.338104,566.588867 171.577927,546.284302 164.178833,518.426208 C161.212982,507.259705 161.021744,496.053375 163.940216,484.838684 C166.849655,473.658661 171.685028,463.251984 180.383728,455.660675 C188.815598,448.302246 198.407059,442.272583 207.526581,435.632172 C191.471954,428.832886 177.054779,418.372589 168.756042,401.172516 C160.527206,384.117401 159.557541,366.613007 166.089890,348.816620 C176.337585,320.898438 203.355347,303.708374 234.093262,305.960541 C251.082092,307.205322 265.392609,314.611176 276.940491,327.118439 C288.485260,339.622314 293.993011,354.662048 294.582672,371.750854 C387.082886,371.750854 479.166595,371.750854 571.838501,371.750854 C570.185486,349.121399 577.473999,329.848511 594.588501,314.780121 C605.538086,305.139557 618.467957,299.725372 633.103516,298.838257 C664.183716,296.954437 691.688965,315.548492 700.947998,344.809204 C706.487793,362.316345 704.862061,379.280853 696.456482,395.642365 C688.014709,412.074219 673.953552,421.977875 656.413696,429.482330 C681.951416,440.229645 698.954468,457.053253 703.401855,484.804718 z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
BIN
apps/dokploy/public/templates/couchdb.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
apps/dokploy/public/templates/glance.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
apps/dokploy/public/templates/homarr.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
5
apps/dokploy/public/templates/it-tools.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" viewBox="0 0 180 180" enable-background="new 0 0 180 180" xml:space="preserve">
|
||||
<path fill="#FEFEFE" opacity="1.000000" stroke="none" d=" M111.000000,181.000000 C74.000000,181.000000 37.500004,181.000000 1.000005,181.000000 C1.000003,121.000008 1.000003,61.000011 1.000002,1.000013 C60.999989,1.000009 120.999977,1.000009 180.999969,1.000004 C180.999985,60.999985 180.999985,120.999969 181.000000,180.999969 C157.833328,181.000000 134.666672,181.000000 111.000000,181.000000 M92.383194,23.019001 C81.313591,22.978577 81.347420,22.985394 79.133850,33.967770 C78.955315,34.853519 78.030640,35.938652 77.187553,36.284145 C71.550491,38.594177 65.841438,40.728523 60.079868,42.949146 C51.854774,36.325741 50.300980,36.060764 45.462185,40.534008 C35.819622,49.448120 35.819626,49.448124 43.224102,60.492462 C41.382980,64.713478 39.115070,68.685867 37.935665,72.958702 C36.732948,77.316010 35.234783,79.833847 30.057184,79.454468 C25.770687,79.140373 23.503710,82.094612 23.123014,86.367462 C22.170498,97.058365 23.448399,102.167702 28.351013,102.077286 C35.643330,101.942780 37.481285,106.068214 38.933784,111.529289 C38.983166,111.714943 39.436890,111.793068 40.802368,112.453255 C43.044167,109.879097 45.249161,106.642052 48.130058,104.203537 C52.509956,100.496193 54.125355,96.469872 53.950626,90.497017 C53.426945,72.595833 67.390541,56.483757 84.586967,54.011436 C104.170532,51.195923 121.810631,62.289631 127.042664,80.711487 C133.866669,104.738655 115.739006,128.447021 90.762108,128.272644 C88.059288,128.253754 84.679626,127.980476 82.777954,129.392670 C77.882332,133.028183 73.640266,137.543808 68.821976,141.999115 C70.466492,143.029861 70.972687,143.572098 71.558319,143.678986 C76.734505,144.623611 80.020096,146.547058 79.674500,152.950134 C79.451477,157.082016 83.111946,159.313339 87.371513,158.879456 C92.749634,158.331635 101.208298,161.697632 101.971764,153.709152 C102.722359,145.855438 106.998207,144.583542 112.480064,142.876846 C114.208542,142.338699 115.968300,141.598587 117.423370,140.548538 C120.332664,138.449066 122.307137,138.727737 125.134735,141.260239 C127.112488,143.031555 131.190781,144.421356 133.444077,143.586884 C144.141052,139.625412 146.261215,130.462555 138.985092,122.166840 C138.788422,121.942619 138.754318,121.575813 138.827591,121.776024 C140.987778,116.496361 143.289413,111.681892 144.889893,106.644653 C145.799866,103.780693 146.954834,102.739929 149.986267,102.681595 C156.969391,102.547234 158.883026,100.417870 158.972443,93.654068 C159.145615,80.552666 159.118896,80.521065 146.636948,78.797089 C144.032028,72.592224 141.475266,66.502083 138.827988,60.196339 C138.910278,60.044697 139.043243,59.482845 139.398239,59.192280 C146.726578,53.194103 145.816498,48.658714 138.725174,42.491474 C138.097733,41.945801 137.535004,41.326450 136.923462,40.761639 C132.204132,36.402893 129.404175,36.516243 124.469460,40.822025 C123.299767,41.842648 121.124886,42.989830 119.991692,42.558125 C114.297371,40.388798 108.795174,37.715183 103.089928,35.157913 C102.101974,24.233391 101.295547,23.213860 92.383194,23.019001 M117.068085,101.337982 C117.646690,96.274162 118.812340,91.188713 118.626854,86.153038 C118.464828,81.754181 115.007317,80.781288 111.666672,83.769424 C108.564766,86.544014 105.743721,89.632278 102.646210,92.412163 C98.519287,96.115883 93.187325,96.131935 89.592575,92.650833 C85.800652,88.978790 85.738319,83.501144 89.590172,79.245682 C92.160202,76.406357 95.071365,73.863434 97.508354,70.921532 C98.644897,69.549515 99.968910,67.175354 99.482567,65.928391 C98.952011,64.568085 96.507538,63.409248 94.753471,63.154884 C75.776215,60.402916 59.611244,77.614937 63.590363,96.427399 C64.295799,99.762573 63.735722,101.856697 61.316978,104.234566 C49.672939,115.681763 38.172298,127.276688 26.731220,138.927643 C21.533726,144.220474 21.828087,151.432770 27.139265,156.091965 C32.103416,160.446732 38.381413,159.921982 43.997818,154.388260 C55.510944,143.044617 66.852051,131.525391 78.450829,120.271019 C79.813171,118.949112 82.506157,117.969688 84.314713,118.299362 C98.749039,120.930519 110.081406,115.422195 117.068085,101.337982 z"/>
|
||||
<path fill="#1CA25B" opacity="1.000000" stroke="none" d=" M92.835266,23.022083 C101.295547,23.213860 102.101974,24.233391 103.089928,35.157913 C108.795174,37.715183 114.297371,40.388798 119.991692,42.558125 C121.124886,42.989830 123.299767,41.842648 124.469460,40.822025 C129.404175,36.516243 132.204132,36.402893 136.923462,40.761639 C137.535004,41.326450 138.097733,41.945801 138.725174,42.491474 C145.816498,48.658714 146.726578,53.194103 139.398239,59.192280 C139.043243,59.482845 138.910278,60.044697 138.827988,60.196339 C141.475266,66.502083 144.032028,72.592224 146.636948,78.797089 C159.118896,80.521065 159.145615,80.552666 158.972443,93.654068 C158.883026,100.417870 156.969391,102.547234 149.986267,102.681595 C146.954834,102.739929 145.799866,103.780693 144.889893,106.644653 C143.289413,111.681892 140.987778,116.496361 138.827591,121.776024 C138.754318,121.575813 138.788422,121.942619 138.985092,122.166840 C146.261215,130.462555 144.141052,139.625412 133.444077,143.586884 C131.190781,144.421356 127.112488,143.031555 125.134735,141.260239 C122.307137,138.727737 120.332664,138.449066 117.423370,140.548538 C115.968300,141.598587 114.208542,142.338699 112.480064,142.876846 C106.998207,144.583542 102.722359,145.855438 101.971764,153.709152 C101.208298,161.697632 92.749634,158.331635 87.371513,158.879456 C83.111946,159.313339 79.451477,157.082016 79.674500,152.950134 C80.020096,146.547058 76.734505,144.623611 71.558319,143.678986 C70.972687,143.572098 70.466492,143.029861 68.821976,141.999115 C73.640266,137.543808 77.882332,133.028183 82.777954,129.392670 C84.679626,127.980476 88.059288,128.253754 90.762108,128.272644 C115.739006,128.447021 133.866669,104.738655 127.042664,80.711487 C121.810631,62.289631 104.170532,51.195923 84.586967,54.011436 C67.390541,56.483757 53.426945,72.595833 53.950626,90.497017 C54.125355,96.469872 52.509956,100.496193 48.130058,104.203537 C45.249161,106.642052 43.044167,109.879097 40.802368,112.453255 C39.436890,111.793068 38.983166,111.714943 38.933784,111.529289 C37.481285,106.068214 35.643330,101.942780 28.351013,102.077286 C23.448399,102.167702 22.170498,97.058365 23.123014,86.367462 C23.503710,82.094612 25.770687,79.140373 30.057184,79.454468 C35.234783,79.833847 36.732948,77.316010 37.935665,72.958702 C39.115070,68.685867 41.382980,64.713478 43.224102,60.492462 C35.819626,49.448124 35.819622,49.448120 45.462185,40.534008 C50.300980,36.060764 51.854774,36.325741 60.079868,42.949146 C65.841438,40.728523 71.550491,38.594177 77.187553,36.284145 C78.030640,35.938652 78.955315,34.853519 79.133850,33.967770 C81.347420,22.985394 81.313591,22.978577 92.835266,23.022083 z"/>
|
||||
<path fill="#202020" opacity="1.000000" stroke="none" d=" M116.952621,101.710526 C110.081406,115.422195 98.749039,120.930519 84.314713,118.299362 C82.506157,117.969688 79.813171,118.949112 78.450829,120.271019 C66.852051,131.525391 55.510944,143.044617 43.997818,154.388260 C38.381413,159.921982 32.103416,160.446732 27.139265,156.091965 C21.828087,151.432770 21.533726,144.220474 26.731220,138.927643 C38.172298,127.276688 49.672939,115.681763 61.316978,104.234566 C63.735722,101.856697 64.295799,99.762573 63.590363,96.427399 C59.611244,77.614937 75.776215,60.402916 94.753471,63.154884 C96.507538,63.409248 98.952011,64.568085 99.482567,65.928391 C99.968910,67.175354 98.644897,69.549515 97.508354,70.921532 C95.071365,73.863434 92.160202,76.406357 89.590172,79.245682 C85.738319,83.501144 85.800652,88.978790 89.592575,92.650833 C93.187325,96.131935 98.519287,96.115883 102.646210,92.412163 C105.743721,89.632278 108.564766,86.544014 111.666672,83.769424 C115.007317,80.781288 118.464828,81.754181 118.626854,86.153038 C118.812340,91.188713 117.646690,96.274162 116.952621,101.710526 z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
9
apps/dokploy/public/templates/superset.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="128px" viewBox="0 0 256 128" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
||||
<title>Superset</title>
|
||||
<g>
|
||||
<path d="M190.218924,0 C168.269282,0 148.049828,12.3487941 128.508879,33.9252584 C109.307183,12.0095415 88.748476,0 65.7810761,0 C27.7508614,0 0,27.1402067 0,63.67771 C0,100.215213 27.7508614,127.016168 65.7810761,127.016168 C89.1555791,127.016168 107.271667,116.058309 127.491121,94.2104426 C147.03207,116.12616 166.912271,127.084018 190.218924,127.084018 C228.249139,127.016168 256,100.316989 256,63.67771 C256,27.038431 228.249139,0 190.218924,0 Z M66.0524781,88.6806255 C49.9379804,88.6806255 40.3371323,78.0620196 40.3371323,64.0169626 C40.3371323,49.9719056 49.9379804,39.0479724 66.0524781,39.0479724 C79.6225815,39.0479724 90.716141,49.9719056 102.725682,64.6954678 C91.3946462,78.4012722 79.4190299,88.6806255 66.0524781,88.6806255 Z M189.065465,88.6806255 C175.698913,88.6806255 164.401802,78.0620196 152.392261,64.0169626 C164.741055,49.2934005 175.359661,39.0479724 189.065465,39.0479724 C205.179963,39.0479724 214.679035,50.1076067 214.679035,64.0169626 C214.679035,77.9263186 205.179963,88.6806255 189.065465,88.6806255 Z" fill="#484848"></path>
|
||||
<path d="M156.124039,117.958124 L181.703684,87.4253909 C171.526107,84.3721177 162.12881,75.2122979 152.392261,63.8473363 L127.491121,94.2104426 C135.643361,103.668805 145.322237,111.695521 156.124039,117.958124 Z" fill="#20A7C9"></path>
|
||||
<path d="M128.508879,33.8913332 C120.41092,24.2972701 110.793109,16.0907501 100.045587,9.60084813 L74.432017,40.4728333 C84.1685661,43.8653591 92.7855818,52.6180758 101.945402,63.7794858 L102.963159,64.4919162 L128.508879,33.8913332 Z" fill="#20A7C9"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,6 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 40 40">
|
||||
<g id="ss11151339769_1">
|
||||
<path d="M 0 40 L 0 0 L 40 0 L 40 40 Z" fill="transparent"></path>
|
||||
<path d="M 34.95 0 L 5.05 0 C 2.262 0 0 2.262 0 5.05 L 0 34.95 C 0 37.738 2.262 40 5.05 40 L 34.95 40 C 37.738 40 40 37.738 40 34.95 L 40 5.05 C 40 2.262 37.738 0 34.95 0 Z M 8.021 14.894 C 8.021 12.709 9.794 10.935 11.979 10.935 L 19.6 10.935 C 19.712 10.935 19.815 11.003 19.862 11.106 C 19.909 11.209 19.888 11.329 19.812 11.415 L 18.141 13.229 C 17.85 13.544 17.441 13.726 17.012 13.726 L 12 13.726 C 11.344 13.726 10.812 14.259 10.812 14.915 L 10.812 17.909 C 10.812 18.294 10.5 18.606 10.115 18.606 L 8.721 18.606 C 8.335 18.606 8.024 18.294 8.024 17.909 L 8.024 14.894 Z M 31.729 25.106 C 31.729 27.291 29.956 29.065 27.771 29.065 L 24.532 29.065 C 22.347 29.065 20.574 27.291 20.574 25.106 L 20.574 19.438 C 20.574 19.053 20.718 18.682 20.979 18.397 L 22.868 16.347 C 22.947 16.262 23.071 16.232 23.182 16.274 C 23.291 16.318 23.365 16.421 23.365 16.538 L 23.365 25.088 C 23.365 25.744 23.897 26.276 24.553 26.276 L 27.753 26.276 C 28.409 26.276 28.941 25.744 28.941 25.088 L 28.941 14.915 C 28.941 14.259 28.409 13.726 27.753 13.726 L 24.032 13.726 C 23.606 13.726 23.2 13.906 22.909 14.218 L 11.812 26.276 L 18.479 26.276 C 18.865 26.276 19.176 26.588 19.176 26.974 L 19.176 28.368 C 19.176 28.753 18.865 29.065 18.479 29.065 L 9.494 29.065 C 8.679 29.065 8.018 28.403 8.018 27.588 L 8.018 26.85 C 8.018 26.479 8.156 26.124 8.409 25.85 L 20.85 12.335 C 21.674 11.441 22.829 10.935 24.044 10.935 L 27.768 10.935 C 29.953 10.935 31.726 12.709 31.726 14.894 L 31.726 25.106 Z" fill="rgb(0,0,0)"></path>
|
||||
</g>
|
||||
<svg width="136" height="136" viewBox="0 0 136 136" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2343_96406)">
|
||||
<path d="M136 2.28882e-05H0L0.000144482 136H136V2.28882e-05ZM27.27 50.6401C27.27 43.2101 33.3 37.1801 40.73 37.1801H66.64C67.02 37.1801 67.37 37.4101 67.53 37.7601C67.69 38.1101 67.62 38.5201 67.36 38.8101L61.68 44.9801C60.69 46.0501 59.3 46.6701 57.84 46.6701H40.8C38.57 46.6701 36.76 48.4801 36.76 50.7101V60.8901C36.76 62.2001 35.7 63.2601 34.39 63.2601H29.65C28.34 63.2601 27.28 62.2001 27.28 60.8901V50.6401H27.27ZM107.88 85.3601C107.88 92.7901 101.85 98.82 94.42 98.82H83.41C75.98 98.82 69.95 92.7901 69.95 85.3601V66.0901C69.95 64.7801 70.44 63.5201 71.33 62.5501L77.75 55.5801C78.02 55.2901 78.44 55.1901 78.82 55.3301C79.19 55.4801 79.44 55.83 79.44 56.23V85.3001C79.44 87.5301 81.25 89.3401 83.48 89.3401H94.36C96.59 89.3401 98.4 87.5301 98.4 85.3001V50.7101C98.4 48.4801 96.59 46.6701 94.36 46.6701H81.71C80.26 46.6701 78.88 47.2801 77.89 48.3401L40.16 89.3401H62.83C64.14 89.3401 65.2 90.4001 65.2 91.7101V96.4501C65.2 97.7601 64.14 98.82 62.83 98.82H32.28C29.51 98.82 27.26 96.5701 27.26 93.8001V91.29C27.26 90.03 27.73 88.8201 28.59 87.8901L70.89 41.9401C73.69 38.9001 77.62 37.1801 81.75 37.1801H94.41C101.84 37.1801 107.87 43.2101 107.87 50.6401V85.3601H107.88Z" fill="black"/>
|
||||
<path d="M27.27 50.6401C27.27 43.2101 33.3 37.1801 40.73 37.1801H66.64C67.02 37.1801 67.37 37.4101 67.53 37.7601C67.69 38.1101 67.62 38.5201 67.36 38.8101L61.68 44.9801C60.69 46.0501 59.3 46.6701 57.84 46.6701H40.8C38.57 46.6701 36.76 48.4801 36.76 50.7101V60.8901C36.76 62.2001 35.7 63.2601 34.39 63.2601H29.65C28.34 63.2601 27.28 62.2001 27.28 60.8901V50.6401H27.27Z" fill="white"/>
|
||||
<path d="M107.88 85.3601C107.88 92.7901 101.85 98.82 94.42 98.82H83.41C75.98 98.82 69.95 92.7901 69.95 85.3601V66.0901C69.95 64.7801 70.44 63.5201 71.33 62.5501L77.75 55.5801C78.02 55.2901 78.44 55.1901 78.82 55.3301C79.19 55.4801 79.44 55.83 79.44 56.23V85.3001C79.44 87.5301 81.25 89.3401 83.48 89.3401H94.36C96.59 89.3401 98.4 87.5301 98.4 85.3001V50.7101C98.4 48.4801 96.59 46.6701 94.36 46.6701H81.71C80.26 46.6701 78.88 47.2801 77.89 48.3401L40.16 89.3401H62.83C64.14 89.3401 65.2 90.4001 65.2 91.7101V96.4501C65.2 97.7601 64.14 98.82 62.83 98.82H32.28C29.51 98.82 27.26 96.5701 27.26 93.8001V91.29C27.26 90.03 27.73 88.8201 28.59 87.8901L70.89 41.9401C73.69 38.9001 77.62 37.1801 81.75 37.1801H94.41C101.84 37.1801 107.87 43.2101 107.87 50.6401V85.3601H107.88Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2343_96406">
|
||||
<rect width="136" height="136" rx="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -2,20 +2,24 @@ import {
|
||||
adminProcedure,
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
publicProcedure,
|
||||
} from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
apiCreateDiscord,
|
||||
apiCreateEmail,
|
||||
apiCreateGotify,
|
||||
apiCreateSlack,
|
||||
apiCreateTelegram,
|
||||
apiFindOneNotification,
|
||||
apiTestDiscordConnection,
|
||||
apiTestEmailConnection,
|
||||
apiTestGotifyConnection,
|
||||
apiTestSlackConnection,
|
||||
apiTestTelegramConnection,
|
||||
apiUpdateDiscord,
|
||||
apiUpdateEmail,
|
||||
apiUpdateGotify,
|
||||
apiUpdateSlack,
|
||||
apiUpdateTelegram,
|
||||
notifications,
|
||||
@@ -24,16 +28,19 @@ import {
|
||||
IS_CLOUD,
|
||||
createDiscordNotification,
|
||||
createEmailNotification,
|
||||
createGotifyNotification,
|
||||
createSlackNotification,
|
||||
createTelegramNotification,
|
||||
findNotificationById,
|
||||
removeNotificationById,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
sendSlackNotification,
|
||||
sendTelegramNotification,
|
||||
updateDiscordNotification,
|
||||
updateEmailNotification,
|
||||
updateGotifyNotification,
|
||||
updateSlackNotification,
|
||||
updateTelegramNotification,
|
||||
} from "@dokploy/server";
|
||||
@@ -300,10 +307,61 @@ export const notificationRouter = createTRPCRouter({
|
||||
telegram: true,
|
||||
discord: true,
|
||||
email: true,
|
||||
gotify: true,
|
||||
},
|
||||
orderBy: desc(notifications.createdAt),
|
||||
...(IS_CLOUD && { where: eq(notifications.adminId, ctx.user.adminId) }),
|
||||
// TODO: Remove this line when the cloud version is ready
|
||||
});
|
||||
}),
|
||||
createGotify: adminProcedure
|
||||
.input(apiCreateGotify)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await createGotifyNotification(input, ctx.user.adminId);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error creating the notification",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
updateGotify: adminProcedure
|
||||
.input(apiUpdateGotify)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const notification = await findNotificationById(input.notificationId);
|
||||
if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to update this notification",
|
||||
});
|
||||
}
|
||||
return await updateGotifyNotification({
|
||||
...input,
|
||||
adminId: ctx.user.adminId,
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
testGotifyConnection: adminProcedure
|
||||
.input(apiTestGotifyConnection)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
await sendGotifyNotification(
|
||||
input,
|
||||
"Test Notification",
|
||||
"Hi, From Dokploy 👋",
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error testing the notification",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ export const projectRouter = createTRPCRouter({
|
||||
.input(apiFindOneProject)
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
const { accesedServices } = await findUserByAuthId(ctx.user.authId);
|
||||
const { accessedServices } = await findUserByAuthId(ctx.user.authId);
|
||||
|
||||
await checkProjectAccess(ctx.user.authId, "access", input.projectId);
|
||||
|
||||
@@ -79,28 +79,28 @@ export const projectRouter = createTRPCRouter({
|
||||
),
|
||||
with: {
|
||||
compose: {
|
||||
where: buildServiceFilter(compose.composeId, accesedServices),
|
||||
where: buildServiceFilter(compose.composeId, accessedServices),
|
||||
},
|
||||
applications: {
|
||||
where: buildServiceFilter(
|
||||
applications.applicationId,
|
||||
accesedServices,
|
||||
accessedServices,
|
||||
),
|
||||
},
|
||||
mariadb: {
|
||||
where: buildServiceFilter(mariadb.mariadbId, accesedServices),
|
||||
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
||||
},
|
||||
mongo: {
|
||||
where: buildServiceFilter(mongo.mongoId, accesedServices),
|
||||
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
||||
},
|
||||
mysql: {
|
||||
where: buildServiceFilter(mysql.mysqlId, accesedServices),
|
||||
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
||||
},
|
||||
postgres: {
|
||||
where: buildServiceFilter(postgres.postgresId, accesedServices),
|
||||
where: buildServiceFilter(postgres.postgresId, accessedServices),
|
||||
},
|
||||
redis: {
|
||||
where: buildServiceFilter(redis.redisId, accesedServices),
|
||||
where: buildServiceFilter(redis.redisId, accessedServices),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -125,18 +125,18 @@ export const projectRouter = createTRPCRouter({
|
||||
}),
|
||||
all: protectedProcedure.query(async ({ ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
const { accesedProjects, accesedServices } = await findUserByAuthId(
|
||||
const { accessedProjects, accessedServices } = await findUserByAuthId(
|
||||
ctx.user.authId,
|
||||
);
|
||||
|
||||
if (accesedProjects.length === 0) {
|
||||
if (accessedProjects.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = await db.query.projects.findMany({
|
||||
where: and(
|
||||
sql`${projects.projectId} IN (${sql.join(
|
||||
accesedProjects.map((projectId) => sql`${projectId}`),
|
||||
accessedProjects.map((projectId) => sql`${projectId}`),
|
||||
sql`, `,
|
||||
)})`,
|
||||
eq(projects.adminId, ctx.user.adminId),
|
||||
@@ -145,27 +145,27 @@ export const projectRouter = createTRPCRouter({
|
||||
applications: {
|
||||
where: buildServiceFilter(
|
||||
applications.applicationId,
|
||||
accesedServices,
|
||||
accessedServices,
|
||||
),
|
||||
with: { domains: true },
|
||||
},
|
||||
mariadb: {
|
||||
where: buildServiceFilter(mariadb.mariadbId, accesedServices),
|
||||
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
||||
},
|
||||
mongo: {
|
||||
where: buildServiceFilter(mongo.mongoId, accesedServices),
|
||||
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
||||
},
|
||||
mysql: {
|
||||
where: buildServiceFilter(mysql.mysqlId, accesedServices),
|
||||
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
||||
},
|
||||
postgres: {
|
||||
where: buildServiceFilter(postgres.postgresId, accesedServices),
|
||||
where: buildServiceFilter(postgres.postgresId, accessedServices),
|
||||
},
|
||||
redis: {
|
||||
where: buildServiceFilter(redis.redisId, accesedServices),
|
||||
where: buildServiceFilter(redis.redisId, accessedServices),
|
||||
},
|
||||
compose: {
|
||||
where: buildServiceFilter(compose.composeId, accesedServices),
|
||||
where: buildServiceFilter(compose.composeId, accessedServices),
|
||||
with: { domains: true },
|
||||
},
|
||||
},
|
||||
@@ -239,10 +239,13 @@ export const projectRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
});
|
||||
function buildServiceFilter(fieldName: AnyPgColumn, accesedServices: string[]) {
|
||||
return accesedServices.length > 0
|
||||
function buildServiceFilter(
|
||||
fieldName: AnyPgColumn,
|
||||
accessedServices: string[],
|
||||
) {
|
||||
return accessedServices.length > 0
|
||||
? sql`${fieldName} IN (${sql.join(
|
||||
accesedServices.map((serviceId) => sql`${serviceId}`),
|
||||
accessedServices.map((serviceId) => sql`${serviceId}`),
|
||||
sql`, `,
|
||||
)})`
|
||||
: sql`1 = 0`;
|
||||
|
||||
@@ -345,7 +345,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
writeConfig("middlewares", input.traefikConfig);
|
||||
return true;
|
||||
}),
|
||||
getUpdateData: adminProcedure.mutation(async () => {
|
||||
getUpdateData: protectedProcedure.mutation(async () => {
|
||||
if (IS_CLOUD) {
|
||||
return DEFAULT_UPDATE_DATA;
|
||||
}
|
||||
@@ -373,10 +373,10 @@ export const settingsRouter = createTRPCRouter({
|
||||
return true;
|
||||
}),
|
||||
|
||||
getDokployVersion: adminProcedure.query(() => {
|
||||
getDokployVersion: protectedProcedure.query(() => {
|
||||
return packageInfo.version;
|
||||
}),
|
||||
getReleaseTag: adminProcedure.query(() => {
|
||||
getReleaseTag: protectedProcedure.query(() => {
|
||||
return getDokployImageTag();
|
||||
}),
|
||||
readDirectories: protectedProcedure
|
||||
|
||||
@@ -14,7 +14,7 @@ export const isWSL = async () => {
|
||||
/** Returns the Docker host IP address. */
|
||||
export const getDockerHost = async (): Promise<string> => {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
if (process.platform === "linux" && !isWSL()) {
|
||||
if (process.platform === "linux" && !(await isWSL())) {
|
||||
try {
|
||||
// Try to get the Docker bridge IP first
|
||||
const { stdout } = await execAsync(
|
||||
|
||||
@@ -101,9 +101,29 @@
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
/* Custom scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 0.3125rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--border));
|
||||
border-radius: 0.3125rem;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: hsl(var(--border)) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.xterm-viewport {
|
||||
|
||||
18
apps/dokploy/templates/cloudflared/docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
cloudflared:
|
||||
image: 'cloudflare/cloudflared:latest'
|
||||
environment:
|
||||
# Don't forget to set this in your Dokploy Environment
|
||||
- 'TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}'
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
command: [
|
||||
"tunnel",
|
||||
|
||||
# More tunnel run parameters here:
|
||||
# https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/tunnel-run-parameters/
|
||||
"--no-autoupdate",
|
||||
#"--protocol", "http2",
|
||||
|
||||
"run"
|
||||
]
|
||||
9
apps/dokploy/templates/cloudflared/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { Schema, Template } from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const envs = [`CLOUDFLARE_TUNNEL_TOKEN="<INSERT TOKEN>"`];
|
||||
|
||||
return {
|
||||
envs,
|
||||
};
|
||||
}
|
||||
48
apps/dokploy/templates/conduwuit/docker-compose.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
# conduwuit
|
||||
# https://conduwuit.puppyirl.gay/deploying/docker-compose.yml
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
image: girlbossceo/conduwuit:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8448:6167
|
||||
volumes:
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
environment:
|
||||
# Edit this in your Dokploy Environment
|
||||
CONDUWUIT_SERVER_NAME: ${CONDUWUIT_SERVER_NAME}
|
||||
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_REGISTRATION_TOKEN: ${CONDUWUIT_REGISTRATION_TOKEN}
|
||||
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
|
||||
# Uncomment if you mapped config toml in volumes
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml'
|
||||
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/element-web:latest
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - 8009:80
|
||||
# volumes:
|
||||
# - ./element_config.json:/app/config.json
|
||||
# depends_on:
|
||||
# - homeserver
|
||||
|
||||
volumes:
|
||||
db:
|
||||
30
apps/dokploy/templates/conduwuit/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const matrixSubdomain = generateRandomDomain(schema);
|
||||
const registrationToken = generatePassword(20);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: matrixSubdomain,
|
||||
port: 6167,
|
||||
serviceName: "homeserver",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`CONDUWUIT_SERVER_NAME=${matrixSubdomain}`,
|
||||
`CONDUWUIT_REGISTRATION_TOKEN=${registrationToken}`,
|
||||
];
|
||||
|
||||
return {
|
||||
envs,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
17
apps/dokploy/templates/couchdb/docker-compose.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
couchdb:
|
||||
image: couchdb:latest
|
||||
ports:
|
||||
- '5984'
|
||||
volumes:
|
||||
- couchdb-data:/opt/couchdb/data
|
||||
environment:
|
||||
- COUCHDB_USER=${COUCHDB_USER}
|
||||
- COUCHDB_PASSWORD=${COUCHDB_PASSWORD}
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
couchdb-data:
|
||||
driver: local
|
||||
28
apps/dokploy/templates/couchdb/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const username = generatePassword(16);
|
||||
const password = generatePassword(32);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
serviceName: "couchdb",
|
||||
host: mainDomain,
|
||||
port: 5984,
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [`COUCHDB_USER=${username}`, `COUCHDB_PASSWORD=${password}`];
|
||||
|
||||
return {
|
||||
envs,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
8
apps/dokploy/templates/glance/docker-compose.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
glance:
|
||||
image: glanceapp/glance
|
||||
volumes:
|
||||
- ../files/app/glance.yml:/app/glance.yml
|
||||
ports:
|
||||
- 8080
|
||||
restart: unless-stopped
|
||||
108
apps/dokploy/templates/glance/index.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 8080,
|
||||
serviceName: "glance",
|
||||
},
|
||||
];
|
||||
|
||||
const mounts: Template["mounts"] = [
|
||||
{
|
||||
filePath: "/app/glance.yml",
|
||||
content: `
|
||||
branding:
|
||||
hide-footer: true
|
||||
logo-text: P
|
||||
|
||||
pages:
|
||||
- name: Home
|
||||
columns:
|
||||
- size: small
|
||||
widgets:
|
||||
- type: calendar
|
||||
|
||||
- type: releases
|
||||
show-source-icon: true
|
||||
repositories:
|
||||
- Dokploy/dokploy
|
||||
- n8n-io/n8n
|
||||
- Budibase/budibase
|
||||
- home-assistant/core
|
||||
- tidbyt/pixlet
|
||||
|
||||
- type: twitch-channels
|
||||
channels:
|
||||
- nmplol
|
||||
- extraemily
|
||||
- qtcinderella
|
||||
- ludwig
|
||||
- timthetatman
|
||||
- mizkif
|
||||
|
||||
- size: full
|
||||
widgets:
|
||||
- type: hacker-news
|
||||
|
||||
- type: videos
|
||||
style: grid-cards
|
||||
channels:
|
||||
- UC3GzdWYwUYI1ACxuP9Nm-eg
|
||||
- UCGbg3DjQdcqWwqOLHpYHXIg
|
||||
- UC24RSoLcjiNZbQcT54j5l7Q
|
||||
limit: 3
|
||||
|
||||
- type: rss
|
||||
limit: 10
|
||||
collapse-after: 3
|
||||
cache: 3h
|
||||
feeds:
|
||||
- url: https://daringfireball.net/feeds/main
|
||||
title: Daring Fireball
|
||||
|
||||
- size: small
|
||||
widgets:
|
||||
- type: weather
|
||||
location: Gansevoort, New York, United States
|
||||
show-area-name: false
|
||||
units: imperial
|
||||
hour-format: 12h
|
||||
|
||||
- type: markets
|
||||
markets:
|
||||
- symbol: SPY
|
||||
name: S&P 500
|
||||
- symbol: VOO
|
||||
name: Vanguard
|
||||
- symbol: BTC-USD
|
||||
name: Bitcoin
|
||||
- symbol: ETH-USD
|
||||
name: Etherium
|
||||
- symbol: NVDA
|
||||
name: NVIDIA
|
||||
- symbol: AAPL
|
||||
name: Apple
|
||||
- symbol: MSFT
|
||||
name: Microsoft
|
||||
- symbol: GOOGL
|
||||
name: Google
|
||||
- symbol: AMD
|
||||
name: AMD
|
||||
- symbol: TSLA
|
||||
name: Tesla`,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
mounts,
|
||||
};
|
||||
}
|
||||
11
apps/dokploy/templates/homarr/docker-compose.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
homarr:
|
||||
image: ghcr.io/homarr-labs/homarr:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
|
||||
- ../homarr/appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY}
|
||||
ports:
|
||||
- 7575
|
||||
27
apps/dokploy/templates/homarr/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const secretKey = generatePassword(64);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 7575,
|
||||
serviceName: "homarr",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [`SECRET_ENCRYPTION_KEY=${secretKey}`];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
8
apps/dokploy/templates/it-tools/docker-compose.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
it-tools:
|
||||
image: corentinth/it-tools:latest
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
20
apps/dokploy/templates/it-tools/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 80,
|
||||
serviceName: "it-tools",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
};
|
||||
}
|
||||
62
apps/dokploy/templates/superset/docker-compose.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
# Note: this is an UNOFFICIAL production docker image build for Superset:
|
||||
# - https://github.com/amancevice/docker-superset
|
||||
#
|
||||
# After deploying this image, you will need to run one of the two
|
||||
# commands below in a terminal within the superset container:
|
||||
# $ superset-demo # Initialise database + load demo charts/datasets
|
||||
# $ superset-init # Initialise database only
|
||||
#
|
||||
# You will be prompted to enter the credentials for the admin user.
|
||||
|
||||
services:
|
||||
superset:
|
||||
image: amancevice/superset
|
||||
restart: always
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
environment:
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
MAPBOX_API_KEY: ${MAPBOX_API_KEY}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
# Note: superset_config.py can be edited in Dokploy's UI Volume Mount
|
||||
- ../files/superset/superset_config.py:/etc/superset/superset_config.py
|
||||
|
||||
db:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
volumes:
|
||||
- redis:/data
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
redis:
|
||||
67
apps/dokploy/templates/superset/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mapboxApiKey = "";
|
||||
const secretKey = generatePassword(30);
|
||||
const postgresDb = "superset";
|
||||
const postgresUser = "superset";
|
||||
const postgresPassword = generatePassword(30);
|
||||
const redisPassword = generatePassword(30);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 8088,
|
||||
serviceName: "superset",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`SECRET_KEY=${secretKey}`,
|
||||
`MAPBOX_API_KEY=${mapboxApiKey}`,
|
||||
`POSTGRES_DB=${postgresDb}`,
|
||||
`POSTGRES_USER=${postgresUser}`,
|
||||
`POSTGRES_PASSWORD=${postgresPassword}`,
|
||||
`REDIS_PASSWORD=${redisPassword}`,
|
||||
];
|
||||
|
||||
const mounts: Template["mounts"] = [
|
||||
{
|
||||
filePath: "./superset/superset_config.py",
|
||||
content: `
|
||||
import os
|
||||
|
||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||
MAPBOX_API_KEY = os.getenv("MAPBOX_API_KEY", "")
|
||||
|
||||
CACHE_CONFIG = {
|
||||
"CACHE_TYPE": "RedisCache",
|
||||
"CACHE_DEFAULT_TIMEOUT": 300,
|
||||
"CACHE_KEY_PREFIX": "superset_",
|
||||
"CACHE_REDIS_HOST": "redis",
|
||||
"CACHE_REDIS_PORT": 6379,
|
||||
"CACHE_REDIS_DB": 1,
|
||||
"CACHE_REDIS_URL": f"redis://:{os.getenv('REDIS_PASSWORD')}@redis:6379/1",
|
||||
}
|
||||
|
||||
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
|
||||
`.trim(),
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
envs,
|
||||
domains,
|
||||
mounts,
|
||||
};
|
||||
}
|
||||
@@ -1074,7 +1074,7 @@ export const templates: TemplateData[] = [
|
||||
website: "https://penpot.app/",
|
||||
docs: "https://docs.penpot.app/",
|
||||
},
|
||||
tags: ["desing", "collaboration"],
|
||||
tags: ["design", "collaboration"],
|
||||
load: () => import("./penpot/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
@@ -1097,7 +1097,7 @@ export const templates: TemplateData[] = [
|
||||
name: "Unsend",
|
||||
version: "v1.2.4",
|
||||
description: "Open source alternative to Resend,Sendgrid, Postmark etc. ",
|
||||
logo: "unsend.png", // we defined the name and the extension of the logo
|
||||
logo: "unsend.png",
|
||||
links: {
|
||||
github: "https://github.com/unsend-dev/unsend",
|
||||
website: "https://unsend.dev/",
|
||||
@@ -1239,4 +1239,106 @@ export const templates: TemplateData[] = [
|
||||
tags: ["matrix", "communication"],
|
||||
load: () => import("./conduit/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "conduwuit",
|
||||
name: "Conduwuit",
|
||||
version: "latest",
|
||||
description:
|
||||
"Well-maintained, featureful Matrix chat homeserver (fork of Conduit)",
|
||||
logo: "conduwuit.svg",
|
||||
links: {
|
||||
github: "https://github.com/girlbossceo/conduwuit",
|
||||
website: "https://conduwuit.puppyirl.gay",
|
||||
docs: "https://conduwuit.puppyirl.gay/configuration.html",
|
||||
},
|
||||
tags: ["backend", "chat", "communication", "matrix", "server"],
|
||||
load: () => import("./conduwuit/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "cloudflared",
|
||||
name: "Cloudflared",
|
||||
version: "latest",
|
||||
description:
|
||||
"A lightweight daemon that securely connects local services to the internet through Cloudflare Tunnel.",
|
||||
logo: "cloudflared.svg",
|
||||
links: {
|
||||
github: "https://github.com/cloudflare/cloudflared",
|
||||
website:
|
||||
"https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/",
|
||||
docs: "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/",
|
||||
},
|
||||
tags: ["cloud", "networking", "security", "tunnel"],
|
||||
load: () => import("./cloudflared/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "couchdb",
|
||||
name: "CouchDB",
|
||||
version: "latest",
|
||||
description:
|
||||
"CouchDB is a document-oriented NoSQL database that excels at replication and horizontal scaling.",
|
||||
logo: "couchdb.png",
|
||||
links: {
|
||||
github: "https://github.com/apache/couchdb",
|
||||
website: "https://couchdb.apache.org/",
|
||||
docs: "https://docs.couchdb.org/en/stable/",
|
||||
},
|
||||
tags: ["database", "storage"],
|
||||
load: () => import("./couchdb/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "it-tools",
|
||||
name: "IT Tools",
|
||||
version: "latest",
|
||||
description: "A collection of handy online it-tools for developers.",
|
||||
logo: "it-tools.svg",
|
||||
links: {
|
||||
github: "https://github.com/CorentinTh/it-tools",
|
||||
website: "https://it-tools.tech",
|
||||
},
|
||||
tags: ["developer", "tools"],
|
||||
load: () => import("./it-tools/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "superset",
|
||||
name: "Superset (Unofficial)",
|
||||
version: "latest",
|
||||
description: "Data visualization and data exploration platform.",
|
||||
logo: "superset.svg",
|
||||
links: {
|
||||
github: "https://github.com/amancevice/docker-superset",
|
||||
website: "https://superset.apache.org",
|
||||
docs: "https://superset.apache.org/docs/intro",
|
||||
},
|
||||
tags: ["analytics", "bi", "dashboard", "database", "sql"],
|
||||
load: () => import("./superset/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "glance",
|
||||
name: "Glance",
|
||||
version: "latest",
|
||||
description:
|
||||
"A self-hosted dashboard that puts all your feeds in one place. Features RSS feeds, weather, bookmarks, site monitoring, and more in a minimal, fast interface.",
|
||||
logo: "glance.png",
|
||||
links: {
|
||||
github: "https://github.com/glanceapp/glance",
|
||||
docs: "https://github.com/glanceapp/glance/blob/main/docs/configuration.md",
|
||||
},
|
||||
tags: ["dashboard", "monitoring", "widgets", "rss"],
|
||||
load: () => import("./glance/index").then((m) => m.generate),
|
||||
},
|
||||
{
|
||||
id: "homarr",
|
||||
name: "Homarr",
|
||||
version: "latest",
|
||||
description:
|
||||
"A sleek, modern dashboard that puts all your apps and services in one place with Docker integration.",
|
||||
logo: "homarr.png",
|
||||
links: {
|
||||
github: "https://github.com/homarr-labs/homarr",
|
||||
docs: "https://homarr.dev/docs/getting-started/installation/docker",
|
||||
website: "https://homarr.dev/",
|
||||
},
|
||||
tags: ["dashboard", "monitoring"],
|
||||
load: () => import("./homarr/index").then((m) => m.generate),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -31,6 +31,15 @@ export const admins = pgTable("admin", {
|
||||
stripeCustomerId: text("stripeCustomerId"),
|
||||
stripeSubscriptionId: text("stripeSubscriptionId"),
|
||||
serversQuantity: integer("serversQuantity").notNull().default(0),
|
||||
cleanupCacheApplications: boolean("cleanupCacheApplications")
|
||||
.notNull()
|
||||
.default(true),
|
||||
cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
|
||||
.notNull()
|
||||
.default(false),
|
||||
cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
|
||||
.notNull()
|
||||
.default(false),
|
||||
});
|
||||
|
||||
export const adminsRelations = relations(admins, ({ one, many }) => ({
|
||||
|
||||
@@ -10,6 +10,7 @@ export const notificationType = pgEnum("notificationType", [
|
||||
"telegram",
|
||||
"discord",
|
||||
"email",
|
||||
"gotify",
|
||||
]);
|
||||
|
||||
export const notifications = pgTable("notification", {
|
||||
@@ -39,6 +40,9 @@ export const notifications = pgTable("notification", {
|
||||
emailId: text("emailId").references(() => email.emailId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
adminId: text("adminId").references(() => admins.adminId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
@@ -84,6 +88,17 @@ export const email = pgTable("email", {
|
||||
toAddresses: text("toAddress").array().notNull(),
|
||||
});
|
||||
|
||||
export const gotify = pgTable("gotify", {
|
||||
gotifyId: text("gotifyId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
serverUrl: text("serverUrl").notNull(),
|
||||
appToken: text("appToken").notNull(),
|
||||
priority: integer("priority").notNull().default(5),
|
||||
decoration: boolean("decoration"),
|
||||
});
|
||||
|
||||
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||
slack: one(slack, {
|
||||
fields: [notifications.slackId],
|
||||
@@ -101,6 +116,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||
fields: [notifications.emailId],
|
||||
references: [email.emailId],
|
||||
}),
|
||||
gotify: one(gotify, {
|
||||
fields: [notifications.gotifyId],
|
||||
references: [gotify.gotifyId],
|
||||
}),
|
||||
admin: one(admins, {
|
||||
fields: [notifications.adminId],
|
||||
references: [admins.adminId],
|
||||
@@ -224,6 +243,39 @@ export const apiTestEmailConnection = apiCreateEmail.pick({
|
||||
fromAddress: true,
|
||||
});
|
||||
|
||||
export const apiCreateGotify = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
})
|
||||
.extend({
|
||||
serverUrl: z.string().min(1),
|
||||
appToken: z.string().min(1),
|
||||
priority: z.number().min(1),
|
||||
decoration: z.boolean(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiUpdateGotify = apiCreateGotify.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
gotifyId: z.string().min(1),
|
||||
adminId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestGotifyConnection = apiCreateGotify
|
||||
.pick({
|
||||
serverUrl: true,
|
||||
appToken: true,
|
||||
priority: true,
|
||||
})
|
||||
.extend({
|
||||
decoration: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const apiFindOneNotification = notificationsSchema
|
||||
.pick({
|
||||
notificationId: true,
|
||||
@@ -242,5 +294,8 @@ export const apiSendTest = notificationsSchema
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
toAddresses: z.array(z.string()),
|
||||
serverUrl: z.string(),
|
||||
appToken: z.string(),
|
||||
priority: z.number(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
@@ -40,11 +40,11 @@ export const users = pgTable("user", {
|
||||
canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
|
||||
.notNull()
|
||||
.default(false),
|
||||
accesedProjects: text("accesedProjects")
|
||||
accessedProjects: text("accesedProjects")
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`ARRAY[]::text[]`),
|
||||
accesedServices: text("accesedServices")
|
||||
accessedServices: text("accesedServices")
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`ARRAY[]::text[]`),
|
||||
@@ -73,8 +73,8 @@ const createSchema = createInsertSchema(users, {
|
||||
token: z.string().min(1),
|
||||
isRegistered: z.boolean().optional(),
|
||||
adminId: z.string(),
|
||||
accesedProjects: z.array(z.string()).optional(),
|
||||
accesedServices: z.array(z.string()).optional(),
|
||||
accessedProjects: z.array(z.string()).optional(),
|
||||
accessedServices: z.array(z.string()).optional(),
|
||||
canCreateProjects: z.boolean().optional(),
|
||||
canCreateServices: z.boolean().optional(),
|
||||
canDeleteProjects: z.boolean().optional(),
|
||||
@@ -106,8 +106,8 @@ export const apiAssignPermissions = createSchema
|
||||
canCreateServices: true,
|
||||
canDeleteProjects: true,
|
||||
canDeleteServices: true,
|
||||
accesedProjects: true,
|
||||
accesedServices: true,
|
||||
accessedProjects: true,
|
||||
accessedServices: true,
|
||||
canAccessToTraefikFiles: true,
|
||||
canAccessToDocker: true,
|
||||
canAccessToAPI: true,
|
||||
|
||||
@@ -40,7 +40,7 @@ import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { encodeBase64 } from "../utils/docker/utils";
|
||||
import { getDokployUrl } from "./admin";
|
||||
import { findAdminById, getDokployUrl } from "./admin";
|
||||
import {
|
||||
createDeployment,
|
||||
createDeploymentPreview,
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
updatePreviewDeployment,
|
||||
} from "./preview-deployment";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { cleanupFullDocker } from "./settings";
|
||||
export type Application = typeof applications.$inferSelect;
|
||||
|
||||
export const createApplication = async (
|
||||
@@ -175,6 +176,7 @@ export const deployApplication = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
|
||||
const deployment = await createDeployment({
|
||||
applicationId: applicationId,
|
||||
@@ -183,6 +185,12 @@ export const deployApplication = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const admin = await findAdminById(application.project.adminId);
|
||||
|
||||
if (admin.cleanupCacheApplications) {
|
||||
await cleanupFullDocker(application?.serverId);
|
||||
}
|
||||
|
||||
if (application.sourceType === "github") {
|
||||
await cloneGithubRepository({
|
||||
...application,
|
||||
@@ -213,6 +221,7 @@ export const deployApplication = async ({
|
||||
applicationType: "application",
|
||||
buildLink,
|
||||
adminId: application.project.adminId,
|
||||
domains: application.domains,
|
||||
});
|
||||
} catch (error) {
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
@@ -243,6 +252,7 @@ export const rebuildApplication = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
const deployment = await createDeployment({
|
||||
applicationId: applicationId,
|
||||
title: titleLog,
|
||||
@@ -250,6 +260,11 @@ export const rebuildApplication = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const admin = await findAdminById(application.project.adminId);
|
||||
|
||||
if (admin.cleanupCacheApplications) {
|
||||
await cleanupFullDocker(application?.serverId);
|
||||
}
|
||||
if (application.sourceType === "github") {
|
||||
await buildApplication(application, deployment.logPath);
|
||||
} else if (application.sourceType === "gitlab") {
|
||||
@@ -284,6 +299,7 @@ export const deployRemoteApplication = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
|
||||
const deployment = await createDeployment({
|
||||
applicationId: applicationId,
|
||||
@@ -293,6 +309,11 @@ export const deployRemoteApplication = async ({
|
||||
|
||||
try {
|
||||
if (application.serverId) {
|
||||
const admin = await findAdminById(application.project.adminId);
|
||||
|
||||
if (admin.cleanupCacheApplications) {
|
||||
await cleanupFullDocker(application?.serverId);
|
||||
}
|
||||
let command = "set -e;";
|
||||
if (application.sourceType === "github") {
|
||||
command += await getGithubCloneCommand({
|
||||
@@ -332,6 +353,7 @@ export const deployRemoteApplication = async ({
|
||||
applicationType: "application",
|
||||
buildLink,
|
||||
adminId: application.project.adminId,
|
||||
domains: application.domains,
|
||||
});
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
@@ -357,14 +379,6 @@ export const deployRemoteApplication = async ({
|
||||
adminId: application.project.adminId,
|
||||
});
|
||||
|
||||
console.log(
|
||||
"Error on ",
|
||||
application.buildType,
|
||||
"/",
|
||||
application.sourceType,
|
||||
error,
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -383,6 +397,7 @@ export const deployPreviewApplication = async ({
|
||||
previewDeploymentId: string;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
const deployment = await createDeploymentPreview({
|
||||
title: titleLog,
|
||||
description: descriptionLog,
|
||||
@@ -439,6 +454,12 @@ export const deployPreviewApplication = async ({
|
||||
application.env = application.previewEnv;
|
||||
application.buildArgs = application.previewBuildArgs;
|
||||
|
||||
const admin = await findAdminById(application.project.adminId);
|
||||
|
||||
if (admin.cleanupCacheOnPreviews) {
|
||||
await cleanupFullDocker(application?.serverId);
|
||||
}
|
||||
|
||||
if (application.sourceType === "github") {
|
||||
await cloneGithubRepository({
|
||||
...application,
|
||||
@@ -448,7 +469,6 @@ export const deployPreviewApplication = async ({
|
||||
});
|
||||
await buildApplication(application, deployment.logPath);
|
||||
}
|
||||
// 4eef09efc46009187d668cf1c25f768d0bde4f91
|
||||
const successComment = getIssueComment(
|
||||
application.name,
|
||||
"success",
|
||||
@@ -490,6 +510,7 @@ export const deployRemotePreviewApplication = async ({
|
||||
previewDeploymentId: string;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
const deployment = await createDeploymentPreview({
|
||||
title: titleLog,
|
||||
description: descriptionLog,
|
||||
@@ -547,10 +568,17 @@ export const deployRemotePreviewApplication = async ({
|
||||
application.buildArgs = application.previewBuildArgs;
|
||||
|
||||
if (application.serverId) {
|
||||
const admin = await findAdminById(application.project.adminId);
|
||||
|
||||
if (admin.cleanupCacheOnPreviews) {
|
||||
await cleanupFullDocker(application?.serverId);
|
||||
}
|
||||
let command = "set -e;";
|
||||
if (application.sourceType === "github") {
|
||||
command += await getGithubCloneCommand({
|
||||
...application,
|
||||
appName: previewDeployment.appName,
|
||||
branch: previewDeployment.branch,
|
||||
serverId: application.serverId,
|
||||
logPath: deployment.logPath,
|
||||
});
|
||||
@@ -600,6 +628,7 @@ export const rebuildRemoteApplication = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
const deployment = await createDeployment({
|
||||
applicationId: applicationId,
|
||||
title: titleLog,
|
||||
@@ -608,6 +637,11 @@ export const rebuildRemoteApplication = async ({
|
||||
|
||||
try {
|
||||
if (application.serverId) {
|
||||
const admin = await findAdminById(application.project.adminId);
|
||||
|
||||
if (admin.cleanupCacheApplications) {
|
||||
await cleanupFullDocker(application?.serverId);
|
||||
}
|
||||
if (application.sourceType !== "docker") {
|
||||
let command = "set -e;";
|
||||
command += getBuildCommand(application, deployment.logPath);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { paths } from "@dokploy/server/constants";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { type apiCreateCompose, compose } from "@dokploy/server/db/schema";
|
||||
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
|
||||
import { generatePassword } from "@dokploy/server/templates/utils";
|
||||
import {
|
||||
buildCompose,
|
||||
getBuildComposeCommand,
|
||||
@@ -45,9 +44,10 @@ import {
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { encodeBase64 } from "../utils/docker/utils";
|
||||
import { getDokployUrl } from "./admin";
|
||||
import { findAdminById, getDokployUrl } from "./admin";
|
||||
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { cleanupFullDocker } from "./settings";
|
||||
|
||||
export type Compose = typeof compose.$inferSelect;
|
||||
|
||||
@@ -206,6 +206,7 @@ export const deployCompose = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const compose = await findComposeById(composeId);
|
||||
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
||||
compose.projectId
|
||||
}/services/compose/${compose.composeId}?tab=deployments`;
|
||||
@@ -216,6 +217,10 @@ export const deployCompose = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const admin = await findAdminById(compose.project.adminId);
|
||||
if (admin.cleanupCacheOnCompose) {
|
||||
await cleanupFullDocker(compose?.serverId);
|
||||
}
|
||||
if (compose.sourceType === "github") {
|
||||
await cloneGithubRepository({
|
||||
...compose,
|
||||
@@ -243,6 +248,7 @@ export const deployCompose = async ({
|
||||
applicationType: "compose",
|
||||
buildLink,
|
||||
adminId: compose.project.adminId,
|
||||
domains: compose.domains,
|
||||
});
|
||||
} catch (error) {
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
@@ -272,6 +278,7 @@ export const rebuildCompose = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const compose = await findComposeById(composeId);
|
||||
|
||||
const deployment = await createDeploymentCompose({
|
||||
composeId: composeId,
|
||||
title: titleLog,
|
||||
@@ -279,6 +286,10 @@ export const rebuildCompose = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const admin = await findAdminById(compose.project.adminId);
|
||||
if (admin.cleanupCacheOnCompose) {
|
||||
await cleanupFullDocker(compose?.serverId);
|
||||
}
|
||||
if (compose.serverId) {
|
||||
await getBuildComposeCommand(compose, deployment.logPath);
|
||||
} else {
|
||||
@@ -310,6 +321,7 @@ export const deployRemoteCompose = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const compose = await findComposeById(composeId);
|
||||
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
||||
compose.projectId
|
||||
}/services/compose/${compose.composeId}?tab=deployments`;
|
||||
@@ -320,6 +332,10 @@ export const deployRemoteCompose = async ({
|
||||
});
|
||||
try {
|
||||
if (compose.serverId) {
|
||||
const admin = await findAdminById(compose.project.adminId);
|
||||
if (admin.cleanupCacheOnCompose) {
|
||||
await cleanupFullDocker(compose?.serverId);
|
||||
}
|
||||
let command = "set -e;";
|
||||
|
||||
if (compose.sourceType === "github") {
|
||||
@@ -366,6 +382,7 @@ export const deployRemoteCompose = async ({
|
||||
applicationType: "compose",
|
||||
buildLink,
|
||||
adminId: compose.project.adminId,
|
||||
domains: compose.domains,
|
||||
});
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
@@ -405,6 +422,7 @@ export const rebuildRemoteCompose = async ({
|
||||
descriptionLog: string;
|
||||
}) => {
|
||||
const compose = await findComposeById(composeId);
|
||||
|
||||
const deployment = await createDeploymentCompose({
|
||||
composeId: composeId,
|
||||
title: titleLog,
|
||||
@@ -412,6 +430,10 @@ export const rebuildRemoteCompose = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const admin = await findAdminById(compose.project.adminId);
|
||||
if (admin.cleanupCacheOnCompose) {
|
||||
await cleanupFullDocker(compose?.serverId);
|
||||
}
|
||||
if (compose.serverId) {
|
||||
await getBuildComposeCommand(compose, deployment.logPath);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@ import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
type apiCreateDiscord,
|
||||
type apiCreateEmail,
|
||||
type apiCreateGotify,
|
||||
type apiCreateSlack,
|
||||
type apiCreateTelegram,
|
||||
type apiUpdateDiscord,
|
||||
type apiUpdateEmail,
|
||||
type apiUpdateGotify,
|
||||
type apiUpdateSlack,
|
||||
type apiUpdateTelegram,
|
||||
discord,
|
||||
email,
|
||||
gotify,
|
||||
notifications,
|
||||
slack,
|
||||
telegram,
|
||||
@@ -379,6 +382,96 @@ export const updateEmailNotification = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const createGotifyNotification = async (
|
||||
input: typeof apiCreateGotify._type,
|
||||
adminId: string,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newGotify = await tx
|
||||
.insert(gotify)
|
||||
.values({
|
||||
serverUrl: input.serverUrl,
|
||||
appToken: input.appToken,
|
||||
priority: input.priority,
|
||||
decoration: input.decoration,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newGotify) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error input: Inserting gotify",
|
||||
});
|
||||
}
|
||||
|
||||
const newDestination = await tx
|
||||
.insert(notifications)
|
||||
.values({
|
||||
gotifyId: newGotify.gotifyId,
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
notificationType: "gotify",
|
||||
adminId: adminId,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error input: Inserting notification",
|
||||
});
|
||||
}
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const updateGotifyNotification = async (
|
||||
input: typeof apiUpdateGotify._type,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newDestination = await tx
|
||||
.update(notifications)
|
||||
.set({
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
adminId: input.adminId,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error Updating notification",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(gotify)
|
||||
.set({
|
||||
serverUrl: input.serverUrl,
|
||||
appToken: input.appToken,
|
||||
priority: input.priority,
|
||||
decoration: input.decoration,
|
||||
})
|
||||
.where(eq(gotify.gotifyId, input.gotifyId));
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const findNotificationById = async (notificationId: string) => {
|
||||
const notification = await db.query.notifications.findFirst({
|
||||
where: eq(notifications.notificationId, notificationId),
|
||||
@@ -387,6 +480,7 @@ export const findNotificationById = async (notificationId: string) => {
|
||||
telegram: true,
|
||||
discord: true,
|
||||
email: true,
|
||||
gotify: true,
|
||||
},
|
||||
});
|
||||
if (!notification) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
execAsync,
|
||||
execAsyncRemote,
|
||||
} from "@dokploy/server/utils/process/execAsync";
|
||||
import { findAdminById } from "./admin";
|
||||
// import packageInfo from "../../../package.json";
|
||||
|
||||
export interface IUpdateData {
|
||||
@@ -213,3 +214,35 @@ echo "$json_output"
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const cleanupFullDocker = async (serverId?: string | null) => {
|
||||
const cleanupImages = "docker image prune --all --force";
|
||||
const cleanupVolumes = "docker volume prune --all --force";
|
||||
const cleanupContainers = "docker container prune --force";
|
||||
const cleanupSystem = "docker system prune --all --force --volumes";
|
||||
const cleanupBuilder = "docker builder prune --all --force";
|
||||
|
||||
try {
|
||||
if (serverId) {
|
||||
await execAsyncRemote(
|
||||
serverId,
|
||||
`
|
||||
${cleanupImages}
|
||||
${cleanupVolumes}
|
||||
${cleanupContainers}
|
||||
${cleanupSystem}
|
||||
${cleanupBuilder}
|
||||
`,
|
||||
);
|
||||
}
|
||||
await execAsync(`
|
||||
${cleanupImages}
|
||||
${cleanupVolumes}
|
||||
${cleanupContainers}
|
||||
${cleanupSystem}
|
||||
${cleanupBuilder}
|
||||
`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ export const addNewProject = async (authId: string, projectId: string) => {
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
accesedProjects: [...user.accesedProjects, projectId],
|
||||
accessedProjects: [...user.accessedProjects, projectId],
|
||||
})
|
||||
.where(eq(users.authId, authId));
|
||||
};
|
||||
@@ -64,7 +64,7 @@ export const addNewService = async (authId: string, serviceId: string) => {
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
accesedServices: [...user.accesedServices, serviceId],
|
||||
accessedServices: [...user.accessedServices, serviceId],
|
||||
})
|
||||
.where(eq(users.authId, authId));
|
||||
};
|
||||
@@ -73,8 +73,9 @@ export const canPerformCreationService = async (
|
||||
userId: string,
|
||||
projectId: string,
|
||||
) => {
|
||||
const { accesedProjects, canCreateServices } = await findUserByAuthId(userId);
|
||||
const haveAccessToProject = accesedProjects.includes(projectId);
|
||||
const { accessedProjects, canCreateServices } =
|
||||
await findUserByAuthId(userId);
|
||||
const haveAccessToProject = accessedProjects.includes(projectId);
|
||||
|
||||
if (canCreateServices && haveAccessToProject) {
|
||||
return true;
|
||||
@@ -87,8 +88,8 @@ export const canPerformAccessService = async (
|
||||
userId: string,
|
||||
serviceId: string,
|
||||
) => {
|
||||
const { accesedServices } = await findUserByAuthId(userId);
|
||||
const haveAccessToService = accesedServices.includes(serviceId);
|
||||
const { accessedServices } = await findUserByAuthId(userId);
|
||||
const haveAccessToService = accessedServices.includes(serviceId);
|
||||
|
||||
if (haveAccessToService) {
|
||||
return true;
|
||||
@@ -101,8 +102,9 @@ export const canPeformDeleteService = async (
|
||||
authId: string,
|
||||
serviceId: string,
|
||||
) => {
|
||||
const { accesedServices, canDeleteServices } = await findUserByAuthId(authId);
|
||||
const haveAccessToService = accesedServices.includes(serviceId);
|
||||
const { accessedServices, canDeleteServices } =
|
||||
await findUserByAuthId(authId);
|
||||
const haveAccessToService = accessedServices.includes(serviceId);
|
||||
|
||||
if (canDeleteServices && haveAccessToService) {
|
||||
return true;
|
||||
@@ -135,9 +137,9 @@ export const canPerformAccessProject = async (
|
||||
authId: string,
|
||||
projectId: string,
|
||||
) => {
|
||||
const { accesedProjects } = await findUserByAuthId(authId);
|
||||
const { accessedProjects } = await findUserByAuthId(authId);
|
||||
|
||||
const haveAccessToProject = accesedProjects.includes(projectId);
|
||||
const haveAccessToProject = accessedProjects.includes(projectId);
|
||||
|
||||
if (haveAccessToProject) {
|
||||
return true;
|
||||
|
||||
@@ -2,10 +2,12 @@ import { db } from "@dokploy/server/db";
|
||||
import { notifications } from "@dokploy/server/db/schema";
|
||||
import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed";
|
||||
import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
sendSlackNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
@@ -39,11 +41,12 @@ export const sendBuildErrorNotifications = async ({
|
||||
discord: true,
|
||||
telegram: true,
|
||||
slack: true,
|
||||
gotify: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack } = notification;
|
||||
const { email, discord, telegram, slack, gotify } = notification;
|
||||
if (email) {
|
||||
const template = await renderAsync(
|
||||
BuildFailedEmail({
|
||||
@@ -112,22 +115,35 @@ export const sendBuildErrorNotifications = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (gotify) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||
await sendGotifyNotification(
|
||||
gotify,
|
||||
decorate("⚠️", "Build Failed"),
|
||||
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
||||
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
||||
`${decorate("❔", `Type: ${applicationType}`)}` +
|
||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||
`${decorate("⚠️", `Error:\n${errorMessage}`)}` +
|
||||
`${decorate("🔗", `Build details:\n${buildLink}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (telegram) {
|
||||
const inlineButton = [
|
||||
[
|
||||
{
|
||||
text: "Deployment Logs",
|
||||
url: buildLink,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
await sendTelegramNotification(
|
||||
telegram,
|
||||
`
|
||||
<b>⚠️ Build Failed</b>
|
||||
|
||||
<b>Project:</b> ${projectName}
|
||||
<b>Application:</b> ${applicationName}
|
||||
<b>Type:</b> ${applicationType}
|
||||
<b>Time:</b> ${date.toLocaleString()}
|
||||
|
||||
<b>Error:</b>
|
||||
<pre>${errorMessage}</pre>
|
||||
|
||||
<b>Build Details:</b> ${buildLink}
|
||||
`,
|
||||
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`,
|
||||
inlineButton,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { notifications } from "@dokploy/server/db/schema";
|
||||
import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
sendSlackNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
@@ -16,6 +19,7 @@ interface Props {
|
||||
applicationType: string;
|
||||
buildLink: string;
|
||||
adminId: string;
|
||||
domains: Domain[];
|
||||
}
|
||||
|
||||
export const sendBuildSuccessNotifications = async ({
|
||||
@@ -24,6 +28,7 @@ export const sendBuildSuccessNotifications = async ({
|
||||
applicationType,
|
||||
buildLink,
|
||||
adminId,
|
||||
domains,
|
||||
}: Props) => {
|
||||
const date = new Date();
|
||||
const unixDate = ~~(Number(date) / 1000);
|
||||
@@ -37,11 +42,12 @@ export const sendBuildSuccessNotifications = async ({
|
||||
discord: true,
|
||||
telegram: true,
|
||||
slack: true,
|
||||
gotify: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack } = notification;
|
||||
const { email, discord, telegram, slack, gotify } = notification;
|
||||
|
||||
if (email) {
|
||||
const template = await renderAsync(
|
||||
@@ -106,19 +112,45 @@ export const sendBuildSuccessNotifications = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (gotify) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||
await sendGotifyNotification(
|
||||
gotify,
|
||||
decorate("✅", "Build Success"),
|
||||
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
||||
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
||||
`${decorate("❔", `Type: ${applicationType}`)}` +
|
||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||
`${decorate("🔗", `Build details:\n${buildLink}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (telegram) {
|
||||
const chunkArray = <T>(array: T[], chunkSize: number): T[][] =>
|
||||
Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
|
||||
array.slice(i * chunkSize, i * chunkSize + chunkSize),
|
||||
);
|
||||
|
||||
const inlineButton = [
|
||||
[
|
||||
{
|
||||
text: "Deployment Logs",
|
||||
url: buildLink,
|
||||
},
|
||||
],
|
||||
...chunkArray(domains, 2).map((chunk) =>
|
||||
chunk.map((data) => ({
|
||||
text: data.host,
|
||||
url: `${data.https ? "https" : "http"}://${data.host}`,
|
||||
})),
|
||||
),
|
||||
];
|
||||
|
||||
await sendTelegramNotification(
|
||||
telegram,
|
||||
`
|
||||
<b>✅ Build Success</b>
|
||||
|
||||
<b>Project:</b> ${projectName}
|
||||
<b>Application:</b> ${applicationName}
|
||||
<b>Type:</b> ${applicationType}
|
||||
<b>Time:</b> ${date.toLocaleString()}
|
||||
|
||||
<b>Build Details:</b> ${buildLink}
|
||||
`,
|
||||
`<b>✅ Build Success</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||
inlineButton,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { error } from "node:console";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { notifications } from "@dokploy/server/db/schema";
|
||||
import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
|
||||
import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
sendSlackNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
@@ -37,11 +40,12 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
discord: true,
|
||||
telegram: true,
|
||||
slack: true,
|
||||
gotify: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack } = notification;
|
||||
const { email, discord, telegram, slack, gotify } = notification;
|
||||
|
||||
if (email) {
|
||||
const template = await renderAsync(
|
||||
@@ -120,19 +124,35 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (gotify) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||
|
||||
await sendGotifyNotification(
|
||||
gotify,
|
||||
decorate(
|
||||
type === "success" ? "✅" : "❌",
|
||||
`Database Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||
),
|
||||
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
||||
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
||||
`${decorate("❔", `Type: ${databaseType}`)}` +
|
||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||
`${type === "error" && errorMessage ? decorate("❌", `Error:\n${errorMessage}`) : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (telegram) {
|
||||
const isError = type === "error" && errorMessage;
|
||||
|
||||
const statusEmoji = type === "success" ? "✅" : "❌";
|
||||
const messageText = `
|
||||
<b>${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"}</b>
|
||||
const typeStatus = type === "success" ? "Successful" : "Failed";
|
||||
const errorMsg = isError
|
||||
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
|
||||
: "";
|
||||
|
||||
<b>Project:</b> ${projectName}
|
||||
<b>Application:</b> ${applicationName}
|
||||
<b>Type:</b> ${databaseType}
|
||||
<b>Time:</b> ${date.toLocaleString()}
|
||||
const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;
|
||||
|
||||
<b>Status:</b> ${type === "success" ? "Successful" : "Failed"}
|
||||
${type === "error" && errorMessage ? `<b>Error:</b> ${errorMessage}` : ""}
|
||||
`;
|
||||
await sendTelegramNotification(telegram, messageText);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ import { db } from "@dokploy/server/db";
|
||||
import { notifications } from "@dokploy/server/db/schema";
|
||||
import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup";
|
||||
import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
sendSlackNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
@@ -26,11 +28,12 @@ export const sendDockerCleanupNotifications = async (
|
||||
discord: true,
|
||||
telegram: true,
|
||||
slack: true,
|
||||
gotify: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack } = notification;
|
||||
const { email, discord, telegram, slack, gotify } = notification;
|
||||
|
||||
if (email) {
|
||||
const template = await renderAsync(
|
||||
@@ -79,14 +82,21 @@ export const sendDockerCleanupNotifications = async (
|
||||
});
|
||||
}
|
||||
|
||||
if (gotify) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||
await sendGotifyNotification(
|
||||
gotify,
|
||||
decorate("✅", "Docker Cleanup"),
|
||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||
`${decorate("📜", `Message:\n${message}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (telegram) {
|
||||
await sendTelegramNotification(
|
||||
telegram,
|
||||
`
|
||||
<b>✅ Docker Cleanup</b>
|
||||
<b>Message:</b> ${message}
|
||||
<b>Time:</b> ${date.toLocaleString()}
|
||||
`,
|
||||
`<b>✅ Docker Cleanup</b>\n\n<b>Message:</b> ${message}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ import { db } from "@dokploy/server/db";
|
||||
import { notifications } from "@dokploy/server/db/schema";
|
||||
import DokployRestartEmail from "@dokploy/server/emails/emails/dokploy-restart";
|
||||
import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
sendSlackNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
@@ -20,11 +22,12 @@ export const sendDokployRestartNotifications = async () => {
|
||||
discord: true,
|
||||
telegram: true,
|
||||
slack: true,
|
||||
gotify: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack } = notification;
|
||||
const { email, discord, telegram, slack, gotify } = notification;
|
||||
|
||||
if (email) {
|
||||
const template = await renderAsync(
|
||||
@@ -64,13 +67,20 @@ export const sendDokployRestartNotifications = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (gotify) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||
await sendGotifyNotification(
|
||||
gotify,
|
||||
decorate("✅", "Dokploy Server Restarted"),
|
||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (telegram) {
|
||||
await sendTelegramNotification(
|
||||
telegram,
|
||||
`
|
||||
<b>✅ Dokploy Serverd Restarted</b>
|
||||
<b>Time:</b> ${date.toLocaleString()}
|
||||
`,
|
||||
`<b>✅ Dokploy Server Restarted</b>\n\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
discord,
|
||||
email,
|
||||
gotify,
|
||||
slack,
|
||||
telegram,
|
||||
} from "@dokploy/server/db/schema";
|
||||
@@ -55,6 +56,10 @@ export const sendDiscordNotification = async (
|
||||
export const sendTelegramNotification = async (
|
||||
connection: typeof telegram.$inferInsert,
|
||||
messageText: string,
|
||||
inlineButton?: {
|
||||
text: string;
|
||||
url: string;
|
||||
}[][],
|
||||
) => {
|
||||
try {
|
||||
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
|
||||
@@ -66,6 +71,9 @@ export const sendTelegramNotification = async (
|
||||
text: messageText,
|
||||
parse_mode: "HTML",
|
||||
disable_web_page_preview: true,
|
||||
reply_markup: {
|
||||
inline_keyboard: inlineButton,
|
||||
},
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -87,3 +95,33 @@ export const sendSlackNotification = async (
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const sendGotifyNotification = async (
|
||||
connection: typeof gotify.$inferInsert,
|
||||
title: string,
|
||||
message: string,
|
||||
) => {
|
||||
const response = await fetch(`${connection.serverUrl}/message`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Gotify-Key": connection.appToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: title,
|
||||
message: message,
|
||||
priority: connection.priority,
|
||||
extras: {
|
||||
"client::display": {
|
||||
contentType: "text/plain",
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to send Gotify notification: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||