mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1a48d4636 | ||
|
|
c34c4b244e | ||
|
|
4991e4b1f2 | ||
|
|
b7c061dcb4 | ||
|
|
701319efdd | ||
|
|
0fdf176648 | ||
|
|
f62d835f57 | ||
|
|
5bb4710952 | ||
|
|
9b71ce9388 | ||
|
|
175d1ec432 | ||
|
|
8454e4f781 | ||
|
|
2e79c7230f | ||
|
|
7ddd3bb8a0 | ||
|
|
b889a9d248 | ||
|
|
fc21c96cd1 | ||
|
|
0341b19c9f | ||
|
|
43095f2435 | ||
|
|
f6128bdf0c | ||
|
|
6be6ec940a | ||
|
|
ba9fc59805 | ||
|
|
63a1039439 | ||
|
|
d52692c6a3 | ||
|
|
b4511ca7a2 | ||
|
|
a3c24f1f2a | ||
|
|
ca599f27f7 | ||
|
|
f36de7b2f5 | ||
|
|
0ce055c001 | ||
|
|
fc011a5661 | ||
|
|
60d6d781be | ||
|
|
9270739eb6 | ||
|
|
87b87b85c0 | ||
|
|
496fd40fa3 | ||
|
|
25fe080582 | ||
|
|
1c41091372 | ||
|
|
9230178005 | ||
|
|
fd092f1248 | ||
|
|
736c186a66 | ||
|
|
3d348ee762 | ||
|
|
6e78f49c2f | ||
|
|
e77b30671b | ||
|
|
244e1227c4 | ||
|
|
9934dac203 | ||
|
|
44ee326057 | ||
|
|
18f892096b | ||
|
|
9954d5b209 | ||
|
|
e0bde5cec9 | ||
|
|
2d4eaeb8b5 | ||
|
|
787506fb6b | ||
|
|
50c8c3a43a | ||
|
|
1f09c06274 | ||
|
|
bb59a0cd3f | ||
|
|
1e4217315b | ||
|
|
b373aca0ff | ||
|
|
87e90cb30b | ||
|
|
135fabb852 | ||
|
|
b0b5b94bb7 | ||
|
|
8f1c1e5202 | ||
|
|
e4c243d7a6 | ||
|
|
c3bca21d14 | ||
|
|
1befdb76e7 | ||
|
|
35652c5c53 | ||
|
|
9524609092 | ||
|
|
38c16fe839 | ||
|
|
c0587b9409 | ||
|
|
e249e878f6 | ||
|
|
2d64815c12 | ||
|
|
af11bc8cd2 | ||
|
|
6779dec1ff | ||
|
|
191a6112ce | ||
|
|
1bf518f768 | ||
|
|
d0d4182fc1 | ||
|
|
736f7c2d77 | ||
|
|
6e38508477 | ||
|
|
afe8160d46 | ||
|
|
ceb4ae62e2 | ||
|
|
67d0dd5bf7 | ||
|
|
6e28545c3f | ||
|
|
382208ae13 | ||
|
|
906e8de13b | ||
|
|
7a5c71cda3 | ||
|
|
7bc6d0777d | ||
|
|
f684ba7b1f | ||
|
|
86ed884162 | ||
|
|
4ff178ea34 | ||
|
|
2eb5c331a1 | ||
|
|
79ad0818f5 | ||
|
|
84c10eec66 | ||
|
|
61673a40e3 | ||
|
|
363ba1d20e | ||
|
|
5fadd73732 | ||
|
|
44e6a117dd | ||
|
|
86bb119052 | ||
|
|
54c70ff177 | ||
|
|
eabe14e4c3 | ||
|
|
342ff4b589 | ||
|
|
680811357b | ||
|
|
9d0edd810e | ||
|
|
35ff65a871 | ||
|
|
675fbb7692 | ||
|
|
295bd06918 | ||
|
|
89635fa27b | ||
|
|
60b19616c1 | ||
|
|
3884dc9259 | ||
|
|
e8648732be | ||
|
|
0f0f32a40d | ||
|
|
1b91376f5e | ||
|
|
aa347abfcd | ||
|
|
e49b190c32 | ||
|
|
66017c8623 | ||
|
|
bab93f8145 |
72
.circleci/canary-config.yml
Normal file
72
.circleci/canary-config.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp .env.production.example .env.production
|
||||
- run:
|
||||
name: Build and push AMD64 image
|
||||
command: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo $VERSION
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
docker build --platform linux/amd64 -t dokploy/dokploy:canary-amd64 .
|
||||
docker push dokploy/dokploy:canary-amd64
|
||||
|
||||
build-arm64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp .env.production.example .env.production
|
||||
- run:
|
||||
name: Build and push ARM64 image
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
docker build --platform linux/arm64 -t dokploy/dokploy:canary-arm64 .
|
||||
docker push dokploy/dokploy:canary-arm64
|
||||
|
||||
combine-manifests:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
steps:
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Create and push multi-arch manifest
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
docker manifest create dokploy/dokploy:canary \
|
||||
dokploy/dokploy:canary-amd64 \
|
||||
dokploy/dokploy:canary-arm64
|
||||
docker manifest push dokploy/dokploy:canary
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-all:
|
||||
jobs:
|
||||
- build-amd64:
|
||||
filters:
|
||||
branches:
|
||||
only: feat/circle
|
||||
- build-arm64:
|
||||
filters:
|
||||
branches:
|
||||
only: feat/circle
|
||||
- combine-manifests:
|
||||
requires:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
filters:
|
||||
branches:
|
||||
only: feat/circle
|
||||
104
.circleci/config.yml
Normal file
104
.circleci/config.yml
Normal file
@@ -0,0 +1,104 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp .env.production.example .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"
|
||||
else
|
||||
TAG="canary"
|
||||
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 .env.production.example .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"
|
||||
else
|
||||
TAG="canary"
|
||||
fi
|
||||
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
|
||||
docker push dokploy/dokploy:${TAG}-arm64
|
||||
|
||||
combine-manifests:
|
||||
docker:
|
||||
- image: cimg/node:18.18.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('./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}
|
||||
else
|
||||
TAG="canary"
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
fi
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-all:
|
||||
jobs:
|
||||
- build-amd64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- build-arm64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- combine-manifests:
|
||||
requires:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
76
.circleci/main-config.yml
Normal file
76
.circleci/main-config.yml
Normal file
@@ -0,0 +1,76 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp .env.production.example .env.production
|
||||
- run:
|
||||
name: Build and push AMD64 image
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
docker build --platform linux/amd64 -t dokploy/dokploy:latest-amd64 .
|
||||
docker push dokploy/dokploy:latest-amd64
|
||||
|
||||
build-arm64:
|
||||
machine:
|
||||
image: ubuntu-2004:current
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Prepare .env file
|
||||
command: |
|
||||
cp .env.production.example .env.production
|
||||
- run:
|
||||
name: Build and push ARM64 image
|
||||
command: |
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
docker build --platform linux/arm64 -t dokploy/dokploy:latest-arm64 .
|
||||
docker push dokploy/dokploy:latest-arm64
|
||||
|
||||
combine-manifests:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
steps:
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Create and push multi-arch manifest
|
||||
command: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
docker manifest create dokploy/dokploy:latest \
|
||||
dokploy/dokploy:latest-amd64 \
|
||||
dokploy/dokploy:latest-arm64
|
||||
docker manifest push dokploy/dokploy:latest
|
||||
|
||||
docker manifest create dokploy/dokploy:${VERSION} \
|
||||
dokploy/dokploy:latest-amd64 \
|
||||
dokploy/dokploy:latest-arm64
|
||||
docker manifest push dokploy/dokploy:${VERSION}
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-all:
|
||||
jobs:
|
||||
- build-amd64:
|
||||
filters:
|
||||
branches:
|
||||
only: main
|
||||
- build-arm64:
|
||||
filters:
|
||||
branches:
|
||||
only: main
|
||||
- combine-manifests:
|
||||
requires:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
filters:
|
||||
branches:
|
||||
only: main
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: [siumauricio]
|
||||
patreon: #
|
||||
open_collective: dokploy
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
||||
76
.github/workflows/pull-request.yml
vendored
76
.github/workflows/pull-request.yml
vendored
@@ -1,59 +1,49 @@
|
||||
name: Pull request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- canary
|
||||
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- canary
|
||||
|
||||
env:
|
||||
HUSKY: 0
|
||||
|
||||
jobs:
|
||||
build-app:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.18.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run Build
|
||||
run: pnpm build
|
||||
- name: Run Tests
|
||||
run: pnpm run test
|
||||
|
||||
build-and-push-docker-on-push:
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare .env file
|
||||
run: |
|
||||
cp .env.production.example .env.production
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Build and push Docker image using custom script
|
||||
run: |
|
||||
chmod +x ./docker/push.sh
|
||||
./docker/push.sh ${{ github.ref_name == 'canary' && 'canary' || '' }}
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
# - name: Run commitlint
|
||||
# run: pnpm commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose
|
||||
|
||||
- name: Run format and lint
|
||||
run: pnpm biome ci
|
||||
|
||||
- name: Run type check
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm run test
|
||||
|
||||
4
.husky/commit-msg
Normal file
4
.husky/commit-msg
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm commitlint --edit $1
|
||||
6
.husky/install.mjs
Normal file
6
.husky/install.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
// Skip Husky install in production and CI
|
||||
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
|
||||
process.exit(0);
|
||||
}
|
||||
const husky = (await import("husky")).default;
|
||||
console.log(husky());
|
||||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
pnpm lint-staged
|
||||
72
Dockerfile
72
Dockerfile
@@ -1,48 +1,65 @@
|
||||
# Etapa 1: Prepare image for building
|
||||
FROM node:18-slim AS base
|
||||
|
||||
# Install dependencies
|
||||
# Disable husky
|
||||
ENV HUSKY=0
|
||||
|
||||
# Set pnpm home
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Enable corepack
|
||||
RUN corepack enable
|
||||
|
||||
# Set workdir
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS base-deps
|
||||
# Install dependencies only for production
|
||||
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy install script for husky
|
||||
COPY .husky/install.mjs ./.husky/install.mjs
|
||||
|
||||
# Copy package.json and pnpm-lock.yaml
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
FROM base-deps AS prod-deps
|
||||
|
||||
# Set production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Install dependencies only for production
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
FROM base-deps AS build
|
||||
|
||||
# Install dependencies only for building
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN pnpm run build
|
||||
# Build the application
|
||||
RUN pnpm build
|
||||
|
||||
# Stage 2: Prepare image for production
|
||||
FROM node:18-slim AS production
|
||||
FROM base AS production
|
||||
|
||||
# Set production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Install dependencies only for production
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && apt-get update && apt-get install -y curl && apt-get install -y apache2-utils && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y curl apache2-utils && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY --from=base /app/.next ./.next
|
||||
COPY --from=base /app/dist ./dist
|
||||
COPY --from=base /app/next.config.mjs ./next.config.mjs
|
||||
COPY --from=base /app/public ./public
|
||||
COPY --from=base /app/package.json ./package.json
|
||||
COPY --from=base /app/drizzle ./drizzle
|
||||
COPY --from=base /app/.env.production ./.env
|
||||
COPY --from=base /app/components.json ./components.json
|
||||
|
||||
# Install dependencies only for production
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
# Copy the rest of the source code
|
||||
COPY --from=build /app/.next ./.next
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY --from=build /app/next.config.mjs ./next.config.mjs
|
||||
COPY --from=build /app/public ./public
|
||||
COPY --from=build /app/package.json ./package.json
|
||||
COPY --from=build /app/drizzle ./drizzle
|
||||
COPY --from=build /app/.env.production ./.env
|
||||
COPY --from=build /app/components.json ./components.json
|
||||
COPY --from=prod-deps /app/node_modules ./node_modules
|
||||
|
||||
# Install docker
|
||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
|
||||
@@ -54,11 +71,10 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
||||
&& ./install.sh \
|
||||
&& pnpm install -g tsx
|
||||
|
||||
|
||||
# Install buildpacks
|
||||
RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||
RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.35.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["pnpm", "start"]
|
||||
CMD ["pnpm", "start"]
|
||||
|
||||
53
README.md
53
README.md
@@ -1,4 +1,3 @@
|
||||
|
||||
<div align="center">
|
||||
<h1 align="center">Dokploy</h1>
|
||||
<div>
|
||||
@@ -11,74 +10,67 @@
|
||||
<br />
|
||||
Dokploy is a free self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
Dokploy include multiples features to make your life easier.
|
||||
|
||||
|
||||
* **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
||||
* **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, Redis.
|
||||
* **Backups**: Automate backups for databases to a external storage destination.
|
||||
* **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
||||
* **Multi Node**: Scale applications to multiples nodes using docker swarm to manage the cluster.
|
||||
* **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
|
||||
* **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||
* **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
||||
* **Docker Management**: Easily deploy and manage Docker containers.
|
||||
* **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
||||
* **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||
|
||||
|
||||
|
||||
- **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
||||
- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, Redis.
|
||||
- **Backups**: Automate backups for databases to a external storage destination.
|
||||
- **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
||||
- **Multi Node**: Scale applications to multiples nodes using docker swarm to manage the cluster.
|
||||
- **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
|
||||
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
||||
- **Docker Management**: Easily deploy and manage Docker containers.
|
||||
- **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
||||
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
To get started run the following command in a VPS:
|
||||
|
||||
|
||||
```bash
|
||||
curl -sSL https://dokploy.com/install.sh | sh
|
||||
```
|
||||
|
||||
|
||||
## 📄 Documentation
|
||||
|
||||
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
||||
|
||||
|
||||
## Video Tutorial
|
||||
|
||||
<a href="https://youtu.be/mznYKPvhcfw">
|
||||
<img src="https://dokploy.com/banner.webp" alt="Watch the video" width="400" style="border-radius:20px;"/>
|
||||
</a>
|
||||
|
||||
|
||||
## Donations
|
||||
|
||||
If you like dokploy, and want to support the project to cover the costs of hosting, testing and development new features, you can donate to the project using the following link:
|
||||
|
||||
Thanks to all the supporters!
|
||||
|
||||
https://opencollective.com/dokploy
|
||||
[Dokploy Open Collective](https://opencollective.com/dokploy)
|
||||
|
||||
Organizations:
|
||||
|
||||
<a href="https://opencollective.com/dokploy"><img src="https://opencollective.com/dokploy/organizations.svg?width=890"></a>
|
||||
|
||||
Individuals:
|
||||
<a href="https://opencollective.com/dokploy"><img src="https://opencollective.com/dokploy/individuals.svg?width=890"></a>
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
## Support OS
|
||||
|
||||
- Ubuntu 24.04 LTS
|
||||
- Ubuntu 24.04 LTS
|
||||
- Ubuntu 23.10
|
||||
- Ubuntu 22.04 LTS
|
||||
- Ubuntu 20.04 LTS
|
||||
- Ubuntu 22.04 LTS
|
||||
- Ubuntu 20.04 LTS
|
||||
- Ubuntu 18.04 LTS
|
||||
- Debian 12
|
||||
- Debian 11
|
||||
@@ -86,9 +78,6 @@ https://opencollective.com/dokploy
|
||||
- Centos 9
|
||||
- Centos 8
|
||||
|
||||
|
||||
|
||||
## Explanation
|
||||
|
||||
[English](README.md) | [中文](README-zh.md) | [Deutsch](README-de.md) | [Русский Язык](README-ru.md)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { load } from "js-yaml";
|
||||
import { addPrefixToAllProperties } from "@/server/utils/docker/compose";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
const composeFile1 = `
|
||||
version: "3.8"
|
||||
|
||||
@@ -79,10 +79,11 @@ test("Add prefix to networks in services with aliases", () => {
|
||||
`frontend-${prefix}`,
|
||||
);
|
||||
|
||||
const networkConfig =
|
||||
actualComposeData?.services?.api?.networks[`frontend-${prefix}`];
|
||||
expect(networkConfig).toBeDefined();
|
||||
expect(networkConfig?.aliases).toContain("api");
|
||||
const networkConfig = actualComposeData?.services?.api?.networks as {
|
||||
[key: string]: { aliases?: string[] };
|
||||
};
|
||||
expect(networkConfig[`frontend-${prefix}`]).toBeDefined();
|
||||
expect(networkConfig[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||
|
||||
expect(actualComposeData.services?.api?.networks).not.toHaveProperty(
|
||||
"frontend-ash",
|
||||
@@ -169,7 +170,9 @@ test("Add prefix to networks in services (combined case)", () => {
|
||||
);
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const apiNetworks = actualComposeData.services?.api?.networks;
|
||||
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(apiNetworks[`frontend-${prefix}`]).toBeDefined();
|
||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||
|
||||
@@ -76,9 +76,11 @@ test("Add prefix to networks in services and root (combined case)", () => {
|
||||
);
|
||||
|
||||
// Caso 2: Objeto con aliases
|
||||
const apiNetworks = actualComposeData.services?.api?.networks;
|
||||
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||
[key: string]: { aliases?: string[] };
|
||||
};
|
||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||
expect(apiNetworks[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||
expect(apiNetworks?.[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||
|
||||
// Caso 3: Objeto con redes simples
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { load, dump } from "js-yaml";
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { addPrefixToSecretsRoot } from "@/server/utils/docker/compose/secrets";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
|
||||
@@ -42,7 +42,7 @@ test("Add prefix to service names with container_name in compose file", () => {
|
||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||
|
||||
// Verificar que el nombre del contenedor ha cambiado correctamente
|
||||
expect(actualComposeData.services[`web-${prefix}`].container_name).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.container_name).toBe(
|
||||
`web_container-${prefix}`,
|
||||
);
|
||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||
@@ -50,10 +50,10 @@ test("Add prefix to service names with container_name in compose file", () => {
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -51,30 +51,30 @@ test("Add prefix to service names with depends_on (array) in compose file", () =
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en depends_on tienen el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].depends_on).toContain(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.depends_on).toContain(
|
||||
`db-${prefix}`,
|
||||
);
|
||||
expect(actualComposeData.services[`web-${prefix}`].depends_on).toContain(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.depends_on).toContain(
|
||||
`api-${prefix}`,
|
||||
);
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
@@ -121,16 +121,16 @@ test("Add prefix to service names with depends_on (object) in compose file", ()
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en depends_on tienen el prefijo
|
||||
const webDependsOn = actualComposeData.services[`web-${prefix}`]
|
||||
.depends_on as Record<string, any>;
|
||||
const webDependsOn = actualComposeData.services?.[`web-${prefix}`]
|
||||
?.depends_on as Record<string, any>;
|
||||
expect(webDependsOn).toHaveProperty(`db-${prefix}`);
|
||||
expect(webDependsOn).toHaveProperty(`api-${prefix}`);
|
||||
expect(webDependsOn[`db-${prefix}`].condition).toBe("service_healthy");
|
||||
@@ -139,12 +139,12 @@ test("Add prefix to service names with depends_on (object) in compose file", ()
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -49,22 +49,22 @@ test("Add prefix to service names with extends (string) in compose file", () =>
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que el nombre en extends tiene el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].extends).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.extends).toBe(
|
||||
`base_service-${prefix}`,
|
||||
);
|
||||
|
||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||
expect(actualComposeData.services[`base_service-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`base_service-${prefix}`]?.image).toBe(
|
||||
"base:latest",
|
||||
);
|
||||
});
|
||||
@@ -109,23 +109,23 @@ test("Add prefix to service names with extends (object) in compose file", () =>
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que el nombre en extends.service tiene el prefijo
|
||||
const webExtends = actualComposeData.services[`web-${prefix}`].extends;
|
||||
const webExtends = actualComposeData.services?.[`web-${prefix}`]?.extends;
|
||||
if (typeof webExtends !== "string") {
|
||||
expect(webExtends.service).toBe(`base_service-${prefix}`);
|
||||
expect(webExtends?.service).toBe(`base_service-${prefix}`);
|
||||
}
|
||||
|
||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||
expect(actualComposeData.services[`base_service-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`base_service-${prefix}`]?.image).toBe(
|
||||
"base:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -50,27 +50,27 @@ test("Add prefix to service names with links in compose file", () => {
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en links tienen el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].links).toContain(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.links).toContain(
|
||||
`db-${prefix}`,
|
||||
);
|
||||
|
||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||
"postgres:latest",
|
||||
);
|
||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -54,23 +54,25 @@ test("Add prefix to service names with volumes_from in compose file", () => {
|
||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||
|
||||
// Verificar que la configuración de la imagen sigue igual
|
||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||
"nginx:latest",
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||
"myapi:latest",
|
||||
);
|
||||
|
||||
// Verificar que los nombres en volumes_from tienen el prefijo
|
||||
expect(actualComposeData.services[`web-${prefix}`].volumes_from).toContain(
|
||||
expect(actualComposeData.services?.[`web-${prefix}`]?.volumes_from).toContain(
|
||||
`shared-${prefix}`,
|
||||
);
|
||||
expect(actualComposeData.services[`api-${prefix}`].volumes_from).toContain(
|
||||
expect(actualComposeData.services?.[`api-${prefix}`]?.volumes_from).toContain(
|
||||
`shared-${prefix}`,
|
||||
);
|
||||
|
||||
// Verificar que el servicio shared también tiene el prefijo
|
||||
expect(actualComposeData.services).toHaveProperty(`shared-${prefix}`);
|
||||
expect(actualComposeData.services).not.toHaveProperty("shared");
|
||||
expect(actualComposeData.services[`shared-${prefix}`].image).toBe("busybox");
|
||||
expect(actualComposeData.services?.[`shared-${prefix}`]?.image).toBe(
|
||||
"busybox",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||
import {
|
||||
addPrefixToVolumesRoot,
|
||||
addPrefixToAllVolumes,
|
||||
addPrefixToVolumesRoot,
|
||||
} from "@/server/utils/docker/compose/volume";
|
||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||
import { load } from "js-yaml";
|
||||
|
||||
98
__test__/drop/drop.test.test.ts
Normal file
98
__test__/drop/drop.test.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { APPLICATIONS_PATH } from "@/server/constants";
|
||||
import { unzipDrop } from "@/server/utils/builders/drop";
|
||||
import AdmZip from "adm-zip";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
const undici = require("undici");
|
||||
globalThis.File = undici.File as any;
|
||||
globalThis.FileList = undici.FileList as any;
|
||||
}
|
||||
|
||||
vi.mock("@/server/constants", () => ({
|
||||
APPLICATIONS_PATH: "./__test__/drop/zips/output",
|
||||
}));
|
||||
|
||||
describe("unzipDrop using real zip files", () => {
|
||||
beforeAll(async () => {
|
||||
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root folder", async () => {
|
||||
const appName = "single-file";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, appName);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
expect(files.some((f) => f.name === "test.txt")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root folder and a subfolder", async () => {
|
||||
const appName = "folderwithfile";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/folder-with-file.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, appName);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
expect(files.some((f) => f.name === "folder1.txt")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with multiple root folders", async () => {
|
||||
const appName = "two-folders";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/two-folders.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, appName);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
|
||||
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||
expect(files.some((f) => f.name === "folder2")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root with a file", async () => {
|
||||
const appName = "nested";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/nested.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, appName);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
|
||||
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||
expect(files.some((f) => f.name === "folder2")).toBe(true);
|
||||
expect(files.some((f) => f.name === "folder3")).toBe(true);
|
||||
});
|
||||
|
||||
it("should correctly extract a zip with a single root with a folder", async () => {
|
||||
const appName = "folder-with-sibling-file";
|
||||
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||
const zip = new AdmZip("./__test__/drop/zips/folder-with-sibling-file.zip");
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
const file = new File([zipBuffer], "single.zip");
|
||||
await unzipDrop(file, appName);
|
||||
|
||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||
|
||||
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||
expect(files.some((f) => f.name === "test.txt")).toBe(true);
|
||||
});
|
||||
});
|
||||
BIN
__test__/drop/zips/folder-with-file.zip
Normal file
BIN
__test__/drop/zips/folder-with-file.zip
Normal file
Binary file not shown.
BIN
__test__/drop/zips/folder-with-sibling-file.zip
Normal file
BIN
__test__/drop/zips/folder-with-sibling-file.zip
Normal file
Binary file not shown.
1
__test__/drop/zips/folder1/folder1.txt
Normal file
1
__test__/drop/zips/folder1/folder1.txt
Normal file
@@ -0,0 +1 @@
|
||||
Gogogogogogo
|
||||
1
__test__/drop/zips/folder2/folder2.txt
Normal file
1
__test__/drop/zips/folder2/folder2.txt
Normal file
@@ -0,0 +1 @@
|
||||
gogogogogog
|
||||
1
__test__/drop/zips/folder3/file3.txt
Normal file
1
__test__/drop/zips/folder3/file3.txt
Normal file
@@ -0,0 +1 @@
|
||||
gogogogogogogogogo
|
||||
BIN
__test__/drop/zips/nested.zip
Normal file
BIN
__test__/drop/zips/nested.zip
Normal file
Binary file not shown.
BIN
__test__/drop/zips/single-file.zip
Normal file
BIN
__test__/drop/zips/single-file.zip
Normal file
Binary file not shown.
1
__test__/drop/zips/test.txt
Normal file
1
__test__/drop/zips/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
dsafasdfasdf
|
||||
BIN
__test__/drop/zips/two-folders.zip
Normal file
BIN
__test__/drop/zips/two-folders.zip
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
|
||||
49
biome.json
49
biome.json
@@ -1,17 +1,34 @@
|
||||
{
|
||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"linter":{
|
||||
"rules": {
|
||||
"correctness":{
|
||||
"useExhaustiveDependencies": "off"
|
||||
},
|
||||
"suspicious":{
|
||||
"noArrayIndexKey": "off"
|
||||
},
|
||||
"a11y":{
|
||||
"noSvgWithoutTitle":"off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"files": {
|
||||
"ignore": ["node_modules/**", ".next/**", "drizzle/**", ".docker"]
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"complexity": {
|
||||
"noUselessCatch": "off",
|
||||
"noBannedTypes": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"useExhaustiveDependencies": "off",
|
||||
"noUnsafeOptionalChaining": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noArrayIndexKey": "off",
|
||||
"noExplicitAny": "off",
|
||||
"noRedeclare": "off"
|
||||
},
|
||||
"a11y": {
|
||||
"noSvgWithoutTitle": "off",
|
||||
"useKeyWithClickEvents": "off",
|
||||
"useAriaPropsForRole": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "styles/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,19 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
|
||||
import { CardTitle } from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Login2FASchema = z.object({
|
||||
pin: z.string().min(6, {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,21 +19,19 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { HelpCircle, Settings } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { HelpCircle, Settings } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const HealthCheckSwarmSchema = z
|
||||
.object({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,8 +7,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -16,11 +15,6 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { toast } from "sonner";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
@@ -31,10 +25,16 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import Link from "next/link";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Server } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { AddSwarmSettings } from "./modify-swarm-settings";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -16,12 +14,14 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { toast } from "sonner";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,13 +18,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -31,6 +25,12 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const AddPortSchema = z.object({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { Rss } from "lucide-react";
|
||||
import React from "react";
|
||||
import { AddPort } from "./add-port";
|
||||
import { DeletePort } from "./delete-port";
|
||||
import { UpdatePort } from "./update-port";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,14 +18,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -32,6 +25,13 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const UpdatePortSchema = z.object({
|
||||
publishedPort: z.number().int().min(1).max(65535),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -11,22 +12,21 @@ import {
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { z } from "zod";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
const AddRedirectchema = z.object({
|
||||
regex: z.string().min(1, "Regex required"),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { Split } from "lucide-react";
|
||||
import React from "react";
|
||||
import { AddRedirect } from "./add-redirect";
|
||||
import { DeleteRedirect } from "./delete-redirect";
|
||||
import { UpdateRedirect } from "./update-redirect";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -11,22 +12,21 @@ import {
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
const UpdateRedirectSchema = z.object({
|
||||
regex: z.string().min(1, "Regex required"),
|
||||
permanent: z.boolean().default(false),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -18,12 +19,11 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { z } from "zod";
|
||||
|
||||
const AddSecuritychema = z.object({
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { LockKeyhole } from "lucide-react";
|
||||
import React from "react";
|
||||
import { AddSecurity } from "./add-security";
|
||||
import { DeleteSecurity } from "./delete-security";
|
||||
import { UpdateSecurity } from "./update-security";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -21,7 +22,6 @@ import React, { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
|
||||
const addResourcesApplication = z.object({
|
||||
memoryReservation: z.number().nullable().optional(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { File } from "lucide-react";
|
||||
import React from "react";
|
||||
import { UpdateTraefikConfig } from "./update-traefik-config";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
|
||||
<CardTitle className="text-xl">Traefik</CardTitle>
|
||||
<CardDescription>
|
||||
Modify the traefik config, in rare cases you may need to add
|
||||
specific config, becarefull because modifying incorrectly can break
|
||||
specific config, be careful because modifying incorrectly can break
|
||||
traefik and your application
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,14 +19,12 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import jsyaml from "js-yaml";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import jsyaml from "js-yaml";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
|
||||
const UpdateTraefikConfigSchema = z.object({
|
||||
traefikConfig: z.string(),
|
||||
@@ -110,12 +110,15 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(open) => {
|
||||
setOpen(open)
|
||||
if (!open) {
|
||||
form.reset();
|
||||
}
|
||||
}}>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
setOpen(open);
|
||||
if (!open) {
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button isLoading={isLoading}>Modify</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import type React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -22,12 +18,16 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
interface Props {
|
||||
serviceId: string;
|
||||
serviceType:
|
||||
@@ -63,6 +63,7 @@ const mySchema = z.discriminatedUnion("type", [
|
||||
z
|
||||
.object({
|
||||
type: z.literal("file"),
|
||||
filePath: z.string().min(1, "File path required"),
|
||||
content: z.string().optional(),
|
||||
})
|
||||
.merge(mountSchema),
|
||||
@@ -81,7 +82,7 @@ export const AddVolumes = ({
|
||||
defaultValues: {
|
||||
type: serviceType === "compose" ? "file" : "bind",
|
||||
hostPath: "",
|
||||
mountPath: "",
|
||||
mountPath: serviceType === "compose" ? "/" : "",
|
||||
},
|
||||
resolver: zodResolver(mySchema),
|
||||
});
|
||||
@@ -125,6 +126,7 @@ export const AddVolumes = ({
|
||||
serviceId,
|
||||
content: data.content,
|
||||
mountPath: data.mountPath,
|
||||
filePath: data.filePath,
|
||||
type: data.type,
|
||||
serviceType,
|
||||
})
|
||||
@@ -288,41 +290,62 @@ export const AddVolumes = ({
|
||||
)}
|
||||
|
||||
{type === "file" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="content"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Any content"
|
||||
className="h-64"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="filePath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>File Path</FormLabel>
|
||||
<FormControl>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Name of the file"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{serviceType !== "compose" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="content"
|
||||
name="mountPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormLabel>Mount Path (In the container)</FormLabel>
|
||||
<FormControl>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Any content"
|
||||
className="h-64"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<Input placeholder="Mount Path" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mountPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Mount Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Mount Path" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertTriangle, Package } from "lucide-react";
|
||||
import React from "react";
|
||||
import { AddVolumes } from "./add-volumes";
|
||||
import { DeleteVolume } from "./delete-volume";
|
||||
import { UpdateVolume } from "./update-volume";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
@@ -73,8 +73,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
|
||||
key={mount.mountId}
|
||||
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
||||
>
|
||||
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Mount Type</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
@@ -91,12 +90,21 @@ export const ShowVolumes = ({ applicationId }: Props) => {
|
||||
)}
|
||||
|
||||
{mount.type === "file" && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Content</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{mount.content}
|
||||
</span>
|
||||
</div>
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Content</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{mount.content}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
{mount.type === "bind" && (
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -118,6 +126,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
|
||||
mountId={mount.mountId}
|
||||
type={mount.type}
|
||||
refetch={refetch}
|
||||
serviceType="application"
|
||||
/>
|
||||
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,15 +18,14 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
const mountSchema = z.object({
|
||||
mountPath: z.string().min(1, "Mount path required"),
|
||||
@@ -48,6 +48,7 @@ const mySchema = z.discriminatedUnion("type", [
|
||||
.object({
|
||||
type: z.literal("file"),
|
||||
content: z.string().optional(),
|
||||
filePath: z.string().min(1, "File path required"),
|
||||
})
|
||||
.merge(mountSchema),
|
||||
]);
|
||||
@@ -58,9 +59,23 @@ interface Props {
|
||||
mountId: string;
|
||||
type: "bind" | "volume" | "file";
|
||||
refetch: () => void;
|
||||
serviceType:
|
||||
| "application"
|
||||
| "postgres"
|
||||
| "redis"
|
||||
| "mongo"
|
||||
| "redis"
|
||||
| "mysql"
|
||||
| "mariadb"
|
||||
| "compose";
|
||||
}
|
||||
|
||||
export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
||||
export const UpdateVolume = ({
|
||||
mountId,
|
||||
type,
|
||||
refetch,
|
||||
serviceType,
|
||||
}: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const { data } = api.mounts.one.useQuery(
|
||||
{
|
||||
@@ -103,6 +118,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
||||
form.reset({
|
||||
content: data.content || "",
|
||||
mountPath: data.mountPath,
|
||||
filePath: data.filePath || "",
|
||||
type: "file",
|
||||
});
|
||||
}
|
||||
@@ -141,6 +157,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
||||
content: data.content,
|
||||
mountPath: data.mountPath,
|
||||
type: data.type,
|
||||
filePath: data.filePath,
|
||||
mountId,
|
||||
})
|
||||
.then(() => {
|
||||
@@ -166,6 +183,11 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
||||
<DialogDescription>Update the mount</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
{type === "file" && (
|
||||
<AlertBlock type="warning">
|
||||
Updating the mount will recreate the file or directory.
|
||||
</AlertBlock>
|
||||
)}
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
@@ -211,40 +233,62 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
||||
)}
|
||||
|
||||
{type === "file" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="content"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="content"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Any content"
|
||||
className="h-64"
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Any content"
|
||||
className="h-64"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="filePath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>File Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
disabled
|
||||
placeholder="Name of the file"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{serviceType !== "compose" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mountPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Mount Path (In the container)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Mount Path" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mountPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Mount Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Mount Path" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
|
||||
@@ -1,213 +1,213 @@
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Cog } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { toast } from "sonner";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { z } from "zod";
|
||||
|
||||
enum BuildType {
|
||||
dockerfile = "dockerfile",
|
||||
heroku_buildpacks = "heroku_buildpacks",
|
||||
paketo_buildpacks = "paketo_buildpacks",
|
||||
nixpacks = "nixpacks",
|
||||
dockerfile = "dockerfile",
|
||||
heroku_buildpacks = "heroku_buildpacks",
|
||||
paketo_buildpacks = "paketo_buildpacks",
|
||||
nixpacks = "nixpacks",
|
||||
}
|
||||
|
||||
const mySchema = z.discriminatedUnion("buildType", [
|
||||
z.object({
|
||||
buildType: z.literal("dockerfile"),
|
||||
dockerfile: z
|
||||
.string({
|
||||
required_error: "Dockerfile path is required",
|
||||
invalid_type_error: "Dockerfile path is required",
|
||||
})
|
||||
.min(1, "Dockerfile required"),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("heroku_buildpacks"),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("paketo_buildpacks"),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("nixpacks"),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("dockerfile"),
|
||||
dockerfile: z
|
||||
.string({
|
||||
required_error: "Dockerfile path is required",
|
||||
invalid_type_error: "Dockerfile path is required",
|
||||
})
|
||||
.min(1, "Dockerfile required"),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("heroku_buildpacks"),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("paketo_buildpacks"),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("nixpacks"),
|
||||
}),
|
||||
]);
|
||||
|
||||
type AddTemplate = z.infer<typeof mySchema>;
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
const { mutateAsync, isLoading } =
|
||||
api.application.saveBuildType.useMutation();
|
||||
const { data, refetch } = api.application.one.useQuery(
|
||||
{
|
||||
applicationId,
|
||||
},
|
||||
{
|
||||
enabled: !!applicationId,
|
||||
},
|
||||
);
|
||||
const { mutateAsync, isLoading } =
|
||||
api.application.saveBuildType.useMutation();
|
||||
const { data, refetch } = api.application.one.useQuery(
|
||||
{
|
||||
applicationId,
|
||||
},
|
||||
{
|
||||
enabled: !!applicationId,
|
||||
},
|
||||
);
|
||||
|
||||
const form = useForm<AddTemplate>({
|
||||
defaultValues: {
|
||||
buildType: BuildType.nixpacks,
|
||||
},
|
||||
resolver: zodResolver(mySchema),
|
||||
});
|
||||
const form = useForm<AddTemplate>({
|
||||
defaultValues: {
|
||||
buildType: BuildType.nixpacks,
|
||||
},
|
||||
resolver: zodResolver(mySchema),
|
||||
});
|
||||
|
||||
const buildType = form.watch("buildType");
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
// TODO: refactor this
|
||||
if (data.buildType === "dockerfile") {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
...(data.buildType && {
|
||||
dockerfile: data.dockerfile || "",
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [form.formState.isSubmitSuccessful, form.reset, data, form]);
|
||||
const buildType = form.watch("buildType");
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
// TODO: refactor this
|
||||
if (data.buildType === "dockerfile") {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
...(data.buildType && {
|
||||
dockerfile: data.dockerfile || "",
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [form.formState.isSubmitSuccessful, form.reset, data, form]);
|
||||
|
||||
const onSubmit = async (data: AddTemplate) => {
|
||||
await mutateAsync({
|
||||
applicationId,
|
||||
buildType: data.buildType,
|
||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Build type saved");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to save the build type");
|
||||
});
|
||||
};
|
||||
const onSubmit = async (data: AddTemplate) => {
|
||||
await mutateAsync({
|
||||
applicationId,
|
||||
buildType: data.buildType,
|
||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Build type saved");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to save the build type");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-start justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex flex-col space-y-0.5">Build Type</span>
|
||||
<p className="flex items-center text-sm font-normal text-muted-foreground">
|
||||
Select the way of building your code
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden space-y-1 text-sm font-normal md:block">
|
||||
<Cog className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 p-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="buildType"
|
||||
defaultValue={form.control._defaultValues.buildType}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>Build Type</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="dockerfile" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Dockerfile
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="nixpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Nixpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="heroku_buildpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Heroku Buildpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="paketo_buildpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Paketo Buildpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{buildType === "dockerfile" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerfile"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Docker File</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"Path of your docker file"}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-start justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex flex-col space-y-0.5">Build Type</span>
|
||||
<p className="flex items-center text-sm font-normal text-muted-foreground">
|
||||
Select the way of building your code
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden space-y-1 text-sm font-normal md:block">
|
||||
<Cog className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 p-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="buildType"
|
||||
defaultValue={form.control._defaultValues.buildType}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>Build Type</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="dockerfile" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Dockerfile
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="nixpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Nixpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="heroku_buildpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Heroku Buildpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="paketo_buildpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Paketo Buildpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{buildType === "dockerfile" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerfile"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Docker File</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"Path of your docker file"}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="flex w-full justify-end">
|
||||
<Button isLoading={isLoading} type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="flex w-full justify-end">
|
||||
<Button isLoading={isLoading} type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { api } from "@/utils/api";
|
||||
import { RefreshCcw } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
@@ -29,8 +29,8 @@ export const RefreshToken = ({ applicationId }: Props) => {
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
domain
|
||||
This action cannot be undone. This will change the refresh token and
|
||||
other tokens will be invalidated.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -10,10 +12,8 @@ import { api } from "@/utils/api";
|
||||
import { RocketIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CancelQueues } from "./cancel-queues";
|
||||
import { ShowDeployment } from "./show-deployment";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { RefreshToken } from "./refresh-token";
|
||||
import { ShowDeployment } from "./show-deployment";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -27,7 +28,6 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { api } from "@/utils/api";
|
||||
import { RefreshCcw } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { GenerateTraefikMe } from "./generate-traefikme";
|
||||
import { GenerateWildCard } from "./generate-wildcard";
|
||||
import Link from "next/link";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { RefreshCcw } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { SquareAsterisk } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,15 +6,15 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { ExternalLink, GlobeIcon, RefreshCcw } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { DeleteDomain } from "./delete-domain";
|
||||
import { api } from "@/utils/api";
|
||||
import { ExternalLink, GlobeIcon, RefreshCcw } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import { AddDomain } from "./add-domain";
|
||||
import { UpdateDomain } from "./update-domain";
|
||||
import { DeleteDomain } from "./delete-domain";
|
||||
import { GenerateDomain } from "./generate-domain";
|
||||
import { UpdateDomain } from "./update-domain";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -27,7 +28,6 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PenBoxIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,10 +7,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -17,11 +14,14 @@ import {
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const addEnvironmentSchema = z.object({
|
||||
environment: z.string(),
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -11,9 +7,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { useEffect } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const DockerProviderSchema = z.object({
|
||||
dockerImage: z.string().min(1, {
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dropzone } from "@/components/ui/dropzone";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { type UploadFile, uploadFileSchema } from "@/utils/schema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
export const SaveDragNDrop = ({ applicationId }: Props) => {
|
||||
const { data, refetch } = api.application.one.useQuery({ applicationId });
|
||||
|
||||
const { mutateAsync, isLoading } =
|
||||
api.application.dropDeployment.useMutation();
|
||||
|
||||
const form = useForm<UploadFile>({
|
||||
defaultValues: {},
|
||||
resolver: zodResolver(uploadFileSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
dropBuildPath: data.dropBuildPath || "",
|
||||
});
|
||||
}
|
||||
}, [data, form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
const zip = form.watch("zip");
|
||||
|
||||
const onSubmit = async (values: UploadFile) => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append("zip", values.zip);
|
||||
formData.append("applicationId", applicationId);
|
||||
if (values.dropBuildPath) {
|
||||
formData.append("dropBuildPath", values.dropBuildPath);
|
||||
}
|
||||
|
||||
await mutateAsync(formData)
|
||||
.then(async () => {
|
||||
toast.success("Deployment saved");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to save the deployment");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<div className="grid md:grid-cols-2 gap-4 ">
|
||||
<div className="md:col-span-2 space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dropBuildPath"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full ">
|
||||
<FormLabel>Build Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Build Path" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="zip"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full ">
|
||||
<FormLabel>Zip file</FormLabel>
|
||||
<FormControl>
|
||||
<Dropzone
|
||||
{...field}
|
||||
dropMessage="Drop files or click here"
|
||||
accept=".zip"
|
||||
onChange={(e) => {
|
||||
if (e instanceof FileList) {
|
||||
field.onChange(e[0]);
|
||||
} else {
|
||||
field.onChange(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{zip instanceof File && (
|
||||
<div className="flex flex-row gap-4 items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{zip.name} ({zip.size} bytes)
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-fit"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
field.onChange(null);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-fit"
|
||||
isLoading={isLoading}
|
||||
disabled={!zip}
|
||||
>
|
||||
Deploy{" "}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -7,8 +7,9 @@ import { api } from "@/utils/api";
|
||||
import { GitBranch, LockIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { SaveDragNDrop } from "./save-drag-n-drop";
|
||||
|
||||
type TabState = "github" | "docker" | "git";
|
||||
type TabState = "github" | "docker" | "git" | "drop";
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
@@ -62,6 +63,12 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
||||
>
|
||||
Git
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="drop"
|
||||
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||
>
|
||||
Drop
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="github" className="w-full p-2">
|
||||
{haveGithubConfigured ? (
|
||||
@@ -89,6 +96,9 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
||||
<TabsContent value="git" className="w-full p-2">
|
||||
<SaveGitProvider applicationId={applicationId} />
|
||||
</TabsContent>
|
||||
<TabsContent value="drop" className="w-full p-2">
|
||||
<SaveDragNDrop applicationId={applicationId} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -4,13 +4,13 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { api } from "@/utils/api";
|
||||
import { CheckCircle2, Terminal } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
import { RedbuildApplication } from "../rebuild-application";
|
||||
import { StartApplication } from "../start-application";
|
||||
import { StopApplication } from "../stop-application";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
import { CheckCircle2, Terminal } from "lucide-react";
|
||||
import { DeployApplication } from "./deploy-application";
|
||||
import { ResetApplication } from "./reset-application";
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,6 +5,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
export const DockerLogs = dynamic(
|
||||
() =>
|
||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -7,7 +9,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -16,16 +17,15 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { AlertTriangle, SquarePen } from "lucide-react";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle, SquarePen } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const updateApplicationSchema = z.object({
|
||||
name: z.string().min(1, {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -17,12 +15,14 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { toast } from "sonner";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { Package } from "lucide-react";
|
||||
import { DeleteVolume } from "../../application/advanced/volumes/delete-volume";
|
||||
import React from "react";
|
||||
import { AddVolumes } from "../../application/advanced/volumes/add-volumes";
|
||||
import { DeleteVolume } from "../../application/advanced/volumes/delete-volume";
|
||||
import { UpdateVolume } from "../../application/advanced/volumes/update-volume";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
||||
key={mount.mountId}
|
||||
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
||||
>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Mount Type</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
@@ -91,12 +91,20 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
||||
)}
|
||||
|
||||
{mount.type === "file" && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Content</span>
|
||||
<span className="text-sm text-muted-foreground w-40 truncate">
|
||||
{mount.content}
|
||||
</span>
|
||||
</div>
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Content</span>
|
||||
<span className="text-sm text-muted-foreground w-40 truncate">
|
||||
{mount.content}
|
||||
</span>
|
||||
</div>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
{mount.type === "bind" && (
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -118,6 +126,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
||||
mountId={mount.mountId}
|
||||
type={mount.type}
|
||||
refetch={refetch}
|
||||
serviceType="compose"
|
||||
/>
|
||||
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { api } from "@/utils/api";
|
||||
import { RefreshCcw } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -9,11 +11,9 @@ import {
|
||||
import { api } from "@/utils/api";
|
||||
import { RocketIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { ShowDeploymentCompose } from "./show-deployment-compose";
|
||||
import { RefreshTokenCompose } from "./refresh-token-compose";
|
||||
import { CancelQueuesCompose } from "./cancel-queues-compose";
|
||||
import { RefreshTokenCompose } from "./refresh-token-compose";
|
||||
import { ShowDeploymentCompose } from "./show-deployment-compose";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,10 +7,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -17,11 +14,14 @@ import {
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const addEnvironmentSchema = z.object({
|
||||
environment: z.string(),
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ExternalLink, Globe, Terminal } from "lucide-react";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { RedbuildCompose } from "./rebuild-compose";
|
||||
import { DeployCompose } from "./deploy-compose";
|
||||
import { StopCompose } from "./stop-compose";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -16,7 +8,15 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { api } from "@/utils/api";
|
||||
import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { toast } from "sonner";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
import { DeployCompose } from "./deploy-compose";
|
||||
import { RedbuildCompose } from "./rebuild-compose";
|
||||
import { StopCompose } from "./stop-compose";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
@@ -50,7 +50,6 @@ export const ComposeActions = ({ composeId }: Props) => {
|
||||
return (
|
||||
<div className="flex flex-row gap-4 w-full flex-wrap ">
|
||||
<DeployCompose composeId={composeId} />
|
||||
|
||||
<Toggle
|
||||
aria-label="Toggle italic"
|
||||
pressed={data?.autoDeploy || false}
|
||||
@@ -67,8 +66,9 @@ export const ComposeActions = ({ composeId }: Props) => {
|
||||
toast.error("Error to update Auto Deploy");
|
||||
});
|
||||
}}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
>
|
||||
Autodeploy
|
||||
Autodeploy {data?.autoDeploy && <CheckCircle2 className="size-4" />}
|
||||
</Toggle>
|
||||
<RedbuildCompose composeId={composeId} />
|
||||
{data?.composeType === "docker-compose" && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { api } from "@/utils/api";
|
||||
import { useEffect } from "react";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -7,14 +7,14 @@ import {
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RandomizeCompose } from "./randomize-compose";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
|
||||
@@ -4,9 +4,9 @@ import { api } from "@/utils/api";
|
||||
import { GitBranch, LockIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
||||
import { ComposeFileEditor } from "../compose-file-editor";
|
||||
import { SaveGitProviderCompose } from "./save-git-provider-compose";
|
||||
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
||||
|
||||
type TabState = "github" | "git" | "raw";
|
||||
interface Props {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -7,12 +8,11 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Dices } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -5,11 +6,10 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import React from "react";
|
||||
import { ShowProviderFormCompose } from "./generic/show";
|
||||
import { ComposeActions } from "./actions";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { api } from "@/utils/api";
|
||||
import React from "react";
|
||||
import { ComposeActions } from "./actions";
|
||||
import { ShowProviderFormCompose } from "./generic/show";
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,6 +5,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
export const DockerLogs = dynamic(
|
||||
() =>
|
||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||
|
||||
@@ -5,8 +5,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -16,7 +15,8 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { useEffect, useState } from "react";
|
||||
import { DockerMonitoring } from "../../monitoring/docker/show";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -7,7 +9,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -16,16 +17,15 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { SquarePen } from "lucide-react";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { SquarePen } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const updateComposeSchema = z.object({
|
||||
name: z.string().min(1, {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -11,36 +18,29 @@ import {
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { z } from "zod";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const AddPostgresBackup1Schema = z.object({
|
||||
destinationId: z.string().min(1, "Destination required"),
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -18,28 +25,21 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Pencil, CheckIcon, ChevronsUpDown, PenBoxIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { z } from "zod";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CheckIcon, ChevronsUpDown, PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const UpdateBackupSchema = z.object({
|
||||
destinationId: z.string().min(1, "Destination required"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import React, { useEffect } from "react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import React, { useEffect } from "react";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import React from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -9,6 +7,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import dynamic from "next/dynamic";
|
||||
import type React from "react";
|
||||
export const DockerLogsId = dynamic(
|
||||
() =>
|
||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
type ColumnFiltersState,
|
||||
type SortingState,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { api, type RouterOutputs } from "@/utils/api";
|
||||
import { type RouterOutputs, api } from "@/utils/api";
|
||||
import { columns } from "./colums";
|
||||
export type Container = NonNullable<
|
||||
RouterOutputs["docker"]["getContainers"]
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import dynamic from "next/dynamic";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const Terminal = dynamic(
|
||||
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
import { AttachAddon } from "@xterm/addon-attach";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { AttachAddon } from "@xterm/addon-attach";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -10,14 +12,12 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { validateAndFormatYAML } from "../application/advanced/traefik/update-traefik-config";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
|
||||
const UpdateServerMiddlewareConfigSchema = z.object({
|
||||
traefikConfig: z.string(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
import { api } from "@/utils/api";
|
||||
import { Workflow, Folder, FileIcon } from "lucide-react";
|
||||
import { Tree } from "@/components/ui/file-tree";
|
||||
import { api } from "@/utils/api";
|
||||
import { FileIcon, Folder, Workflow } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ShowTraefikFile } from "./show-traefik-file";
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { z } from "zod";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ShowMariadbResources } from "./show-mariadb-resources";
|
||||
import { toast } from "sonner";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -15,8 +8,15 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import React, { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { ShowVolumes } from "../volumes/show-volumes";
|
||||
import { ShowMariadbResources } from "./show-mariadb-resources";
|
||||
|
||||
const addDockerImage = z.object({
|
||||
dockerImage: z.string().min(1, "Docker image is required"),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -21,7 +22,6 @@ import React, { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
|
||||
const addResourcesMariadb = z.object({
|
||||
memoryReservation: z.number().nullable().optional(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,20 +6,20 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { DatabaseBackup, Play } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { AddBackup } from "../../database/backups/add-backup";
|
||||
import { DeleteBackup } from "../../database/backups/delete-backup";
|
||||
import { UpdateBackup } from "../../database/backups/update-backup";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
import { DatabaseBackup, Play } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
import { AddBackup } from "../../database/backups/add-backup";
|
||||
import { DeleteBackup } from "../../database/backups/delete-backup";
|
||||
import { UpdateBackup } from "../../database/backups/update-backup";
|
||||
interface Props {
|
||||
mariadbId: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -6,10 +7,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -17,9 +14,14 @@ import {
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { z } from "zod";
|
||||
|
||||
const addEnvironmentSchema = z.object({
|
||||
environment: z.string(),
|
||||
@@ -32,6 +34,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ShowMariadbEnvironment = ({ mariadbId }: Props) => {
|
||||
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
||||
const { mutateAsync, isLoading } = api.mariadb.saveEnvironment.useMutation();
|
||||
|
||||
const { data, refetch } = api.mariadb.one.useQuery(
|
||||
@@ -74,11 +77,26 @@ export const ShowMariadbEnvironment = ({ mariadbId }: Props) => {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
<Card className="bg-background">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
||||
<CardDescription>
|
||||
You can add environment variables to your database.
|
||||
</CardDescription>
|
||||
{" "}
|
||||
<CardHeader className="flex flex-row w-full items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
||||
<CardDescription>
|
||||
You can add environment variables to your resource.
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
<Toggle
|
||||
aria-label="Toggle bold"
|
||||
pressed={isEnvVisible}
|
||||
onPressedChange={setIsEnvVisible}
|
||||
>
|
||||
{isEnvVisible ? (
|
||||
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</Toggle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
@@ -95,6 +113,7 @@ export const ShowMariadbEnvironment = ({ mariadbId }: Props) => {
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
language="properties"
|
||||
disabled={isEnvVisible}
|
||||
placeholder={`NODE_ENV=production
|
||||
PORT=3000
|
||||
`}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -22,7 +23,6 @@ import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
|
||||
const DockerProviderSchema = z.object({
|
||||
externalPort: z.preprocess((a) => {
|
||||
@@ -44,6 +44,7 @@ interface Props {
|
||||
mariadbId: string;
|
||||
}
|
||||
export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
||||
const { data: ip } = api.settings.getIp.useQuery();
|
||||
const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
|
||||
const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
|
||||
const [connectionUrl, setConnectionUrl] = useState("");
|
||||
@@ -76,10 +77,9 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
const buildConnectionUrl = () => {
|
||||
const hostname = window.location.hostname;
|
||||
const port = form.watch("externalPort") || data?.externalPort;
|
||||
|
||||
return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${hostname}:${port}/${data?.databaseName}`;
|
||||
return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}/${data?.databaseName}`;
|
||||
};
|
||||
|
||||
setConnectionUrl(buildConnectionUrl());
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { StopMariadb } from "./stop-mariadb";
|
||||
import { StartMariadb } from "../start-mariadb";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
import { Terminal } from "lucide-react";
|
||||
import React from "react";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
import { StartMariadb } from "../start-mariadb";
|
||||
import { DeployMariadb } from "./deploy-mariadb";
|
||||
import { ResetMariadb } from "./reset-mariadb";
|
||||
import { StopMariadb } from "./stop-mariadb";
|
||||
|
||||
interface Props {
|
||||
mariadbId: string;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user