mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-06 06:25:28 +02:00
Compare commits
154 Commits
v0.24.6
...
feat/concu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2da2b2dd39 | ||
|
|
7273c636a0 | ||
|
|
d6a0585bae | ||
|
|
935d1686f2 | ||
|
|
349248105a | ||
|
|
d922568510 | ||
|
|
44ae4df151 | ||
|
|
77fdda4c09 | ||
|
|
8a1e36cc3b | ||
|
|
1635bab44f | ||
|
|
4a52459015 | ||
|
|
17f333ac2a | ||
|
|
d770307d64 | ||
|
|
aa434cbdea | ||
|
|
c42054b965 | ||
|
|
03588bf375 | ||
|
|
8c420ff4f5 | ||
|
|
cbf6f95891 | ||
|
|
2d2a3d74ec | ||
|
|
56b9fb531a | ||
|
|
59aaa1a47a | ||
|
|
5e4444610c | ||
|
|
34e6cd87df | ||
|
|
31b13b8d34 | ||
|
|
746cf76cf3 | ||
|
|
46c53a05bf | ||
|
|
f97f6d8178 | ||
|
|
c653dd604f | ||
|
|
40877e4370 | ||
|
|
65203036f2 | ||
|
|
2ef5f967a9 | ||
|
|
b20c95ffbc | ||
|
|
09b2492585 | ||
|
|
ca1fa7c4f7 | ||
|
|
112b898d98 | ||
|
|
8185482bcd | ||
|
|
dd8f5dba09 | ||
|
|
e72a468c7e | ||
|
|
02dd793dfb | ||
|
|
64ef033950 | ||
|
|
32f7bdf398 | ||
|
|
8d73b77a19 | ||
|
|
2e3d4f1021 | ||
|
|
ba1f4dbd3a | ||
|
|
653beac3d9 | ||
|
|
37c34fdadc | ||
|
|
69d676178f | ||
|
|
6612c92b4f | ||
|
|
88c8fe4614 | ||
|
|
623fc26de5 | ||
|
|
220576fd63 | ||
|
|
07c23292da | ||
|
|
72fca80047 | ||
|
|
1e7f614bb6 | ||
|
|
e2662a0ec5 | ||
|
|
c96c25ca9f | ||
|
|
4afd2d11fa | ||
|
|
ff20bb2731 | ||
|
|
8a802b0739 | ||
|
|
e511173283 | ||
|
|
763b1a344a | ||
|
|
a4041185f1 | ||
|
|
522dcd6c08 | ||
|
|
b4d5935875 | ||
|
|
8cc054389a | ||
|
|
58b78e1ee3 | ||
|
|
5b0a8fde9c | ||
|
|
7e641c0ed5 | ||
|
|
83531ceacd | ||
|
|
6d9a1db8af | ||
|
|
e3979d2c48 | ||
|
|
5f39dfee3a | ||
|
|
774365c68e | ||
|
|
0cdacb5501 | ||
|
|
e266b1a620 | ||
|
|
81ab71ba23 | ||
|
|
ae92725554 | ||
|
|
974d1d8b26 | ||
|
|
7367601e26 | ||
|
|
861b390707 | ||
|
|
4e7378371d | ||
|
|
b6cbf9127d | ||
|
|
661c517dfc | ||
|
|
f3ec01ec77 | ||
|
|
f4499463fe | ||
|
|
6e069154ef | ||
|
|
66e6b56053 | ||
|
|
6248abd88e | ||
|
|
2c591cbd03 | ||
|
|
3864c50deb | ||
|
|
83e8c82c4a | ||
|
|
a41137aacc | ||
|
|
213acd6287 | ||
|
|
0781336b8f | ||
|
|
28811ca66d | ||
|
|
ed53bdd0fa | ||
|
|
957d1b5966 | ||
|
|
ad359defae | ||
|
|
a4bbcea282 | ||
|
|
15e62961e8 | ||
|
|
429c1e4cd8 | ||
|
|
1904a3d1e9 | ||
|
|
f0278f354b | ||
|
|
9763dce045 | ||
|
|
ef6dcaf363 | ||
|
|
37b056cd4b | ||
|
|
8bbef02e39 | ||
|
|
231b8ed19d | ||
|
|
cfa0135932 | ||
|
|
85bce827eb | ||
|
|
1fe12ba93e | ||
|
|
4b1146ab6b | ||
|
|
fe2f6f842b | ||
|
|
c3f29c2694 | ||
|
|
d5307cb5d6 | ||
|
|
b99556b389 | ||
|
|
112a1dedec | ||
|
|
edbdc01a1e | ||
|
|
883e1d1bfe | ||
|
|
33d6c2073b | ||
|
|
33873ce1e9 | ||
|
|
1d94c85c2b | ||
|
|
1758655f66 | ||
|
|
029eed7755 | ||
|
|
f017536396 | ||
|
|
ba5505cf81 | ||
|
|
5ff5da9ff9 | ||
|
|
e96a8ea4ad | ||
|
|
42864d2472 | ||
|
|
ae25ea265c | ||
|
|
0755f28307 | ||
|
|
3d10d48425 | ||
|
|
6e79183f6a | ||
|
|
2318fb062a | ||
|
|
14b4bc9d85 | ||
|
|
4c72f1894c | ||
|
|
ebd632df04 | ||
|
|
4878ed2b6f | ||
|
|
1d3ab2bafa | ||
|
|
7cb7cfa2a8 | ||
|
|
6009697710 | ||
|
|
6be86b49bb | ||
|
|
1e81244e0b | ||
|
|
f659ea463d | ||
|
|
22c7c6e6fb | ||
|
|
caf57276a4 | ||
|
|
15c6c7e657 | ||
|
|
8be0db385a | ||
|
|
025d439f71 | ||
|
|
9baafb83ff | ||
|
|
1f9ef473f1 | ||
|
|
a0bbf7be23 | ||
|
|
a5bc384d77 | ||
|
|
f2ae39aa86 |
21
.github/pull_request_template.md
vendored
Normal file
21
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
## What is this PR about?
|
||||||
|
|
||||||
|
Please describe in a short paragraph what this PR is about.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
Before submitting this PR, please make sure that:
|
||||||
|
|
||||||
|
- [ ] You created a dedicated branch based on the `canary` branch.
|
||||||
|
- [ ] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
|
||||||
|
- [ ] You have tested this PR in your local instance.
|
||||||
|
|
||||||
|
## Issues related (if applicable)
|
||||||
|
|
||||||
|
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`, `fixes #ISSUE_NUMBER`, `resolves #ISSUE_NUMBER`
|
||||||
|
|
||||||
|
Example: `closes #123`
|
||||||
|
|
||||||
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
|
If you include a video or screenshot, would be awesome so we can see the changes in action.
|
||||||
4
.github/workflows/create-pr.yml
vendored
4
.github/workflows/create-pr.yml
vendored
@@ -19,17 +19,14 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Get version from package.json
|
- name: Get version from package.json
|
||||||
id: package_version
|
|
||||||
run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV
|
run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get latest GitHub tag
|
- name: Get latest GitHub tag
|
||||||
id: latest_tag
|
|
||||||
run: |
|
run: |
|
||||||
LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1)
|
LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1)
|
||||||
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
||||||
echo $LATEST_TAG
|
echo $LATEST_TAG
|
||||||
- name: Compare versions
|
- name: Compare versions
|
||||||
id: compare_versions
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then
|
if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then
|
||||||
VERSION_CHANGED="true"
|
VERSION_CHANGED="true"
|
||||||
@@ -42,7 +39,6 @@ jobs:
|
|||||||
echo "Latest tag: ${{ env.LATEST_TAG }}"
|
echo "Latest tag: ${{ env.LATEST_TAG }}"
|
||||||
echo "Version changed: $VERSION_CHANGED"
|
echo "Version changed: $VERSION_CHANGED"
|
||||||
- name: Check if a PR already exists
|
- name: Check if a PR already exists
|
||||||
id: check_pr
|
|
||||||
run: |
|
run: |
|
||||||
PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length')
|
PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length')
|
||||||
echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV
|
echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV
|
||||||
|
|||||||
2
.github/workflows/dokploy.yml
vendored
2
.github/workflows/dokploy.yml
vendored
@@ -2,7 +2,7 @@ name: Dokploy Docker Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, canary]
|
branches: [main, canary, "fix/re-apply-database-migration-fix"]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|||||||
39
.github/workflows/pull-request.yml
vendored
39
.github/workflows/pull-request.yml
vendored
@@ -4,9 +4,15 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, canary]
|
branches: [main, canary]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-and-typecheck:
|
pr-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
job: [build, test, typecheck]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
@@ -15,32 +21,5 @@ jobs:
|
|||||||
node-version: 20.16.0
|
node-version: 20.16.0
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm run server:build
|
- run: pnpm server:build
|
||||||
- run: pnpm typecheck
|
- run: pnpm ${{ matrix.job }}
|
||||||
|
|
||||||
build-and-test:
|
|
||||||
needs: lint-and-typecheck
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20.16.0
|
|
||||||
cache: "pnpm"
|
|
||||||
- run: pnpm install --frozen-lockfile
|
|
||||||
- run: pnpm run server:build
|
|
||||||
- run: pnpm build
|
|
||||||
|
|
||||||
parallel-tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20.16.0
|
|
||||||
cache: "pnpm"
|
|
||||||
- run: pnpm install --frozen-lockfile
|
|
||||||
- run: pnpm run server:build
|
|
||||||
- run: pnpm test
|
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ pnpm run dokploy:dev
|
|||||||
|
|
||||||
Go to http://localhost:3000 to see the development server
|
Go to http://localhost:3000 to see the development server
|
||||||
|
|
||||||
Note: this project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off.
|
> [!NOTE]
|
||||||
|
> This project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -117,10 +118,10 @@ In the case you lost your password, you can reset it using the following command
|
|||||||
pnpm run reset-password
|
pnpm run reset-password
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to test the webhooks on development mode using localtunnel, make sure to install `localtunnel`
|
If you want to test the webhooks on development mode using localtunnel, make sure to install [`localtunnel`](https://localtunnel.app/)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bunx lt --port 3000
|
pnpm dlx localtunnel --port 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
If you run into permission issues of docker run the following command
|
If you run into permission issues of docker run the following command
|
||||||
@@ -152,7 +153,7 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.
|
|||||||
|
|
||||||
## Pull Request
|
## Pull Request
|
||||||
|
|
||||||
- The `main` branch is the source of truth and should always reflect the latest stable release.
|
- The `canary` branch is the source of truth and should always reflect the latest stable release.
|
||||||
- Create a new branch for each feature or bug fix.
|
- Create a new branch for each feature or bug fix.
|
||||||
- Make sure to add tests for your changes.
|
- Make sure to add tests for your changes.
|
||||||
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
|
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
|
||||||
@@ -161,6 +162,12 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.
|
|||||||
- If your pull request fixes an open issue, please reference the issue in the pull request description.
|
- If your pull request fixes an open issue, please reference the issue in the pull request description.
|
||||||
- Once your pull request is merged, you will be automatically added as a contributor to the project.
|
- Once your pull request is merged, you will be automatically added as a contributor to the project.
|
||||||
|
|
||||||
|
**Important Considerations for Pull Requests:**
|
||||||
|
|
||||||
|
- **Focus and Scope:** Each Pull Request should ideally address a single, well-defined problem or introduce one new feature. This greatly facilitates review and reduces the chances of introducing unintended side effects.
|
||||||
|
- **Avoid Unfocused Changes:** Please avoid submitting Pull Requests that contain only minor changes such as whitespace adjustments, IDE-generated formatting, or removal of unused variables, unless these are part of a larger, clearly defined refactor or a dedicated "cleanup" Pull Request that addresses a specific `good first issue` or maintenance task.
|
||||||
|
- **Issue Association:** For any significant change, it's highly recommended to open an issue first to discuss the proposed solution with the community and maintainers. This ensures alignment and avoids duplicated effort. If your PR resolves an existing issue, please link it in the description (e.g., `Fixes #123`, `Closes #456`).
|
||||||
|
|
||||||
Thank you for your contribution!
|
Thank you for your contribution!
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"inngest": "3.40.1",
|
||||||
"@dokploy/server": "workspace:*",
|
"@dokploy/server": "workspace:*",
|
||||||
"@hono/node-server": "^1.14.3",
|
"@hono/node-server": "^1.14.3",
|
||||||
"@hono/zod-validator": "0.3.0",
|
"@hono/zod-validator": "0.3.0",
|
||||||
|
|||||||
@@ -2,21 +2,79 @@ import { serve } from "@hono/node-server";
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { Queue } from "@nerimity/mimiqueue";
|
import { Inngest } from "inngest";
|
||||||
import { createClient } from "redis";
|
import { serve as serveInngest } from "inngest/hono";
|
||||||
import { logger } from "./logger.js";
|
import { logger } from "./logger.js";
|
||||||
import { type DeployJob, deployJobSchema } from "./schema.js";
|
import { type DeployJob, deployJobSchema } from "./schema.js";
|
||||||
import { deploy } from "./utils.js";
|
import { deploy } from "./utils.js";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
const redisClient = createClient({
|
|
||||||
url: process.env.REDIS_URL,
|
// Initialize Inngest client
|
||||||
|
export const inngest = new Inngest({
|
||||||
|
id: "dokploy-deployments",
|
||||||
|
name: "Dokploy Deployment Service",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const deploymentFunction = inngest.createFunction(
|
||||||
|
{
|
||||||
|
id: "deploy-application",
|
||||||
|
name: "Deploy Application",
|
||||||
|
concurrency: [
|
||||||
|
{
|
||||||
|
key: "event.data.serverId",
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
retries: 0,
|
||||||
|
},
|
||||||
|
{ event: "deployment/requested" },
|
||||||
|
|
||||||
|
async ({ event, step }) => {
|
||||||
|
const jobData = event.data as DeployJob;
|
||||||
|
|
||||||
|
return await step.run("execute-deployment", async () => {
|
||||||
|
logger.info("Deploying started");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await deploy(jobData);
|
||||||
|
logger.info("Deployment finished", result);
|
||||||
|
|
||||||
|
// Send success event
|
||||||
|
await inngest.send({
|
||||||
|
name: "deployment/completed",
|
||||||
|
data: {
|
||||||
|
...jobData,
|
||||||
|
result,
|
||||||
|
status: "success",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Deployment failed", { jobData, error });
|
||||||
|
|
||||||
|
// Send failure event
|
||||||
|
await inngest.send({
|
||||||
|
name: "deployment/failed",
|
||||||
|
data: {
|
||||||
|
...jobData,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
status: "failed",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
app.use(async (c, next) => {
|
app.use(async (c, next) => {
|
||||||
if (c.req.path === "/health") {
|
if (c.req.path === "/health" || c.req.path === "/api/inngest") {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const authHeader = c.req.header("X-API-Key");
|
const authHeader = c.req.header("X-API-Key");
|
||||||
|
|
||||||
if (process.env.API_KEY !== authHeader) {
|
if (process.env.API_KEY !== authHeader) {
|
||||||
@@ -26,36 +84,55 @@ app.use(async (c, next) => {
|
|||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
|
app.post("/deploy", zValidator("json", deployJobSchema), async (c) => {
|
||||||
const data = c.req.valid("json");
|
const data = c.req.valid("json");
|
||||||
queue.add(data, { groupName: data.serverId });
|
logger.info("Received deployment request", data);
|
||||||
return c.json(
|
|
||||||
{
|
try {
|
||||||
message: "Deployment Added",
|
// Send event to Inngest instead of adding to Redis queue
|
||||||
},
|
await inngest.send({
|
||||||
200,
|
name: "deployment/requested",
|
||||||
);
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("Deployment event sent to Inngest", {
|
||||||
|
serverId: data.serverId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
message: "Deployment Added to Inngest Queue",
|
||||||
|
serverId: data.serverId,
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error", error);
|
||||||
|
logger.error("Failed to send deployment event", error);
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
message: "Failed to queue deployment",
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/health", async (c) => {
|
app.get("/health", async (c) => {
|
||||||
return c.json({ status: "ok" });
|
return c.json({ status: "ok" });
|
||||||
});
|
});
|
||||||
|
|
||||||
const queue = new Queue({
|
// Serve Inngest functions endpoint
|
||||||
name: "deployments",
|
app.on(
|
||||||
process: async (job: DeployJob) => {
|
["GET", "POST", "PUT"],
|
||||||
logger.info("Deploying job", job);
|
"/api/inngest",
|
||||||
return await deploy(job);
|
serveInngest({
|
||||||
},
|
client: inngest,
|
||||||
redisClient,
|
functions: [deploymentFunction],
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
(async () => {
|
|
||||||
await redisClient.connect();
|
|
||||||
await redisClient.flushAll();
|
|
||||||
logger.info("Redis Cleaned");
|
|
||||||
})();
|
|
||||||
|
|
||||||
const port = Number.parseInt(process.env.PORT || "3000");
|
const port = Number.parseInt(process.env.PORT || "3000");
|
||||||
logger.info("Starting Deployments Server ✅", port);
|
logger.info("Starting Deployments Server with Inngest ✅", port);
|
||||||
serve({ fetch: app.fetch, port });
|
serve({ fetch: app.fetch, port });
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (e) {
|
||||||
if (job.applicationType === "application") {
|
if (job.applicationType === "application") {
|
||||||
await updateApplicationStatus(job.applicationId, "error");
|
await updateApplicationStatus(job.applicationId, "error");
|
||||||
} else if (job.applicationType === "compose") {
|
} else if (job.applicationType === "compose") {
|
||||||
@@ -76,6 +76,8 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
previewStatus: "error",
|
previewStatus: "error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ if (typeof window === "undefined") {
|
|||||||
const baseApp: ApplicationNested = {
|
const baseApp: ApplicationNested = {
|
||||||
railpackVersion: "0.2.2",
|
railpackVersion: "0.2.2",
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
|
previewLabels: [],
|
||||||
herokuVersion: "",
|
herokuVersion: "",
|
||||||
giteaBranch: "",
|
giteaBranch: "",
|
||||||
giteaBuildPath: "",
|
giteaBuildPath: "",
|
||||||
|
|||||||
74
apps/dokploy/__test__/env/shared.test.ts
vendored
74
apps/dokploy/__test__/env/shared.test.ts
vendored
@@ -177,3 +177,77 @@ COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{project.APP_NAME}}'"
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("prepareEnvironmentVariables (self references)", () => {
|
||||||
|
it("resolves self references correctly", () => {
|
||||||
|
const serviceEnv = `
|
||||||
|
ENVIRONMENT=staging
|
||||||
|
DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
|
||||||
|
SELF_REF=\${{ENVIRONMENT}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(serviceEnv, "");
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"ENVIRONMENT=staging",
|
||||||
|
"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
|
||||||
|
"SELF_REF=staging",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on undefined self references", () => {
|
||||||
|
const serviceEnv = `
|
||||||
|
MISSING_VAR=\${{UNDEFINED_VAR}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(() => prepareEnvironmentVariables(serviceEnv, "")).toThrow(
|
||||||
|
"Invalid service environment variable: UNDEFINED_VAR",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows overriding and still resolving from self", () => {
|
||||||
|
const serviceEnv = `
|
||||||
|
ENVIRONMENT=production
|
||||||
|
OVERRIDE_ENV=\${{ENVIRONMENT}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(serviceEnv, "");
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"ENVIRONMENT=production",
|
||||||
|
"OVERRIDE_ENV=production",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves multiple self references inside one value", () => {
|
||||||
|
const serviceEnv = `
|
||||||
|
ENVIRONMENT=staging
|
||||||
|
APP_NAME=MyApp
|
||||||
|
COMPLEX=\${{APP_NAME}}-\${{ENVIRONMENT}}-\${{APP_NAME}}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(serviceEnv, "");
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"ENVIRONMENT=staging",
|
||||||
|
"APP_NAME=MyApp",
|
||||||
|
"COMPLEX=MyApp-staging-MyApp",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles quotes with self references", () => {
|
||||||
|
const serviceEnv = `
|
||||||
|
ENVIRONMENT=production
|
||||||
|
QUOTED="'\${{ENVIRONMENT}}'"
|
||||||
|
MIXED="\"Double \${{ENVIRONMENT}}\""
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolved = prepareEnvironmentVariables(serviceEnv, "");
|
||||||
|
|
||||||
|
expect(resolved).toEqual([
|
||||||
|
"ENVIRONMENT=production",
|
||||||
|
"QUOTED='production'",
|
||||||
|
'MIXED="Double production"',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const baseApp: ApplicationNested = {
|
|||||||
railpackVersion: "0.2.2",
|
railpackVersion: "0.2.2",
|
||||||
rollbackActive: false,
|
rollbackActive: false,
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
|
previewLabels: [],
|
||||||
herokuVersion: "",
|
herokuVersion: "",
|
||||||
giteaRepository: "",
|
giteaRepository: "",
|
||||||
giteaOwner: "",
|
giteaOwner: "",
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
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";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -26,12 +32,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
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
|
const HealthCheckSwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -181,21 +181,38 @@ const addSwarmSettings = z.object({
|
|||||||
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
|
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
id: string;
|
||||||
|
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddSwarmSettings = ({ applicationId }: Props) => {
|
export const AddSwarmSettings = ({ id, type }: Props) => {
|
||||||
const { data, refetch } = api.application.one.useQuery(
|
const queryMap = {
|
||||||
{
|
postgres: () =>
|
||||||
applicationId,
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
},
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
{
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
enabled: !!applicationId,
|
mariadb: () =>
|
||||||
},
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
);
|
application: () =>
|
||||||
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
};
|
||||||
|
const { data, refetch } = queryMap[type]
|
||||||
|
? queryMap[type]()
|
||||||
|
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||||
|
|
||||||
const { mutateAsync, isError, error, isLoading } =
|
const mutationMap = {
|
||||||
api.application.update.useMutation();
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
|
redis: () => api.redis.update.useMutation(),
|
||||||
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
|
application: () => api.application.update.useMutation(),
|
||||||
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutateAsync, isError, error, isLoading } = mutationMap[type]
|
||||||
|
? mutationMap[type]()
|
||||||
|
: api.mongo.update.useMutation();
|
||||||
|
|
||||||
const form = useForm<AddSwarmSettings>({
|
const form = useForm<AddSwarmSettings>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -244,7 +261,12 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async (data: AddSwarmSettings) => {
|
const onSubmit = async (data: AddSwarmSettings) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId: id || "",
|
||||||
|
postgresId: id || "",
|
||||||
|
redisId: id || "",
|
||||||
|
mysqlId: id || "",
|
||||||
|
mariadbId: id || "",
|
||||||
|
mongoId: id || "",
|
||||||
healthCheckSwarm: data.healthCheckSwarm,
|
healthCheckSwarm: data.healthCheckSwarm,
|
||||||
restartPolicySwarm: data.restartPolicySwarm,
|
restartPolicySwarm: data.restartPolicySwarm,
|
||||||
placementSwarm: data.placementSwarm,
|
placementSwarm: data.placementSwarm,
|
||||||
@@ -270,7 +292,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
Swarm Settings
|
Swarm Settings
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-5xl p-0">
|
<DialogContent className="sm:max-w-5xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Swarm Settings</DialogTitle>
|
<DialogTitle>Swarm Settings</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -278,10 +300,10 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
<div className="px-4">
|
<div>
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info">
|
||||||
Changing settings such as placements may cause the logs/monitoring
|
Changing settings such as placements may cause the logs/monitoring,
|
||||||
to be unavailable.
|
backups and other features to be unavailable.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -289,13 +311,13 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
<form
|
<form
|
||||||
id="hook-form-add-permissions"
|
id="hook-form-add-permissions"
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative"
|
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative mt-4"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="healthCheckSwarm"
|
name="healthCheckSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Health Check</FormLabel>
|
<FormLabel>Health Check</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -351,7 +373,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="restartPolicySwarm"
|
name="restartPolicySwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Restart Policy</FormLabel>
|
<FormLabel>Restart Policy</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -405,7 +427,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="placementSwarm"
|
name="placementSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Placement</FormLabel>
|
<FormLabel>Placement</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -471,7 +493,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="updateConfigSwarm"
|
name="updateConfigSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Update Config</FormLabel>
|
<FormLabel>Update Config</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -529,7 +551,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="rollbackConfigSwarm"
|
name="rollbackConfigSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Rollback Config</FormLabel>
|
<FormLabel>Rollback Config</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -587,7 +609,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="modeSwarm"
|
name="modeSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Mode</FormLabel>
|
<FormLabel>Mode</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -650,7 +672,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="networkSwarm"
|
name="networkSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Network</FormLabel>
|
<FormLabel>Network</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -709,7 +731,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="labelsSwarm"
|
name="labelsSwarm"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
|
<FormItem className="relative ">
|
||||||
<FormLabel>Labels</FormLabel>
|
<FormLabel>Labels</FormLabel>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Server } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -26,43 +33,57 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Server } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
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 { AddSwarmSettings } from "./modify-swarm-settings";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
id: string;
|
||||||
|
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddRedirectchema = z.object({
|
const AddRedirectchema = z.object({
|
||||||
replicas: z.number().min(1, "Replicas must be at least 1"),
|
replicas: z.number().min(1, "Replicas must be at least 1"),
|
||||||
registryId: z.string(),
|
registryId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddCommand = z.infer<typeof AddRedirectchema>;
|
type AddCommand = z.infer<typeof AddRedirectchema>;
|
||||||
|
|
||||||
export const ShowClusterSettings = ({ applicationId }: Props) => {
|
export const ShowClusterSettings = ({ id, type }: Props) => {
|
||||||
const { data } = api.application.one.useQuery(
|
const queryMap = {
|
||||||
{
|
postgres: () =>
|
||||||
applicationId,
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
},
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
{ enabled: !!applicationId },
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
);
|
mariadb: () =>
|
||||||
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
|
application: () =>
|
||||||
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
};
|
||||||
|
const { data, refetch } = queryMap[type]
|
||||||
|
? queryMap[type]()
|
||||||
|
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||||
const { data: registries } = api.registry.all.useQuery();
|
const { data: registries } = api.registry.all.useQuery();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const mutationMap = {
|
||||||
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
|
redis: () => api.redis.update.useMutation(),
|
||||||
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
|
application: () => api.application.update.useMutation(),
|
||||||
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = api.application.update.useMutation();
|
const { mutateAsync, isLoading } = mutationMap[type]
|
||||||
|
? mutationMap[type]()
|
||||||
|
: api.mongo.update.useMutation();
|
||||||
|
|
||||||
const form = useForm<AddCommand>({
|
const form = useForm<AddCommand>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
registryId: data?.registryId || "",
|
...(type === "application" && data && "registryId" in data
|
||||||
|
? {
|
||||||
|
registryId: data?.registryId || "",
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
replicas: data?.replicas || 1,
|
replicas: data?.replicas || 1,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(AddRedirectchema),
|
resolver: zodResolver(AddRedirectchema),
|
||||||
@@ -71,7 +92,11 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.command) {
|
if (data?.command) {
|
||||||
form.reset({
|
form.reset({
|
||||||
registryId: data?.registryId || "",
|
...(type === "application" && data && "registryId" in data
|
||||||
|
? {
|
||||||
|
registryId: data?.registryId || "",
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
replicas: data?.replicas || 1,
|
replicas: data?.replicas || 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -79,18 +104,25 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async (data: AddCommand) => {
|
const onSubmit = async (data: AddCommand) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId: id || "",
|
||||||
registryId:
|
postgresId: id || "",
|
||||||
data?.registryId === "none" || !data?.registryId
|
redisId: id || "",
|
||||||
? null
|
mysqlId: id || "",
|
||||||
: data?.registryId,
|
mariadbId: id || "",
|
||||||
|
mongoId: id || "",
|
||||||
|
...(type === "application"
|
||||||
|
? {
|
||||||
|
registryId:
|
||||||
|
data?.registryId === "none" || !data?.registryId
|
||||||
|
? null
|
||||||
|
: data?.registryId,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
replicas: data?.replicas,
|
replicas: data?.replicas,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Command Updated");
|
toast.success("Command Updated");
|
||||||
await utils.application.one.invalidate({
|
await refetch();
|
||||||
applicationId,
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error updating the command");
|
toast.error("Error updating the command");
|
||||||
@@ -103,10 +135,10 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-xl">Cluster Settings</CardTitle>
|
<CardTitle className="text-xl">Cluster Settings</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Add the registry and the replicas of the application
|
Modify swarm settings for the service.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<AddSwarmSettings applicationId={applicationId} />
|
<AddSwarmSettings id={id} type={type} />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info">
|
||||||
@@ -144,58 +176,62 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{registries && registries?.length === 0 ? (
|
{type === "application" && (
|
||||||
<div className="pt-10">
|
|
||||||
<div className="flex flex-col items-center gap-3">
|
|
||||||
<Server className="size-8 text-muted-foreground" />
|
|
||||||
<span className="text-base text-muted-foreground">
|
|
||||||
To use a cluster feature, you need to configure at least a
|
|
||||||
registry first. Please, go to{" "}
|
|
||||||
<Link
|
|
||||||
href="/dashboard/settings/cluster"
|
|
||||||
className="text-foreground"
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</Link>{" "}
|
|
||||||
to do so.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<FormField
|
{registries && registries?.length === 0 ? (
|
||||||
control={form.control}
|
<div className="pt-10">
|
||||||
name="registryId"
|
<div className="flex flex-col items-center gap-3">
|
||||||
render={({ field }) => (
|
<Server className="size-8 text-muted-foreground" />
|
||||||
<FormItem>
|
<span className="text-base text-muted-foreground">
|
||||||
<FormLabel>Select a registry</FormLabel>
|
To use a cluster feature, you need to configure at least
|
||||||
<Select
|
a registry first. Please, go to{" "}
|
||||||
onValueChange={field.onChange}
|
<Link
|
||||||
defaultValue={field.value}
|
href="/dashboard/settings/cluster"
|
||||||
>
|
className="text-foreground"
|
||||||
<SelectTrigger>
|
>
|
||||||
<SelectValue placeholder="Select a registry" />
|
Settings
|
||||||
</SelectTrigger>
|
</Link>{" "}
|
||||||
<SelectContent>
|
to do so.
|
||||||
<SelectGroup>
|
</span>
|
||||||
{registries?.map((registry) => (
|
</div>
|
||||||
<SelectItem
|
</div>
|
||||||
key={registry.registryId}
|
) : (
|
||||||
value={registry.registryId}
|
<>
|
||||||
>
|
<FormField
|
||||||
{registry.registryName}
|
control={form.control}
|
||||||
</SelectItem>
|
name="registryId"
|
||||||
))}
|
render={({ field }) => (
|
||||||
<SelectItem value={"none"}>None</SelectItem>
|
<FormItem>
|
||||||
<SelectLabel>
|
<FormLabel>Select a registry</FormLabel>
|
||||||
Registries ({registries?.length})
|
<Select
|
||||||
</SelectLabel>
|
onValueChange={field.onChange}
|
||||||
</SelectGroup>
|
defaultValue={field.value}
|
||||||
</SelectContent>
|
>
|
||||||
</Select>
|
<SelectTrigger>
|
||||||
</FormItem>
|
<SelectValue placeholder="Select a registry" />
|
||||||
)}
|
</SelectTrigger>
|
||||||
/>
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{registries?.map((registry) => (
|
||||||
|
<SelectItem
|
||||||
|
key={registry.registryId}
|
||||||
|
value={registry.registryId}
|
||||||
|
>
|
||||||
|
{registry.registryName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value={"none"}>None</SelectItem>
|
||||||
|
<SelectLabel>
|
||||||
|
Registries ({registries?.length})
|
||||||
|
</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
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 { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -16,11 +21,7 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
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";
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Code2, Globe2, HardDrive } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -27,12 +33,6 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Code2, Globe2, HardDrive } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const ImportSchema = z.object({
|
const ImportSchema = z.object({
|
||||||
base64: z.string(),
|
base64: z.string(),
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm, useWatch } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -26,12 +32,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const AddPortSchema = z.object({
|
const AddPortSchema = z.object({
|
||||||
publishedPort: z.number().int().min(1).max(65535),
|
publishedPort: z.number().int().min(1).max(65535),
|
||||||
@@ -80,6 +80,11 @@ export const HandlePorts = ({
|
|||||||
resolver: zodResolver(AddPortSchema),
|
resolver: zodResolver(AddPortSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const publishMode = useWatch({
|
||||||
|
control: form.control,
|
||||||
|
name: "publishMode",
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset({
|
form.reset({
|
||||||
publishedPort: data?.publishedPort ?? 0,
|
publishedPort: data?.publishedPort ?? 0,
|
||||||
@@ -253,6 +258,16 @@ export const HandlePorts = ({
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{publishMode === "host" && (
|
||||||
|
<AlertBlock type="warning" className="mt-4">
|
||||||
|
<strong>Host Mode Limitation:</strong> When using Host publish
|
||||||
|
mode, Docker Swarm has limitations that prevent proper container
|
||||||
|
updates during deployments. Old containers may not be replaced
|
||||||
|
automatically. Consider using Ingress mode instead, or be prepared
|
||||||
|
to manually stop/start the application after deployments.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Rss, Trash2 } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -9,9 +11,8 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Rss, Trash2 } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { HandlePorts } from "./handle-ports";
|
import { HandlePorts } from "./handle-ports";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -30,12 +36,6 @@ import {
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const AddRedirectchema = z.object({
|
const AddRedirectchema = z.object({
|
||||||
regex: z.string().min(1, "Regex required"),
|
regex: z.string().min(1, "Regex required"),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Split, Trash2 } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -8,8 +10,6 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Split, Trash2 } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { HandleRedirect } from "./handle-redirect";
|
import { HandleRedirect } from "./handle-redirect";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -19,12 +25,6 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon, PlusIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const AddSecuritychema = z.object({
|
const AddSecuritychema = z.object({
|
||||||
username: z.string().min(1, "Username is required"),
|
username: z.string().min(1, "Username is required"),
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import { LockKeyhole, Trash2 } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -7,12 +10,9 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { LockKeyhole, Trash2 } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { HandleSecurity } from "./handle-security";
|
import { HandleSecurity } from "./handle-security";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { InfoIcon } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -23,12 +29,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { InfoIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const addResourcesSchema = z.object({
|
const addResourcesSchema = z.object({
|
||||||
memoryReservation: z.string().optional(),
|
memoryReservation: z.string().optional(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { File, Loader2 } from "lucide-react";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -7,8 +8,8 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { File, Loader2 } from "lucide-react";
|
|
||||||
import { UpdateTraefikConfig } from "./update-traefik-config";
|
import { UpdateTraefikConfig } from "./update-traefik-config";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
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 { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -19,12 +25,6 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
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";
|
|
||||||
|
|
||||||
const UpdateTraefikConfigSchema = z.object({
|
const UpdateTraefikConfigSchema = z.object({
|
||||||
traefikConfig: z.string(),
|
traefikConfig: z.string(),
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import type React from "react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -22,13 +29,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import type React from "react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
serviceType:
|
serviceType:
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -20,12 +26,6 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const mountSchema = z.object({
|
const mountSchema = z.object({
|
||||||
mountPath: z.string().min(1, "Mount path required"),
|
mountPath: z.string().min(1, "Mount path required"),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Paintbrush } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -11,8 +13,6 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Paintbrush } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -10,8 +12,6 @@ import {
|
|||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { RefreshCcw } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
@@ -7,8 +9,6 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { TerminalLine } from "../../docker/logs/terminal-line";
|
import { TerminalLine } from "../../docker/logs/terminal-line";
|
||||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@/utils/api";
|
import type { RouterOutputs } from "@/utils/api";
|
||||||
import { useState } from "react";
|
|
||||||
import { ShowDeployment } from "../deployments/show-deployment";
|
import { ShowDeployment } from "../deployments/show-deployment";
|
||||||
import { ShowDeployments } from "./show-deployments";
|
import { ShowDeployments } from "./show-deployments";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
@@ -10,10 +13,7 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { type RouterOutputs, api } from "@/utils/api";
|
import { api, type RouterOutputs } from "@/utils/api";
|
||||||
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
|
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
|
||||||
import { CancelQueues } from "./cancel-queues";
|
import { CancelQueues } from "./cancel-queues";
|
||||||
import { RefreshToken } from "./refresh-token";
|
import { RefreshToken } from "./refresh-token";
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Copy, HelpCircle, Server } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -8,8 +10,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Copy, HelpCircle, Server } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
domain: {
|
domain: {
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
import {
|
||||||
|
CheckCircle2,
|
||||||
|
ExternalLink,
|
||||||
|
GlobeIcon,
|
||||||
|
InfoIcon,
|
||||||
|
Loader2,
|
||||||
|
PenBoxIcon,
|
||||||
|
RefreshCw,
|
||||||
|
Server,
|
||||||
|
Trash2,
|
||||||
|
XCircle,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -15,21 +30,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
|
||||||
CheckCircle2,
|
|
||||||
ExternalLink,
|
|
||||||
GlobeIcon,
|
|
||||||
InfoIcon,
|
|
||||||
Loader2,
|
|
||||||
PenBoxIcon,
|
|
||||||
RefreshCw,
|
|
||||||
Server,
|
|
||||||
Trash2,
|
|
||||||
XCircle,
|
|
||||||
} from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { DnsHelperModal } from "./dns-helper-modal";
|
import { DnsHelperModal } from "./dns-helper-modal";
|
||||||
import { AddDomain } from "./handle-domain";
|
import { AddDomain } from "./handle-domain";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||||
|
import { type CSSProperties, useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -16,12 +22,6 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
|
||||||
import { type CSSProperties, useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import type { ServiceType } from "../advanced/show-resources";
|
import type { ServiceType } from "../advanced/show-resources";
|
||||||
|
|
||||||
const addEnvironmentSchema = z.object({
|
const addEnvironmentSchema = z.object({
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Card } from "@/components/ui/card";
|
|
||||||
import { Form } from "@/components/ui/form";
|
|
||||||
import { Secrets } from "@/components/ui/secrets";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { Form } from "@/components/ui/form";
|
||||||
|
import { Secrets } from "@/components/ui/secrets";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
const addEnvironmentSchema = z.object({
|
const addEnvironmentSchema = z.object({
|
||||||
env: z.string(),
|
env: z.string(),
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -40,13 +47,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const BitbucketProviderSchema = z.object({
|
const BitbucketProviderSchema = z.object({
|
||||||
buildPath: z.string().min(1, "Path is required").default("/"),
|
buildPath: z.string().min(1, "Path is required").default("/"),
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
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 { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -9,11 +14,6 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
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({
|
const DockerProviderSchema = z.object({
|
||||||
dockerImage: z.string().min(1, {
|
dockerImage: z.string().min(1, {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
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";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Dropzone } from "@/components/ui/dropzone";
|
import { Dropzone } from "@/components/ui/dropzone";
|
||||||
import {
|
import {
|
||||||
@@ -11,11 +16,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { type UploadFile, uploadFileSchema } from "@/utils/schema";
|
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 {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -25,17 +35,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const GitProviderSchema = z.object({
|
const GitProviderSchema = z.object({
|
||||||
buildPath: z.string().min(1, "Path is required").default("/"),
|
buildPath: z.string().min(1, "Path is required").default("/"),
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -40,13 +47,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
interface GiteaRepository {
|
interface GiteaRepository {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -39,13 +46,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const GithubProviderSchema = z.object({
|
const GithubProviderSchema = z.object({
|
||||||
buildPath: z.string().min(1, "Path is required").default("/"),
|
buildPath: z.string().min(1, "Path is required").default("/"),
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -40,13 +47,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const GitlabProviderSchema = z.object({
|
const GitlabProviderSchema = z.object({
|
||||||
buildPath: z.string().min(1, "Path is required").default("/"),
|
buildPath: z.string().min(1, "Path is required").default("/"),
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import { GitBranch, Loader2, UploadCloud } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { SaveDockerProvider } from "@/components/dashboard/application/general/generic/save-docker-provider";
|
import { SaveDockerProvider } from "@/components/dashboard/application/general/generic/save-docker-provider";
|
||||||
import { SaveGitProvider } from "@/components/dashboard/application/general/generic/save-git-provider";
|
import { SaveGitProvider } from "@/components/dashboard/application/general/generic/save-git-provider";
|
||||||
import { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider";
|
import { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider";
|
||||||
@@ -5,18 +9,14 @@ import { SaveGithubProvider } from "@/components/dashboard/application/general/g
|
|||||||
import {
|
import {
|
||||||
BitbucketIcon,
|
BitbucketIcon,
|
||||||
DockerIcon,
|
DockerIcon,
|
||||||
GitIcon,
|
|
||||||
GiteaIcon,
|
GiteaIcon,
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
|
GitIcon,
|
||||||
GitlabIcon,
|
GitlabIcon,
|
||||||
} from "@/components/icons/data-tools-icons";
|
} from "@/components/icons/data-tools-icons";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { GitBranch, Loader2, UploadCloud } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { SaveBitbucketProvider } from "./save-bitbucket-provider";
|
import { SaveBitbucketProvider } from "./save-bitbucket-provider";
|
||||||
import { SaveDragNDrop } from "./save-drag-n-drop";
|
import { SaveDragNDrop } from "./save-drag-n-drop";
|
||||||
import { SaveGitlabProvider } from "./save-gitlab-provider";
|
import { SaveGitlabProvider } from "./save-gitlab-provider";
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { AlertCircle, GitBranch, Unlink } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
BitbucketIcon,
|
BitbucketIcon,
|
||||||
GitIcon,
|
|
||||||
GiteaIcon,
|
GiteaIcon,
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
|
GitIcon,
|
||||||
GitlabIcon,
|
GitlabIcon,
|
||||||
} from "@/components/icons/data-tools-icons";
|
} from "@/components/icons/data-tools-icons";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
@@ -10,7 +11,6 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import type { RouterOutputs } from "@/utils/api";
|
import type { RouterOutputs } from "@/utils/api";
|
||||||
import { AlertCircle, GitBranch, Unlink } from "lucide-react";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
service:
|
service:
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
import {
|
||||||
|
Ban,
|
||||||
|
CheckCircle2,
|
||||||
|
Hammer,
|
||||||
|
RefreshCcw,
|
||||||
|
Rocket,
|
||||||
|
Terminal,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { ShowBuildChooseForm } from "@/components/dashboard/application/build/show";
|
import { ShowBuildChooseForm } from "@/components/dashboard/application/build/show";
|
||||||
import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show";
|
import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
@@ -11,18 +22,8 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
||||||
import {
|
|
||||||
Ban,
|
|
||||||
CheckCircle2,
|
|
||||||
Hammer,
|
|
||||||
RefreshCcw,
|
|
||||||
Rocket,
|
|
||||||
Terminal,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
isLoading={data?.applicationStatus === "running"}
|
// isLoading={data?.applicationStatus === "running"}
|
||||||
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
|
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||||
>
|
>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -18,9 +21,6 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
export const DockerLogs = dynamic(
|
export const DockerLogs = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Dices } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import type z from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -33,15 +39,8 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import { domain } from "@/server/db/validations/domain";
|
import { domain } from "@/server/db/validations/domain";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { api } from "@/utils/api";
|
||||||
import { Dices } from "lucide-react";
|
|
||||||
import type z from "zod";
|
|
||||||
|
|
||||||
type Domain = z.infer<typeof domain>;
|
type Domain = z.infer<typeof domain>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
import {
|
||||||
|
ExternalLink,
|
||||||
|
FileText,
|
||||||
|
GitPullRequest,
|
||||||
|
Loader2,
|
||||||
|
PenSquare,
|
||||||
|
RocketIcon,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
@@ -13,16 +23,6 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
|
||||||
ExternalLink,
|
|
||||||
FileText,
|
|
||||||
GitPullRequest,
|
|
||||||
Loader2,
|
|
||||||
PenSquare,
|
|
||||||
RocketIcon,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
|
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
|
||||||
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
||||||
import { AddPreviewDomain } from "./add-preview-domain";
|
import { AddPreviewDomain } from "./add-preview-domain";
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { HelpCircle, Plus, Settings2, X } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -27,13 +34,13 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Settings2 } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const schema = z
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -42,6 +49,7 @@ const schema = z
|
|||||||
wildcardDomain: z.string(),
|
wildcardDomain: z.string(),
|
||||||
port: z.number(),
|
port: z.number(),
|
||||||
previewLimit: z.number(),
|
previewLimit: z.number(),
|
||||||
|
previewLabels: z.array(z.string()).optional(),
|
||||||
previewHttps: z.boolean(),
|
previewHttps: z.boolean(),
|
||||||
previewPath: z.string(),
|
previewPath: z.string(),
|
||||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
|
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
|
||||||
@@ -81,6 +89,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
wildcardDomain: "*.traefik.me",
|
wildcardDomain: "*.traefik.me",
|
||||||
port: 3000,
|
port: 3000,
|
||||||
previewLimit: 3,
|
previewLimit: 3,
|
||||||
|
previewLabels: [],
|
||||||
previewHttps: false,
|
previewHttps: false,
|
||||||
previewPath: "/",
|
previewPath: "/",
|
||||||
previewCertificateType: "none",
|
previewCertificateType: "none",
|
||||||
@@ -102,6 +111,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
buildArgs: data.previewBuildArgs || "",
|
buildArgs: data.previewBuildArgs || "",
|
||||||
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
||||||
port: data.previewPort || 3000,
|
port: data.previewPort || 3000,
|
||||||
|
previewLabels: data.previewLabels || [],
|
||||||
previewLimit: data.previewLimit || 3,
|
previewLimit: data.previewLimit || 3,
|
||||||
previewHttps: data.previewHttps || false,
|
previewHttps: data.previewHttps || false,
|
||||||
previewPath: data.previewPath || "/",
|
previewPath: data.previewPath || "/",
|
||||||
@@ -119,6 +129,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
previewBuildArgs: formData.buildArgs,
|
previewBuildArgs: formData.buildArgs,
|
||||||
previewWildcard: formData.wildcardDomain,
|
previewWildcard: formData.wildcardDomain,
|
||||||
previewPort: formData.port,
|
previewPort: formData.port,
|
||||||
|
previewLabels: formData.previewLabels,
|
||||||
applicationId,
|
applicationId,
|
||||||
previewLimit: formData.previewLimit,
|
previewLimit: formData.previewLimit,
|
||||||
previewHttps: formData.previewHttps,
|
previewHttps: formData.previewHttps,
|
||||||
@@ -200,6 +211,90 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="previewLabels"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="md:col-span-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FormLabel>Preview Labels</FormLabel>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
Add a labels that will trigger a preview
|
||||||
|
deployment for a pull request. If no labels
|
||||||
|
are specified, all pull requests will trigger
|
||||||
|
a preview deployment.
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2 mb-2">
|
||||||
|
{field.value?.map((label, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
variant="secondary"
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
<X
|
||||||
|
className="size-3 cursor-pointer hover:text-destructive"
|
||||||
|
onClick={() => {
|
||||||
|
const newLabels = [...(field.value || [])];
|
||||||
|
newLabels.splice(index, 1);
|
||||||
|
field.onChange(newLabels);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Enter a label (e.g. enhancements, needs-review)"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
const input = e.currentTarget;
|
||||||
|
const label = input.value.trim();
|
||||||
|
if (label) {
|
||||||
|
field.onChange([
|
||||||
|
...(field.value || []),
|
||||||
|
label,
|
||||||
|
]);
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => {
|
||||||
|
const input = document.querySelector(
|
||||||
|
'input[placeholder*="Enter a label"]',
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const label = input.value.trim();
|
||||||
|
if (label) {
|
||||||
|
field.onChange([...(field.value || []), label]);
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="previewLimit"
|
name="previewLimit"
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -18,11 +23,6 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
rollbackActive: z.boolean(),
|
rollbackActive: z.boolean(),
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
DatabaseZap,
|
||||||
|
Info,
|
||||||
|
PenBoxIcon,
|
||||||
|
PlusCircle,
|
||||||
|
RefreshCw,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -35,18 +47,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
DatabaseZap,
|
|
||||||
Info,
|
|
||||||
PenBoxIcon,
|
|
||||||
PlusCircle,
|
|
||||||
RefreshCw,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import type { CacheType } from "../domains/handle-domain";
|
import type { CacheType } from "../domains/handle-domain";
|
||||||
|
|
||||||
export const commonCronExpressions = [
|
export const commonCronExpressions = [
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
import {
|
||||||
|
ClipboardList,
|
||||||
|
Clock,
|
||||||
|
Loader2,
|
||||||
|
Play,
|
||||||
|
Terminal,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -15,15 +24,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
|
||||||
ClipboardList,
|
|
||||||
Clock,
|
|
||||||
Loader2,
|
|
||||||
Play,
|
|
||||||
Terminal,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
||||||
import { HandleSchedules } from "./handle-schedules";
|
import { HandleSchedules } from "./handle-schedules";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -20,12 +26,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const updateApplicationSchema = z.object({
|
const updateApplicationSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
DatabaseZap,
|
||||||
|
Info,
|
||||||
|
PenBoxIcon,
|
||||||
|
PlusCircle,
|
||||||
|
RefreshCw,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -34,18 +46,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
DatabaseZap,
|
|
||||||
Info,
|
|
||||||
PenBoxIcon,
|
|
||||||
PlusCircle,
|
|
||||||
RefreshCw,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import type { CacheType } from "../domains/handle-domain";
|
import type { CacheType } from "../domains/handle-domain";
|
||||||
import { commonCronExpressions } from "../schedules/handle-schedules";
|
import { commonCronExpressions } from "../schedules/handle-schedules";
|
||||||
|
|
||||||
@@ -55,7 +55,12 @@ const formSchema = z
|
|||||||
cronExpression: z.string().min(1, "Cron expression is required"),
|
cronExpression: z.string().min(1, "Cron expression is required"),
|
||||||
volumeName: z.string().min(1, "Volume name is required"),
|
volumeName: z.string().min(1, "Volume name is required"),
|
||||||
prefix: z.string(),
|
prefix: z.string(),
|
||||||
// keepLatestCount: z.coerce.number().optional(),
|
keepLatestCount: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.gte(1, "Must be at least 1")
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
turnOff: z.boolean().default(false),
|
turnOff: z.boolean().default(false),
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true),
|
||||||
serviceType: z.enum([
|
serviceType: z.enum([
|
||||||
@@ -108,6 +113,7 @@ export const HandleVolumeBackups = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
||||||
|
const [keepLatestCountInput, setKeepLatestCountInput] = useState("");
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
@@ -117,7 +123,7 @@ export const HandleVolumeBackups = ({
|
|||||||
cronExpression: "",
|
cronExpression: "",
|
||||||
volumeName: "",
|
volumeName: "",
|
||||||
prefix: "",
|
prefix: "",
|
||||||
// keepLatestCount: undefined,
|
keepLatestCount: undefined,
|
||||||
turnOff: false,
|
turnOff: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
serviceName: "",
|
serviceName: "",
|
||||||
@@ -173,13 +179,19 @@ export const HandleVolumeBackups = ({
|
|||||||
cronExpression: volumeBackup.cronExpression,
|
cronExpression: volumeBackup.cronExpression,
|
||||||
volumeName: volumeBackup.volumeName || "",
|
volumeName: volumeBackup.volumeName || "",
|
||||||
prefix: volumeBackup.prefix,
|
prefix: volumeBackup.prefix,
|
||||||
// keepLatestCount: volumeBackup.keepLatestCount || undefined,
|
keepLatestCount: volumeBackup.keepLatestCount || undefined,
|
||||||
turnOff: volumeBackup.turnOff,
|
turnOff: volumeBackup.turnOff,
|
||||||
enabled: volumeBackup.enabled || false,
|
enabled: volumeBackup.enabled || false,
|
||||||
serviceName: volumeBackup.serviceName || "",
|
serviceName: volumeBackup.serviceName || "",
|
||||||
destinationId: volumeBackup.destinationId,
|
destinationId: volumeBackup.destinationId,
|
||||||
serviceType: volumeBackup.serviceType,
|
serviceType: volumeBackup.serviceType,
|
||||||
});
|
});
|
||||||
|
setKeepLatestCountInput(
|
||||||
|
volumeBackup.keepLatestCount !== null &&
|
||||||
|
volumeBackup.keepLatestCount !== undefined
|
||||||
|
? String(volumeBackup.keepLatestCount)
|
||||||
|
: "",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [form, volumeBackup, volumeBackupId]);
|
}, [form, volumeBackup, volumeBackupId]);
|
||||||
|
|
||||||
@@ -190,8 +202,12 @@ export const HandleVolumeBackups = ({
|
|||||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||||
if (!id && !volumeBackupId) return;
|
if (!id && !volumeBackupId) return;
|
||||||
|
|
||||||
|
const preparedKeepLatestCount =
|
||||||
|
keepLatestCountInput === "" ? null : (values.keepLatestCount ?? null);
|
||||||
|
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
...values,
|
...values,
|
||||||
|
keepLatestCount: preparedKeepLatestCount,
|
||||||
destinationId: values.destinationId,
|
destinationId: values.destinationId,
|
||||||
volumeBackupId: volumeBackupId || "",
|
volumeBackupId: volumeBackupId || "",
|
||||||
serviceType: volumeBackupType,
|
serviceType: volumeBackupType,
|
||||||
@@ -257,9 +273,8 @@ export const HandleVolumeBackups = ({
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"overflow-y-auto",
|
|
||||||
volumeBackupType === "compose" || volumeBackupType === "application"
|
volumeBackupType === "compose" || volumeBackupType === "application"
|
||||||
? "max-h-[95vh] sm:max-w-2xl"
|
? "sm:max-w-2xl"
|
||||||
: " sm:max-w-lg",
|
: " sm:max-w-lg",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -600,29 +615,38 @@ export const HandleVolumeBackups = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="keepLatestCount"
|
name="keepLatestCount"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Keep Latest Count</FormLabel>
|
<FormLabel>Keep Latest Backups</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
|
||||||
placeholder="5"
|
|
||||||
{...field}
|
{...field}
|
||||||
onChange={(e) =>
|
type="number"
|
||||||
field.onChange(Number(e.target.value) || undefined)
|
min={1}
|
||||||
}
|
autoComplete="off"
|
||||||
|
placeholder="Leave empty to keep all"
|
||||||
|
value={keepLatestCountInput}
|
||||||
|
onChange={(e) => {
|
||||||
|
const raw = e.target.value;
|
||||||
|
setKeepLatestCountInput(raw);
|
||||||
|
if (raw === "") {
|
||||||
|
field.onChange(undefined);
|
||||||
|
} else if (/^\d+$/.test(raw)) {
|
||||||
|
field.onChange(Number(raw));
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Number of backup files to keep (optional)
|
How many recent backups to keep. Empty means no cleanup.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/> */}
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -35,14 +43,6 @@ import {
|
|||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import copy from "copy-to-clipboard";
|
|
||||||
import { debounce } from "lodash";
|
|
||||||
import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { formatBytes } from "../../database/backups/restore-backup";
|
import { formatBytes } from "../../database/backups/restore-backup";
|
||||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
import {
|
||||||
|
ClipboardList,
|
||||||
|
DatabaseBackup,
|
||||||
|
Loader2,
|
||||||
|
Play,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -15,14 +23,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
|
||||||
ClipboardList,
|
|
||||||
DatabaseBackup,
|
|
||||||
Loader2,
|
|
||||||
Play,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
||||||
import { HandleVolumeBackups } from "./handle-volume-backups";
|
import { HandleVolumeBackups } from "./handle-volume-backups";
|
||||||
import { RestoreVolumeBackups } from "./restore-volume-backups";
|
import { RestoreVolumeBackups } from "./restore-volume-backups";
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
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 { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -18,11 +23,7 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
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";
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { AlertTriangle, Loader2 } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
composeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema for Isolated Deployment
|
||||||
|
const isolatedSchema = z.object({
|
||||||
|
isolatedDeployment: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type IsolatedSchema = z.infer<typeof isolatedSchema>;
|
||||||
|
|
||||||
|
export const IsolatedDeploymentTab = ({ composeId }: Props) => {
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const [compose, setCompose] = useState<string>("");
|
||||||
|
const [isPreviewLoading, setIsPreviewLoading] = useState<boolean>(false);
|
||||||
|
const { mutateAsync, error, isError } =
|
||||||
|
api.compose.isolatedDeployment.useMutation();
|
||||||
|
|
||||||
|
const [isOpenPreview, setIsOpenPreview] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
|
||||||
|
|
||||||
|
const { data, refetch } = api.compose.one.useQuery(
|
||||||
|
{ composeId },
|
||||||
|
{ enabled: !!composeId },
|
||||||
|
);
|
||||||
|
|
||||||
|
const form = useForm<IsolatedSchema>({
|
||||||
|
defaultValues: {
|
||||||
|
isolatedDeployment: false,
|
||||||
|
},
|
||||||
|
resolver: zodResolver(isolatedSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
form.reset({
|
||||||
|
isolatedDeployment: data?.isolatedDeployment || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||||
|
|
||||||
|
const onSubmit = async (formData: IsolatedSchema) => {
|
||||||
|
await updateCompose({
|
||||||
|
composeId,
|
||||||
|
isolatedDeployment: formData?.isolatedDeployment || false,
|
||||||
|
})
|
||||||
|
.then(async (_data) => {
|
||||||
|
await refetch();
|
||||||
|
toast.success("Compose updated");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error updating the compose");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatePreview = async () => {
|
||||||
|
setIsOpenPreview(true);
|
||||||
|
setIsPreviewLoading(true);
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
composeId,
|
||||||
|
suffix: data?.appName || "",
|
||||||
|
}).then(async (data) => {
|
||||||
|
await utils.project.all.invalidate();
|
||||||
|
setCompose(data);
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
toast.error("Error generating preview");
|
||||||
|
setIsOpenPreview(false);
|
||||||
|
} finally {
|
||||||
|
setIsPreviewLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-background">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl">Enable Isolated Deployment</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure isolated deployment to the compose file.
|
||||||
|
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
||||||
|
<span>
|
||||||
|
This feature creates an isolated environment for your deployment
|
||||||
|
by adding unique prefixes to all resources. It establishes a
|
||||||
|
dedicated network based on your compose file's name, ensuring your
|
||||||
|
services run in isolation. This prevents conflicts when running
|
||||||
|
multiple instances of the same template or services with identical
|
||||||
|
names.
|
||||||
|
</span>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium mb-2">
|
||||||
|
Resources that will be isolated:
|
||||||
|
</h4>
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
<li>Docker networks</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
id="isolated-deployment-form"
|
||||||
|
className="grid w-full gap-4"
|
||||||
|
>
|
||||||
|
{isError && (
|
||||||
|
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
|
||||||
|
<AlertTriangle className="text-red-600 dark:text-red-400" />
|
||||||
|
<span className="text-sm text-red-600 dark:text-red-400">
|
||||||
|
{error?.message}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col lg:flex-col gap-4 w-full">
|
||||||
|
<div>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="isolatedDeployment"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>
|
||||||
|
Enable Isolated Deployment ({data?.appName})
|
||||||
|
</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Enable isolated deployment to the compose file.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
||||||
|
<Button
|
||||||
|
form="isolated-deployment-form"
|
||||||
|
type="submit"
|
||||||
|
className="lg:w-fit"
|
||||||
|
isLoading={form.formState.isSubmitting}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={generatePreview}
|
||||||
|
isLoading={isPreviewLoading}
|
||||||
|
variant="secondary"
|
||||||
|
className="lg:w-fit"
|
||||||
|
>
|
||||||
|
Preview Compose
|
||||||
|
</Button>
|
||||||
|
<Dialog open={isOpenPreview} onOpenChange={setIsOpenPreview}>
|
||||||
|
<DialogContent className="sm:max-w-6xl max-h-[80vh]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Isolated Deployment Preview</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Preview of the compose file with isolated deployment
|
||||||
|
configuration
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col gap-4 overflow-auto">
|
||||||
|
{isPreviewLoading ? (
|
||||||
|
<div className="flex flex-col items-center justify-center py-12 gap-4">
|
||||||
|
<Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Generating compose preview...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<pre>
|
||||||
|
<CodeEditor
|
||||||
|
value={compose || ""}
|
||||||
|
language="yaml"
|
||||||
|
readOnly
|
||||||
|
height="60vh"
|
||||||
|
/>
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
import type { ServiceType } from "@dokploy/server/db/schema";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import { Copy, Trash2 } from "lucide-react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
@@ -20,15 +30,6 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import type { ServiceType } from "@dokploy/server/db/schema";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import copy from "copy-to-clipboard";
|
|
||||||
import { Copy, Trash2 } from "lucide-react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const deleteComposeSchema = z.object({
|
const deleteComposeSchema = z.object({
|
||||||
projectName: z.string().min(1, {
|
projectName: z.string().min(1, {
|
||||||
@@ -114,6 +115,12 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isDisabled =
|
||||||
|
(data &&
|
||||||
|
"applicationStatus" in data &&
|
||||||
|
data?.applicationStatus === "running") ||
|
||||||
|
(data && "composeStatus" in data && data?.composeStatus === "running");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@@ -202,6 +209,12 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
{isDisabled && (
|
||||||
|
<AlertBlock type="warning" className="w-full mt-5">
|
||||||
|
Cannot delete the service while it is running. Please wait for the
|
||||||
|
build to finish and then try again.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -211,8 +224,10 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
disabled={isDisabled}
|
||||||
form="hook-form-delete-compose"
|
form="hook-form-delete-compose"
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
@@ -8,10 +12,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
||||||
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
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 { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -8,13 +13,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { api } from "@/utils/api";
|
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 { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
|
||||||
import { ShowUtilities } from "./show-utilities";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
@@ -142,9 +141,7 @@ services:
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
<div className="flex justify-between flex-col lg:flex-row gap-2">
|
<div className="flex justify-between flex-col lg:flex-row gap-2">
|
||||||
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
|
<div className="w-full flex flex-col lg:flex-row gap-4 items-end" />
|
||||||
<ShowUtilities composeId={composeId} />
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
form="hook-form-save-compose-file"
|
form="hook-form-save-compose-file"
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -40,13 +47,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const BitbucketProviderSchema = z.object({
|
const BitbucketProviderSchema = z.object({
|
||||||
composePath: z.string().min(1),
|
composePath: z.string().min(1),
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -27,14 +35,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const GitProviderSchema = z.object({
|
const GitProviderSchema = z.object({
|
||||||
composePath: z.string().min(1),
|
composePath: z.string().min(1),
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, Plus, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -41,13 +48,6 @@ import {
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import type { Repository } from "@/utils/gitea-utils";
|
import type { Repository } from "@/utils/gitea-utils";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, Plus, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const GiteaProviderSchema = z.object({
|
const GiteaProviderSchema = z.object({
|
||||||
composePath: z.string().min(1),
|
composePath: z.string().min(1),
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -39,13 +46,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const GithubProviderSchema = z.object({
|
const GithubProviderSchema = z.object({
|
||||||
composePath: z.string().min(1),
|
composePath: z.string().min(1),
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -40,13 +47,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const GitlabProviderSchema = z.object({
|
const GitlabProviderSchema = z.object({
|
||||||
composePath: z.string().min(1),
|
composePath: z.string().min(1),
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
|
import { CodeIcon, GitBranch, Loader2 } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
|
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
|
||||||
import {
|
import {
|
||||||
BitbucketIcon,
|
BitbucketIcon,
|
||||||
GitIcon,
|
|
||||||
GiteaIcon,
|
GiteaIcon,
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
|
GitIcon,
|
||||||
GitlabIcon,
|
GitlabIcon,
|
||||||
} from "@/components/icons/data-tools-icons";
|
} from "@/components/icons/data-tools-icons";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { CodeIcon, GitBranch, Loader2 } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { ComposeFileEditor } from "../compose-file-editor";
|
import { ComposeFileEditor } from "../compose-file-editor";
|
||||||
import { ShowConvertedCompose } from "../show-converted-compose";
|
import { ShowConvertedCompose } from "../show-converted-compose";
|
||||||
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
|
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { AlertTriangle } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
composeId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
isolatedDeployment: z.boolean().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
type Schema = z.infer<typeof schema>;
|
|
||||||
|
|
||||||
export const IsolatedDeployment = ({ composeId }: Props) => {
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const [compose, setCompose] = useState<string>("");
|
|
||||||
const { mutateAsync, error, isError } =
|
|
||||||
api.compose.isolatedDeployment.useMutation();
|
|
||||||
|
|
||||||
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
|
|
||||||
|
|
||||||
const { data, refetch } = api.compose.one.useQuery(
|
|
||||||
{ composeId },
|
|
||||||
{ enabled: !!composeId },
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
const form = useForm<Schema>({
|
|
||||||
defaultValues: {
|
|
||||||
isolatedDeployment: false,
|
|
||||||
},
|
|
||||||
resolver: zodResolver(schema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
randomizeCompose();
|
|
||||||
if (data) {
|
|
||||||
form.reset({
|
|
||||||
isolatedDeployment: data?.isolatedDeployment || false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
|
||||||
|
|
||||||
const onSubmit = async (formData: Schema) => {
|
|
||||||
await updateCompose({
|
|
||||||
composeId,
|
|
||||||
isolatedDeployment: formData?.isolatedDeployment || false,
|
|
||||||
})
|
|
||||||
.then(async (_data) => {
|
|
||||||
await randomizeCompose();
|
|
||||||
await refetch();
|
|
||||||
toast.success("Compose updated");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error updating the compose");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomizeCompose = async () => {
|
|
||||||
await mutateAsync({
|
|
||||||
composeId,
|
|
||||||
suffix: data?.appName || "",
|
|
||||||
}).then(async (data) => {
|
|
||||||
await utils.project.all.invalidate();
|
|
||||||
setCompose(data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Isolate Deployment</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Use this option to isolate the deployment of this compose file.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
|
||||||
<span>
|
|
||||||
This feature creates an isolated environment for your deployment by
|
|
||||||
adding unique prefixes to all resources. It establishes a dedicated
|
|
||||||
network based on your compose file's name, ensuring your services run
|
|
||||||
in isolation. This prevents conflicts when running multiple instances
|
|
||||||
of the same template or services with identical names.
|
|
||||||
</span>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium mb-2">
|
|
||||||
Resources that will be isolated:
|
|
||||||
</h4>
|
|
||||||
<ul className="list-disc list-inside">
|
|
||||||
<li>Docker volumes</li>
|
|
||||||
<li>Docker networks</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
id="hook-form-add-project"
|
|
||||||
className="grid w-full gap-4"
|
|
||||||
>
|
|
||||||
{isError && (
|
|
||||||
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
|
|
||||||
<AlertTriangle className="text-red-600 dark:text-red-400" />
|
|
||||||
<span className="text-sm text-red-600 dark:text-red-400">
|
|
||||||
{error?.message}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-col gap-4 w-full ">
|
|
||||||
<div>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="isolatedDeployment"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>
|
|
||||||
Enable Isolated Deployment ({data?.appName})
|
|
||||||
</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Enable isolated deployment to the compose file.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
|
||||||
<Button
|
|
||||||
form="hook-form-add-project"
|
|
||||||
type="submit"
|
|
||||||
className="lg:w-fit"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<Label>Preview</Label>
|
|
||||||
<pre>
|
|
||||||
<CodeEditor
|
|
||||||
value={compose || ""}
|
|
||||||
language="yaml"
|
|
||||||
readOnly
|
|
||||||
height="50rem"
|
|
||||||
/>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { AlertTriangle } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -18,12 +24,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { AlertTriangle } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Loader2, Puzzle, RefreshCw } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -10,9 +13,6 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Loader2, Puzzle, RefreshCw } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
@@ -62,7 +62,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info" className="mb-4">
|
||||||
Preview your docker-compose file with added domains. Note: At least
|
Preview your docker-compose file with added domains. Note: At least
|
||||||
one domain must be specified for this conversion to take effect.
|
one domain must be specified for this conversion to take effect.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
@@ -79,7 +79,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row gap-2 justify-end">
|
<div className="flex flex-row gap-2 justify-end my-4">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { IsolatedDeployment } from "./isolated-deployment";
|
|
||||||
import { RandomizeCompose } from "./randomize-compose";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
composeId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ShowUtilities = ({ composeId }: Props) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
return (
|
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="ghost">Show Utilities</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-5xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Utilities </DialogTitle>
|
|
||||||
<DialogDescription>Modify the application data</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<Tabs defaultValue="isolated">
|
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
|
||||||
<TabsTrigger value="isolated">Isolated Deployment</TabsTrigger>
|
|
||||||
<TabsTrigger value="randomize">Randomize Compose</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="randomize" className="pt-5">
|
|
||||||
<RandomizeCompose composeId={composeId} />
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="isolated" className="pt-5">
|
|
||||||
<IsolatedDeployment composeId={composeId} />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { ComposeActions } from "./actions";
|
import { ComposeActions } from "./actions";
|
||||||
import { ShowProviderFormCompose } from "./generic/show";
|
import { ShowProviderFormCompose } from "./generic/show";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
|
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
@@ -19,9 +22,6 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
export const DockerLogs = dynamic(
|
export const DockerLogs = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
|
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
@@ -18,9 +21,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
export const DockerLogs = dynamic(
|
export const DockerLogs = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -20,12 +26,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const updateComposeSchema = z.object({
|
const updateComposeSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronsUpDown,
|
||||||
|
DatabaseZap,
|
||||||
|
Info,
|
||||||
|
PenBoxIcon,
|
||||||
|
PlusIcon,
|
||||||
|
RefreshCw,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -48,19 +62,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
DatabaseZap,
|
|
||||||
Info,
|
|
||||||
PenBoxIcon,
|
|
||||||
PlusIcon,
|
|
||||||
RefreshCw,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { commonCronExpressions } from "../../application/schedules/handle-schedules";
|
import { commonCronExpressions } from "../../application/schedules/handle-schedules";
|
||||||
|
|
||||||
type CacheType = "cache" | "fetch";
|
type CacheType = "cache" | "fetch";
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronsUpDown,
|
||||||
|
Copy,
|
||||||
|
DatabaseZap,
|
||||||
|
RefreshCw,
|
||||||
|
RotateCcw,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -47,21 +62,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import copy from "copy-to-clipboard";
|
|
||||||
import { debounce } from "lodash";
|
|
||||||
import {
|
|
||||||
CheckIcon,
|
|
||||||
ChevronsUpDown,
|
|
||||||
Copy,
|
|
||||||
DatabaseZap,
|
|
||||||
RefreshCw,
|
|
||||||
RotateCcw,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import type { ServiceType } from "../../application/advanced/show-resources";
|
import type { ServiceType } from "../../application/advanced/show-resources";
|
||||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
import {
|
||||||
|
ClipboardList,
|
||||||
|
Database,
|
||||||
|
DatabaseBackup,
|
||||||
|
Play,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
MariadbIcon,
|
MariadbIcon,
|
||||||
MongodbIcon,
|
MongodbIcon,
|
||||||
@@ -22,16 +32,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
|
||||||
ClipboardList,
|
|
||||||
Database,
|
|
||||||
DatabaseBackup,
|
|
||||||
Play,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import type { ServiceType } from "../../application/advanced/show-resources";
|
import type { ServiceType } from "../../application/advanced/show-resources";
|
||||||
import { ShowDeploymentsModal } from "../../application/deployments/show-deployments-modal";
|
import { ShowDeploymentsModal } from "../../application/deployments/show-deployments-modal";
|
||||||
import { HandleBackup } from "./handle-backup";
|
import { HandleBackup } from "./handle-backup";
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
import { Download as DownloadIcon, Loader2, Pause, Play } from "lucide-react";
|
||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Download as DownloadIcon, Loader2 } from "lucide-react";
|
|
||||||
import React, { useEffect, useRef } from "react";
|
|
||||||
import { LineCountFilter } from "./line-count-filter";
|
import { LineCountFilter } from "./line-count-filter";
|
||||||
import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter";
|
import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter";
|
||||||
import { StatusLogsFilter } from "./status-logs-filter";
|
import { StatusLogsFilter } from "./status-logs-filter";
|
||||||
import { TerminalLine } from "./terminal-line";
|
import { TerminalLine } from "./terminal-line";
|
||||||
import { type LogLine, getLogType, parseLogs } from "./utils";
|
import { getLogType, type LogLine, parseLogs } from "./utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
containerId: string;
|
containerId: string;
|
||||||
@@ -61,6 +62,9 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
const [showTimestamp, setShowTimestamp] = React.useState(true);
|
const [showTimestamp, setShowTimestamp] = React.useState(true);
|
||||||
const [since, setSince] = React.useState<TimeFilter>("all");
|
const [since, setSince] = React.useState<TimeFilter>("all");
|
||||||
const [typeFilter, setTypeFilter] = React.useState<string[]>([]);
|
const [typeFilter, setTypeFilter] = React.useState<string[]>([]);
|
||||||
|
const [isPaused, setIsPaused] = React.useState(false);
|
||||||
|
const [messageBuffer, setMessageBuffer] = React.useState<string[]>([]);
|
||||||
|
const isPausedRef = useRef(false);
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
|
||||||
@@ -85,15 +89,38 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
const handleLines = (lines: number) => {
|
const handleLines = (lines: number) => {
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
setLines(lines);
|
setLines(lines);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSince = (value: TimeFilter) => {
|
const handleSince = (value: TimeFilter) => {
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
setSince(value);
|
setSince(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePauseResume = () => {
|
||||||
|
if (isPaused) {
|
||||||
|
// Resume: Apply all buffered messages
|
||||||
|
if (messageBuffer.length > 0) {
|
||||||
|
const bufferedContent = messageBuffer.join("");
|
||||||
|
setRawLogs((prev) => {
|
||||||
|
const updated = prev + bufferedContent;
|
||||||
|
const splitLines = updated.split("\n");
|
||||||
|
if (splitLines.length > lines) {
|
||||||
|
return splitLines.slice(-lines).join("\n");
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
setMessageBuffer([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newPausedState = !isPaused;
|
||||||
|
setIsPaused(newPausedState);
|
||||||
|
isPausedRef.current = newPausedState;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerId) return;
|
if (!containerId) return;
|
||||||
|
|
||||||
@@ -102,6 +129,10 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
|
// Reset pause state when container changes
|
||||||
|
setIsPaused(false);
|
||||||
|
isPausedRef.current = false;
|
||||||
|
|
||||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
const params = new globalThis.URLSearchParams({
|
const params = new globalThis.URLSearchParams({
|
||||||
@@ -140,14 +171,22 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
|
|
||||||
ws.onmessage = (e) => {
|
ws.onmessage = (e) => {
|
||||||
if (!isCurrentConnection) return;
|
if (!isCurrentConnection) return;
|
||||||
setRawLogs((prev) => {
|
|
||||||
const updated = prev + e.data;
|
if (isPausedRef.current) {
|
||||||
const splitLines = updated.split("\n");
|
// When paused, buffer the messages instead of displaying them
|
||||||
if (splitLines.length > lines) {
|
setMessageBuffer((prev) => [...prev, e.data]);
|
||||||
return splitLines.slice(-lines).join("\n");
|
} else {
|
||||||
}
|
// When not paused, display messages normally
|
||||||
return updated;
|
setRawLogs((prev) => {
|
||||||
});
|
const updated = prev + e.data;
|
||||||
|
const splitLines = updated.split("\n");
|
||||||
|
if (splitLines.length > lines) {
|
||||||
|
return splitLines.slice(-lines).join("\n");
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
if (noDataTimeout) clearTimeout(noDataTimeout);
|
if (noDataTimeout) clearTimeout(noDataTimeout);
|
||||||
};
|
};
|
||||||
@@ -210,9 +249,15 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sync isPausedRef with isPaused state
|
||||||
|
useEffect(() => {
|
||||||
|
isPausedRef.current = isPaused;
|
||||||
|
}, [isPaused]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRawLogs("");
|
setRawLogs("");
|
||||||
setFilteredLogs([]);
|
setFilteredLogs([]);
|
||||||
|
setMessageBuffer([]);
|
||||||
}, [containerId]);
|
}, [containerId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -260,17 +305,48 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<div className="flex gap-2">
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
className="h-9 sm:w-auto w-full"
|
size="sm"
|
||||||
onClick={handleDownload}
|
className="h-9"
|
||||||
disabled={filteredLogs.length === 0 || !data?.Name}
|
onClick={handlePauseResume}
|
||||||
>
|
title={isPaused ? "Resume logs" : "Pause logs"}
|
||||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
>
|
||||||
Download logs
|
{isPaused ? (
|
||||||
</Button>
|
<Play className="mr-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Pause className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
{isPaused ? "Resume" : "Pause"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-9 sm:w-auto w-full"
|
||||||
|
onClick={handleDownload}
|
||||||
|
disabled={filteredLogs.length === 0 || !data?.Name}
|
||||||
|
>
|
||||||
|
<DownloadIcon className="mr-2 h-4 w-4" />
|
||||||
|
Download logs
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isPaused && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Pause className="h-4 w-4" />
|
||||||
|
<span>
|
||||||
|
Logs paused
|
||||||
|
{messageBuffer.length > 0 && (
|
||||||
|
<span className="ml-1 font-medium">
|
||||||
|
({messageBuffer.length} messages buffered)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import { CheckIcon, Hash } from "lucide-react";
|
||||||
|
import React, { useCallback, useRef } from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -7,10 +11,6 @@ import {
|
|||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Command as CommandPrimitive } from "cmdk";
|
|
||||||
import { debounce } from "lodash";
|
|
||||||
import { CheckIcon, Hash } from "lucide-react";
|
|
||||||
import React, { useCallback, useRef } from "react";
|
|
||||||
|
|
||||||
const lineCountOptions = [
|
const lineCountOptions = [
|
||||||
{ label: "100 lines", value: 100 },
|
{ label: "100 lines", value: 100 },
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import type React from "react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -7,8 +9,6 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import type React from "react";
|
|
||||||
export const DockerLogsId = dynamic(
|
export const DockerLogsId = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import type React from "react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -7,8 +9,6 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import type React from "react";
|
|
||||||
export const DockerLogsId = dynamic(
|
export const DockerLogsId = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CheckIcon } from "lucide-react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CheckIcon } from "lucide-react";
|
|
||||||
|
|
||||||
export type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h";
|
export type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { CheckIcon } from "lucide-react";
|
||||||
|
import type React from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -13,8 +15,6 @@ import {
|
|||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CheckIcon } from "lucide-react";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
interface StatusLogsFilterProps {
|
interface StatusLogsFilterProps {
|
||||||
value?: string[];
|
value?: string[];
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { FancyAnsi } from "fancy-ansi";
|
||||||
|
import { escapeRegExp } from "lodash";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -7,9 +9,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { FancyAnsi } from "fancy-ansi";
|
import { getLogType, type LogLine } from "./utils";
|
||||||
import { escapeRegExp } from "lodash";
|
|
||||||
import { type LogLine, getLogType } from "./utils";
|
|
||||||
|
|
||||||
interface LogLineProps {
|
interface LogLineProps {
|
||||||
log: LogLine;
|
log: LogLine;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ColumnDef } from "@tanstack/react-table";
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -8,8 +8,6 @@ import {
|
|||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { ShowContainerConfig } from "../config/show-container-config";
|
import { ShowContainerConfig } from "../config/show-container-config";
|
||||||
import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs";
|
import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs";
|
||||||
import { DockerTerminalModal } from "../terminal/docker-terminal-modal";
|
import { DockerTerminalModal } from "../terminal/docker-terminal-modal";
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
import {
|
||||||
|
type ColumnFiltersState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
type SortingState,
|
||||||
|
useReactTable,
|
||||||
|
type VisibilityState,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import { ChevronDown, Container } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -21,20 +34,7 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { type RouterOutputs, api } from "@/utils/api";
|
import { api, type RouterOutputs } from "@/utils/api";
|
||||||
import {
|
|
||||||
type ColumnFiltersState,
|
|
||||||
type SortingState,
|
|
||||||
type VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { ChevronDown, Container } from "lucide-react";
|
|
||||||
import * as React from "react";
|
|
||||||
import { columns } from "./colums";
|
import { columns } from "./colums";
|
||||||
export type Container = NonNullable<
|
export type Container = NonNullable<
|
||||||
RouterOutputs["docker"]["getContainers"]
|
RouterOutputs["docker"]["getContainers"]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -9,8 +11,6 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
const Terminal = dynamic(
|
const Terminal = dynamic(
|
||||||
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Terminal } from "@xterm/xterm";
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { FitAddon } from "xterm-addon-fit";
|
import { FitAddon } from "xterm-addon-fit";
|
||||||
import "@xterm/xterm/css/xterm.css";
|
import "@xterm/xterm/css/xterm.css";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import { AttachAddon } from "@xterm/addon-attach";
|
import { AttachAddon } from "@xterm/addon-attach";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -12,12 +17,6 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
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 { validateAndFormatYAML } from "../application/advanced/traefik/update-traefik-config";
|
||||||
|
|
||||||
const UpdateServerMiddlewareConfigSchema = z.object({
|
const UpdateServerMiddlewareConfigSchema = z.object({
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { FileIcon, Folder, Loader2, Workflow } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -8,8 +10,6 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Tree } from "@/components/ui/file-tree";
|
import { Tree } from "@/components/ui/file-tree";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { FileIcon, Folder, Loader2, Workflow } from "lucide-react";
|
|
||||||
import React from "react";
|
|
||||||
import { ShowTraefikFile } from "./show-traefik-file";
|
import { ShowTraefikFile } from "./show-traefik-file";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import {
|
||||||
|
Building2,
|
||||||
|
Calendar,
|
||||||
|
CheckIcon,
|
||||||
|
ChevronsUpDown,
|
||||||
|
Copy,
|
||||||
|
CreditCard,
|
||||||
|
Fingerprint,
|
||||||
|
Key,
|
||||||
|
Server,
|
||||||
|
Settings2,
|
||||||
|
Shield,
|
||||||
|
UserIcon,
|
||||||
|
XIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { Logo } from "@/components/shared/logo";
|
import { Logo } from "@/components/shared/logo";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -26,25 +45,6 @@ import {
|
|||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import copy from "copy-to-clipboard";
|
|
||||||
import { format } from "date-fns";
|
|
||||||
import {
|
|
||||||
Building2,
|
|
||||||
Calendar,
|
|
||||||
CheckIcon,
|
|
||||||
ChevronsUpDown,
|
|
||||||
Copy,
|
|
||||||
CreditCard,
|
|
||||||
Fingerprint,
|
|
||||||
Key,
|
|
||||||
Server,
|
|
||||||
Settings2,
|
|
||||||
Shield,
|
|
||||||
UserIcon,
|
|
||||||
XIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
type User = typeof authClient.$Infer.Session.user;
|
type User = typeof authClient.$Infer.Session.user;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -19,12 +25,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
externalPort: z.preprocess((a) => {
|
externalPort: z.preprocess((a) => {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -9,10 +13,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
||||||
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -20,12 +26,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const updateMariadbSchema = z.object({
|
const updateMariadbSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -19,12 +25,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
externalPort: z.preprocess((a) => {
|
externalPort: z.preprocess((a) => {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -9,12 +13,9 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
||||||
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mongoId: string;
|
mongoId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -20,12 +26,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const updateMongoSchema = z.object({
|
const updateMongoSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "recharts";
|
} from "recharts";
|
||||||
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
||||||
import { convertMemoryToBytes } from "./show-free-container-monitoring";
|
import { convertMemoryToBytes } from "./show-free-container-monitoring";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
acummulativeData: DockerStatsJSON["memory"];
|
acummulativeData: DockerStatsJSON["memory"];
|
||||||
memoryLimitGB: number;
|
memoryLimitGB: number;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
import type { DockerStatsJSON } from "./show-free-container-monitoring";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
acummulativeData: DockerStatsJSON["network"];
|
acummulativeData: DockerStatsJSON["network"];
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user