Compare commits

..

30 Commits

Author SHA1 Message Date
Mauricio Siu
6968cb6930 Merge pull request #1465 from zaaakher/docs/guides
docs: update `CONTRIBUTING.md` and add `GUIDES.md`
2025-03-29 14:28:24 -06:00
Mauricio Siu
a431e4c58e Update CONTRIBUTING.md 2025-03-29 14:28:11 -06:00
Mauricio Siu
c5b4b85470 Merge pull request #1578 from Dokploy/fix/biome-lint
chore(workflow): add Biome code formatting workflow for canary branch
2025-03-29 13:41:49 -06:00
Mauricio Siu
b1ef9d25b1 chore(workflow): add autofix.ci workflow for automatic code formatting on canary branch 2025-03-29 13:41:05 -06:00
Mauricio Siu
74f7c51530 chore(workflow): add autofix.ci workflow for automatic code formatting with Biome 2025-03-29 13:39:57 -06:00
Mauricio Siu
4ba2b9fe8d chore(workflow): add new Biome formatting workflow for canary branch 2025-03-29 13:38:55 -06:00
Mauricio Siu
413eda50f4 chore(workflow): simplify AutoFix action usage in Biome workflow 2025-03-29 13:37:43 -06:00
Mauricio Siu
9f09681708 chore(workflow): streamline Biome setup by replacing Node.js and pnpm steps with biomeJs action 2025-03-29 13:37:01 -06:00
Mauricio Siu
8eb174812d chore(workflow): replace manual commit step with AutoFix action for Biome formatting 2025-03-29 13:35:50 -06:00
Mauricio Siu
be77f114eb Merge ca42708035 into beadcf871a 2025-03-29 19:30:24 +00:00
Mauricio Siu
ca42708035 chore(workflow): configure git user for automated commits and enforce push 2025-03-29 13:30:18 -06:00
Mauricio Siu
8b03454a87 chore(workflow): update Biome workflow to push changes to the correct branch 2025-03-29 13:28:50 -06:00
Mauricio Siu
fa7f749f84 refactor(dokploy): standardize code formatting and improve readability across multiple components 2025-03-29 13:26:44 -06:00
Mauricio Siu
3daecd7d71 chore(workflow): update pnpm version to 9.5.0 in Biome workflow 2025-03-29 13:20:48 -06:00
Mauricio Siu
0666b5b292 chore(workflow): add pnpm setup step to Biome workflow 2025-03-29 13:20:07 -06:00
Mauricio Siu
b288ddd826 chore(workflow): add Biome code formatting workflow for canary branch 2025-03-29 13:18:50 -06:00
Mauricio Siu
beadcf871a Merge pull request #1577 from Dokploy/1568-dokploy---nextjs-affected-by-cve-2025-29927
refactor(dokploy): remove lucia-auth adapter and related authenticati…
2025-03-29 12:23:26 -06:00
Mauricio Siu
ee49dadf0b refactor(dokploy): remove lucia-auth adapter and related authentication logic; update next.js version to 15.2.4 2025-03-29 12:17:14 -06:00
Mauricio Siu
46de83a1de Merge pull request #1576 from Dokploy/1564-downloaded-ssh-keys-are-always-named-as-id_rsa-keys
refactor(ssh-keys): simplify downloadKey function and filename genera…
2025-03-29 12:13:38 -06:00
Mauricio Siu
fee5024b7d refactor(ssh-keys): simplify downloadKey function and filename generation logic 2025-03-29 12:13:22 -06:00
Mauricio Siu
e0433e9f7b Merge pull request #1554 from Dokploy/1061-custom-docker-service-hostname
1061 custom docker service hostname
2025-03-23 23:58:28 -06:00
Mauricio Siu
d29ff881fc fix(redis-connection): update Redis host configuration to use environment variable for production 2025-03-23 23:43:38 -06:00
Mauricio Siu
568c3a1d06 chore(workflow): update branches for Dokploy Docker build to use custom hostname 2025-03-23 23:38:55 -06:00
Mauricio Siu
e9fd280fa2 refactor(server): comment out initialization of Postgres, Traefik, and Redis for testing purposes 2025-03-23 23:28:53 -06:00
Mauricio Siu
9535fca28f Merge pull request #1540 from vicke4/backup-deletion-fix
fix(backups): auto deletion of backups
2025-03-23 04:31:37 -06:00
Mauricio Siu
dd62d603e0 Merge pull request #1550 from Dokploy/1543-preview-docker-compose-button-null-when-git-is-provider
feat(dashboard): add informational alert for docker-compose preview r…
2025-03-23 04:30:58 -06:00
Mauricio Siu
8d227e2a2c feat(dashboard): add informational alert for docker-compose preview requirements 2025-03-23 04:30:00 -06:00
vicke4
68d0a48843 fix(backups): auto deletion of backups 2025-03-21 01:36:11 +05:30
Zakher Masri
bc78100613 remove redis part 2025-03-18 12:02:03 +03:00
Zakher Masri
ac0922d742 docs: update CONTRIBUTING.md and add GUIDES.md 2025-03-11 14:38:37 +03:00
30 changed files with 1045 additions and 1667 deletions

View File

@@ -2,7 +2,7 @@ name: Dokploy Docker Build
on:
push:
branches: [main, canary, "feat/better-auth-2"]
branches: [main, canary, "1061-custom-docker-service-hostname"]
env:
IMAGE_NAME: dokploy/dokploy

22
.github/workflows/format.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: autofix.ci
on:
push:
branches: [canary]
pull_request:
branches: [canary]
jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup biomeJs
uses: biomejs/setup-biome@v2
- name: Run Biome formatter
run: biome format . --write
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef

View File

@@ -61,9 +61,9 @@ pnpm install
cp apps/dokploy/.env.example apps/dokploy/.env
```
## Development
## Requirements
Is required to have **Docker** installed on your machine.
- [Docker](/GUIDES.md#docker)
### Setup

49
GUIDES.md Normal file
View File

@@ -0,0 +1,49 @@
# Docker
Here's how to install docker on different operating systems:
## macOS
1. Visit [Docker Desktop for Mac](https://www.docker.com/products/docker-desktop)
2. Download the Docker Desktop installer
3. Double-click the downloaded `.dmg` file
4. Drag Docker to your Applications folder
5. Open Docker Desktop from Applications
6. Follow the onboarding tutorial if desired
## Linux
### Ubuntu
```bash
# Update package index
sudo apt-get update
# Install prerequisites
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Set up stable repository
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
```
## Windows
1. Enable WSL2 if not already enabled
2. Visit [Docker Desktop for Windows](https://www.docker.com/products/docker-desktop)
3. Download the installer
4. Run the installer and follow the prompts
5. Start Docker Desktop from the Start menu

View File

@@ -115,7 +115,11 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Username" autoComplete="username" {...field} />
<Input
placeholder="Username"
autoComplete="username"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -130,7 +134,12 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="Password" autoComplete="one-time-code" {...field} type="password" />
<Input
placeholder="Password"
autoComplete="one-time-code"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -147,7 +147,9 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Enable Isolated Deployment ({data?.appName})</FormLabel>
<FormLabel>
Enable Isolated Deployment ({data?.appName})
</FormLabel>
<FormDescription>
Enable isolated deployment to the compose file.
</FormDescription>

View File

@@ -62,6 +62,11 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<AlertBlock type="info">
Preview your docker-compose file with added domains. Note: At least
one domain must be specified for this conversion to take effect.
</AlertBlock>
<div className="flex flex-row gap-2 justify-end">
<Button
variant="secondary"

View File

@@ -286,16 +286,21 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
<FormItem>
<FormLabel>Keep the latest</FormLabel>
<FormControl>
<Input type="number" placeholder={"keeps all the backups if left empty"} {...field} />
<Input
type="number"
placeholder={"keeps all the backups if left empty"}
{...field}
/>
</FormControl>
<FormDescription>
Optional. If provided, only keeps the latest N backups in the cloud.
Optional. If provided, only keeps the latest N backups
in the cloud.
</FormDescription>
<FormMessage />
</FormItem>
);
}}
/>
/>
<FormField
control={form.control}
name="enabled"

View File

@@ -92,7 +92,9 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
enabled: backup.enabled || false,
prefix: backup.prefix,
schedule: backup.schedule,
keepLatestCount: backup.keepLatestCount ? Number(backup.keepLatestCount) : undefined,
keepLatestCount: backup.keepLatestCount
? Number(backup.keepLatestCount)
: undefined,
});
}
}, [form, form.reset, backup]);
@@ -274,10 +276,15 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
<FormItem>
<FormLabel>Keep the latest</FormLabel>
<FormControl>
<Input type="number" placeholder={"keeps all the backups if left empty"} {...field} />
<Input
type="number"
placeholder={"keeps all the backups if left empty"}
{...field}
/>
</FormControl>
<FormDescription>
Optional. If provided, only keeps the latest N backups in the cloud.
Optional. If provided, only keeps the latest N backups
in the cloud.
</FormDescription>
<FormMessage />
</FormItem>

View File

@@ -27,145 +27,149 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
});
type DockerProvider = z.infer<typeof DockerProviderSchema>;
interface Props {
mariadbId: string;
mariadbId: string;
}
export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
mariadbId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
mariadbId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
form,
data?.databaseName,
data?.databaseUser,
getIp,
]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid md:grid-cols-2 gap-4 ">
<div className="md:col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="3306"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
{/* jdbc:mariadb://5.161.59.207:3306/pixel-calculate?user=mariadb&password=HdVXfq6hM7W7F1 */}
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
form,
data?.databaseName,
data?.databaseUser,
getIp,
]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid md:grid-cols-2 gap-4 ">
<div className="md:col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="3306"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
{/* jdbc:mariadb://5.161.59.207:3306/pixel-calculate?user=mariadb&password=HdVXfq6hM7W7F1 */}
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
};

View File

@@ -27,144 +27,148 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
});
type DockerProvider = z.infer<typeof DockerProviderSchema>;
interface Props {
mongoId: string;
mongoId: string;
}
export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.mongo.one.useQuery({ mongoId });
const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.mongo.one.useQuery({ mongoId });
const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
mongoId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
mongoId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`;
};
return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`;
};
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
form,
data?.databaseUser,
getIp,
]);
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
form,
data?.databaseUser,
getIp,
]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="27017"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="27017"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
};

View File

@@ -27,144 +27,148 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
});
type DockerProvider = z.infer<typeof DockerProviderSchema>;
interface Props {
mysqlId: string;
mysqlId: string;
}
export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
mysqlId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
mysqlId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
data?.databaseName,
data?.databaseUser,
form,
getIp,
]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="3306"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput disabled value={connectionUrl} />
</div>
</div>
)}
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
data?.databaseName,
data?.databaseUser,
form,
getIp,
]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="3306"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput disabled value={connectionUrl} />
</div>
</div>
)}
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
};

View File

@@ -27,146 +27,150 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
});
type DockerProvider = z.infer<typeof DockerProviderSchema>;
interface Props {
postgresId: string;
postgresId: string;
}
export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.postgres.one.useQuery({ postgresId });
const { mutateAsync, isLoading } =
api.postgres.saveExternalPort.useMutation();
const getIp = data?.server?.ipAddress || ip;
const [connectionUrl, setConnectionUrl] = useState("");
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.postgres.one.useQuery({ postgresId });
const { mutateAsync, isLoading } =
api.postgres.saveExternalPort.useMutation();
const getIp = data?.server?.ipAddress || ip;
const [connectionUrl, setConnectionUrl] = useState("");
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
postgresId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
postgresId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
useEffect(() => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
form,
data?.databaseName,
getIp,
]);
setConnectionUrl(buildConnectionUrl());
}, [
data?.appName,
data?.externalPort,
data?.databasePassword,
form,
data?.databaseName,
getIp,
]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="5432"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="5432"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
};

View File

@@ -5,58 +5,58 @@ import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
interface Props {
postgresId: string;
postgresId: string;
}
export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
const { data } = api.postgres.one.useQuery({ postgresId });
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">Internal Credentials</CardTitle>
</CardHeader>
<CardContent className="flex w-full flex-row gap-4">
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
<div className="flex flex-col gap-2">
<Label>User</Label>
<Input disabled value={data?.databaseUser} />
</div>
<div className="flex flex-col gap-2">
<Label>Database Name</Label>
<Input disabled value={data?.databaseName} />
</div>
<div className="flex flex-col gap-2">
<Label>Password</Label>
<div className="flex flex-row gap-4">
<ToggleVisibilityInput
value={data?.databasePassword}
disabled
/>
</div>
</div>
<div className="flex flex-col gap-2">
<Label>Internal Port (Container)</Label>
<Input disabled value="5432" />
</div>
const { data } = api.postgres.one.useQuery({ postgresId });
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">Internal Credentials</CardTitle>
</CardHeader>
<CardContent className="flex w-full flex-row gap-4">
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
<div className="flex flex-col gap-2">
<Label>User</Label>
<Input disabled value={data?.databaseUser} />
</div>
<div className="flex flex-col gap-2">
<Label>Database Name</Label>
<Input disabled value={data?.databaseName} />
</div>
<div className="flex flex-col gap-2">
<Label>Password</Label>
<div className="flex flex-row gap-4">
<ToggleVisibilityInput
value={data?.databasePassword}
disabled
/>
</div>
</div>
<div className="flex flex-col gap-2">
<Label>Internal Port (Container)</Label>
<Input disabled value="5432" />
</div>
<div className="flex flex-col gap-2">
<Label>Internal Host</Label>
<Input disabled value={data?.appName} />
</div>
<div className="flex flex-col gap-2">
<Label>Internal Host</Label>
<Input disabled value={data?.appName} />
</div>
<div className="flex flex-col gap-2">
<Label>Internal Connection URL </Label>
<ToggleVisibilityInput
disabled
value={`postgresql://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:5432/${data?.databaseName}`}
/>
</div>
</div>
</CardContent>
</Card>
</div>
</>
);
<div className="flex flex-col gap-2">
<Label>Internal Connection URL </Label>
<ToggleVisibilityInput
disabled
value={`postgresql://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:5432/${data?.databaseName}`}
/>
</div>
</div>
</CardContent>
</Card>
</div>
</>
);
};
// ReplyError: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-w

View File

@@ -28,139 +28,139 @@ import { toast } from "sonner";
import { z } from "zod";
const updatePostgresSchema = z.object({
name: z.string().min(1, {
message: "Name is required",
}),
description: z.string().optional(),
name: z.string().min(1, {
message: "Name is required",
}),
description: z.string().optional(),
});
type UpdatePostgres = z.infer<typeof updatePostgresSchema>;
interface Props {
postgresId: string;
postgresId: string;
}
export const UpdatePostgres = ({ postgresId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, error, isError, isLoading } =
api.postgres.update.useMutation();
const { data } = api.postgres.one.useQuery(
{
postgresId,
},
{
enabled: !!postgresId,
}
);
const form = useForm<UpdatePostgres>({
defaultValues: {
description: data?.description ?? "",
name: data?.name ?? "",
},
resolver: zodResolver(updatePostgresSchema),
});
useEffect(() => {
if (data) {
form.reset({
description: data.description ?? "",
name: data.name,
});
}
}, [data, form, form.reset]);
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, error, isError, isLoading } =
api.postgres.update.useMutation();
const { data } = api.postgres.one.useQuery(
{
postgresId,
},
{
enabled: !!postgresId,
},
);
const form = useForm<UpdatePostgres>({
defaultValues: {
description: data?.description ?? "",
name: data?.name ?? "",
},
resolver: zodResolver(updatePostgresSchema),
});
useEffect(() => {
if (data) {
form.reset({
description: data.description ?? "",
name: data.name,
});
}
}, [data, form, form.reset]);
const onSubmit = async (formData: UpdatePostgres) => {
await mutateAsync({
name: formData.name,
postgresId: postgresId,
description: formData.description || "",
})
.then(() => {
toast.success("Postgres updated successfully");
utils.postgres.one.invalidate({
postgresId: postgresId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error updating Postgres");
})
.finally(() => {});
};
const onSubmit = async (formData: UpdatePostgres) => {
await mutateAsync({
name: formData.name,
postgresId: postgresId,
description: formData.description || "",
})
.then(() => {
toast.success("Postgres updated successfully");
utils.postgres.one.invalidate({
postgresId: postgresId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error updating Postgres");
})
.finally(() => {});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<PenBox className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Modify Postgres</DialogTitle>
<DialogDescription>Update the Postgres data</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<PenBox className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Modify Postgres</DialogTitle>
<DialogDescription>Update the Postgres data</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-update-postgres"
className="grid w-full gap-4 "
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Vandelay Industries" {...field} />
</FormControl>
<div className="grid gap-4">
<div className="grid items-center gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-update-postgres"
className="grid w-full gap-4 "
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Vandelay Industries" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Description about your project..."
className="resize-none"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Description about your project..."
className="resize-none"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-update-postgres"
type="submit"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Update
</Button>
</DialogFooter>
</form>
</Form>
</div>
</div>
</DialogContent>
</Dialog>
);
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-update-postgres"
type="submit"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Update
</Button>
</DialogFooter>
</form>
</Form>
</div>
</div>
</DialogContent>
</Dialog>
);
};

View File

@@ -186,7 +186,9 @@ export const ShowProjects = () => {
target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
>
<span className="truncate">{domain.host}</span>
<span className="truncate">
{domain.host}
</span>
<ExternalLinkIcon className="size-4 shrink-0" />
</Link>
</DropdownMenuItem>
@@ -222,7 +224,9 @@ export const ShowProjects = () => {
target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
>
<span className="truncate">{domain.host}</span>
<span className="truncate">
{domain.host}
</span>
<ExternalLinkIcon className="size-4 shrink-0" />
</Link>
</DropdownMenuItem>

View File

@@ -27,138 +27,142 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
externalPort: z.preprocess((a) => {
if (a !== null) {
const parsed = Number.parseInt(z.string().parse(a), 10);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
}, z
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
});
type DockerProvider = z.infer<typeof DockerProviderSchema>;
interface Props {
redisId: string;
redisId: string;
}
export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.redis.one.useQuery({ redisId });
const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const { data: ip } = api.settings.getIp.useQuery();
const { data, refetch } = api.redis.one.useQuery({ redisId });
const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
});
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
useEffect(() => {
if (data?.externalPort) {
form.reset({
externalPort: data.externalPort,
});
}
}, [form.reset, data, form]);
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
redisId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
const onSubmit = async (values: DockerProvider) => {
await mutateAsync({
externalPort: values.externalPort,
redisId,
})
.then(async () => {
toast.success("External Port updated");
await refetch();
})
.catch(() => {
toast.error("Error saving the external port");
});
};
useEffect(() => {
const buildConnectionUrl = () => {
const _hostname = window.location.hostname;
const port = form.watch("externalPort") || data?.externalPort;
useEffect(() => {
const buildConnectionUrl = () => {
const _hostname = window.location.hostname;
const port = form.watch("externalPort") || data?.externalPort;
return `redis://default:${data?.databasePassword}@${getIp}:${port}`;
};
return `redis://default:${data?.databasePassword}@${getIp}:${port}`;
};
setConnectionUrl(buildConnectionUrl());
}, [data?.appName, data?.externalPort, data?.databasePassword, form, getIp]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="6379"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
setConnectionUrl(buildConnectionUrl());
}, [data?.appName, data?.externalPort, data?.databasePassword, form, getIp]);
return (
<>
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">External Credentials</CardTitle>
<CardDescription>
In order to make the database reachable trought internet is
required to set a port, make sure the port is not used by another
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex w-full flex-col gap-4">
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to fix the database url connection.
</AlertBlock>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<div className="grid grid-cols-2 gap-4 ">
<div className="col-span-2 space-y-4">
<FormField
control={form.control}
name="externalPort"
render={({ field }) => {
return (
<FormItem>
<FormLabel>External Port (Internet)</FormLabel>
<FormControl>
<Input
placeholder="6379"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</div>
{!!data?.externalPort && (
<div className="grid w-full gap-8">
<div className="flex flex-col gap-3">
<Label>External Host</Label>
<ToggleVisibilityInput value={connectionUrl} disabled />
</div>
</div>
)}
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
<div className="flex justify-end">
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
</div>
</>
);
};

View File

@@ -112,15 +112,17 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
toast.error("Error generating the SSH Key");
});
const downloadKey = (
content: string,
defaultFilename: string,
keyType: "private" | "public",
) => {
const downloadKey = (content: string, keyType: "private" | "public") => {
const keyName = form.watch("name");
const publicKey = form.watch("publicKey");
// Extract algorithm type from public key
const isEd25519 = publicKey.startsWith("ssh-ed25519");
const defaultName = isEd25519 ? "id_ed25519" : "id_rsa";
const filename = keyName
? `${keyName}${sshKeyId ? `_${sshKeyId}` : ""}_${keyType}_${defaultFilename}`
: `${keyType}_${defaultFilename}`;
? `${keyName}${sshKeyId ? `_${sshKeyId}` : ""}_${keyType}_${defaultName}${keyType === "public" ? ".pub" : ""}`
: `${defaultName}${keyType === "public" ? ".pub" : ""}`;
const blob = new Blob([content], { type: "text/plain" });
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
@@ -273,7 +275,7 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
variant="outline"
size="default"
onClick={() =>
downloadKey(form.watch("privateKey"), "id_rsa", "private")
downloadKey(form.watch("privateKey"), "private")
}
className="flex items-center gap-2"
>
@@ -287,11 +289,7 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
variant="outline"
size="default"
onClick={() =>
downloadKey(
form.watch("publicKey"),
"id_rsa.pub",
"public",
)
downloadKey(form.watch("publicKey"), "public")
}
className="flex items-center gap-2"
>

View File

@@ -161,7 +161,11 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
{...field}
onChange={(e) => {
const value = e.target.value;
field.onChange(value === "" ? undefined : Number(value));
field.onChange(
value === ""
? undefined
: Number(value),
);
}}
value={field.value || ""}
className="w-full dark:bg-black"
@@ -189,7 +193,11 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
{...field}
onChange={(e) => {
const value = e.target.value;
field.onChange(value === "" ? undefined : Number(value));
field.onChange(
value === ""
? undefined
: Number(value),
);
}}
value={field.value || ""}
className="w-full dark:bg-black"

View File

@@ -37,7 +37,9 @@ export const BreadcrumbSidebar = ({ list }: Props) => {
)}
</BreadcrumbLink>
</BreadcrumbItem>
{_index + 1 < list.length && <BreadcrumbSeparator className="block" />}
{_index + 1 < list.length && (
<BreadcrumbSeparator className="block" />
)}
</Fragment>
))}
</BreadcrumbList>

View File

@@ -1,32 +0,0 @@
// middleware.ts
import { verifyRequestOrigin } from "lucia";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function middleware(request: NextRequest): Promise<NextResponse> {
if (request.method === "GET") {
return NextResponse.next();
}
const originHeader = request.headers.get("Origin");
const hostHeader = request.headers.get("Host");
if (
!originHeader ||
!hostHeader ||
!verifyRequestOrigin(originHeader, [hostHeader])
) {
return new NextResponse(null, {
status: 403,
});
}
return NextResponse.next();
}
export const config = {
matcher: [
// Don't handle HMR requests for the dev server we rewrite to
"/settings",
"/dashboard/(.*)",
"/invitation",
],
};

View File

@@ -5,23 +5,23 @@
/** @type {import("next").NextConfig} */
const nextConfig = {
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
transpilePackages: ["@dokploy/server"],
/**
* If you are using `appDir` then you must comment the below `i18n` config out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
},
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
transpilePackages: ["@dokploy/server"],
/**
* If you are using `appDir` then you must comment the below `i18n` config out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
export default nextConfig;

View File

@@ -53,7 +53,6 @@
"@dokploy/trpc-openapi": "0.0.4",
"@faker-js/faker": "^8.4.1",
"@hookform/resolvers": "^3.9.0",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@octokit/auth-app": "^6.0.4",
"@octokit/webhooks": "^13.2.7",
"@radix-ui/react-accordion": "1.1.2",
@@ -113,11 +112,10 @@
"js-cookie": "^3.0.5",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"lucia": "^3.0.1",
"lucide-react": "^0.469.0",
"micromatch": "4.0.8",
"nanoid": "3",
"next": "^15.0.1",
"next": "^15.2.4",
"next-i18next": "^15.3.1",
"next-themes": "^0.2.1",
"node-os-utils": "1.3.7",

View File

@@ -1,4 +1,3 @@
import { authRouter } from "@/server/api/routers/auth";
import { createTRPCRouter } from "../api/trpc";
import { adminRouter } from "./routers/admin";
import { aiRouter } from "./routers/ai";
@@ -44,7 +43,6 @@ import { userRouter } from "./routers/user";
export const appRouter = createTRPCRouter({
admin: adminRouter,
docker: dockerRouter,
auth: authRouter,
project: projectRouter,
application: applicationRouter,
mysql: mysqlRouter,

View File

@@ -1,326 +0,0 @@
import { createTRPCRouter } from "../trpc";
export const authRouter = createTRPCRouter({
// createAdmin: publicProcedure.mutation(async ({ input }) => {
// try {
// if (!IS_CLOUD) {
// const admin = await db.query.admins.findFirst({});
// if (admin) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "Admin already exists",
// });
// }
// }
// const newAdmin = await createAdmin(input);
// if (IS_CLOUD) {
// await sendDiscordNotificationWelcome(newAdmin);
// await sendVerificationEmail(newAdmin.id);
// return {
// status: "success",
// type: "cloud",
// };
// }
// // const session = await lucia.createSession(newAdmin.id || "", {});
// // ctx.res.appendHeader(
// // "Set-Cookie",
// // lucia.createSessionCookie(session.id).serialize(),
// // );
// return {
// status: "success",
// type: "selfhosted",
// };
// } catch (error) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// // @ts-ignore
// message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`,
// cause: error,
// });
// }
// }),
// createUser: publicProcedure.mutation(async ({ input }) => {
// try {
// const _token = await getUserByToken(input.token);
// // if (token.isExpired) {
// // throw new TRPCError({
// // code: "BAD_REQUEST",
// // message: "Invalid token",
// // });
// // }
// // const newUser = await createUser(input);
// // if (IS_CLOUD) {
// // await sendVerificationEmail(token.authId);
// // return true;
// // }
// // const session = await lucia.createSession(newUser?.authId || "", {});
// // ctx.res.appendHeader(
// // "Set-Cookie",
// // lucia.createSessionCookie(session.id).serialize(),
// // );
// return true;
// } catch (error) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "Error creating the user",
// cause: error,
// });
// }
// }),
// login: publicProcedure.mutation(async ({ input }) => {
// try {
// const auth = await findAuthByEmail(input.email);
// const correctPassword = bcrypt.compareSync(
// input.password,
// auth?.password || "",
// );
// if (!correctPassword) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "Credentials do not match",
// });
// }
// if (auth?.confirmationToken && IS_CLOUD) {
// await sendVerificationEmail(auth.id);
// throw new TRPCError({
// code: "BAD_REQUEST",
// message:
// "Email not confirmed, we have sent you a confirmation email please check your inbox.",
// });
// }
// if (auth?.is2FAEnabled) {
// return {
// is2FAEnabled: true,
// authId: auth.id,
// };
// }
// // const session = await lucia.createSession(auth?.id || "", {});
// // ctx.res.appendHeader(
// // "Set-Cookie",
// // lucia.createSessionCookie(session.id).serialize(),
// // );
// return {
// is2FAEnabled: false,
// authId: auth?.id,
// };
// } catch (error) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: `Error: ${error instanceof Error ? error.message : "Login error"}`,
// cause: error,
// });
// }
// }),
// get: protectedProcedure.query(async ({ ctx }) => {
// const memberResult = await db.query.member.findFirst({
// where: and(
// eq(member.userId, ctx.user.id),
// eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
// ),
// with: {
// user: true,
// },
// });
// return memberResult;
// }),
// logout: protectedProcedure.mutation(async ({ ctx }) => {
// const { req } = ctx;
// const { session } = await validateRequest(req);
// if (!session) return false;
// // await lucia.invalidateSession(session.id);
// // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
// return true;
// }),
// update: protectedProcedure.mutation(async ({ ctx, input }) => {
// const currentAuth = await findAuthByEmail(ctx.user.email);
// if (input.currentPassword || input.password) {
// const correctPassword = bcrypt.compareSync(
// input.currentPassword || "",
// currentAuth?.password || "",
// );
// if (!correctPassword) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "Current password is incorrect",
// });
// }
// }
// // const auth = await updateAuthById(ctx.user.authId, {
// // ...(input.email && { email: input.email.toLowerCase() }),
// // ...(input.password && {
// // password: bcrypt.hashSync(input.password, 10),
// // }),
// // ...(input.image && { image: input.image }),
// // });
// return auth;
// }),
// removeSelfAccount: protectedProcedure
// .input(
// z.object({
// password: z.string().min(1),
// }),
// )
// .mutation(async ({ ctx, input }) => {
// if (!IS_CLOUD) {
// throw new TRPCError({
// code: "NOT_FOUND",
// message: "This feature is only available in the cloud version",
// });
// }
// const currentAuth = await findAuthByEmail(ctx.user.email);
// const correctPassword = bcrypt.compareSync(
// input.password,
// currentAuth?.password || "",
// );
// if (!correctPassword) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "Password is incorrect",
// });
// }
// const { req } = ctx;
// const { session } = await validateRequest(req);
// if (!session) return false;
// // await lucia.invalidateSession(session.id);
// // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
// // if (ctx.user.rol === "owner") {
// // await removeAdminByAuthId(ctx.user.authId);
// // } else {
// // await removeUserByAuthId(ctx.user.authId);
// // }
// return true;
// }),
// generateToken: protectedProcedure.mutation(async ({ ctx }) => {
// const auth = await findUserById(ctx.user.id);
// console.log(auth);
// // if (auth.token) {
// // await luciaToken.invalidateSession(auth.token);
// // }
// // const session = await luciaToken.createSession(auth?.id || "", {
// // expiresIn: 60 * 60 * 24 * 30,
// // });
// // await updateUser(auth.id, {
// // token: session.id,
// // });
// return auth;
// }),
// verifyToken: protectedProcedure.mutation(async () => {
// return true;
// }),
// one: adminProcedure
// .input(z.object({ userId: z.string().min(1) }))
// .query(async ({ input }) => {
// // TODO: Check if the user is admin or member
// const user = await findUserById(input.userId);
// return user;
// }),
// sendResetPasswordEmail: publicProcedure
// .input(
// z.object({
// email: z.string().min(1).email(),
// }),
// )
// .mutation(async ({ input }) => {
// if (!IS_CLOUD) {
// throw new TRPCError({
// code: "NOT_FOUND",
// message: "This feature is only available in the cloud version",
// });
// }
// const authR = await db.query.auth.findFirst({
// where: eq(auth.email, input.email),
// });
// if (!authR) {
// throw new TRPCError({
// code: "NOT_FOUND",
// message: "User not found",
// });
// }
// const token = nanoid();
// await updateAuthById(authR.id, {
// resetPasswordToken: token,
// // Make resetPassword in 24 hours
// resetPasswordExpiresAt: new Date(
// new Date().getTime() + 24 * 60 * 60 * 1000,
// ).toISOString(),
// });
// await sendEmailNotification(
// {
// fromAddress: process.env.SMTP_FROM_ADDRESS!,
// toAddresses: [authR.email],
// smtpServer: process.env.SMTP_SERVER!,
// smtpPort: Number(process.env.SMTP_PORT),
// username: process.env.SMTP_USERNAME!,
// password: process.env.SMTP_PASSWORD!,
// },
// "Reset Password",
// `
// Reset your password by clicking the link below:
// The link will expire in 24 hours.
// <a href="${WEBSITE_URL}/reset-password?token=${token}">
// Reset Password
// </a>
// `,
// );
// }),
});
// export const sendVerificationEmail = async (authId: string) => {
// const token = nanoid();
// const result = await updateAuthById(authId, {
// confirmationToken: token,
// confirmationExpiresAt: new Date(
// new Date().getTime() + 24 * 60 * 60 * 1000,
// ).toISOString(),
// });
// if (!result) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "User not found",
// });
// }
// await sendEmailNotification(
// {
// fromAddress: process.env.SMTP_FROM_ADDRESS || "",
// toAddresses: [result?.email],
// smtpServer: process.env.SMTP_SERVER || "",
// smtpPort: Number(process.env.SMTP_PORT),
// username: process.env.SMTP_USERNAME || "",
// password: process.env.SMTP_PASSWORD || "",
// },
// "Confirm your email | Dokploy",
// `
// Welcome to Dokploy!
// Please confirm your email by clicking the link below:
// <a href="${WEBSITE_URL}/confirm-email?token=${result?.confirmationToken}">
// Confirm Email
// </a>
// `,
// );
// return true;
// };
// export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
// await sendDiscordNotification(
// {
// webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
// },
// {
// title: "New User Registered",
// color: 0x00ff00,
// fields: [
// {
// name: "Email",
// value: newAdmin.email,
// inline: true,
// },
// ],
// timestamp: newAdmin.createdAt,
// footer: {
// text: "Dokploy User Registration Notification",
// },
// },
// );
// };

View File

@@ -1,5 +1,8 @@
import type { ConnectionOptions } from "bullmq";
export const redisConfig: ConnectionOptions = {
host: process.env.NODE_ENV === "production" ? "dokploy-redis" : "127.0.0.1",
host:
process.env.NODE_ENV === "production"
? process.env.REDIS_HOST || "dokploy-redis"
: "127.0.0.1",
};

View File

@@ -7,9 +7,6 @@ import {
createDefaultTraefikConfig,
initCronJobs,
initializeNetwork,
initializePostgres,
initializeRedis,
initializeTraefik,
sendDokployRestartNotifications,
setupDirectories,
} from "@dokploy/server";
@@ -49,14 +46,7 @@ void app.prepare().then(async () => {
await initializeNetwork();
createDefaultTraefikConfig();
createDefaultServerTraefikConfig();
await initializePostgres();
await initializeTraefik();
await initializeRedis();
initCronJobs();
// Timeout to wait for the database to be ready
await new Promise((resolve) => setTimeout(resolve, 7000));
await migration();
await sendDokployRestartNotifications();
}

View File

@@ -42,7 +42,6 @@
"drizzle-dbml-generator":"0.10.0",
"better-auth":"1.2.4",
"@faker-js/faker": "^8.4.1",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@octokit/auth-app": "^6.0.4",
"@react-email/components": "^0.0.21",
"@trpc/server": "^10.43.6",
@@ -59,7 +58,6 @@
"hi-base32": "^0.5.1",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"lucia": "^3.0.1",
"nanoid": "3",
"node-os-utils": "1.3.7",
"node-pty": "1.0.0",

View File

@@ -80,7 +80,8 @@ export const initCronJobs = async () => {
console.log(
`PG-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
);
runPostgresBackup(pg, backup);
await runPostgresBackup(pg, backup);
await keepLatestNBackups(backup, pg.serverId);
});
}
}
@@ -112,6 +113,7 @@ export const initCronJobs = async () => {
`MARIADB-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
);
await runMariadbBackup(maria, backup);
await keepLatestNBackups(backup, maria.serverId);
});
}
}
@@ -141,6 +143,7 @@ export const initCronJobs = async () => {
`MONGO-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
);
await runMongoBackup(mongo, backup);
await keepLatestNBackups(backup, mongo.serverId);
});
}
}
@@ -170,6 +173,7 @@ export const initCronJobs = async () => {
`MYSQL-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
);
await runMySqlBackup(mysql, backup);
await keepLatestNBackups(backup, mysql.serverId);
});
}
}

502
pnpm-lock.yaml generated
View File

@@ -148,9 +148,6 @@ importers:
'@hookform/resolvers':
specifier: ^3.9.0
version: 3.9.0(react-hook-form@7.52.1(react@18.2.0))
'@lucia-auth/adapter-drizzle':
specifier: 1.0.7
version: 1.0.7(lucia@3.2.0)
'@octokit/auth-app':
specifier: ^6.0.4
version: 6.1.1
@@ -237,7 +234,7 @@ importers:
version: 10.45.2(@trpc/server@10.45.2)
'@trpc/next':
specifier: ^10.43.6
version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@trpc/react-query':
specifier: ^10.43.6
version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -328,9 +325,6 @@ importers:
lodash:
specifier: 4.17.21
version: 4.17.21
lucia:
specifier: ^3.0.1
version: 3.2.0
lucide-react:
specifier: ^0.469.0
version: 0.469.0(react@18.2.0)
@@ -341,14 +335,14 @@ importers:
specifier: '3'
version: 3.3.7
next:
specifier: ^15.0.1
version: 15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
specifier: ^15.2.4
version: 15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next-i18next:
specifier: ^15.3.1
version: 15.3.1(i18next@23.16.5)(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
version: 15.3.1(i18next@23.16.5)(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
next-themes:
specifier: ^0.2.1
version: 0.2.1(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
version: 0.2.1(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
node-os-utils:
specifier: 1.3.7
version: 1.3.7
@@ -615,9 +609,6 @@ importers:
'@faker-js/faker':
specifier: ^8.4.1
version: 8.4.1
'@lucia-auth/adapter-drizzle':
specifier: 1.0.7
version: 1.0.7(lucia@3.2.0)
'@octokit/auth-app':
specifier: ^6.0.4
version: 6.1.1
@@ -681,9 +672,6 @@ importers:
lodash:
specifier: 4.17.21
version: 4.17.21
lucia:
specifier: ^3.0.1
version: 3.2.0
micromatch:
specifier: 4.0.8
version: 4.0.8
@@ -1107,12 +1095,6 @@ packages:
'@drizzle-team/brocli@0.10.2':
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
'@emnapi/core@0.45.0':
resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==}
'@emnapi/runtime@0.45.0':
resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==}
'@emnapi/runtime@1.3.1':
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
@@ -1901,11 +1883,6 @@ packages:
'@lezer/yaml@1.0.3':
resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==}
'@lucia-auth/adapter-drizzle@1.0.7':
resolution: {integrity: sha512-X/V7fLBca8EC/gPXCntwbQpb0+F9oEuRoHElvsi9rCrdnGhCMNxHgwAvgiQ6pes+rIYpyvx4n3hvjqo/fPo03A==}
peerDependencies:
lucia: 3.x
'@mapbox/node-pre-gyp@1.0.11':
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
@@ -1945,53 +1922,53 @@ packages:
peerDependencies:
redis: ^4.7.0
'@next/env@15.0.1':
resolution: {integrity: sha512-lc4HeDUKO9gxxlM5G2knTRifqhsY6yYpwuHspBZdboZe0Gp+rZHBNNSIjmQKDJIdRXiXGyVnSD6gafrbQPvILQ==}
'@next/env@15.2.4':
resolution: {integrity: sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==}
'@next/swc-darwin-arm64@15.0.1':
resolution: {integrity: sha512-C9k/Xv4sxkQRTA37Z6MzNq3Yb1BJMmSqjmwowoWEpbXTkAdfOwnoKOpAb71ItSzoA26yUTIo6ZhN8rKGu4ExQw==}
'@next/swc-darwin-arm64@15.2.4':
resolution: {integrity: sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@15.0.1':
resolution: {integrity: sha512-uHl13HXOuq1G7ovWFxCACDJHTSDVbn/sbLv8V1p+7KIvTrYQ5HNoSmKBdYeEKRRCbEmd+OohOgg9YOp8Ux3MBg==}
'@next/swc-darwin-x64@15.2.4':
resolution: {integrity: sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@15.0.1':
resolution: {integrity: sha512-LvyhvxHOihFTEIbb35KxOc3q8w8G4xAAAH/AQnsYDEnOvwawjL2eawsB59AX02ki6LJdgDaHoTEnC54Gw+82xw==}
'@next/swc-linux-arm64-gnu@15.2.4':
resolution: {integrity: sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@15.0.1':
resolution: {integrity: sha512-vFmCGUFNyk/A5/BYcQNhAQqPIw01RJaK6dRO+ZEhz0DncoW+hJW1kZ8aH2UvTX27zPq3m85zN5waMSbZEmANcQ==}
'@next/swc-linux-arm64-musl@15.2.4':
resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@15.0.1':
resolution: {integrity: sha512-5by7IYq0NCF8rouz6Qg9T97jYU68kaClHPfGpQG2lCZpSYHtSPQF1kjnqBTd34RIqPKMbCa4DqCufirgr8HM5w==}
'@next/swc-linux-x64-gnu@15.2.4':
resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@15.0.1':
resolution: {integrity: sha512-lmYr6H3JyDNBJLzklGXLfbehU3ay78a+b6UmBGlHls4xhDXBNZfgb0aI67sflrX+cGBnv1LgmWzFlYrAYxS1Qw==}
'@next/swc-linux-x64-musl@15.2.4':
resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@15.0.1':
resolution: {integrity: sha512-DS8wQtl6diAj0eZTdH0sefykm4iXMbHT4MOvLwqZiIkeezKpkgPFcEdFlz3vKvXa2R/2UEgMh48z1nEpNhjeOQ==}
'@next/swc-win32-arm64-msvc@15.2.4':
resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-x64-msvc@15.0.1':
resolution: {integrity: sha512-4Ho2ggvDdMKlZ/0e9HNdZ9ngeaBwtc+2VS5oCeqrbXqOgutX6I4U2X/42VBw0o+M5evn4/7v3zKgGHo+9v/VjA==}
'@next/swc-win32-x64-msvc@15.2.4':
resolution: {integrity: sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -2007,180 +1984,6 @@ packages:
resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
engines: {node: ^14.21.3 || >=16}
'@node-rs/argon2-android-arm-eabi@1.7.0':
resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [android]
'@node-rs/argon2-android-arm64@1.7.0':
resolution: {integrity: sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@node-rs/argon2-darwin-arm64@1.7.0':
resolution: {integrity: sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@node-rs/argon2-darwin-x64@1.7.0':
resolution: {integrity: sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@node-rs/argon2-freebsd-x64@1.7.0':
resolution: {integrity: sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@node-rs/argon2-linux-arm-gnueabihf@1.7.0':
resolution: {integrity: sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@node-rs/argon2-linux-arm64-gnu@1.7.0':
resolution: {integrity: sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@node-rs/argon2-linux-arm64-musl@1.7.0':
resolution: {integrity: sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@node-rs/argon2-linux-x64-gnu@1.7.0':
resolution: {integrity: sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@node-rs/argon2-linux-x64-musl@1.7.0':
resolution: {integrity: sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@node-rs/argon2-wasm32-wasi@1.7.0':
resolution: {integrity: sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
'@node-rs/argon2-win32-arm64-msvc@1.7.0':
resolution: {integrity: sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@node-rs/argon2-win32-ia32-msvc@1.7.0':
resolution: {integrity: sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@node-rs/argon2-win32-x64-msvc@1.7.0':
resolution: {integrity: sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@node-rs/argon2@1.7.0':
resolution: {integrity: sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==}
engines: {node: '>= 10'}
'@node-rs/bcrypt-android-arm-eabi@1.9.0':
resolution: {integrity: sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [android]
'@node-rs/bcrypt-android-arm64@1.9.0':
resolution: {integrity: sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@node-rs/bcrypt-darwin-arm64@1.9.0':
resolution: {integrity: sha512-CQiS+F9Pa0XozvkXR1g7uXE9QvBOPOplDg0iCCPRYTN9PqA5qYxhwe48G3o+v2UeQceNRrbnEtWuANm7JRqIhw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@node-rs/bcrypt-darwin-x64@1.9.0':
resolution: {integrity: sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@node-rs/bcrypt-freebsd-x64@1.9.0':
resolution: {integrity: sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0':
resolution: {integrity: sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@node-rs/bcrypt-linux-arm64-gnu@1.9.0':
resolution: {integrity: sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@node-rs/bcrypt-linux-arm64-musl@1.9.0':
resolution: {integrity: sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@node-rs/bcrypt-linux-x64-gnu@1.9.0':
resolution: {integrity: sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@node-rs/bcrypt-linux-x64-musl@1.9.0':
resolution: {integrity: sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@node-rs/bcrypt-wasm32-wasi@1.9.0':
resolution: {integrity: sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
'@node-rs/bcrypt-win32-arm64-msvc@1.9.0':
resolution: {integrity: sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@node-rs/bcrypt-win32-ia32-msvc@1.9.0':
resolution: {integrity: sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@node-rs/bcrypt-win32-x64-msvc@1.9.0':
resolution: {integrity: sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@node-rs/bcrypt@1.9.0':
resolution: {integrity: sha512-u2OlIxW264bFUfvbFqDz9HZKFjwe8FHFtn7T/U8mYjPZ7DWYpbUB+/dkW/QgYfMSfR0ejkyuWaBBe0coW7/7ig==}
engines: {node: '>= 10'}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -3513,8 +3316,8 @@ packages:
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/helpers@0.5.13':
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@szmarczak/http-timer@5.0.1':
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
@@ -3595,9 +3398,6 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
'@tybys/wasm-util@0.8.3':
resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==}
'@types/adm-zip@0.5.5':
resolution: {integrity: sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==}
@@ -4975,9 +4775,6 @@ packages:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
fs-monkey@1.0.6:
resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -5673,9 +5470,6 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
lucia@3.2.0:
resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==}
lucide-react@0.469.0:
resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==}
peerDependencies:
@@ -5737,13 +5531,6 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
memfs-browser@3.5.10302:
resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==}
memfs@3.5.3:
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
engines: {node: '>= 4.0.0'}
memfs@4.11.0:
resolution: {integrity: sha512-+6kz90/YQoZuHvg3rn1CGPMZfEMaU5xe7xIavZMNiom2RNesiI8S37p9O9n+PlIUnUgretjLdM6HnqpZYl3X2g==}
engines: {node: '>= 4.0.0'}
@@ -6004,16 +5791,16 @@ packages:
react: '*'
react-dom: '*'
next@15.0.1:
resolution: {integrity: sha512-PSkFkr/w7UnFWm+EP8y/QpHrJXMqpZzAXpergB/EqLPOh4SGPJXv1wj4mslr2hUZBAS9pX7/9YLIdxTv6fwytw==}
engines: {node: '>=18.18.0'}
next@15.2.4:
resolution: {integrity: sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@playwright/test': ^1.41.2
babel-plugin-react-compiler: '*'
react: ^18.2.0 || 19.0.0-rc-69d4b800-20241021
react-dom: ^18.2.0 || 19.0.0-rc-69d4b800-20241021
react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
sass: ^1.3.0
peerDependenciesMeta:
'@opentelemetry/api':
@@ -6177,10 +5964,6 @@ packages:
openapi-types@12.1.3:
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
oslo@1.2.0:
resolution: {integrity: sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==}
deprecated: Package is no longer supported. Please see https://oslojs.dev for the successor project.
otpauth@9.3.4:
resolution: {integrity: sha512-qXv+lpsCUO9ewitLYfeDKbLYt7UUCivnU/fwGK2OqhgrCBsRkTUNKWsgKAhkXG3aistOY+jEeuL90JEBu6W3mQ==}
@@ -8017,16 +7800,6 @@ snapshots:
'@drizzle-team/brocli@0.10.2': {}
'@emnapi/core@0.45.0':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/runtime@0.45.0':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.3.1':
dependencies:
tslib: 2.8.1
@@ -8521,10 +8294,6 @@ snapshots:
'@lezer/highlight': 1.2.0
'@lezer/lr': 1.4.2
'@lucia-auth/adapter-drizzle@1.0.7(lucia@3.2.0)':
dependencies:
lucia: 3.2.0
'@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)':
dependencies:
detect-libc: 2.0.3
@@ -8563,30 +8332,30 @@ snapshots:
async-await-queue: 2.1.4
redis: 4.7.0
'@next/env@15.0.1': {}
'@next/env@15.2.4': {}
'@next/swc-darwin-arm64@15.0.1':
'@next/swc-darwin-arm64@15.2.4':
optional: true
'@next/swc-darwin-x64@15.0.1':
'@next/swc-darwin-x64@15.2.4':
optional: true
'@next/swc-linux-arm64-gnu@15.0.1':
'@next/swc-linux-arm64-gnu@15.2.4':
optional: true
'@next/swc-linux-arm64-musl@15.0.1':
'@next/swc-linux-arm64-musl@15.2.4':
optional: true
'@next/swc-linux-x64-gnu@15.0.1':
'@next/swc-linux-x64-gnu@15.2.4':
optional: true
'@next/swc-linux-x64-musl@15.0.1':
'@next/swc-linux-x64-musl@15.2.4':
optional: true
'@next/swc-win32-arm64-msvc@15.0.1':
'@next/swc-win32-arm64-msvc@15.2.4':
optional: true
'@next/swc-win32-x64-msvc@15.0.1':
'@next/swc-win32-x64-msvc@15.2.4':
optional: true
'@noble/ciphers@0.6.0': {}
@@ -8595,134 +8364,6 @@ snapshots:
'@noble/hashes@1.7.1': {}
'@node-rs/argon2-android-arm-eabi@1.7.0':
optional: true
'@node-rs/argon2-android-arm64@1.7.0':
optional: true
'@node-rs/argon2-darwin-arm64@1.7.0':
optional: true
'@node-rs/argon2-darwin-x64@1.7.0':
optional: true
'@node-rs/argon2-freebsd-x64@1.7.0':
optional: true
'@node-rs/argon2-linux-arm-gnueabihf@1.7.0':
optional: true
'@node-rs/argon2-linux-arm64-gnu@1.7.0':
optional: true
'@node-rs/argon2-linux-arm64-musl@1.7.0':
optional: true
'@node-rs/argon2-linux-x64-gnu@1.7.0':
optional: true
'@node-rs/argon2-linux-x64-musl@1.7.0':
optional: true
'@node-rs/argon2-wasm32-wasi@1.7.0':
dependencies:
'@emnapi/core': 0.45.0
'@emnapi/runtime': 0.45.0
'@tybys/wasm-util': 0.8.3
memfs-browser: 3.5.10302
optional: true
'@node-rs/argon2-win32-arm64-msvc@1.7.0':
optional: true
'@node-rs/argon2-win32-ia32-msvc@1.7.0':
optional: true
'@node-rs/argon2-win32-x64-msvc@1.7.0':
optional: true
'@node-rs/argon2@1.7.0':
optionalDependencies:
'@node-rs/argon2-android-arm-eabi': 1.7.0
'@node-rs/argon2-android-arm64': 1.7.0
'@node-rs/argon2-darwin-arm64': 1.7.0
'@node-rs/argon2-darwin-x64': 1.7.0
'@node-rs/argon2-freebsd-x64': 1.7.0
'@node-rs/argon2-linux-arm-gnueabihf': 1.7.0
'@node-rs/argon2-linux-arm64-gnu': 1.7.0
'@node-rs/argon2-linux-arm64-musl': 1.7.0
'@node-rs/argon2-linux-x64-gnu': 1.7.0
'@node-rs/argon2-linux-x64-musl': 1.7.0
'@node-rs/argon2-wasm32-wasi': 1.7.0
'@node-rs/argon2-win32-arm64-msvc': 1.7.0
'@node-rs/argon2-win32-ia32-msvc': 1.7.0
'@node-rs/argon2-win32-x64-msvc': 1.7.0
'@node-rs/bcrypt-android-arm-eabi@1.9.0':
optional: true
'@node-rs/bcrypt-android-arm64@1.9.0':
optional: true
'@node-rs/bcrypt-darwin-arm64@1.9.0':
optional: true
'@node-rs/bcrypt-darwin-x64@1.9.0':
optional: true
'@node-rs/bcrypt-freebsd-x64@1.9.0':
optional: true
'@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0':
optional: true
'@node-rs/bcrypt-linux-arm64-gnu@1.9.0':
optional: true
'@node-rs/bcrypt-linux-arm64-musl@1.9.0':
optional: true
'@node-rs/bcrypt-linux-x64-gnu@1.9.0':
optional: true
'@node-rs/bcrypt-linux-x64-musl@1.9.0':
optional: true
'@node-rs/bcrypt-wasm32-wasi@1.9.0':
dependencies:
'@emnapi/core': 0.45.0
'@emnapi/runtime': 0.45.0
'@tybys/wasm-util': 0.8.3
memfs-browser: 3.5.10302
optional: true
'@node-rs/bcrypt-win32-arm64-msvc@1.9.0':
optional: true
'@node-rs/bcrypt-win32-ia32-msvc@1.9.0':
optional: true
'@node-rs/bcrypt-win32-x64-msvc@1.9.0':
optional: true
'@node-rs/bcrypt@1.9.0':
optionalDependencies:
'@node-rs/bcrypt-android-arm-eabi': 1.9.0
'@node-rs/bcrypt-android-arm64': 1.9.0
'@node-rs/bcrypt-darwin-arm64': 1.9.0
'@node-rs/bcrypt-darwin-x64': 1.9.0
'@node-rs/bcrypt-freebsd-x64': 1.9.0
'@node-rs/bcrypt-linux-arm-gnueabihf': 1.9.0
'@node-rs/bcrypt-linux-arm64-gnu': 1.9.0
'@node-rs/bcrypt-linux-arm64-musl': 1.9.0
'@node-rs/bcrypt-linux-x64-gnu': 1.9.0
'@node-rs/bcrypt-linux-x64-musl': 1.9.0
'@node-rs/bcrypt-wasm32-wasi': 1.9.0
'@node-rs/bcrypt-win32-arm64-msvc': 1.9.0
'@node-rs/bcrypt-win32-ia32-msvc': 1.9.0
'@node-rs/bcrypt-win32-x64-msvc': 1.9.0
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -10348,7 +9989,7 @@ snapshots:
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.13':
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@@ -10389,13 +10030,13 @@ snapshots:
dependencies:
'@trpc/server': 10.45.2
'@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
'@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@trpc/client': 10.45.2(@trpc/server@10.45.2)
'@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@trpc/server': 10.45.2
next: 15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next: 15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -10421,11 +10062,6 @@ snapshots:
'@tsconfig/node16@1.0.4':
optional: true
'@tybys/wasm-util@0.8.3':
dependencies:
tslib: 2.8.1
optional: true
'@types/adm-zip@0.5.5':
dependencies:
'@types/node': 20.14.10
@@ -11844,9 +11480,6 @@ snapshots:
dependencies:
minipass: 3.3.6
fs-monkey@1.0.6:
optional: true
fs.realpath@1.0.0: {}
fsevents@2.3.3:
@@ -12616,10 +12249,6 @@ snapshots:
yallist: 4.0.0
optional: true
lucia@3.2.0:
dependencies:
oslo: 1.2.0
lucide-react@0.469.0(react@18.2.0):
dependencies:
react: 18.2.0
@@ -12758,16 +12387,6 @@ snapshots:
media-typer@0.3.0: {}
memfs-browser@3.5.10302:
dependencies:
memfs: 3.5.3
optional: true
memfs@3.5.3:
dependencies:
fs-monkey: 1.0.6
optional: true
memfs@4.11.0:
dependencies:
'@jsonjoy.com/json-pack': 1.0.4(tslib@2.6.3)
@@ -13064,7 +12683,7 @@ snapshots:
neotraverse@0.6.18: {}
next-i18next@15.3.1(i18next@23.16.5)(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
next-i18next@15.3.1(i18next@23.16.5)(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.25.0
'@types/hoist-non-react-statics': 3.3.5
@@ -13072,21 +12691,21 @@ snapshots:
hoist-non-react-statics: 3.3.2
i18next: 23.16.5
i18next-fs-backend: 2.3.2
next: 15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next: 15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-i18next: 15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next-themes@0.2.1(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
next-themes@0.2.1(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
next: 15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next: 15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@next/env': 15.0.1
'@next/env': 15.2.4
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.13
'@swc/helpers': 0.5.15
busboy: 1.6.0
caniuse-lite: 1.0.30001643
postcss: 8.4.31
@@ -13094,14 +12713,14 @@ snapshots:
react-dom: 18.2.0(react@18.2.0)
styled-jsx: 5.1.6(react@18.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.0.1
'@next/swc-darwin-x64': 15.0.1
'@next/swc-linux-arm64-gnu': 15.0.1
'@next/swc-linux-arm64-musl': 15.0.1
'@next/swc-linux-x64-gnu': 15.0.1
'@next/swc-linux-x64-musl': 15.0.1
'@next/swc-win32-arm64-msvc': 15.0.1
'@next/swc-win32-x64-msvc': 15.0.1
'@next/swc-darwin-arm64': 15.2.4
'@next/swc-darwin-x64': 15.2.4
'@next/swc-linux-arm64-gnu': 15.2.4
'@next/swc-linux-arm64-musl': 15.2.4
'@next/swc-linux-x64-gnu': 15.2.4
'@next/swc-linux-x64-musl': 15.2.4
'@next/swc-win32-arm64-msvc': 15.2.4
'@next/swc-win32-x64-msvc': 15.2.4
'@opentelemetry/api': 1.9.0
sharp: 0.33.5
transitivePeerDependencies:
@@ -13274,11 +12893,6 @@ snapshots:
openapi-types@12.1.3: {}
oslo@1.2.0:
dependencies:
'@node-rs/argon2': 1.7.0
'@node-rs/bcrypt': 1.9.0
otpauth@9.3.4:
dependencies:
'@noble/hashes': 1.5.0
@@ -13477,7 +13091,7 @@ snapshots:
postcss@8.4.31:
dependencies:
nanoid: 3.3.7
nanoid: 3.3.8
picocolors: 1.0.1
source-map-js: 1.2.0