mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge branch 'canary' into feature/managed-servers
This commit is contained in:
79
.github/workflows/dokploy.yml
vendored
79
.github/workflows/dokploy.yml
vendored
@@ -138,6 +138,8 @@ jobs:
|
||||
needs: [combine-manifests]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.get_version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -160,3 +162,80 @@ jobs:
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
sync-version:
|
||||
needs: [generate-release]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Sync version to MCP repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo
|
||||
cd /tmp/mcp-repo
|
||||
|
||||
jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run fetch-openapi
|
||||
pnpm run generate
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
--allow-empty
|
||||
git push
|
||||
|
||||
echo "✅ MCP repo synced to version ${{ needs.generate-release.outputs.version }}"
|
||||
|
||||
- name: Sync version to CLI repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo
|
||||
cd /tmp/cli-repo
|
||||
|
||||
jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
cp ${{ github.workspace }}/openapi.json ./openapi.json
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run generate
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
--allow-empty
|
||||
git push
|
||||
|
||||
echo "✅ CLI repo synced to version ${{ needs.generate-release.outputs.version }}"
|
||||
|
||||
- name: Sync version to SDK repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/sdk.git /tmp/sdk-repo
|
||||
cd /tmp/sdk-repo
|
||||
|
||||
jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
cp ${{ github.workspace }}/openapi.json ./openapi.json
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run generate
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
--allow-empty
|
||||
git push
|
||||
|
||||
echo "✅ SDK repo synced to version ${{ needs.generate-release.outputs.version }}"
|
||||
|
||||
21
.github/workflows/sync-openapi-docs.yml
vendored
21
.github/workflows/sync-openapi-docs.yml
vendored
@@ -110,3 +110,24 @@ jobs:
|
||||
|
||||
echo "✅ OpenAPI synced to CLI repository successfully"
|
||||
|
||||
- name: Sync to SDK repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/sdk.git sdk-repo
|
||||
|
||||
cd sdk-repo
|
||||
|
||||
cp -f ../openapi.json openapi.json
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
|
||||
git add openapi.json
|
||||
git commit -m "chore: sync OpenAPI specification [skip ci]" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
-m "Updated: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" \
|
||||
--allow-empty
|
||||
|
||||
git push
|
||||
|
||||
echo "✅ OpenAPI synced to SDK repository successfully"
|
||||
|
||||
|
||||
83
.github/workflows/sync-version.yml
vendored
83
.github/workflows/sync-version.yml
vendored
@@ -1,83 +0,0 @@
|
||||
name: Sync version to MCP and CLI repos
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-version:
|
||||
name: Sync version to external repos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Dokploy repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(jq -r .version apps/dokploy/package.json | sed 's/^v//')
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: Sync version to MCP repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo
|
||||
cd /tmp/mcp-repo
|
||||
|
||||
# Regenerate tools from latest OpenAPI spec
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run fetch-openapi
|
||||
pnpm run generate
|
||||
|
||||
# Bump version after install so pnpm install doesn't overwrite it
|
||||
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
-m "Release: ${{ github.event.release.html_url }}" \
|
||||
--allow-empty
|
||||
|
||||
git push
|
||||
|
||||
|
||||
- name: Sync version to CLI repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo
|
||||
|
||||
cd /tmp/cli-repo
|
||||
|
||||
# Copy latest openapi spec and regenerate commands
|
||||
cp ${{ github.workspace }}/openapi.json ./openapi.json
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run generate
|
||||
|
||||
# Bump version after install so pnpm install doesn't overwrite it
|
||||
if [ -f package.json ]; then
|
||||
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
fi
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
-m "Release: ${{ github.event.release.html_url }}" \
|
||||
--allow-empty
|
||||
|
||||
git push
|
||||
|
||||
echo "CLI repo synced to version ${{ steps.get_version.outputs.version }}"
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -4,5 +4,8 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,4 +494,49 @@ describe("processTemplate", () => {
|
||||
expect(result.mounts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isolated deployment config", () => {
|
||||
it("should default to isolated=true when not specified", () => {
|
||||
const template: CompleteTemplate = {
|
||||
metadata: {} as any,
|
||||
variables: {},
|
||||
config: {
|
||||
domains: [],
|
||||
env: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(template.config.isolated).toBeUndefined();
|
||||
// undefined !== false => isolatedDeployment = true
|
||||
expect(template.config.isolated !== false).toBe(true);
|
||||
});
|
||||
|
||||
it("should be isolated when isolated=true is explicitly set", () => {
|
||||
const template: CompleteTemplate = {
|
||||
metadata: {} as any,
|
||||
variables: {},
|
||||
config: {
|
||||
isolated: true,
|
||||
domains: [],
|
||||
env: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(template.config.isolated !== false).toBe(true);
|
||||
});
|
||||
|
||||
it("should disable isolated deployment when isolated=false", () => {
|
||||
const template: CompleteTemplate = {
|
||||
metadata: {} as any,
|
||||
variables: {},
|
||||
config: {
|
||||
isolated: false,
|
||||
domains: [],
|
||||
env: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(template.config.isolated !== false).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,9 +30,7 @@ describe("helpers functions", () => {
|
||||
const domain = processValue("${domain}", {}, mockSchema);
|
||||
expect(domain.startsWith(`${mockSchema.projectName}-`)).toBeTruthy();
|
||||
expect(
|
||||
domain.endsWith(
|
||||
`${mockSchema.serverIp.replaceAll(".", "-")}.traefik.me`,
|
||||
),
|
||||
domain.endsWith(`${mockSchema.serverIp.replaceAll(".", "-")}.sslip.io`),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import type { RouterOutputs } from "@/utils/api";
|
||||
import type { ValidationStates } from "./show-domains";
|
||||
import { AddDomain } from "./handle-domain";
|
||||
import { DnsHelperModal } from "./dns-helper-modal";
|
||||
import { AddDomain } from "./handle-domain";
|
||||
import type { ValidationStates } from "./show-domains";
|
||||
|
||||
export type Domain =
|
||||
| RouterOutputs["domain"]["byApplicationId"][0]
|
||||
@@ -168,7 +168,7 @@ export const createColumns = ({
|
||||
{domain.certificateType}
|
||||
</Badge>
|
||||
)}
|
||||
{!domain.host.includes("traefik.me") && (
|
||||
{!domain.host.includes("sslip.io") && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -256,7 +256,7 @@ export const createColumns = ({
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{!domain.host.includes("traefik.me") && (
|
||||
{!domain.host.includes("sslip.io") && (
|
||||
<DnsHelperModal
|
||||
domain={{
|
||||
host: domain.host,
|
||||
|
||||
@@ -225,7 +225,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
const https = form.watch("https");
|
||||
const domainType = form.watch("domainType");
|
||||
const host = form.watch("host");
|
||||
const isTraefikMeDomain = host?.includes("traefik.me") || false;
|
||||
const isTraefikMeDomain = host?.includes("sslip.io") || false;
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
@@ -513,7 +513,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
{!canGenerateTraefikMeDomains &&
|
||||
field.value.includes("traefik.me") && (
|
||||
field.value.includes("sslip.io") && (
|
||||
<AlertBlock type="warning">
|
||||
You need to set an IP address in your{" "}
|
||||
<Link
|
||||
@@ -524,12 +524,12 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
||||
: "Web Server -> Server -> Update Server IP"}
|
||||
</Link>{" "}
|
||||
to make your traefik.me domain work.
|
||||
to make your sslip.io domain work.
|
||||
</AlertBlock>
|
||||
)}
|
||||
{isTraefikMeDomain && (
|
||||
<AlertBlock type="info">
|
||||
<strong>Note:</strong> traefik.me is a public HTTP
|
||||
<strong>Note:</strong> sslip.io is a public HTTP
|
||||
service and does not support SSL/HTTPS. HTTPS and
|
||||
certificate options will not have any effect.
|
||||
</AlertBlock>
|
||||
@@ -567,7 +567,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
sideOffset={5}
|
||||
className="max-w-[10rem]"
|
||||
>
|
||||
<p>Generate traefik.me domain</p>
|
||||
<p>Generate sslip.io domain</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -425,7 +425,7 @@ export const ShowDomains = ({ id, type }: Props) => {
|
||||
</Badge>
|
||||
)}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{!item.host.includes("traefik.me") && (
|
||||
{!item.host.includes("sslip.io") && (
|
||||
<DnsHelperModal
|
||||
domain={{
|
||||
host: item.host,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -57,7 +58,10 @@ const BitbucketProviderSchema = z.object({
|
||||
slug: z.string().optional(),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
enableSubmodules: z.boolean().optional(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -41,7 +42,10 @@ const GitProviderSchema = z.object({
|
||||
repositoryURL: z.string().min(1, {
|
||||
message: "Repository URL is required",
|
||||
}),
|
||||
branch: z.string().min(1, "Branch required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
sshKey: z.string().optional(),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
enableSubmodules: z.boolean().default(false),
|
||||
@@ -107,110 +111,103 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="flex items-end col-span-2 gap-4">
|
||||
<div className="grow">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repositoryURL"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center justify-between">
|
||||
<FormLabel>Repository URL</FormLabel>
|
||||
{field.value?.startsWith("https://") && (
|
||||
<Link
|
||||
href={field.value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
<GitIcon className="h-4 w-4" />
|
||||
<span>View Repository</span>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input placeholder="Repository URL" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{sshKeys && sshKeys.length > 0 ? (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sshKey"
|
||||
render={({ field }) => (
|
||||
<FormItem className="basis-40">
|
||||
<FormLabel className="w-full inline-flex justify-between">
|
||||
SSH Key
|
||||
<LockIcon className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
key={field.value}
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a key" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{sshKeys?.map((sshKey) => (
|
||||
<SelectItem
|
||||
key={sshKey.sshKeyId}
|
||||
value={sshKey.sshKeyId}
|
||||
>
|
||||
{sshKey.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectItem value="none">None</SelectItem>
|
||||
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => router.push("/dashboard/settings/ssh-keys")}
|
||||
type="button"
|
||||
>
|
||||
<KeyRoundIcon className="size-4" /> Add SSH Key
|
||||
</Button>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 items-start">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="repositoryURL"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2 lg:col-span-3">
|
||||
<div className="flex items-center justify-between h-5">
|
||||
<FormLabel>Repository URL</FormLabel>
|
||||
{field.value?.startsWith("https://") && (
|
||||
<Link
|
||||
href={field.value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
<GitIcon className="h-4 w-4" />
|
||||
<span>View Repository</span>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input placeholder="Repository URL" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
/>
|
||||
{sshKeys && sshKeys.length > 0 ? (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="branch"
|
||||
name="sshKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Branch</FormLabel>
|
||||
<FormItem className="col-span-2 lg:col-span-1">
|
||||
<FormLabel className="w-full inline-flex justify-between">
|
||||
SSH Key
|
||||
<LockIcon className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Branch" {...field} />
|
||||
<Select
|
||||
key={field.value}
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a key" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{sshKeys?.map((sshKey) => (
|
||||
<SelectItem
|
||||
key={sshKey.sshKeyId}
|
||||
value={sshKey.sshKeyId}
|
||||
>
|
||||
{sshKey.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectItem value="none">None</SelectItem>
|
||||
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => router.push("/dashboard/settings/ssh-keys")}
|
||||
type="button"
|
||||
className="col-span-2 lg:col-span-1 lg:mt-7"
|
||||
>
|
||||
<KeyRoundIcon className="size-4" /> Add SSH Key
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="branch"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Branch</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Branch" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="buildPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Build Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="/" {...field} />
|
||||
@@ -223,7 +220,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
||||
control={form.control}
|
||||
name="watchPaths"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2">
|
||||
<FormItem className="col-span-2 lg:col-span-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel>Watch Paths</FormLabel>
|
||||
<TooltipProvider>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -72,7 +73,10 @@ const GiteaProviderSchema = z.object({
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
giteaId: z.string().min(1, "Gitea Provider is required"),
|
||||
watchPaths: z.array(z.string()).default([]),
|
||||
enableSubmodules: z.boolean().optional(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -55,7 +56,10 @@ const GithubProviderSchema = z.object({
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
githubId: z.string().min(1, "Github Provider is required"),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
triggerType: z.enum(["push", "tag"]).default("push"),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -58,7 +59,10 @@ const GitlabProviderSchema = z.object({
|
||||
id: z.number().nullable(),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
enableSubmodules: z.boolean().default(false),
|
||||
|
||||
@@ -58,7 +58,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Deploy Settings</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-row gap-4 flex-wrap">
|
||||
<CardContent className="grid grid-cols-2 lg:flex lg:flex-row lg:flex-wrap gap-4">
|
||||
<TooltipProvider delayDuration={0} disableHoverableContent={false}>
|
||||
{canDeploy && (
|
||||
<DialogAction
|
||||
@@ -274,14 +274,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2 col-span-2"
|
||||
>
|
||||
<Terminal className="size-4 mr-1" />
|
||||
Open Terminal
|
||||
</Button>
|
||||
</DockerTerminalModal>
|
||||
{canUpdateService && (
|
||||
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
||||
<div className="flex flex-row items-center gap-2 justify-between rounded-md px-4 py-2 border col-span-2 md:col-span-1">
|
||||
<span className="text-sm font-medium">Autodeploy</span>
|
||||
<Switch
|
||||
aria-label="Toggle autodeploy"
|
||||
@@ -305,7 +305,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
||||
)}
|
||||
|
||||
{canUpdateService && (
|
||||
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
||||
<div className="flex flex-row items-center gap-2 justify-between rounded-md px-4 py-2 border col-span-2 md:col-span-1">
|
||||
<span className="text-sm font-medium">Clean Cache</span>
|
||||
<Switch
|
||||
aria-label="Toggle clean cache"
|
||||
|
||||
@@ -87,7 +87,7 @@ export const AddPreviewDomain = ({
|
||||
});
|
||||
|
||||
const host = form.watch("host");
|
||||
const isTraefikMeDomain = host?.includes("traefik.me") || false;
|
||||
const isTraefikMeDomain = host?.includes("sslip.io") || false;
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
@@ -162,7 +162,7 @@ export const AddPreviewDomain = ({
|
||||
<FormItem>
|
||||
{isTraefikMeDomain && (
|
||||
<AlertBlock type="info">
|
||||
<strong>Note:</strong> traefik.me is a public HTTP
|
||||
<strong>Note:</strong> sslip.io is a public HTTP
|
||||
service and does not support SSL/HTTPS. HTTPS and
|
||||
certificate options will not have any effect.
|
||||
</AlertBlock>
|
||||
@@ -202,7 +202,7 @@ export const AddPreviewDomain = ({
|
||||
sideOffset={5}
|
||||
className="max-w-[10rem]"
|
||||
>
|
||||
<p>Generate traefik.me domain</p>
|
||||
<p>Generate sslip.io domain</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -88,7 +88,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
env: "",
|
||||
wildcardDomain: "*.traefik.me",
|
||||
wildcardDomain: "*.sslip.io",
|
||||
port: 3000,
|
||||
previewLimit: 3,
|
||||
previewLabels: [],
|
||||
@@ -102,7 +102,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
|
||||
const previewHttps = form.watch("previewHttps");
|
||||
const wildcardDomain = form.watch("wildcardDomain");
|
||||
const isTraefikMeDomain = wildcardDomain?.includes("traefik.me") || false;
|
||||
const isTraefikMeDomain = wildcardDomain?.includes("sslip.io") || false;
|
||||
|
||||
useEffect(() => {
|
||||
setIsEnabled(data?.isPreviewDeploymentsActive || false);
|
||||
@@ -114,7 +114,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
env: data.previewEnv || "",
|
||||
buildArgs: data.previewBuildArgs || "",
|
||||
buildSecrets: data.previewBuildSecrets || "",
|
||||
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
||||
wildcardDomain: data.previewWildcard || "*.sslip.io",
|
||||
port: data.previewPort || 3000,
|
||||
previewLabels: data.previewLabels || [],
|
||||
previewLimit: data.previewLimit || 3,
|
||||
@@ -173,7 +173,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
<div className="grid gap-4">
|
||||
{isTraefikMeDomain && (
|
||||
<AlertBlock type="info">
|
||||
<strong>Note:</strong> traefik.me is a public HTTP service and
|
||||
<strong>Note:</strong> sslip.io is a public HTTP service and
|
||||
does not support SSL/HTTPS. HTTPS and certificate options will
|
||||
not have any effect.
|
||||
</AlertBlock>
|
||||
@@ -192,7 +192,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Wildcard Domain</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="*.traefik.me" {...field} />
|
||||
<Input placeholder="*.sslip.io" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -80,6 +80,7 @@ export const commonCronExpressions = [
|
||||
const formSchema = z
|
||||
.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
description: z.string().optional(),
|
||||
cronExpression: z.string().min(1, "Cron expression is required"),
|
||||
shellType: z.enum(["bash", "sh"]).default("bash"),
|
||||
command: z.string(),
|
||||
@@ -224,6 +225,7 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
||||
resolver: standardSchemaResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
description: "",
|
||||
cronExpression: "",
|
||||
shellType: "bash",
|
||||
command: "",
|
||||
@@ -263,6 +265,7 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
||||
if (scheduleId && schedule) {
|
||||
form.reset({
|
||||
name: schedule.name,
|
||||
description: schedule.description || "",
|
||||
cronExpression: schedule.cronExpression,
|
||||
shellType: schedule.shellType,
|
||||
command: schedule.command,
|
||||
@@ -479,6 +482,26 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Backs up the database every day at midnight"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Optional description of what this schedule does
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ScheduleFormField
|
||||
name="cronExpression"
|
||||
formControl={form.control}
|
||||
|
||||
@@ -125,6 +125,11 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
||||
{schedule.enabled ? "Enabled" : "Disabled"}
|
||||
</Badge>
|
||||
</div>
|
||||
{schedule.description && (
|
||||
<p className="text-xs text-muted-foreground/70 [overflow-wrap:anywhere] line-clamp-2">
|
||||
{schedule.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground flex-wrap">
|
||||
<Badge
|
||||
variant="outline"
|
||||
|
||||
@@ -2,6 +2,10 @@ import { Loader2, MoreHorizontal, RefreshCw } from "lucide-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { ShowContainerConfig } from "@/components/dashboard/docker/config/show-container-config";
|
||||
import { ShowContainerMounts } from "@/components/dashboard/docker/mounts/show-container-mounts";
|
||||
import { ShowContainerNetworks } from "@/components/dashboard/docker/networks/show-container-networks";
|
||||
import { DockerTerminalModal } from "@/components/dashboard/docker/terminal/docker-terminal-modal";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -36,10 +40,6 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { api } from "@/utils/api";
|
||||
import { ShowContainerConfig } from "@/components/dashboard/docker/config/show-container-config";
|
||||
import { ShowContainerMounts } from "@/components/dashboard/docker/mounts/show-container-mounts";
|
||||
import { ShowContainerNetworks } from "@/components/dashboard/docker/networks/show-container-networks";
|
||||
import { DockerTerminalModal } from "@/components/dashboard/docker/terminal/docker-terminal-modal";
|
||||
|
||||
const DockerLogsId = dynamic(
|
||||
() =>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -57,7 +58,10 @@ const BitbucketProviderSchema = z.object({
|
||||
slug: z.string().optional(),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
enableSubmodules: z.boolean().default(false),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -41,7 +42,10 @@ const GitProviderSchema = z.object({
|
||||
repositoryURL: z.string().min(1, {
|
||||
message: "Repository URL is required",
|
||||
}),
|
||||
branch: z.string().min(1, "Branch required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
sshKey: z.string().optional(),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
enableSubmodules: z.boolean().default(false),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, Plus, X, HelpCircle } from "lucide-react";
|
||||
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 { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -57,7 +58,10 @@ const GiteaProviderSchema = z.object({
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
giteaId: z.string().min(1, "Gitea Provider is required"),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
enableSubmodules: z.boolean().default(false),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -55,7 +56,10 @@ const GithubProviderSchema = z.object({
|
||||
owner: z.string().min(1, "Owner is required"),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
githubId: z.string().min(1, "Github Provider is required"),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
triggerType: z.enum(["push", "tag"]).default("push"),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -58,7 +59,10 @@ const GitlabProviderSchema = z.object({
|
||||
gitlabPathNamespace: z.string().min(1),
|
||||
})
|
||||
.required(),
|
||||
branch: z.string().min(1, "Branch is required"),
|
||||
branch: z
|
||||
.string()
|
||||
.min(1, "Branch is required")
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
enableSubmodules: z.boolean().default(false),
|
||||
|
||||
@@ -288,7 +288,6 @@ export const RestoreBackup = ({
|
||||
toast.error("Please select a database type");
|
||||
return;
|
||||
}
|
||||
console.log({ data });
|
||||
setIsDeploying(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
"use client";
|
||||
import { Bot, Loader2, RotateCcw, Settings, X } from "lucide-react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import {
|
||||
Bot,
|
||||
Check,
|
||||
Copy,
|
||||
Loader2,
|
||||
RotateCcw,
|
||||
Settings,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
@@ -30,6 +39,7 @@ const MAX_LOG_LINES = 200;
|
||||
export function AnalyzeLogs({ logs, context }: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [aiId, setAiId] = useState<string>("");
|
||||
const [copied, setCopied] = useState(false);
|
||||
const { data: providers } = api.ai.getEnabledProviders.useQuery(undefined, {
|
||||
enabled: open,
|
||||
});
|
||||
@@ -52,6 +62,15 @@ export function AnalyzeLogs({ logs, context }: Props) {
|
||||
mutate({ aiId, logs: logsText, context });
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!data?.analysis) return;
|
||||
const success = copy(data.analysis);
|
||||
if (success) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
@@ -71,7 +90,7 @@ export function AnalyzeLogs({ logs, context }: Props) {
|
||||
disabled={logs.length === 0}
|
||||
title="Analyze logs with AI"
|
||||
>
|
||||
<Bot className="mr-2 h-4 w-4" />
|
||||
<Bot className="mr-2 size-4" />
|
||||
AI
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@@ -168,6 +187,18 @@ export function AnalyzeLogs({ logs, context }: Props) {
|
||||
)}
|
||||
Re-analyze
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleCopy}
|
||||
title="Copy analysis to clipboard"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
|
||||
@@ -347,11 +347,13 @@ export const DockerLogsId: React.FC<Props> = ({
|
||||
title={isPaused ? "Resume logs" : "Pause logs"}
|
||||
>
|
||||
{isPaused ? (
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
<Play className="size-4" />
|
||||
) : (
|
||||
<Pause className="mr-2 h-4 w-4" />
|
||||
<Pause className="size-4" />
|
||||
)}
|
||||
{isPaused ? "Resume" : "Pause"}
|
||||
<span className="hidden lg:ml-2 lg:inline">
|
||||
{isPaused ? "Resume" : "Pause"}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -362,11 +364,13 @@ export const DockerLogsId: React.FC<Props> = ({
|
||||
title="Copy logs to clipboard"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="mr-2 h-4 w-4" />
|
||||
<Check className="size-4" />
|
||||
) : (
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
<Copy className="size-4" />
|
||||
)}
|
||||
Copy
|
||||
<span className="hidden lg:ml-2 lg:inline">
|
||||
{copied ? "Copied" : "Copy"}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -374,17 +378,18 @@ export const DockerLogsId: React.FC<Props> = ({
|
||||
className="h-9 sm:w-auto w-full"
|
||||
onClick={handleDownload}
|
||||
disabled={filteredLogs.length === 0 || !data?.Name}
|
||||
title="Download logs as text file"
|
||||
>
|
||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
||||
Download logs
|
||||
<DownloadIcon className="size-4" />
|
||||
<span className="hidden lg:ml-2 lg:inline">Download logs</span>
|
||||
</Button>
|
||||
<AnalyzeLogs logs={filteredLogs} context="runtime" />
|
||||
</div>
|
||||
</div>
|
||||
{isPaused && (
|
||||
<AlertBlock type="warning">
|
||||
<AlertBlock type="warning" className="items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<Pause className="h-4 w-4" />
|
||||
<Pause className="size-4" />
|
||||
<span>
|
||||
Logs paused
|
||||
{messageBuffer.length > 0 && (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import {
|
||||
uploadFileToContainerSchema,
|
||||
type UploadFileToContainer,
|
||||
uploadFileToContainerSchema,
|
||||
} from "@/utils/schema";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { toast } from "sonner";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
mariadbId: string;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { toast } from "sonner";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
mongoId: string;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { toast } from "sonner";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
mysqlId: string;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { toast } from "sonner";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
postgresId: string;
|
||||
|
||||
@@ -632,7 +632,6 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
||||
control={form.control}
|
||||
name="enableNamespaces"
|
||||
render={({ field }) => {
|
||||
console.log(field.value);
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Enable Namespaces</FormLabel>
|
||||
|
||||
494
apps/dokploy/components/dashboard/project/add-import.tsx
Normal file
494
apps/dokploy/components/dashboard/project/add-import.tsx
Normal file
@@ -0,0 +1,494 @@
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { Code2, FileInput, Globe2, HardDrive, HelpCircle } 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 { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { slugify } from "@/lib/slug";
|
||||
import { api } from "@/utils/api";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX } from "@/utils/schema";
|
||||
|
||||
const AddImportSchema = z.object({
|
||||
name: z.string().min(1, { message: "Name is required" }),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1, { message: "App name is required" })
|
||||
.regex(APP_NAME_REGEX, { message: APP_NAME_MESSAGE }),
|
||||
base64: z.string().min(1, { message: "Base64 content is required" }),
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
type AddImport = z.infer<typeof AddImportSchema>;
|
||||
|
||||
type TemplateInfo = {
|
||||
compose: string;
|
||||
template: {
|
||||
domains: Array<{
|
||||
serviceName: string;
|
||||
port: number;
|
||||
path?: string;
|
||||
host?: string;
|
||||
}>;
|
||||
envs: string[];
|
||||
mounts: Array<{ filePath: string; content: string }>;
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
environmentId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddImport = ({ environmentId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [previewOpen, setPreviewOpen] = useState(false);
|
||||
const [mountOpen, setMountOpen] = useState(false);
|
||||
const [selectedMount, setSelectedMount] = useState<{
|
||||
filePath: string;
|
||||
content: string;
|
||||
} | null>(null);
|
||||
const [templateInfo, setTemplateInfo] = useState<TemplateInfo | null>(null);
|
||||
|
||||
const slug = slugify(projectName);
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
const shouldShowServerDropdown = !!(servers && servers.length > 0);
|
||||
|
||||
const { mutateAsync: previewTemplate, isPending: isProcessing } =
|
||||
api.compose.previewTemplate.useMutation();
|
||||
const { mutateAsync: createCompose, isPending: isCreating } =
|
||||
api.compose.create.useMutation();
|
||||
const { mutateAsync: importCompose, isPending: isImporting } =
|
||||
api.compose.import.useMutation();
|
||||
|
||||
const form = useForm<AddImport>({
|
||||
defaultValues: { name: "", appName: `${slug}-`, base64: "" },
|
||||
resolver: zodResolver(AddImportSchema),
|
||||
});
|
||||
|
||||
const resetAll = () => {
|
||||
form.reset({ name: "", appName: `${slug}-`, base64: "" });
|
||||
setTemplateInfo(null);
|
||||
setPreviewOpen(false);
|
||||
setMountOpen(false);
|
||||
setSelectedMount(null);
|
||||
};
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) resetAll();
|
||||
setVisible(open);
|
||||
};
|
||||
|
||||
const handleLoad = async (data: AddImport) => {
|
||||
try {
|
||||
const result = await previewTemplate({
|
||||
appName: data.appName,
|
||||
base64: data.base64.trim(),
|
||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||
});
|
||||
setTemplateInfo(result);
|
||||
setPreviewOpen(true);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Error processing template",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImport = async () => {
|
||||
const data = form.getValues();
|
||||
try {
|
||||
const compose = await createCompose({
|
||||
name: data.name,
|
||||
appName: data.appName,
|
||||
environmentId,
|
||||
composeType: "docker-compose",
|
||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||
});
|
||||
await importCompose({
|
||||
composeId: compose.composeId,
|
||||
base64: data.base64.trim(),
|
||||
});
|
||||
toast.success("Compose imported successfully");
|
||||
await utils.environment.one.invalidate({ environmentId });
|
||||
resetAll();
|
||||
setVisible(false);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Error importing compose",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelPreview = () => {
|
||||
setPreviewOpen(false);
|
||||
setTemplateInfo(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={visible} onOpenChange={handleOpenChange}>
|
||||
<DialogTrigger className="w-full">
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<FileInput className="size-4 text-muted-foreground" />
|
||||
<span>Import</span>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Import Compose</DialogTitle>
|
||||
<DialogDescription>
|
||||
Paste a base64-encoded compose export to preview and import it
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-import"
|
||||
onSubmit={form.handleSubmit(handleLoad)}
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My App"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value || "";
|
||||
form.setValue(
|
||||
"appName",
|
||||
`${slug}-${slugify(val.trim())}`,
|
||||
);
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{shouldShowServerDropdown && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="serverId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className="z-[999] w-[300px]"
|
||||
align="start"
|
||||
side="top"
|
||||
>
|
||||
<span>
|
||||
If no server is selected, the compose will be
|
||||
deployed on the server where the user is logged
|
||||
in.
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={
|
||||
field.value || (!isCloud ? "dokploy" : undefined)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
!isCloud ? "Dokploy" : "Select a Server"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{!isCloud && (
|
||||
<SelectItem value="dokploy">
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>Dokploy</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
Default
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
)}
|
||||
{servers?.map((server) => (
|
||||
<SelectItem
|
||||
key={server.serverId}
|
||||
value={server.serverId}
|
||||
>
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>{server.name}</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
{server.ipAddress}
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>
|
||||
Servers (
|
||||
{(servers?.length ?? 0) + (!isCloud ? 1 : 0)})
|
||||
</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>App Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-app" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="base64"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Configuration (Base64)</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Paste your base64-encoded compose export here..."
|
||||
className="font-mono resize-none h-32"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
isLoading={isCreating || isProcessing}
|
||||
>
|
||||
Load
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Preview modal */}
|
||||
<Dialog
|
||||
open={previewOpen}
|
||||
onOpenChange={(open) => !open && handleCancelPreview()}
|
||||
>
|
||||
<DialogContent className="max-w-[60vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold">
|
||||
Template Information
|
||||
</DialogTitle>
|
||||
<DialogDescription className="space-y-2">
|
||||
<p>Review the template information before importing</p>
|
||||
<AlertBlock type="warning">
|
||||
Warning: This will remove all existing environment variables,
|
||||
mounts, and domains from this service.
|
||||
</AlertBlock>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Code2 className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">Docker Compose</h3>
|
||||
</div>
|
||||
<CodeEditor
|
||||
language="yaml"
|
||||
value={templateInfo?.compose || ""}
|
||||
className="font-mono"
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
{templateInfo?.template.domains &&
|
||||
templateInfo.template.domains.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe2 className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">Domains</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{templateInfo.template.domains.map((domain, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg border bg-card p-3 text-card-foreground shadow-sm"
|
||||
>
|
||||
<div className="font-medium">
|
||||
{domain.serviceName}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div>Port: {domain.port}</div>
|
||||
{domain.host && <div>Host: {domain.host}</div>}
|
||||
{domain.path && <div>Path: {domain.path}</div>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{templateInfo?.template.envs &&
|
||||
templateInfo.template.envs.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Code2 className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">
|
||||
Environment Variables
|
||||
</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{templateInfo.template.envs.map((env, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg truncate border bg-card p-2 font-mono text-sm"
|
||||
>
|
||||
{env}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{templateInfo?.template.mounts &&
|
||||
templateInfo.template.mounts.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<HardDrive className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">Mounts</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{templateInfo.template.mounts.map((mount, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg border bg-card p-2 font-mono text-sm hover:bg-accent cursor-pointer transition-colors"
|
||||
onClick={() => {
|
||||
setSelectedMount(mount);
|
||||
setMountOpen(true);
|
||||
}}
|
||||
>
|
||||
{mount.filePath}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button variant="outline" onClick={handleCancelPreview}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button isLoading={isImporting} onClick={handleImport}>
|
||||
Import
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Mount content modal */}
|
||||
<Dialog open={mountOpen} onOpenChange={setMountOpen}>
|
||||
<DialogContent className="max-w-[50vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl font-bold">
|
||||
{selectedMount?.filePath}
|
||||
</DialogTitle>
|
||||
<DialogDescription>Mount File Content</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="h-[45vh] pr-4">
|
||||
<CodeEditor
|
||||
language="yaml"
|
||||
value={selectedMount?.content || ""}
|
||||
className="font-mono"
|
||||
readOnly
|
||||
/>
|
||||
</ScrollArea>
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button onClick={() => setMountOpen(false)}>Close</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
BookText,
|
||||
Bookmark,
|
||||
BookText,
|
||||
CheckIcon,
|
||||
ChevronsUpDown,
|
||||
Globe,
|
||||
|
||||
@@ -344,7 +344,7 @@ export const ShowProjects = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
|
||||
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border flex flex-col">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between gap-2 overflow-clip">
|
||||
<span className="flex flex-col gap-1.5 ">
|
||||
@@ -491,7 +491,7 @@ export const ShowProjects = () => {
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardFooter className="pt-4">
|
||||
<CardFooter className="pt-4 mt-auto">
|
||||
<div className="space-y-1 text-xs flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
||||
<DateTooltip date={project.createdAt}>
|
||||
Created
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { toast } from "sonner";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
redisId: string;
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { NumberInput } from "@/components/ui/input";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -35,6 +34,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { NumberInput } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
@@ -7,8 +9,6 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
serverId?: string;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { EnterpriseFeatureLocked } from "@/components/proprietary/enterprise-feature-gate";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { EnterpriseFeatureLocked } from "@/components/proprietary/enterprise-feature-gate";
|
||||
import { api, type RouterOutputs } from "@/utils/api";
|
||||
|
||||
/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */
|
||||
|
||||
@@ -141,14 +141,14 @@ export const WebDomain = () => {
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 md:grid-cols-2"
|
||||
className="grid w-full gap-4 grid-cols-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="domain"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormItem className="col-span-2 md:col-span-1">
|
||||
<FormLabel>Domain</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -168,7 +168,7 @@ export const WebDomain = () => {
|
||||
name="letsEncryptEmail"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormItem className="col-span-2 md:col-span-1">
|
||||
<FormLabel>Let's Encrypt Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -209,7 +209,7 @@ export const WebDomain = () => {
|
||||
name="certificateType"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="md:col-span-2">
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Certificate Provider</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ServerIcon } from "lucide-react";
|
||||
import { CopyIcon, ServerIcon } from "lucide-react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { toast } from "sonner";
|
||||
import { ShowDokployActions } from "./servers/actions/show-dokploy-actions";
|
||||
import { ShowStorageActions } from "./servers/actions/show-storage-actions";
|
||||
import { ShowTraefikActions } from "./servers/actions/show-traefik-actions";
|
||||
@@ -49,8 +51,17 @@ export const WebServer = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center flex-wrap justify-between gap-4">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-muted-foreground flex items-center gap-1.5">
|
||||
Server IP: {webServerSettings?.serverIp}
|
||||
{webServerSettings?.serverIp && (
|
||||
<CopyIcon
|
||||
className="size-3.5 cursor-pointer hover:text-foreground transition-colors"
|
||||
onClick={() => {
|
||||
copy(webServerSettings.serverIp ?? "");
|
||||
toast.success("Copied to clipboard");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Version: {dokployVersion}
|
||||
|
||||
@@ -342,7 +342,7 @@ export const AdvanceBreadcrumb = () => {
|
||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||
>
|
||||
<FolderInput className="size-4 text-muted-foreground" />
|
||||
<span className="font-medium max-w-[150px] truncate">
|
||||
<span className="font-medium max-w-[50px] md:max-w-[150px] truncate">
|
||||
{currentProject?.name || "Select Project"}
|
||||
</span>
|
||||
<ChevronDown className="size-4 text-muted-foreground" />
|
||||
@@ -478,7 +478,7 @@ export const AdvanceBreadcrumb = () => {
|
||||
aria-expanded={environmentOpen}
|
||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||
>
|
||||
<span className="font-medium max-w-[150px] truncate">
|
||||
<span className="font-medium max-w-[50px] md:max-w-[150px] truncate">
|
||||
{currentEnvironment?.name || "production"}
|
||||
</span>
|
||||
<ChevronDown className="size-4 text-muted-foreground" />
|
||||
@@ -533,7 +533,7 @@ export const AdvanceBreadcrumb = () => {
|
||||
)}
|
||||
|
||||
{projectEnvironments && projectEnvironments.length === 1 && (
|
||||
<p className="text-sm font-normal ml-1">
|
||||
<p className="text-sm font-normal ml-1 max-w-[50px] md:max-w-[150px] truncate">
|
||||
{currentEnvironment?.name || "production"}
|
||||
</p>
|
||||
)}
|
||||
@@ -551,7 +551,7 @@ export const AdvanceBreadcrumb = () => {
|
||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||
>
|
||||
{getServiceIcon(currentService.type)}
|
||||
<span className="font-medium max-w-[150px] truncate">
|
||||
<span className="font-medium max-w-[50px] md:max-w-[150px] truncate">
|
||||
{currentService.name}
|
||||
</span>
|
||||
<ChevronDown className="size-4 text-muted-foreground" />
|
||||
@@ -617,7 +617,7 @@ export const AdvanceBreadcrumb = () => {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-7 ml-1"
|
||||
className="size-7 ml-1 hidden md:flex"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||
|
||||
@@ -63,6 +63,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
className={cn(
|
||||
buttonVariants({ variant, size, className }),
|
||||
"flex gap-2",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
|
||||
1
apps/dokploy/drizzle/0166_nosy_slapstick.sql
Normal file
1
apps/dokploy/drizzle/0166_nosy_slapstick.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "schedule" ADD COLUMN "description" text;
|
||||
8318
apps/dokploy/drizzle/meta/0166_snapshot.json
Normal file
8318
apps/dokploy/drizzle/meta/0166_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1163,6 +1163,13 @@
|
||||
"when": 1775845419261,
|
||||
"tag": "0165_abnormal_greymalkin",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 166,
|
||||
"version": "7",
|
||||
"when": 1778303519111,
|
||||
"tag": "0166_nosy_slapstick",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -28,6 +28,7 @@ try {
|
||||
"wait-for-postgres": "wait-for-postgres.ts",
|
||||
"reset-password": "reset-password.ts",
|
||||
"reset-2fa": "reset-2fa.ts",
|
||||
"migrate-auth-secret": "scripts/migrate-auth-secret.ts",
|
||||
},
|
||||
bundle: true,
|
||||
platform: "node",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.29.2",
|
||||
"version": "v0.29.4",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -14,6 +14,7 @@
|
||||
"wait-for-postgres-dev": "tsx -r dotenv/config wait-for-postgres.ts",
|
||||
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
||||
"reset-2fa": "node -r dotenv/config dist/reset-2fa.mjs",
|
||||
"migrate-auth-secret": "node -r dotenv/config dist/migrate-auth-secret.mjs",
|
||||
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
||||
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
||||
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
||||
@@ -126,7 +127,7 @@
|
||||
"next-themes": "^0.2.1",
|
||||
"nextjs-toploader": "^3.9.17",
|
||||
"node-os-utils": "2.0.1",
|
||||
"node-pty": "1.0.0",
|
||||
"node-pty": "1.1.0",
|
||||
"node-schedule": "2.1.1",
|
||||
"nodemailer": "6.9.14",
|
||||
"octokit": "3.1.2",
|
||||
|
||||
@@ -32,6 +32,7 @@ import { AddAiAssistant } from "@/components/dashboard/project/add-ai-assistant"
|
||||
import { AddApplication } from "@/components/dashboard/project/add-application";
|
||||
import { AddCompose } from "@/components/dashboard/project/add-compose";
|
||||
import { AddDatabase } from "@/components/dashboard/project/add-database";
|
||||
import { AddImport } from "@/components/dashboard/project/add-import";
|
||||
import { AddTemplate } from "@/components/dashboard/project/add-template";
|
||||
import { AdvancedEnvironmentSelector } from "@/components/dashboard/project/advanced-environment-selector";
|
||||
import { DuplicateProject } from "@/components/dashboard/project/duplicate-project";
|
||||
@@ -1091,6 +1092,10 @@ const EnvironmentPage = (
|
||||
projectName={projectData?.name}
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
<AddImport
|
||||
projectName={projectData?.name}
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
@@ -1099,7 +1104,7 @@ const EnvironmentPage = (
|
||||
</div>
|
||||
<CardContent className="space-y-2 py-8 border-t gap-4 flex flex-col min-h-[60vh]">
|
||||
<>
|
||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div className="flex flex-col gap-4 2xl:flex-row 2xl:items-center 2xl:justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
@@ -1620,9 +1625,9 @@ const EnvironmentPage = (
|
||||
<ContextMenuTrigger asChild>
|
||||
<Link
|
||||
href={`/dashboard/project/${projectId}/environment/${environmentId}/services/${service.type}/${service.id}`}
|
||||
className="block"
|
||||
className="block h-full"
|
||||
>
|
||||
<Card className="flex flex-col group relative cursor-pointer bg-transparent transition-colors hover:bg-border">
|
||||
<Card className="flex flex-col h-full group relative cursor-pointer bg-transparent transition-colors hover:bg-border">
|
||||
{service.serverId && (
|
||||
<div className="absolute -left-1 -top-2">
|
||||
<ServerIcon className="size-4 text-muted-foreground" />
|
||||
|
||||
@@ -93,6 +93,7 @@ const Service = (
|
||||
);
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
@@ -147,8 +148,9 @@ const Service = (
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (data?.server?.ipAddress) {
|
||||
copy(data.server.ipAddress);
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -85,6 +85,7 @@ const Service = (
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -134,8 +135,9 @@ const Service = (
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (data?.server?.ipAddress) {
|
||||
copy(data.server.ipAddress);
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
@@ -10,6 +11,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { toast } from "sonner";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
@@ -61,6 +63,7 @@ const Libsql = (
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
@@ -99,6 +102,14 @@ const Libsql = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
@@ -10,6 +11,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { toast } from "sonner";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
@@ -63,6 +65,7 @@ const Mariadb = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
@@ -111,6 +114,14 @@ const Mariadb = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
@@ -10,6 +11,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { toast } from "sonner";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
@@ -63,6 +65,7 @@ const Mongo = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -110,6 +113,14 @@ const Mongo = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -10,6 +11,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { toast } from "sonner";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
@@ -62,6 +64,7 @@ const MySql = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -110,6 +113,14 @@ const MySql = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
@@ -10,6 +11,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { toast } from "sonner";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
@@ -62,6 +64,7 @@ const Postgresql = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -109,6 +112,14 @@ const Postgresql = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
@@ -10,6 +11,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { toast } from "sonner";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
@@ -62,6 +64,7 @@ const Redis = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -109,6 +112,14 @@ const Redis = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -4,8 +4,8 @@ import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card";
|
||||
import { ShowSwarmContainers } from "@/components/dashboard/swarm/containers/show-swarm-containers";
|
||||
import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
||||
97
apps/dokploy/scripts/migrate-auth-secret.ts
Normal file
97
apps/dokploy/scripts/migrate-auth-secret.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Use this command to automatically migrate the auth secret: curl -sSL https://dokploy.com/security/0.29.3.sh | bash
|
||||
* Migration script: re-encrypt 2FA secrets after rotating BETTER_AUTH_SECRET.
|
||||
*
|
||||
* Usage:
|
||||
* OLD_SECRET=<old_secret> NEW_SECRET=<new_secret> npx tsx apps/dokploy/scripts/migrate-auth-secret.ts
|
||||
*
|
||||
* Both OLD_SECRET and NEW_SECRET are required.
|
||||
* Run this BEFORE restarting Dokploy with the new secret.
|
||||
*/
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { twoFactor } from "@dokploy/server/db/schema";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const OLD_SECRET = process.env.OLD_SECRET as string;
|
||||
const NEW_SECRET = process.env.NEW_SECRET as string;
|
||||
|
||||
if (!OLD_SECRET || !NEW_SECRET) {
|
||||
console.error(
|
||||
"❌ OLD_SECRET and NEW_SECRET environment variables are required.",
|
||||
);
|
||||
console.error(
|
||||
" Usage: OLD_SECRET=<old> NEW_SECRET=<new> npx tsx apps/dokploy/scripts/migrate-auth-secret.ts",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (OLD_SECRET === NEW_SECRET) {
|
||||
console.error("❌ OLD_SECRET and NEW_SECRET must be different.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function reEncrypt(
|
||||
value: string,
|
||||
oldSecret: string,
|
||||
newSecret: string,
|
||||
): Promise<string> {
|
||||
const plaintext = await symmetricDecrypt({ key: oldSecret, data: value });
|
||||
return symmetricEncrypt({ key: newSecret, data: plaintext });
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("🔍 Fetching 2FA records...");
|
||||
const records = await db.select().from(twoFactor);
|
||||
|
||||
if (records.length === 0) {
|
||||
console.log("✅ No 2FA records found, nothing to migrate.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`📦 Found ${records.length} 2FA record(s) to migrate.`);
|
||||
|
||||
let migrated = 0;
|
||||
let failed = 0;
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
for (const record of records) {
|
||||
try {
|
||||
const [newSecret, newBackupCodes] = await Promise.all([
|
||||
reEncrypt(record.secret, OLD_SECRET, NEW_SECRET),
|
||||
reEncrypt(record.backupCodes, OLD_SECRET, NEW_SECRET),
|
||||
]);
|
||||
|
||||
await tx
|
||||
.update(twoFactor)
|
||||
.set({ secret: newSecret, backupCodes: newBackupCodes })
|
||||
.where(eq(twoFactor.id, record.id));
|
||||
|
||||
migrated++;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`❌ Failed to migrate record ${record.id} (userId: ${record.userId}):`,
|
||||
err,
|
||||
);
|
||||
failed++;
|
||||
throw err; // rollback the whole transaction
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`✅ Migrated ${migrated} record(s) successfully.`);
|
||||
|
||||
if (failed > 0) {
|
||||
console.error(
|
||||
`❌ ${failed} record(s) failed — transaction was rolled back.`,
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("❌ Migration failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -25,8 +25,8 @@ import { findProjectById } from "@dokploy/server/services/project";
|
||||
import {
|
||||
getProviderHeaders,
|
||||
getProviderName,
|
||||
selectAIProvider,
|
||||
type Model,
|
||||
selectAIProvider,
|
||||
} from "@dokploy/server/utils/ai/select-ai-provider";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { generateText } from "ai";
|
||||
|
||||
@@ -640,7 +640,7 @@ export const composeRouter = createTRPCRouter({
|
||||
name: input.id,
|
||||
sourceType: "raw",
|
||||
appName: appName,
|
||||
isolatedDeployment: true,
|
||||
isolatedDeployment: template.config.config?.isolated !== false,
|
||||
});
|
||||
|
||||
await addNewService(ctx, compose.composeId);
|
||||
@@ -862,6 +862,76 @@ export const composeRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
previewTemplate: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
base64: z.string(),
|
||||
appName: z.string(),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
if (input.serverId) {
|
||||
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||
if (!accessibleIds.has(input.serverId)) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to access this server",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const decodedData = Buffer.from(input.base64, "base64").toString(
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
let serverIp = "127.0.0.1";
|
||||
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
serverIp = server.ipAddress;
|
||||
} else if (process.env.NODE_ENV !== "development") {
|
||||
const settings = await getWebServerSettings();
|
||||
serverIp = settings?.serverIp || "127.0.0.1";
|
||||
}
|
||||
|
||||
const templateData = JSON.parse(decodedData);
|
||||
const config = parse(templateData.config) as CompleteTemplate;
|
||||
|
||||
if (!templateData.compose || !config) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
"Invalid template format. Must contain compose and config fields",
|
||||
});
|
||||
}
|
||||
|
||||
const configModified = {
|
||||
...config,
|
||||
variables: {
|
||||
APP_NAME: input.appName,
|
||||
...config.variables,
|
||||
},
|
||||
};
|
||||
|
||||
const processedTemplate = processTemplate(configModified, {
|
||||
serverIp,
|
||||
projectName: input.appName,
|
||||
});
|
||||
|
||||
return {
|
||||
compose: templateData.compose,
|
||||
template: processedTemplate,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `Error processing template: ${error instanceof Error ? error.message : error}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
import: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
||||
@@ -151,6 +151,14 @@ export const deploymentRouter = createTRPCRouter({
|
||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||
deployment: ["cancel"],
|
||||
});
|
||||
} else if (deployment.schedule?.serverId) {
|
||||
const targetServer = await findServerById(deployment.schedule.serverId);
|
||||
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You don't have access to this deployment.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!deployment.pid) {
|
||||
@@ -188,6 +196,14 @@ export const deploymentRouter = createTRPCRouter({
|
||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||
deployment: ["cancel"],
|
||||
});
|
||||
} else if (deployment.schedule?.serverId) {
|
||||
const targetServer = await findServerById(deployment.schedule.serverId);
|
||||
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You don't have access to this deployment.",
|
||||
});
|
||||
}
|
||||
}
|
||||
const result = await removeDeployment(input.deploymentId);
|
||||
await audit(ctx, {
|
||||
@@ -197,4 +213,47 @@ export const deploymentRouter = createTRPCRouter({
|
||||
});
|
||||
return result;
|
||||
}),
|
||||
|
||||
readLogs: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
deploymentId: z.string().min(1),
|
||||
tail: z.number().int().min(1).max(10000).default(100),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const deployment = await findDeploymentById(input.deploymentId);
|
||||
const serviceId = deployment.applicationId || deployment.composeId;
|
||||
if (serviceId) {
|
||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||
deployment: ["read"],
|
||||
});
|
||||
} else if (deployment.schedule?.serverId) {
|
||||
const targetServer = await findServerById(deployment.schedule.serverId);
|
||||
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You don't have access to this deployment.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!deployment.logPath) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const command = `tail -n ${input.tail} "${deployment.logPath}" 2>/dev/null || echo ""`;
|
||||
const serverId = deployment.serverId || deployment.schedule?.serverId;
|
||||
if (serverId) {
|
||||
const { stdout } = await execAsyncRemote(serverId, command);
|
||||
return stdout;
|
||||
}
|
||||
|
||||
if (IS_CLOUD) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { stdout } = await execAsync(command);
|
||||
return stdout;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
findEnvironmentById,
|
||||
findLibsqlById,
|
||||
findProjectById,
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
stopService,
|
||||
stopServiceRemote,
|
||||
updateLibsqlById,
|
||||
getAccessibleServerIds,
|
||||
} from "@dokploy/server";
|
||||
import {
|
||||
addNewService,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
findEnvironmentById,
|
||||
findMySqlById,
|
||||
findProjectById,
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getServiceContainerCommand,
|
||||
IS_CLOUD,
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
stopService,
|
||||
stopServiceRemote,
|
||||
updateMySqlById,
|
||||
getAccessibleServerIds,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
|
||||
@@ -295,6 +295,14 @@ export const organizationRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
// Owner role is non-delegable — no one can invite as owner
|
||||
if (input.role === "owner") {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Cannot invite a user with the owner role",
|
||||
});
|
||||
}
|
||||
|
||||
// If assigning a custom role, verify it exists
|
||||
if (!["owner", "admin", "member"].includes(input.role)) {
|
||||
const customRole = await db.query.organizationRole.findFirst({
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
findEnvironmentById,
|
||||
findPostgresById,
|
||||
findProjectById,
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getMountPath,
|
||||
getServiceContainerCommand,
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
stopService,
|
||||
stopServiceRemote,
|
||||
updatePostgresById,
|
||||
getAccessibleServerIds,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
|
||||
@@ -856,8 +856,6 @@ export const projectRouter = createTRPCRouter({
|
||||
ctx.session.activeOrganizationId,
|
||||
).then((value) => value.environment);
|
||||
|
||||
console.log("targetProject", targetProject);
|
||||
|
||||
if (input.includeServices) {
|
||||
const servicesToDuplicate = input.selectedServices || [];
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
findEnvironmentById,
|
||||
findProjectById,
|
||||
findRedisById,
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getServiceContainerCommand,
|
||||
IS_CLOUD,
|
||||
@@ -19,7 +20,6 @@ import {
|
||||
stopService,
|
||||
stopServiceRemote,
|
||||
updateRedisById,
|
||||
getAccessibleServerIds,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
findServerById,
|
||||
findServersByUserId,
|
||||
findUserById,
|
||||
getAccessibleServerIds,
|
||||
getPublicIpWithFallback,
|
||||
haveActiveServices,
|
||||
IS_CLOUD,
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
serverValidate,
|
||||
setupMonitoring,
|
||||
updateServerById,
|
||||
getAccessibleServerIds,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
apiUpdateUser,
|
||||
invitation,
|
||||
member,
|
||||
session,
|
||||
user,
|
||||
} from "@dokploy/server/db/schema";
|
||||
import {
|
||||
@@ -32,7 +33,7 @@ import {
|
||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import { and, asc, eq, gt } from "drizzle-orm";
|
||||
import { and, asc, eq, gt, ne } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
@@ -229,6 +230,15 @@ export const userRouter = createTRPCRouter({
|
||||
password: bcrypt.hashSync(input.password, 10),
|
||||
})
|
||||
.where(eq(account.userId, ctx.user.id));
|
||||
|
||||
await db
|
||||
.delete(session)
|
||||
.where(
|
||||
and(
|
||||
eq(session.userId, ctx.user.id),
|
||||
ne(session.id, ctx.session.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -594,6 +604,13 @@ export const userRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
if (input.role === "owner") {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Cannot create a user with the owner role",
|
||||
});
|
||||
}
|
||||
|
||||
return await createOrganizationUserWithCredentials({
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
email: input.email,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import type http from "node:http";
|
||||
import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { encodeBase64 } from "@dokploy/server/utils/docker/utils";
|
||||
import { readValidDirectory } from "@dokploy/server/wss/utils";
|
||||
import { Client } from "ssh2";
|
||||
import { WebSocketServer } from "ws";
|
||||
@@ -70,9 +71,9 @@ export const setupDeploymentLogsWebSocketServer = (
|
||||
sshClient = new Client();
|
||||
sshClient
|
||||
.on("ready", () => {
|
||||
const command = `
|
||||
tail -n +1 -f ${logPath};
|
||||
`;
|
||||
const encodedPath = encodeBase64(logPath);
|
||||
const command = `tail -n +1 -f "$(echo '${encodedPath}' | base64 -d)"`;
|
||||
|
||||
sshClient!.exec(command, (err, stream) => {
|
||||
if (err) {
|
||||
sshClient!.end();
|
||||
|
||||
@@ -29,7 +29,7 @@ export const generateRandomDomain = ({
|
||||
const hash = randomBytes(3).toString("hex");
|
||||
const slugIp = serverIp.replaceAll(".", "-");
|
||||
|
||||
return `${projectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.traefik.me`;
|
||||
return `${projectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.sslip.io`;
|
||||
};
|
||||
|
||||
export const generateHash = (projectName: string, quantity = 3): string => {
|
||||
|
||||
@@ -28,7 +28,13 @@ export const uploadFileToContainerSchema = zfd.formData({
|
||||
.min(1)
|
||||
.regex(/^[a-zA-Z0-9.\-_]+$/, "Invalid container ID"),
|
||||
file: zfd.file(),
|
||||
destinationPath: z.string().min(1),
|
||||
destinationPath: z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(
|
||||
/^[a-zA-Z0-9.\-_/]+$/,
|
||||
"Invalid destination path: only alphanumeric characters, dots, dashes, underscores, and forward slashes are allowed",
|
||||
),
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"micromatch": "4.0.8",
|
||||
"nanoid": "3.3.11",
|
||||
"node-os-utils": "2.0.1",
|
||||
"node-pty": "1.0.0",
|
||||
"node-pty": "1.1.0",
|
||||
"node-schedule": "2.1.1",
|
||||
"nodemailer": "6.9.14",
|
||||
"octokit": "3.1.2",
|
||||
|
||||
@@ -83,11 +83,6 @@ const getDockerConfig = (): Docker => {
|
||||
|
||||
export const docker = getDockerConfig();
|
||||
|
||||
// When not set, use the legacy default so 2FA remains working for users who
|
||||
// enabled it before BETTER_AUTH_SECRET was introduced.
|
||||
export const BETTER_AUTH_SECRET =
|
||||
process.env.BETTER_AUTH_SECRET || "better-auth-secret-123456789";
|
||||
|
||||
export const paths = (isServer = false) => {
|
||||
const BASE_PATH =
|
||||
isServer || process.env.NODE_ENV === "production"
|
||||
|
||||
@@ -9,7 +9,7 @@ export const {
|
||||
POSTGRES_PORT = "5432",
|
||||
} = process.env;
|
||||
|
||||
function readSecret(path: string): string {
|
||||
export function readSecret(path: string): string {
|
||||
try {
|
||||
return fs.readFileSync(path, "utf8").trim();
|
||||
} catch {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
@@ -432,17 +433,22 @@ export const apiSaveBuildType = createSchema
|
||||
.required()
|
||||
.merge(createSchema.pick({ publishDirectory: true, isStaticSpa: true }));
|
||||
|
||||
const branchField = z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(VALID_BRANCH_REGEX, "Invalid branch name");
|
||||
|
||||
export const apiSaveGithubProvider = createSchema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
repository: true,
|
||||
branch: true,
|
||||
owner: true,
|
||||
buildPath: true,
|
||||
githubId: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
branch: branchField,
|
||||
triggerType: z.enum(["push", "tag"]).default("push"),
|
||||
})
|
||||
.required()
|
||||
@@ -451,7 +457,6 @@ export const apiSaveGithubProvider = createSchema
|
||||
export const apiSaveGitlabProvider = createSchema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
gitlabBranch: true,
|
||||
gitlabBuildPath: true,
|
||||
gitlabOwner: true,
|
||||
gitlabRepository: true,
|
||||
@@ -460,11 +465,11 @@ export const apiSaveGitlabProvider = createSchema
|
||||
gitlabPathNamespace: true,
|
||||
})
|
||||
.required()
|
||||
.extend({ gitlabBranch: branchField })
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveBitbucketProvider = createSchema
|
||||
.pick({
|
||||
bitbucketBranch: true,
|
||||
bitbucketBuildPath: true,
|
||||
bitbucketOwner: true,
|
||||
bitbucketRepository: true,
|
||||
@@ -473,18 +478,19 @@ export const apiSaveBitbucketProvider = createSchema
|
||||
applicationId: true,
|
||||
})
|
||||
.required()
|
||||
.extend({ bitbucketBranch: branchField })
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveGiteaProvider = createSchema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
giteaBranch: true,
|
||||
giteaBuildPath: true,
|
||||
giteaOwner: true,
|
||||
giteaRepository: true,
|
||||
giteaId: true,
|
||||
})
|
||||
.required()
|
||||
.extend({ giteaBranch: branchField })
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveDockerProvider = createSchema
|
||||
@@ -499,7 +505,6 @@ export const apiSaveDockerProvider = createSchema
|
||||
|
||||
export const apiSaveGitProvider = createSchema
|
||||
.pick({
|
||||
customGitBranch: true,
|
||||
applicationId: true,
|
||||
customGitBuildPath: true,
|
||||
customGitUrl: true,
|
||||
@@ -507,6 +512,7 @@ export const apiSaveGitProvider = createSchema
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required()
|
||||
.extend({ customGitBranch: branchField })
|
||||
.merge(
|
||||
createSchema.pick({
|
||||
customGitSSHKeyId: true,
|
||||
|
||||
@@ -44,11 +44,29 @@ export const registryRelations = relations(registry, ({ many }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
// Image references require a lowercase namespace (e.g. Docker Hub username).
|
||||
const registryUsernameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.transform((s) => s.toLowerCase());
|
||||
|
||||
// Registry URLs must be hostname[:port] only — no shell metacharacters
|
||||
// Empty string is allowed (means default/Docker Hub registry)
|
||||
const registryUrlSchema = z
|
||||
.string()
|
||||
.refine(
|
||||
(val) =>
|
||||
val === "" ||
|
||||
/^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?(:\d{1,5})?$/.test(val),
|
||||
"Registry URL must be a valid hostname or hostname:port (e.g. registry.example.com or localhost:5000)",
|
||||
);
|
||||
|
||||
const createSchema = createInsertSchema(registry, {
|
||||
registryName: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
username: registryUsernameSchema,
|
||||
password: z.string().min(1),
|
||||
registryUrl: z.string(),
|
||||
registryUrl: registryUrlSchema,
|
||||
organizationId: z.string().min(1),
|
||||
registryId: z.string().min(1),
|
||||
registryType: z.enum(["cloud"]),
|
||||
@@ -59,9 +77,9 @@ export const apiCreateRegistry = createSchema
|
||||
.pick({})
|
||||
.extend({
|
||||
registryName: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
username: registryUsernameSchema,
|
||||
password: z.string().min(1),
|
||||
registryUrl: z.string(),
|
||||
registryUrl: registryUrlSchema,
|
||||
registryType: z.enum(["cloud"]),
|
||||
imagePrefix: z.string().nullable().optional(),
|
||||
})
|
||||
@@ -72,9 +90,9 @@ export const apiCreateRegistry = createSchema
|
||||
|
||||
export const apiTestRegistry = createSchema.pick({}).extend({
|
||||
registryName: z.string().optional(),
|
||||
username: z.string().min(1),
|
||||
username: registryUsernameSchema,
|
||||
password: z.string().min(1),
|
||||
registryUrl: z.string(),
|
||||
registryUrl: registryUrlSchema,
|
||||
registryType: z.enum(["cloud"]),
|
||||
imagePrefix: z.string().nullable().optional(),
|
||||
serverId: z.string().optional(),
|
||||
|
||||
@@ -24,6 +24,7 @@ export const schedules = pgTable("schedule", {
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
cronExpression: text("cronExpression").notNull(),
|
||||
appName: text("appName")
|
||||
.notNull()
|
||||
|
||||
@@ -109,7 +109,7 @@ export * from "./utils/notifications/docker-cleanup";
|
||||
export * from "./utils/notifications/dokploy-restart";
|
||||
export * from "./utils/notifications/server-threshold";
|
||||
export * from "./utils/notifications/utils";
|
||||
export * from "./verification/send-verification-email";
|
||||
export * from "./utils/git-branch-validation";
|
||||
export * from "./utils/process/execAsync";
|
||||
export * from "./utils/process/spawnAsync";
|
||||
export * from "./utils/providers/bitbucket";
|
||||
@@ -135,4 +135,5 @@ export * from "./utils/traefik/types";
|
||||
export * from "./utils/traefik/web-server";
|
||||
export * from "./utils/volume-backups/index";
|
||||
export * from "./utils/watch-paths/should-deploy";
|
||||
export * from "./verification/send-verification-email";
|
||||
export * from "./wss/utils";
|
||||
|
||||
28
packages/server/src/lib/auth-secret.ts
Normal file
28
packages/server/src/lib/auth-secret.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { readSecret } from "../db/constants";
|
||||
|
||||
const HARDCODED_LEGACY_SECRET = "better-auth-secret-123456789";
|
||||
|
||||
const { BETTER_AUTH_SECRET, BETTER_AUTH_SECRET_FILE } = process.env;
|
||||
|
||||
function resolveBetterAuthSecret(): string {
|
||||
if (BETTER_AUTH_SECRET) {
|
||||
return BETTER_AUTH_SECRET;
|
||||
}
|
||||
if (BETTER_AUTH_SECRET_FILE) {
|
||||
return readSecret(BETTER_AUTH_SECRET_FILE);
|
||||
}
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
console.warn(`
|
||||
⚠️ [DEPRECATED AUTH CONFIG]
|
||||
BETTER_AUTH_SECRET is not set via environment variable or Docker secret.
|
||||
Falling back to the insecure hardcoded default — this is a CRITICAL SECURITY RISK.
|
||||
This mode WILL BE REMOVED in a future release.
|
||||
|
||||
Please migrate to Docker Secrets:
|
||||
curl -sSL https://dokploy.com/security/0.29.3.sh | bash
|
||||
`);
|
||||
}
|
||||
return HARDCODED_LEGACY_SECRET;
|
||||
}
|
||||
|
||||
export const betterAuthSecret = resolveBetterAuthSecret();
|
||||
@@ -7,7 +7,7 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { APIError } from "better-auth/api";
|
||||
import { admin, organization, twoFactor } from "better-auth/plugins";
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { BETTER_AUTH_SECRET, IS_CLOUD } from "../constants";
|
||||
import { IS_CLOUD } from "../constants";
|
||||
import { db } from "../db";
|
||||
import * as schema from "../db/schema";
|
||||
import {
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from "../verification/send-verification-email";
|
||||
import { getPublicIpWithFallback } from "../wss/utils";
|
||||
import { ac, adminRole, memberRole, ownerRole } from "./access-control";
|
||||
import { betterAuthSecret } from "./auth-secret";
|
||||
|
||||
const { handler, api } = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
@@ -38,8 +39,9 @@ const { handler, api } = betterAuth({
|
||||
"/organization/create",
|
||||
"/organization/update",
|
||||
"/organization/delete",
|
||||
...(!IS_CLOUD ? ["/verify-email"] : []),
|
||||
],
|
||||
secret: BETTER_AUTH_SECRET,
|
||||
secret: betterAuthSecret,
|
||||
...(!IS_CLOUD
|
||||
? {
|
||||
advanced: {
|
||||
|
||||
@@ -229,7 +229,7 @@ export const suggestVariants = async ({
|
||||
|
||||
Domain Rules - For each service that needs to be exposed to the internet:
|
||||
1. Define a domain with:
|
||||
- host: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me
|
||||
- host: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.sslip.io
|
||||
- port: the internal port the service runs on
|
||||
- serviceName: the name of the service in the docker-compose
|
||||
2. Make sure the service is properly configured to work with the specified port
|
||||
|
||||
@@ -655,6 +655,8 @@ export const getAllContainerStats = async (serverId?: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const destinationPathRegex = /^[a-zA-Z0-9.\-_/]+$/;
|
||||
|
||||
export const uploadFileToContainer = async (
|
||||
containerId: string,
|
||||
fileBuffer: Buffer,
|
||||
@@ -667,7 +669,12 @@ export const uploadFileToContainer = async (
|
||||
throw new Error("Invalid container ID");
|
||||
}
|
||||
|
||||
// Ensure destination path starts with /
|
||||
if (!destinationPathRegex.test(destinationPath)) {
|
||||
throw new Error(
|
||||
"Invalid destination path: shell metacharacters are not allowed",
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedPath = destinationPath.startsWith("/")
|
||||
? destinationPath
|
||||
: `/${destinationPath}`;
|
||||
|
||||
@@ -136,7 +136,7 @@ export const createPreviewDeployment = async (
|
||||
where: eq(organization.id, application.environment.project.organizationId),
|
||||
});
|
||||
const generateDomain = await generateWildcardDomain(
|
||||
application.previewWildcard || "*.traefik.me",
|
||||
application.previewWildcard || "*.sslip.io",
|
||||
appName,
|
||||
application.server?.ipAddress || "",
|
||||
org?.ownerId || "",
|
||||
@@ -238,7 +238,7 @@ const generateWildcardDomain = async (
|
||||
throw new Error('The base domain must start with "*."');
|
||||
}
|
||||
const hash = `${appName}`;
|
||||
if (baseDomain.includes("traefik.me")) {
|
||||
if (baseDomain.includes("sslip.io")) {
|
||||
let ip = "";
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
|
||||
@@ -85,7 +85,7 @@ export const removeRegistry = async (registryId: string) => {
|
||||
}
|
||||
|
||||
if (!IS_CLOUD) {
|
||||
await execAsync(`docker logout ${response.registryUrl}`);
|
||||
await execAsync(`docker logout ${shEscape(response.registryUrl)}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface CompleteTemplate {
|
||||
[key: string]: string;
|
||||
};
|
||||
config: {
|
||||
isolated?: boolean;
|
||||
domains: Array<{
|
||||
serviceName: string;
|
||||
port: number;
|
||||
|
||||
@@ -38,15 +38,15 @@ export const generateRandomDomain = ({
|
||||
const slugIp = serverIp.replaceAll(".", "-").replaceAll(":", "-");
|
||||
|
||||
// Domain labels have a max length of 63 characters
|
||||
// Reserve space for: hash (6) + separators (1-2) + ip section + dot + traefik.me (10)
|
||||
// Approx: 6 + 2 + (variable ip length) + 11 = ~19-30 chars for other parts
|
||||
// Reserve space for: hash (6) + separators (1-2) + ip section + dot + sslip.io (8)
|
||||
// Approx: 6 + 2 + (variable ip length) + 9 = ~19-30 chars for other parts
|
||||
const maxProjectNameLength = 40;
|
||||
const truncatedProjectName =
|
||||
projectName.length > maxProjectNameLength
|
||||
? projectName.substring(0, maxProjectNameLength)
|
||||
: projectName;
|
||||
|
||||
return `${truncatedProjectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.traefik.me`;
|
||||
return `${truncatedProjectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.sslip.io`;
|
||||
};
|
||||
|
||||
export const generateHash = (length = 8): string => {
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface CompleteTemplate {
|
||||
};
|
||||
variables: Record<string, string>;
|
||||
config: {
|
||||
isolated?: boolean;
|
||||
domains: DomainConfig[];
|
||||
env:
|
||||
| Record<string, string | boolean | number>
|
||||
|
||||
@@ -18,7 +18,7 @@ function formatBytes(bytes?: number) {
|
||||
if (bytes === 0) return "0 B";
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
const value = bytes / Math.pow(1024, i);
|
||||
const value = bytes / 1024 ** i;
|
||||
return `${value.toFixed(2)} ${sizes[i]} (${bytes} bytes)`;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ export const getRegistryTag = (registry: Registry, imageName: string) => {
|
||||
// Extract the repository name (last part after '/')
|
||||
const repositoryName = extractRepositoryName(imageName);
|
||||
|
||||
// Build the final tag using registry's username/prefix
|
||||
const targetPrefix = imagePrefix || username;
|
||||
// Build the final tag using registry's username/prefix (must be lowercase for valid image refs)
|
||||
const targetPrefix = (imagePrefix || username).toLowerCase();
|
||||
const finalRegistry = registryUrl || "";
|
||||
|
||||
return finalRegistry
|
||||
|
||||
3
packages/server/src/utils/git-branch-validation.ts
Normal file
3
packages/server/src/utils/git-branch-validation.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// Valid git branch names per git-check-ref-format rules.
|
||||
// Rejects shell metacharacters that would enable command injection.
|
||||
export const VALID_BRANCH_REGEX = /^[a-zA-Z0-9._\-/]+$/;
|
||||
@@ -218,7 +218,11 @@ export const writeConfigRemote = async (
|
||||
try {
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
await execAsyncRemote(serverId, `echo '${traefikConfig}' > ${configPath}`);
|
||||
const encoded = encodeBase64(traefikConfig);
|
||||
await execAsyncRemote(
|
||||
serverId,
|
||||
`echo "${encoded}" | base64 -d > "${configPath}"`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error saving the YAML config file:", e);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ export const readValidDirectory = (
|
||||
directory: string,
|
||||
serverId?: string | null,
|
||||
) => {
|
||||
if (!/^[\w/. :-]{1,500}$/.test(directory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { BASE_PATH } = paths(!!serverId);
|
||||
|
||||
const resolvedBase = path.resolve(BASE_PATH);
|
||||
|
||||
190
pnpm-lock.yaml
generated
190
pnpm-lock.yaml
generated
@@ -51,7 +51,7 @@ importers:
|
||||
version: 4.12.2
|
||||
inngest:
|
||||
specifier: 3.40.1
|
||||
version: 3.40.1(h3@1.15.1)(hono@4.12.2)(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3)
|
||||
version: 3.40.1(encoding@0.1.13)(h3@1.15.1)(hono@4.12.2)(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3)
|
||||
pino:
|
||||
specifier: 9.4.0
|
||||
version: 9.4.0
|
||||
@@ -115,10 +115,10 @@ importers:
|
||||
version: 2.0.30(zod@4.3.6)
|
||||
'@better-auth/api-key':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(219be630f7f6fef2e235cef94eaddc25))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))
|
||||
'@better-auth/sso':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(219be630f7f6fef2e235cef94eaddc25))(better-call@2.0.2(zod@4.3.6))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))(better-call@2.0.2(zod@4.3.6))
|
||||
'@codemirror/autocomplete':
|
||||
specifier: ^6.18.6
|
||||
version: 6.20.0
|
||||
@@ -277,10 +277,10 @@ importers:
|
||||
version: 3.7.1(ai@6.0.97(zod@4.3.6))(zod@4.3.6)
|
||||
bcrypt:
|
||||
specifier: 5.1.1
|
||||
version: 5.1.1
|
||||
version: 5.1.1(encoding@0.1.13)
|
||||
better-auth:
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(219be630f7f6fef2e235cef94eaddc25)
|
||||
version: 1.5.4(71a760b327c31dd12606432855d01199)
|
||||
bl:
|
||||
specifier: 6.0.11
|
||||
version: 6.0.11
|
||||
@@ -354,8 +354,8 @@ importers:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1
|
||||
node-pty:
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0
|
||||
specifier: 1.1.0
|
||||
version: 1.1.0
|
||||
node-schedule:
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1
|
||||
@@ -630,10 +630,10 @@ importers:
|
||||
version: 2.0.30(zod@4.3.6)
|
||||
'@better-auth/api-key':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(8933545d763d3f096150f97f9213a424))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))
|
||||
'@better-auth/sso':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(8933545d763d3f096150f97f9213a424))(better-call@2.0.2(zod@4.3.6))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))(better-call@2.0.2(zod@4.3.6))
|
||||
'@better-auth/utils':
|
||||
specifier: 0.3.1
|
||||
version: 0.3.1
|
||||
@@ -669,10 +669,10 @@ importers:
|
||||
version: 3.7.1(ai@6.0.97(zod@4.3.6))(zod@4.3.6)
|
||||
bcrypt:
|
||||
specifier: 5.1.1
|
||||
version: 5.1.1
|
||||
version: 5.1.1(encoding@0.1.13)
|
||||
better-auth:
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(8933545d763d3f096150f97f9213a424)
|
||||
version: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
||||
better-call:
|
||||
specifier: 2.0.2
|
||||
version: 2.0.2(zod@4.3.6)
|
||||
@@ -716,8 +716,8 @@ importers:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1
|
||||
node-pty:
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0
|
||||
specifier: 1.1.0
|
||||
version: 1.1.0
|
||||
node-schedule:
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1
|
||||
@@ -778,7 +778,7 @@ importers:
|
||||
devDependencies:
|
||||
'@better-auth/cli':
|
||||
specifier: 1.4.21
|
||||
version: 1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0)(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@1.21.7)(tsx@4.16.2)(yaml@2.8.1))
|
||||
version: 1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0(socks@2.8.8))(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1))
|
||||
'@types/adm-zip':
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.7
|
||||
@@ -3433,30 +3433,35 @@ packages:
|
||||
|
||||
'@react-email/body@0.0.8':
|
||||
resolution: {integrity: sha512-gqdkNYlIaIw0OdpWu8KjIcQSIFvx7t2bZpXVxMMvBS859Ia1+1X3b5RNbjI3S1ZqLddUf7owOHkO4MiXGE+nxg==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/button@0.0.15':
|
||||
resolution: {integrity: sha512-9Zi6SO3E8PoHYDfcJTecImiHLyitYWmIRs0HE3Ogra60ZzlWP2EXu+AZqwQnhXuq+9pbgwBWNWxB5YPetNPTNA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/code-block@0.0.5':
|
||||
resolution: {integrity: sha512-mmInpZsSIkNaYC1y40/S0XXrIqbTzrpllP6J1JMJuDOBG8l5T7pNl4V+gwfsSTvy9hVsuzQFmhHK8kVb1UXv3A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/code-inline@0.0.2':
|
||||
resolution: {integrity: sha512-0cmgbbibFeOJl0q04K9jJlPDuJ+SEiX/OG6m3Ko7UOkG3TqjRD8Dtvkij6jNDVfUh/zESpqJCP2CxrCLLMUjdA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/column@0.0.10':
|
||||
resolution: {integrity: sha512-MnP8Mnwipr0X3XtdD6jMLckb0sI5/IlS6Kl/2F6/rsSWBJy5Gg6nizlekTdkwDmy0kNSe3/1nGU0Zqo98pl63Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
@@ -3470,59 +3475,69 @@ packages:
|
||||
'@react-email/container@0.0.12':
|
||||
resolution: {integrity: sha512-HFu8Pu5COPFfeZxSL+wKv/TV5uO/sp4zQ0XkRCdnGkj/xoq0lqOHVDL4yC2Pu6fxXF/9C3PHDA++5uEYV5WVJw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/font@0.0.6':
|
||||
resolution: {integrity: sha512-sZZFvEZ4U3vNCAZ8wXqIO3DuGJR2qE/8m2fEH+tdqwa532zGO3zW+UlCTg0b9455wkJSzEBeaWik0IkNvjXzxw==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/head@0.0.9':
|
||||
resolution: {integrity: sha512-dF3Uv1qy3oh+IU2atXdv5Xk0hk2udOlMb1A/MNGngC0eHyoEV9ThA0XvhN7mm5x9dDLkVamoWUKXDtmkiuSRqQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/heading@0.0.12':
|
||||
resolution: {integrity: sha512-eB7mpnAvDmwvQLoPuwEiPRH4fPXWe6ltz6Ptbry2BlI88F0a2k11Ghb4+sZHBqg7vVw/MKbqEgtLqr3QJ/KfCQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/hr@0.0.8':
|
||||
resolution: {integrity: sha512-JLVvpCg2wYKEB+n/PGCggWG9fRU5e4lxsGdpK5SDLsCL0ic3OLKSpHMfeE+ZSuw0GixAVVQN7F64PVJHQkd4MQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/html@0.0.8':
|
||||
resolution: {integrity: sha512-arII3wBNLpeJtwyIJXPaILm5BPKhA+nvdC1F9QkuKcOBJv2zXctn8XzPqyGqDfdplV692ulNJP7XY55YqbKp6w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/img@0.0.8':
|
||||
resolution: {integrity: sha512-jx/rPuKo31tV18fu7P5rRqelaH5wkhg83Dq7uLwJpfqhbi4KFBGeBfD0Y3PiLPPoh+WvYf+Adv9W2ghNW8nOMQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/link@0.0.8':
|
||||
resolution: {integrity: sha512-nVikuTi8WJHa6Baad4VuRUbUCa/7EtZ1Qy73TRejaCHn+vhetc39XGqHzKLNh+Z/JFL8Hv9g+4AgG16o2R0ogQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/markdown@0.0.10':
|
||||
resolution: {integrity: sha512-MH0xO+NJ4IuJcx9nyxbgGKAMXyudFjCZ0A2GQvuWajemW9qy2hgnJ3mW3/z5lwcenG+JPn7JyO/iZpizQ7u1tA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/preview@0.0.9':
|
||||
resolution: {integrity: sha512-2fyAA/zzZYfYmxfyn3p2YOIU30klyA6Dq4ytyWq4nfzQWWglt5hNDE0cMhObvRtfjM9ghMSVtoELAb0MWiF/kw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
@@ -3536,24 +3551,28 @@ packages:
|
||||
'@react-email/row@0.0.8':
|
||||
resolution: {integrity: sha512-JsB6pxs/ZyjYpEML3nbwJRGAerjcN/Pa/QG48XUwnT/MioDWrUuyQuefw+CwCrSUZ2P1IDrv2tUD3/E3xzcoKw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/section@0.0.12':
|
||||
resolution: {integrity: sha512-UCD/N/BeOTN4h3VZBUaFdiSem6HnpuxD1Q51TdBFnqeNqS5hBomp8LWJJ9s4gzwHWk1XPdNfLA3I/fJwulJshg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/tailwind@0.0.18':
|
||||
resolution: {integrity: sha512-ob8CXX/Pqq1U8YfL5OJTL48WJkixizyoXMMRYTiDLDN9LVLU7lSLtcK9kOD9CgFbO2yUPQr7/5+7gnQJ+cXa8Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-email/text@0.0.8':
|
||||
resolution: {integrity: sha512-uvN2TNWMrfC9wv/LLmMLbbEN1GrMWZb9dBK14eYxHHAEHCeyvGb5ePZZ2MPyzO7Y5yTC+vFEnCEr76V+hWMxCQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
@@ -5351,6 +5370,9 @@ packages:
|
||||
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
encoding@0.1.13:
|
||||
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
@@ -5797,6 +5819,10 @@ packages:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -5843,6 +5869,7 @@ packages:
|
||||
inngest@3.40.1:
|
||||
resolution: {integrity: sha512-SC9Ly28i8NI+WymttE8Jk41L9r/wHXWOnlQoy7e7yoQyZI+R2C4S77DpFwzgEaqGT/H8puc1VDli84RoaffXBg==}
|
||||
engines: {node: '>=14'}
|
||||
deprecated: 'CRITICAL SECURITY: upgrade to >=3.54.0'
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': '>=1.27.3'
|
||||
'@vercel/node': '>=2.15.9'
|
||||
@@ -5897,6 +5924,10 @@ packages:
|
||||
resolution: {integrity: sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
|
||||
ip-address@10.2.0:
|
||||
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
ip-regex@5.0.0:
|
||||
resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -6578,6 +6609,9 @@ packages:
|
||||
node-addon-api@5.1.0:
|
||||
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-addon-api@8.5.0:
|
||||
resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
@@ -6622,8 +6656,8 @@ packages:
|
||||
resolution: {integrity: sha512-rH2N3qHZETLhdgTGhMMCE8zU3gsWO4we1MFtrSiAI7tYWrnJRc6dk2PseV4co3Lb0v/MbRONLQI2biHQYbpTpg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
node-pty@1.0.0:
|
||||
resolution: {integrity: sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==}
|
||||
node-pty@1.1.0:
|
||||
resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==}
|
||||
|
||||
node-releases@2.0.27:
|
||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||
@@ -7564,6 +7598,14 @@ packages:
|
||||
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
socks@2.8.8:
|
||||
resolution: {integrity: sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
sonic-boom@4.2.1:
|
||||
resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
|
||||
|
||||
@@ -8016,6 +8058,7 @@ packages:
|
||||
|
||||
uuid@10.0.0:
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
uuid@11.1.0:
|
||||
@@ -8024,10 +8067,12 @@ packages:
|
||||
|
||||
uuid@8.3.2:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
uuid@9.0.1:
|
||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
valibot@1.2.0:
|
||||
@@ -8625,21 +8670,21 @@ snapshots:
|
||||
|
||||
'@balena/dockerignore@1.0.2': {}
|
||||
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(219be630f7f6fef2e235cef94eaddc25))':
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
better-auth: 1.5.4(219be630f7f6fef2e235cef94eaddc25)
|
||||
better-auth: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
||||
zod: 4.3.6
|
||||
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(8933545d763d3f096150f97f9213a424))':
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
better-auth: 1.5.4(8933545d763d3f096150f97f9213a424)
|
||||
better-auth: 1.5.4(71a760b327c31dd12606432855d01199)
|
||||
zod: 4.3.6
|
||||
|
||||
'@better-auth/cli@1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0)(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@1.21.7)(tsx@4.16.2)(yaml@2.8.1))':
|
||||
'@better-auth/cli@1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0(socks@2.8.8))(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/preset-react': 7.28.5(@babel/core@7.29.0)
|
||||
@@ -8651,7 +8696,7 @@ snapshots:
|
||||
'@mrleebo/prisma-ast': 0.13.1
|
||||
'@prisma/client': 5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
'@types/pg': 8.16.0
|
||||
better-auth: 1.4.21(db78b83f9b5449d160708cdf9d272aa3)
|
||||
better-auth: 1.4.21(97e31320bc7dc8a33b04861de973b388)
|
||||
better-sqlite3: 12.6.2
|
||||
c12: 3.3.3
|
||||
chalk: 5.6.2
|
||||
@@ -8772,11 +8817,11 @@ snapshots:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
|
||||
'@better-auth/mongo-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)':
|
||||
'@better-auth/mongo-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0(socks@2.8.8))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
mongodb: 7.1.0
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
|
||||
'@better-auth/prisma-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
@@ -8785,24 +8830,24 @@ snapshots:
|
||||
'@prisma/client': 5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
prisma: 7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(219be630f7f6fef2e235cef94eaddc25))(better-call@2.0.2(zod@4.3.6))':
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))(better-call@2.0.2(zod@4.3.6))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
better-auth: 1.5.4(219be630f7f6fef2e235cef94eaddc25)
|
||||
better-auth: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
||||
better-call: 2.0.2(zod@4.3.6)
|
||||
fast-xml-parser: 5.5.1
|
||||
jose: 6.1.3
|
||||
samlify: 2.10.2
|
||||
zod: 4.3.6
|
||||
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(8933545d763d3f096150f97f9213a424))(better-call@2.0.2(zod@4.3.6))':
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))(better-call@2.0.2(zod@4.3.6))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
better-auth: 1.5.4(8933545d763d3f096150f97f9213a424)
|
||||
better-auth: 1.5.4(71a760b327c31dd12606432855d01199)
|
||||
better-call: 2.0.2(zod@4.3.6)
|
||||
fast-xml-parser: 5.5.1
|
||||
jose: 6.1.3
|
||||
@@ -9429,12 +9474,12 @@ snapshots:
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@mapbox/node-pre-gyp@1.0.11':
|
||||
'@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)':
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
https-proxy-agent: 5.0.1
|
||||
make-dir: 3.1.0
|
||||
node-fetch: 2.7.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
nopt: 5.0.0
|
||||
npmlog: 5.0.1
|
||||
rimraf: 3.0.2
|
||||
@@ -9724,7 +9769,7 @@ snapshots:
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@opentelemetry/auto-instrumentations-node@0.56.1(@opentelemetry/api@1.9.0)':
|
||||
'@opentelemetry/auto-instrumentations-node@0.56.1(@opentelemetry/api@1.9.0)(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
|
||||
@@ -9771,7 +9816,7 @@ snapshots:
|
||||
'@opentelemetry/resource-detector-aws': 1.12.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resource-detector-azure': 0.6.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resource-detector-container': 0.6.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resource-detector-gcp': 0.33.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resource-detector-gcp': 0.33.1(@opentelemetry/api@1.9.0)(encoding@0.1.13)
|
||||
'@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-node': 0.57.2(@opentelemetry/api@1.9.0)
|
||||
transitivePeerDependencies:
|
||||
@@ -10308,13 +10353,13 @@ snapshots:
|
||||
'@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.39.0
|
||||
|
||||
'@opentelemetry/resource-detector-gcp@0.33.1(@opentelemetry/api@1.9.0)':
|
||||
'@opentelemetry/resource-detector-gcp@0.33.1(@opentelemetry/api@1.9.0)(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.39.0
|
||||
gcp-metadata: 6.1.1
|
||||
gcp-metadata: 6.1.1(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
@@ -12485,9 +12530,9 @@ snapshots:
|
||||
dependencies:
|
||||
tweetnacl: 0.14.5
|
||||
|
||||
bcrypt@5.1.1:
|
||||
bcrypt@5.1.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.11
|
||||
'@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13)
|
||||
node-addon-api: 5.1.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -12495,7 +12540,7 @@ snapshots:
|
||||
|
||||
before-after-hook@2.2.3: {}
|
||||
|
||||
better-auth@1.4.21(db78b83f9b5449d160708cdf9d272aa3):
|
||||
better-auth@1.4.21(97e31320bc7dc8a33b04861de973b388):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
|
||||
'@better-auth/telemetry': 1.4.21(@better-auth/core@1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))
|
||||
@@ -12514,7 +12559,7 @@ snapshots:
|
||||
better-sqlite3: 12.6.2
|
||||
drizzle-kit: 0.31.9
|
||||
drizzle-orm: 0.41.0(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
mongodb: 7.1.0
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
mysql2: 3.15.3
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
pg: 8.18.0
|
||||
@@ -12523,13 +12568,13 @@ snapshots:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@1.21.7)(tsx@4.16.2)(yaml@2.8.1)
|
||||
|
||||
better-auth@1.5.4(219be630f7f6fef2e235cef94eaddc25):
|
||||
better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/drizzle-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))
|
||||
'@better-auth/kysely-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)
|
||||
'@better-auth/memory-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)
|
||||
'@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)
|
||||
'@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0(socks@2.8.8))
|
||||
'@better-auth/prisma-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
'@better-auth/telemetry': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))
|
||||
'@better-auth/utils': 0.3.1
|
||||
@@ -12547,7 +12592,7 @@ snapshots:
|
||||
better-sqlite3: 12.6.2
|
||||
drizzle-kit: 0.31.9
|
||||
drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
mongodb: 7.1.0
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
mysql2: 3.15.3
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
pg: 8.18.0
|
||||
@@ -12558,13 +12603,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@cloudflare/workers-types'
|
||||
|
||||
better-auth@1.5.4(8933545d763d3f096150f97f9213a424):
|
||||
better-auth@1.5.4(71a760b327c31dd12606432855d01199):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/drizzle-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))
|
||||
'@better-auth/kysely-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)
|
||||
'@better-auth/memory-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)
|
||||
'@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)
|
||||
'@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0(socks@2.8.8))
|
||||
'@better-auth/prisma-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
'@better-auth/telemetry': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))
|
||||
'@better-auth/utils': 0.3.1
|
||||
@@ -12582,7 +12627,7 @@ snapshots:
|
||||
better-sqlite3: 12.6.2
|
||||
drizzle-kit: 0.31.9
|
||||
drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
mongodb: 7.1.0
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
mysql2: 3.15.3
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
pg: 8.18.0
|
||||
@@ -12986,9 +13031,9 @@ snapshots:
|
||||
dependencies:
|
||||
luxon: 3.7.2
|
||||
|
||||
cross-fetch@4.1.0:
|
||||
cross-fetch@4.1.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
@@ -13294,6 +13339,11 @@ snapshots:
|
||||
|
||||
empathic@2.0.0: {}
|
||||
|
||||
encoding@0.1.13:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
optional: true
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
@@ -13511,20 +13561,20 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wide-align: 1.1.5
|
||||
|
||||
gaxios@6.7.1:
|
||||
gaxios@6.7.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
is-stream: 2.0.1
|
||||
node-fetch: 2.7.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
uuid: 9.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
gcp-metadata@6.1.1:
|
||||
gcp-metadata@6.1.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
gaxios: 6.7.1
|
||||
gaxios: 6.7.1(encoding@0.1.13)
|
||||
google-logging-utils: 0.0.2
|
||||
json-bigint: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
@@ -13804,6 +13854,11 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
optional: true
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
@@ -13838,13 +13893,13 @@ snapshots:
|
||||
|
||||
inline-style-parser@0.2.7: {}
|
||||
|
||||
inngest@3.40.1(h3@1.15.1)(hono@4.12.2)(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3):
|
||||
inngest@3.40.1(encoding@0.1.13)(h3@1.15.1)(hono@4.12.2)(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@bufbuild/protobuf': 2.11.0
|
||||
'@inngest/ai': 0.1.7
|
||||
'@jpwilliams/waitgroup': 2.1.1
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/auto-instrumentations-node': 0.56.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/auto-instrumentations-node': 0.56.1(@opentelemetry/api@1.9.0)(encoding@0.1.13)
|
||||
'@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-trace-otlp-http': 0.57.2(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
|
||||
@@ -13853,7 +13908,7 @@ snapshots:
|
||||
'@types/debug': 4.1.12
|
||||
canonicalize: 1.0.8
|
||||
chalk: 4.1.2
|
||||
cross-fetch: 4.1.0
|
||||
cross-fetch: 4.1.0(encoding@0.1.13)
|
||||
debug: 4.4.3
|
||||
hash.js: 1.1.7
|
||||
json-stringify-safe: 5.0.1
|
||||
@@ -13910,6 +13965,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ip-address@10.2.0:
|
||||
optional: true
|
||||
|
||||
ip-regex@5.0.0: {}
|
||||
|
||||
iron-webcrypto@1.2.1: {}
|
||||
@@ -14595,11 +14653,13 @@ snapshots:
|
||||
'@types/whatwg-url': 13.0.0
|
||||
whatwg-url: 14.2.0
|
||||
|
||||
mongodb@7.1.0:
|
||||
mongodb@7.1.0(socks@2.8.8):
|
||||
dependencies:
|
||||
'@mongodb-js/saslprep': 1.4.6
|
||||
bson: 7.2.0
|
||||
mongodb-connection-string-url: 7.0.1
|
||||
optionalDependencies:
|
||||
socks: 2.8.8
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
@@ -14643,7 +14703,8 @@ snapshots:
|
||||
dependencies:
|
||||
lru.min: 1.1.4
|
||||
|
||||
nan@2.25.0: {}
|
||||
nan@2.25.0:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
@@ -14702,6 +14763,8 @@ snapshots:
|
||||
|
||||
node-addon-api@5.1.0: {}
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-addon-api@8.5.0:
|
||||
optional: true
|
||||
|
||||
@@ -14714,9 +14777,11 @@ snapshots:
|
||||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
node-fetch@2.7.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
optionalDependencies:
|
||||
encoding: 0.1.13
|
||||
|
||||
node-forge@1.3.3: {}
|
||||
|
||||
@@ -14732,9 +14797,9 @@ snapshots:
|
||||
|
||||
node-os-utils@2.0.1: {}
|
||||
|
||||
node-pty@1.0.0:
|
||||
node-pty@1.1.0:
|
||||
dependencies:
|
||||
nan: 2.25.0
|
||||
node-addon-api: 7.1.1
|
||||
|
||||
node-releases@2.0.27: {}
|
||||
|
||||
@@ -15763,6 +15828,15 @@ snapshots:
|
||||
|
||||
slugify@1.6.6: {}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
optional: true
|
||||
|
||||
socks@2.8.8:
|
||||
dependencies:
|
||||
ip-address: 10.2.0
|
||||
smart-buffer: 4.2.0
|
||||
optional: true
|
||||
|
||||
sonic-boom@4.2.1:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
|
||||
Reference in New Issue
Block a user