mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-09 21:45:17 +00:00
Merge pull request #2601 from OliverGeneser/feat/libsql
feat: add libSQL database
This commit is contained in:
@@ -19,7 +19,7 @@ Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies th
|
|||||||
Dokploy includes multiple features to make your life easier.
|
Dokploy includes multiple features to make your life easier.
|
||||||
|
|
||||||
- **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
- **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
||||||
- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, and Redis.
|
- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, libsql, and Redis.
|
||||||
- **Backups**: Automate backups for databases to an external storage destination.
|
- **Backups**: Automate backups for databases to an external storage destination.
|
||||||
- **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
- **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
||||||
- **Multi Node**: Scale applications to multiple nodes using Docker Swarm to manage the cluster.
|
- **Multi Node**: Scale applications to multiple nodes using Docker Swarm to manage the cluster.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const ENTERPRISE_RESOURCES = [
|
|||||||
"domain",
|
"domain",
|
||||||
"destination",
|
"destination",
|
||||||
"notification",
|
"notification",
|
||||||
|
"tag",
|
||||||
"logs",
|
"logs",
|
||||||
"monitoring",
|
"monitoring",
|
||||||
"auditLog",
|
"auditLog",
|
||||||
|
|||||||
+8
-8
@@ -110,16 +110,16 @@ const menuItems: MenuItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const hasStopGracePeriodSwarm = (
|
|
||||||
value: unknown,
|
|
||||||
): value is { stopGracePeriodSwarm: bigint | number | string | null } =>
|
|
||||||
typeof value === "object" &&
|
|
||||||
value !== null &&
|
|
||||||
"stopGracePeriodSwarm" in value;
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "application"
|
||||||
|
| "libsql"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "postgres"
|
||||||
|
| "redis";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddSwarmSettings = ({ id, type }: Props) => {
|
export const AddSwarmSettings = ({ id, type }: Props) => {
|
||||||
|
|||||||
+15
-14
@@ -37,7 +37,7 @@ import { AddSwarmSettings } from "./modify-swarm-settings";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type: "application" | "mariadb" | "mongo" | "mysql" | "postgres" | "redis";
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddRedirectchema = z.object({
|
const AddRedirectchema = z.object({
|
||||||
@@ -49,15 +49,15 @@ type AddCommand = z.infer<typeof AddRedirectchema>;
|
|||||||
|
|
||||||
export const ShowClusterSettings = ({ id, type }: Props) => {
|
export const ShowClusterSettings = ({ id, type }: Props) => {
|
||||||
const queryMap = {
|
const queryMap = {
|
||||||
|
application: () =>
|
||||||
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
|
mariadb: () =>
|
||||||
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
postgres: () =>
|
postgres: () =>
|
||||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
|
||||||
mariadb: () =>
|
|
||||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
|
||||||
application: () =>
|
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -65,12 +65,13 @@ export const ShowClusterSettings = ({ id, type }: Props) => {
|
|||||||
const { data: registries } = api.registry.all.useQuery();
|
const { data: registries } = api.registry.all.useQuery();
|
||||||
|
|
||||||
const mutationMap = {
|
const mutationMap = {
|
||||||
|
application: () => api.application.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
postgres: () => api.postgres.update.useMutation(),
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
redis: () => api.redis.update.useMutation(),
|
redis: () => api.redis.update.useMutation(),
|
||||||
mysql: () => api.mysql.update.useMutation(),
|
|
||||||
mariadb: () => api.mariadb.update.useMutation(),
|
|
||||||
application: () => api.application.update.useMutation(),
|
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync, isPending } = mutationMap[type]
|
const { mutateAsync, isPending } = mutationMap[type]
|
||||||
@@ -105,11 +106,11 @@ export const ShowClusterSettings = ({ id, type }: Props) => {
|
|||||||
const onSubmit = async (data: AddCommand) => {
|
const onSubmit = async (data: AddCommand) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId: id || "",
|
applicationId: id || "",
|
||||||
postgresId: id || "",
|
|
||||||
redisId: id || "",
|
|
||||||
mysqlId: id || "",
|
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
mysqlId: id || "",
|
||||||
|
postgresId: id || "",
|
||||||
|
redisId: id || "",
|
||||||
...(type === "application"
|
...(type === "application"
|
||||||
? {
|
? {
|
||||||
registryId:
|
registryId:
|
||||||
|
|||||||
+11
-1
@@ -28,7 +28,14 @@ export const endpointSpecFormSchema = z.object({
|
|||||||
|
|
||||||
interface EndpointSpecFormProps {
|
interface EndpointSpecFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EndpointSpecForm = ({ id, type }: EndpointSpecFormProps) => {
|
export const EndpointSpecForm = ({ id, type }: EndpointSpecFormProps) => {
|
||||||
@@ -44,6 +51,7 @@ export const EndpointSpecForm = ({ id, type }: EndpointSpecFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -56,6 +64,7 @@ export const EndpointSpecForm = ({ id, type }: EndpointSpecFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -94,6 +103,7 @@ export const EndpointSpecForm = ({ id, type }: EndpointSpecFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
endpointSpecSwarm: hasAnyValue ? formData : null,
|
endpointSpecSwarm: hasAnyValue ? formData : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -26,7 +26,14 @@ export const healthCheckFormSchema = z.object({
|
|||||||
|
|
||||||
interface HealthCheckFormProps {
|
interface HealthCheckFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => {
|
export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => {
|
||||||
@@ -42,6 +49,7 @@ export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -54,6 +62,7 @@ export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -104,6 +113,7 @@ export const HealthCheckForm = ({ id, type }: HealthCheckFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
healthCheckSwarm: hasAnyValue ? formData : null,
|
healthCheckSwarm: hasAnyValue ? formData : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -29,7 +29,14 @@ export const labelsFormSchema = z.object({
|
|||||||
|
|
||||||
interface LabelsFormProps {
|
interface LabelsFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LabelsForm = ({ id, type }: LabelsFormProps) => {
|
export const LabelsForm = ({ id, type }: LabelsFormProps) => {
|
||||||
@@ -45,6 +52,7 @@ export const LabelsForm = ({ id, type }: LabelsFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -57,6 +65,7 @@ export const LabelsForm = ({ id, type }: LabelsFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -112,6 +121,7 @@ export const LabelsForm = ({ id, type }: LabelsFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
labelsSwarm: labelsToSend,
|
labelsSwarm: labelsToSend,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -23,7 +23,14 @@ import { api } from "@/utils/api";
|
|||||||
|
|
||||||
interface ModeFormProps {
|
interface ModeFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModeForm = ({ id, type }: ModeFormProps) => {
|
export const ModeForm = ({ id, type }: ModeFormProps) => {
|
||||||
@@ -39,6 +46,7 @@ export const ModeForm = ({ id, type }: ModeFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -51,6 +59,7 @@ export const ModeForm = ({ id, type }: ModeFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -95,6 +104,7 @@ export const ModeForm = ({ id, type }: ModeFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
modeSwarm: null,
|
modeSwarm: null,
|
||||||
});
|
});
|
||||||
toast.success("Mode updated successfully");
|
toast.success("Mode updated successfully");
|
||||||
@@ -122,6 +132,7 @@ export const ModeForm = ({ id, type }: ModeFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
modeSwarm: modeData,
|
modeSwarm: modeData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -35,7 +35,14 @@ export const networkFormSchema = z.object({
|
|||||||
|
|
||||||
interface NetworkFormProps {
|
interface NetworkFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NetworkForm = ({ id, type }: NetworkFormProps) => {
|
export const NetworkForm = ({ id, type }: NetworkFormProps) => {
|
||||||
@@ -51,6 +58,7 @@ export const NetworkForm = ({ id, type }: NetworkFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -63,6 +71,7 @@ export const NetworkForm = ({ id, type }: NetworkFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -132,6 +141,7 @@ export const NetworkForm = ({ id, type }: NetworkFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
networkSwarm: networksToSend,
|
networkSwarm: networksToSend,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -34,7 +34,14 @@ export const placementFormSchema = z.object({
|
|||||||
|
|
||||||
interface PlacementFormProps {
|
interface PlacementFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlacementForm = ({ id, type }: PlacementFormProps) => {
|
export const PlacementForm = ({ id, type }: PlacementFormProps) => {
|
||||||
@@ -50,6 +57,7 @@ export const PlacementForm = ({ id, type }: PlacementFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -62,6 +70,7 @@ export const PlacementForm = ({ id, type }: PlacementFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -114,6 +123,7 @@ export const PlacementForm = ({ id, type }: PlacementFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
placementSwarm: hasAnyValue
|
placementSwarm: hasAnyValue
|
||||||
? {
|
? {
|
||||||
...formData,
|
...formData,
|
||||||
|
|||||||
+11
-1
@@ -32,7 +32,14 @@ export const restartPolicyFormSchema = z.object({
|
|||||||
|
|
||||||
interface RestartPolicyFormProps {
|
interface RestartPolicyFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RestartPolicyForm = ({ id, type }: RestartPolicyFormProps) => {
|
export const RestartPolicyForm = ({ id, type }: RestartPolicyFormProps) => {
|
||||||
@@ -48,6 +55,7 @@ export const RestartPolicyForm = ({ id, type }: RestartPolicyFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -60,6 +68,7 @@ export const RestartPolicyForm = ({ id, type }: RestartPolicyFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -104,6 +113,7 @@ export const RestartPolicyForm = ({ id, type }: RestartPolicyFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
restartPolicySwarm: hasAnyValue ? formData : null,
|
restartPolicySwarm: hasAnyValue ? formData : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -34,7 +34,14 @@ export const rollbackConfigFormSchema = z.object({
|
|||||||
|
|
||||||
interface RollbackConfigFormProps {
|
interface RollbackConfigFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RollbackConfigForm = ({ id, type }: RollbackConfigFormProps) => {
|
export const RollbackConfigForm = ({ id, type }: RollbackConfigFormProps) => {
|
||||||
@@ -50,6 +57,7 @@ export const RollbackConfigForm = ({ id, type }: RollbackConfigFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -62,6 +70,7 @@ export const RollbackConfigForm = ({ id, type }: RollbackConfigFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -103,6 +112,7 @@ export const RollbackConfigForm = ({ id, type }: RollbackConfigFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
rollbackConfigSwarm: (hasAnyValue ? formData : null) as any,
|
rollbackConfigSwarm: (hasAnyValue ? formData : null) as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -23,7 +23,14 @@ const hasStopGracePeriodSwarm = (
|
|||||||
|
|
||||||
interface StopGracePeriodFormProps {
|
interface StopGracePeriodFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StopGracePeriodForm = ({ id, type }: StopGracePeriodFormProps) => {
|
export const StopGracePeriodForm = ({ id, type }: StopGracePeriodFormProps) => {
|
||||||
@@ -39,6 +46,7 @@ export const StopGracePeriodForm = ({ id, type }: StopGracePeriodFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -51,6 +59,7 @@ export const StopGracePeriodForm = ({ id, type }: StopGracePeriodFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -88,6 +97,7 @@ export const StopGracePeriodForm = ({ id, type }: StopGracePeriodFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
stopGracePeriodSwarm: formData.value,
|
stopGracePeriodSwarm: formData.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -34,7 +34,14 @@ export const updateConfigFormSchema = z.object({
|
|||||||
|
|
||||||
interface UpdateConfigFormProps {
|
interface UpdateConfigFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
|
type:
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "redis"
|
||||||
|
| "application"
|
||||||
|
| "libsql";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdateConfigForm = ({ id, type }: UpdateConfigFormProps) => {
|
export const UpdateConfigForm = ({ id, type }: UpdateConfigFormProps) => {
|
||||||
@@ -50,6 +57,7 @@ export const UpdateConfigForm = ({ id, type }: UpdateConfigFormProps) => {
|
|||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -62,6 +70,7 @@ export const UpdateConfigForm = ({ id, type }: UpdateConfigFormProps) => {
|
|||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync } = mutationMap[type]
|
const { mutateAsync } = mutationMap[type]
|
||||||
@@ -109,6 +118,7 @@ export const UpdateConfigForm = ({ id, type }: UpdateConfigFormProps) => {
|
|||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
updateConfigSwarm: (hasAnyValue ? formData : null) as any,
|
updateConfigSwarm: (hasAnyValue ? formData : null) as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,13 @@ const ULIMIT_PRESETS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export type ServiceType =
|
export type ServiceType =
|
||||||
| "postgres"
|
| "application"
|
||||||
| "mongo"
|
| "libsql"
|
||||||
| "redis"
|
|
||||||
| "mysql"
|
|
||||||
| "mariadb"
|
| "mariadb"
|
||||||
| "application";
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "postgres"
|
||||||
|
| "redis";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -105,27 +106,29 @@ type AddResources = z.infer<typeof addResourcesSchema>;
|
|||||||
|
|
||||||
export const ShowResources = ({ id, type }: Props) => {
|
export const ShowResources = ({ id, type }: Props) => {
|
||||||
const queryMap = {
|
const queryMap = {
|
||||||
|
application: () =>
|
||||||
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
|
mariadb: () =>
|
||||||
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
postgres: () =>
|
postgres: () =>
|
||||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
|
||||||
mariadb: () =>
|
|
||||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
|
||||||
application: () =>
|
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||||
|
|
||||||
const mutationMap = {
|
const mutationMap = {
|
||||||
|
application: () => api.application.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
postgres: () => api.postgres.update.useMutation(),
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
redis: () => api.redis.update.useMutation(),
|
redis: () => api.redis.update.useMutation(),
|
||||||
mysql: () => api.mysql.update.useMutation(),
|
|
||||||
mariadb: () => api.mariadb.update.useMutation(),
|
|
||||||
application: () => api.application.update.useMutation(),
|
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutateAsync, isPending } = mutationMap[type]
|
const { mutateAsync, isPending } = mutationMap[type]
|
||||||
@@ -155,19 +158,20 @@ export const ShowResources = ({ id, type }: Props) => {
|
|||||||
cpuReservation: data?.cpuReservation || undefined,
|
cpuReservation: data?.cpuReservation || undefined,
|
||||||
memoryLimit: data?.memoryLimit || undefined,
|
memoryLimit: data?.memoryLimit || undefined,
|
||||||
memoryReservation: data?.memoryReservation || undefined,
|
memoryReservation: data?.memoryReservation || undefined,
|
||||||
ulimitsSwarm: data?.ulimitsSwarm || [],
|
ulimitsSwarm: (data as any)?.ulimitsSwarm || [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data, form, form.reset]);
|
}, [data, form, form.reset]);
|
||||||
|
|
||||||
const onSubmit = async (formData: AddResources) => {
|
const onSubmit = async (formData: AddResources) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
|
applicationId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
mysqlId: id || "",
|
||||||
postgresId: id || "",
|
postgresId: id || "",
|
||||||
redisId: id || "",
|
redisId: id || "",
|
||||||
mysqlId: id || "",
|
|
||||||
mariadbId: id || "",
|
|
||||||
applicationId: id || "",
|
|
||||||
cpuLimit: formData.cpuLimit || null,
|
cpuLimit: formData.cpuLimit || null,
|
||||||
cpuReservation: formData.cpuReservation || null,
|
cpuReservation: formData.cpuReservation || null,
|
||||||
memoryLimit: formData.memoryLimit || null,
|
memoryLimit: formData.memoryLimit || null,
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ interface Props {
|
|||||||
serviceId: string;
|
serviceId: string;
|
||||||
serviceType:
|
serviceType:
|
||||||
| "application"
|
| "application"
|
||||||
| "postgres"
|
| "compose"
|
||||||
| "redis"
|
| "libsql"
|
||||||
| "mongo"
|
|
||||||
| "redis"
|
|
||||||
| "mysql"
|
|
||||||
| "mariadb"
|
| "mariadb"
|
||||||
| "compose";
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "postgres"
|
||||||
|
| "redis";
|
||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,23 +29,25 @@ export const ShowVolumes = ({ id, type }: Props) => {
|
|||||||
if (!canRead) return null;
|
if (!canRead) return null;
|
||||||
|
|
||||||
const queryMap = {
|
const queryMap = {
|
||||||
|
application: () =>
|
||||||
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
|
compose: () =>
|
||||||
|
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
|
mariadb: () =>
|
||||||
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
postgres: () =>
|
postgres: () =>
|
||||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
|
||||||
mariadb: () =>
|
|
||||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
|
||||||
application: () =>
|
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
|
||||||
compose: () =>
|
|
||||||
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
|
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||||
const { mutateAsync: deleteVolume, isPending: isRemoving } =
|
const { mutateAsync: deleteVolume, isPending: isRemoving } =
|
||||||
api.mounts.remove.useMutation();
|
api.mounts.remove.useMutation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
<CardHeader className="flex flex-row justify-between flex-wrap gap-4">
|
<CardHeader className="flex flex-row justify-between flex-wrap gap-4">
|
||||||
|
|||||||
@@ -67,13 +67,13 @@ interface Props {
|
|||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
serviceType:
|
serviceType:
|
||||||
| "application"
|
| "application"
|
||||||
| "postgres"
|
| "compose"
|
||||||
| "redis"
|
| "libsql"
|
||||||
| "mongo"
|
|
||||||
| "redis"
|
|
||||||
| "mysql"
|
|
||||||
| "mariadb"
|
| "mariadb"
|
||||||
| "compose";
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "postgres"
|
||||||
|
| "redis";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdateVolume = ({
|
export const UpdateVolume = ({
|
||||||
@@ -253,7 +253,7 @@ export const UpdateVolume = ({
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="content"
|
name="content"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="max-w-full max-w-[45rem]">
|
<FormItem className="w-full max-w-[45rem]">
|
||||||
<FormLabel>Content</FormLabel>
|
<FormLabel>Content</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
@@ -39,15 +39,16 @@ export const ShowEnvironment = ({ id, type }: Props) => {
|
|||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
const canWrite = permissions?.envVars.write ?? false;
|
const canWrite = permissions?.envVars.write ?? false;
|
||||||
const queryMap = {
|
const queryMap = {
|
||||||
postgres: () =>
|
compose: () =>
|
||||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
|
||||||
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
|
||||||
mariadb: () =>
|
mariadb: () =>
|
||||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
compose: () =>
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
|
postgres: () =>
|
||||||
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
};
|
};
|
||||||
const { data, refetch } = queryMap[type]
|
const { data, refetch } = queryMap[type]
|
||||||
? queryMap[type]()
|
? queryMap[type]()
|
||||||
@@ -55,12 +56,13 @@ export const ShowEnvironment = ({ id, type }: Props) => {
|
|||||||
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
||||||
|
|
||||||
const mutationMap = {
|
const mutationMap = {
|
||||||
postgres: () => api.postgres.update.useMutation(),
|
compose: () => api.compose.update.useMutation(),
|
||||||
redis: () => api.redis.update.useMutation(),
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
mysql: () => api.mysql.update.useMutation(),
|
|
||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
compose: () => api.compose.update.useMutation(),
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
|
redis: () => api.redis.update.useMutation(),
|
||||||
};
|
};
|
||||||
const { mutateAsync, isPending } = mutationMap[type]
|
const { mutateAsync, isPending } = mutationMap[type]
|
||||||
? mutationMap[type]()
|
? mutationMap[type]()
|
||||||
@@ -87,12 +89,13 @@ export const ShowEnvironment = ({ id, type }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async (formData: EnvironmentSchema) => {
|
const onSubmit = async (formData: EnvironmentSchema) => {
|
||||||
mutateAsync({
|
mutateAsync({
|
||||||
|
composeId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
|
mariadbId: id || "",
|
||||||
mongoId: id || "",
|
mongoId: id || "",
|
||||||
|
mysqlId: id || "",
|
||||||
postgresId: id || "",
|
postgresId: id || "",
|
||||||
redisId: id || "",
|
redisId: id || "",
|
||||||
mysqlId: id || "",
|
|
||||||
mariadbId: id || "",
|
|
||||||
composeId: id || "",
|
|
||||||
env: formData.environment,
|
env: formData.environment,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ const formSchema = z
|
|||||||
"mongo",
|
"mongo",
|
||||||
"mysql",
|
"mysql",
|
||||||
"redis",
|
"redis",
|
||||||
|
"libsql",
|
||||||
]),
|
]),
|
||||||
serviceName: z.string(),
|
serviceName: z.string(),
|
||||||
destinationId: z.string().min(1, "Destination required"),
|
destinationId: z.string().min(1, "Destination required"),
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
mariadb: () =>
|
mariadb: () =>
|
||||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
application: () =>
|
application: () =>
|
||||||
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
|
||||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
@@ -72,6 +73,7 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
redis: () => api.redis.remove.useMutation(),
|
redis: () => api.redis.remove.useMutation(),
|
||||||
mysql: () => api.mysql.remove.useMutation(),
|
mysql: () => api.mysql.remove.useMutation(),
|
||||||
mariadb: () => api.mariadb.remove.useMutation(),
|
mariadb: () => api.mariadb.remove.useMutation(),
|
||||||
|
libsql: () => api.libsql.remove.useMutation(),
|
||||||
application: () => api.application.delete.useMutation(),
|
application: () => api.application.delete.useMutation(),
|
||||||
mongo: () => api.mongo.remove.useMutation(),
|
mongo: () => api.mongo.remove.useMutation(),
|
||||||
compose: () => api.compose.delete.useMutation(),
|
compose: () => api.compose.delete.useMutation(),
|
||||||
@@ -98,6 +100,7 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
redisId: id || "",
|
redisId: id || "",
|
||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
applicationId: id || "",
|
applicationId: id || "",
|
||||||
composeId: id || "",
|
composeId: id || "",
|
||||||
deleteVolumes,
|
deleteVolumes,
|
||||||
|
|||||||
@@ -65,7 +65,13 @@ import { ScheduleFormField } from "../../application/schedules/handle-schedules"
|
|||||||
|
|
||||||
type CacheType = "cache" | "fetch";
|
type CacheType = "cache" | "fetch";
|
||||||
|
|
||||||
type DatabaseType = "postgres" | "mariadb" | "mysql" | "mongo" | "web-server";
|
type DatabaseType =
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mysql"
|
||||||
|
| "mongo"
|
||||||
|
| "web-server"
|
||||||
|
| "libsql";
|
||||||
|
|
||||||
const Schema = z
|
const Schema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -77,7 +83,7 @@ const Schema = z
|
|||||||
keepLatestCount: z.coerce.number().optional(),
|
keepLatestCount: z.coerce.number().optional(),
|
||||||
serviceName: z.string().nullable(),
|
serviceName: z.string().nullable(),
|
||||||
databaseType: z
|
databaseType: z
|
||||||
.enum(["postgres", "mariadb", "mysql", "mongo", "web-server"])
|
.enum(["postgres", "mariadb", "mysql", "mongo", "web-server", "libsql"])
|
||||||
.optional(),
|
.optional(),
|
||||||
backupType: z.enum(["database", "compose"]),
|
backupType: z.enum(["database", "compose"]),
|
||||||
metadata: z
|
metadata: z
|
||||||
@@ -209,7 +215,12 @@ export const HandleBackup = ({
|
|||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
database: databaseType === "web-server" ? "dokploy" : "",
|
database:
|
||||||
|
databaseType === "web-server"
|
||||||
|
? "dokploy"
|
||||||
|
: databaseType === "libsql"
|
||||||
|
? "iku.db"
|
||||||
|
: "",
|
||||||
destinationId: "",
|
destinationId: "",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
prefix: "/",
|
prefix: "/",
|
||||||
@@ -246,7 +257,9 @@ export const HandleBackup = ({
|
|||||||
? backup?.database
|
? backup?.database
|
||||||
: databaseType === "web-server"
|
: databaseType === "web-server"
|
||||||
? "dokploy"
|
? "dokploy"
|
||||||
: "",
|
: databaseType === "libsql"
|
||||||
|
? "iku.db"
|
||||||
|
: "",
|
||||||
destinationId: backup?.destinationId ?? "",
|
destinationId: backup?.destinationId ?? "",
|
||||||
enabled: backup?.enabled ?? true,
|
enabled: backup?.enabled ?? true,
|
||||||
prefix: backup?.prefix ?? "/",
|
prefix: backup?.prefix ?? "/",
|
||||||
@@ -281,11 +294,15 @@ export const HandleBackup = ({
|
|||||||
? {
|
? {
|
||||||
mongoId: id,
|
mongoId: id,
|
||||||
}
|
}
|
||||||
: databaseType === "web-server"
|
: databaseType === "libsql"
|
||||||
? {
|
? {
|
||||||
userId: id,
|
libsqlId: id,
|
||||||
}
|
}
|
||||||
: undefined;
|
: databaseType === "web-server"
|
||||||
|
? {
|
||||||
|
userId: id,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
await createBackup({
|
await createBackup({
|
||||||
destinationId: data.destinationId,
|
destinationId: data.destinationId,
|
||||||
@@ -568,7 +585,10 @@ export const HandleBackup = ({
|
|||||||
<FormLabel>Database</FormLabel>
|
<FormLabel>Database</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
disabled={databaseType === "web-server"}
|
disabled={
|
||||||
|
databaseType === "web-server" ||
|
||||||
|
databaseType === "libsql"
|
||||||
|
}
|
||||||
placeholder={"dokploy"}
|
placeholder={"dokploy"}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const RestoreBackupSchema = z
|
|||||||
message: "Database name is required",
|
message: "Database name is required",
|
||||||
}),
|
}),
|
||||||
databaseType: z
|
databaseType: z
|
||||||
.enum(["postgres", "mariadb", "mysql", "mongo", "web-server"])
|
.enum(["postgres", "mariadb", "mysql", "mongo", "web-server", "libsql"])
|
||||||
.optional(),
|
.optional(),
|
||||||
backupType: z.enum(["database", "compose"]).default("database"),
|
backupType: z.enum(["database", "compose"]).default("database"),
|
||||||
metadata: z
|
metadata: z
|
||||||
@@ -211,7 +211,12 @@ export const RestoreBackup = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
destinationId: "",
|
destinationId: "",
|
||||||
backupFile: "",
|
backupFile: "",
|
||||||
databaseName: databaseType === "web-server" ? "dokploy" : "",
|
databaseName:
|
||||||
|
databaseType === "web-server"
|
||||||
|
? "dokploy"
|
||||||
|
: databaseType === "libsql"
|
||||||
|
? "iku.db"
|
||||||
|
: "",
|
||||||
databaseType:
|
databaseType:
|
||||||
backupType === "compose" ? ("postgres" as DatabaseType) : databaseType,
|
backupType === "compose" ? ("postgres" as DatabaseType) : databaseType,
|
||||||
backupType: backupType,
|
backupType: backupType,
|
||||||
@@ -523,7 +528,10 @@ export const RestoreBackup = ({
|
|||||||
<Input
|
<Input
|
||||||
placeholder="Enter database name"
|
placeholder="Enter database name"
|
||||||
{...field}
|
{...field}
|
||||||
disabled={databaseType === "web-server"}
|
disabled={
|
||||||
|
databaseType === "web-server" ||
|
||||||
|
databaseType === "libsql"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -53,14 +53,16 @@ export const ShowBackups = ({
|
|||||||
const queryMap =
|
const queryMap =
|
||||||
backupType === "database"
|
backupType === "database"
|
||||||
? {
|
? {
|
||||||
postgres: () =>
|
|
||||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
|
||||||
mysql: () =>
|
|
||||||
api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
|
||||||
mariadb: () =>
|
mariadb: () =>
|
||||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
mongo: () =>
|
mongo: () =>
|
||||||
api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||||
|
mysql: () =>
|
||||||
|
api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
|
postgres: () =>
|
||||||
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
|
libsql: () =>
|
||||||
|
api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
"web-server": () => api.user.getBackups.useQuery(),
|
"web-server": () => api.user.getBackups.useQuery(),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
@@ -77,10 +79,11 @@ export const ShowBackups = ({
|
|||||||
const mutationMap =
|
const mutationMap =
|
||||||
backupType === "database"
|
backupType === "database"
|
||||||
? {
|
? {
|
||||||
postgres: api.backup.manualBackupPostgres.useMutation(),
|
|
||||||
mysql: api.backup.manualBackupMySql.useMutation(),
|
|
||||||
mariadb: api.backup.manualBackupMariadb.useMutation(),
|
mariadb: api.backup.manualBackupMariadb.useMutation(),
|
||||||
mongo: api.backup.manualBackupMongo.useMutation(),
|
mongo: api.backup.manualBackupMongo.useMutation(),
|
||||||
|
mysql: api.backup.manualBackupMySql.useMutation(),
|
||||||
|
postgres: api.backup.manualBackupPostgres.useMutation(),
|
||||||
|
libsql: api.backup.manualBackupLibsql.useMutation(),
|
||||||
"web-server": api.backup.manualBackupWebServer.useMutation(),
|
"web-server": api.backup.manualBackupWebServer.useMutation(),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|||||||
+251
@@ -0,0 +1,251 @@
|
|||||||
|
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
const DockerProviderSchema = z.object({
|
||||||
|
externalPort: z.preprocess((a) => {
|
||||||
|
if (a === null || a === undefined || a === "") return null;
|
||||||
|
const parsed = Number.parseInt(String(a), 10);
|
||||||
|
return Number.isNaN(parsed) ? null : parsed;
|
||||||
|
}, z
|
||||||
|
.number()
|
||||||
|
.gte(0, "Range must be 0 - 65535")
|
||||||
|
.lte(65535, "Range must be 0 - 65535")
|
||||||
|
.nullable()),
|
||||||
|
externalGRPCPort: z.preprocess((a) => {
|
||||||
|
if (a === null || a === undefined || a === "") return null;
|
||||||
|
const parsed = Number.parseInt(String(a), 10);
|
||||||
|
return Number.isNaN(parsed) ? null : parsed;
|
||||||
|
}, z
|
||||||
|
.number()
|
||||||
|
.gte(0, "Range must be 0 - 65535")
|
||||||
|
.lte(65535, "Range must be 0 - 65535")
|
||||||
|
.nullable()),
|
||||||
|
externalAdminPort: z.preprocess((a) => {
|
||||||
|
if (a === null || a === undefined || a === "") return null;
|
||||||
|
const parsed = Number.parseInt(String(a), 10);
|
||||||
|
return Number.isNaN(parsed) ? null : parsed;
|
||||||
|
}, 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 {
|
||||||
|
libsqlId: string;
|
||||||
|
}
|
||||||
|
export const ShowExternalLibsqlCredentials = ({ libsqlId }: Props) => {
|
||||||
|
const { data: ip } = api.settings.getIp.useQuery();
|
||||||
|
const { data, refetch } = api.libsql.one.useQuery({ libsqlId });
|
||||||
|
const { mutateAsync, isPending } = api.libsql.saveExternalPorts.useMutation();
|
||||||
|
const [connectionUrl, setConnectionUrl] = useState("");
|
||||||
|
const [connectionGRPCUrl, setGRPCConnectionUrl] = useState("");
|
||||||
|
const getIp = data?.server?.ipAddress || ip;
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: {},
|
||||||
|
resolver: zodResolver(DockerProviderSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
form.reset({
|
||||||
|
externalPort: data.externalPort,
|
||||||
|
externalGRPCPort: data.externalGRPCPort,
|
||||||
|
externalAdminPort: data.externalAdminPort,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [form.reset, data, form]);
|
||||||
|
|
||||||
|
const onSubmit = async (values: DockerProvider) => {
|
||||||
|
await mutateAsync({
|
||||||
|
externalPort: values.externalPort,
|
||||||
|
externalGRPCPort: values.externalGRPCPort,
|
||||||
|
externalAdminPort: values.externalAdminPort,
|
||||||
|
libsqlId,
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
toast.success("External port/ports updated");
|
||||||
|
await refetch();
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
toast.error(error?.message || "Error saving the external port/ports");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const port = form.watch("externalPort") || data?.externalPort;
|
||||||
|
setConnectionUrl(
|
||||||
|
`http://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data?.sqldNode !== "replica") {
|
||||||
|
const grpcPort = form.watch("externalGRPCPort") || data?.externalGRPCPort;
|
||||||
|
setGRPCConnectionUrl(
|
||||||
|
`http://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${grpcPort}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
data?.externalGRPCPort,
|
||||||
|
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 through the internet, you
|
||||||
|
must set a port and ensure that the port is not being 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 }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>External Port (Internet)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="8080"
|
||||||
|
{...field}
|
||||||
|
value={field.value as string}
|
||||||
|
/>
|
||||||
|
</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="grid grid-cols-2 gap-4">
|
||||||
|
<div className="col-span-2 space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="externalAdminPort"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>External Admin Port (Internet)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="5000"
|
||||||
|
{...field}
|
||||||
|
value={field.value as string}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{data?.sqldNode !== "replica" && (
|
||||||
|
<>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="col-span-2 space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="externalGRPCPort"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>External GRPC Port (Internet)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="5001"
|
||||||
|
{...field}
|
||||||
|
value={field.value as string}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!!data?.externalGRPCPort && (
|
||||||
|
<div className="grid w-full gap-8">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label>External GRPC Host</Label>
|
||||||
|
<ToggleVisibilityInput
|
||||||
|
value={connectionGRPCUrl}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit" isLoading={isPending}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
|
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
|
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
libsqlId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowGeneralLibsql = ({ libsqlId }: Props) => {
|
||||||
|
const { data, refetch } = api.libsql.one.useQuery(
|
||||||
|
{
|
||||||
|
libsqlId,
|
||||||
|
},
|
||||||
|
{ enabled: !!libsqlId },
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutateAsync: reload, isPending: isReloading } =
|
||||||
|
api.libsql.reload.useMutation();
|
||||||
|
|
||||||
|
const { mutateAsync: start, isPending: isStarting } =
|
||||||
|
api.libsql.start.useMutation();
|
||||||
|
|
||||||
|
const { mutateAsync: stop, isPending: isStopping } =
|
||||||
|
api.libsql.stop.useMutation();
|
||||||
|
|
||||||
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
|
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
|
||||||
|
const [isDeploying, setIsDeploying] = useState(false);
|
||||||
|
api.libsql.deployWithLogs.useSubscription(
|
||||||
|
{
|
||||||
|
libsqlId: libsqlId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: isDeploying,
|
||||||
|
onData(log) {
|
||||||
|
if (!isDrawerOpen) {
|
||||||
|
setIsDrawerOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log === "Deployment completed successfully!") {
|
||||||
|
setIsDeploying(false);
|
||||||
|
}
|
||||||
|
const parsedLogs = parseLogs(log);
|
||||||
|
setFilteredLogs((prev) => [...prev, ...parsedLogs]);
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
console.error("Deployment logs error:", error);
|
||||||
|
setIsDeploying(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full flex-col gap-5 ">
|
||||||
|
<Card className="bg-background">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl">Deploy Settings</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-row gap-4 flex-wrap">
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<DialogAction
|
||||||
|
title="Deploy Libsql"
|
||||||
|
description="Are you sure you want to deploy this Libsql?"
|
||||||
|
type="default"
|
||||||
|
onClick={async () => {
|
||||||
|
setIsDeploying(true);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
isLoading={data?.applicationStatus === "running"}
|
||||||
|
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Rocket className="size-4 mr-1" />
|
||||||
|
Deploy
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipContent sideOffset={5} className="z-[60]">
|
||||||
|
<p>Downloads and sets up the Libsql database</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</DialogAction>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<DialogAction
|
||||||
|
title="Reload Libsql"
|
||||||
|
description="Are you sure you want to reload this libsql?"
|
||||||
|
type="default"
|
||||||
|
onClick={async () => {
|
||||||
|
await reload({
|
||||||
|
libsqlId: libsqlId,
|
||||||
|
appName: data?.appName || "",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Libsql reloaded successfully");
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error reloading Libsql");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
isLoading={isReloading}
|
||||||
|
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<RefreshCcw className="size-4 mr-1" />
|
||||||
|
Reload
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipContent sideOffset={5} className="z-[60]">
|
||||||
|
<p>Restart the Libsql service without rebuilding</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</DialogAction>
|
||||||
|
</TooltipProvider>
|
||||||
|
{data?.applicationStatus === "idle" ? (
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<DialogAction
|
||||||
|
title="Start Libsql"
|
||||||
|
description="Are you sure you want to start this Libsql?"
|
||||||
|
type="default"
|
||||||
|
onClick={async () => {
|
||||||
|
await start({
|
||||||
|
libsqlId: libsqlId,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Libsql started successfully");
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error starting Libsql");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
isLoading={isStarting}
|
||||||
|
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<CheckCircle2 className="size-4 mr-1" />
|
||||||
|
Start
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipContent sideOffset={5} className="z-[60]">
|
||||||
|
<p>
|
||||||
|
Start the Libsql database (requires a previous
|
||||||
|
successful setup)
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</DialogAction>
|
||||||
|
</TooltipProvider>
|
||||||
|
) : (
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<DialogAction
|
||||||
|
title="Stop Libsql"
|
||||||
|
description="Are you sure you want to stop this Libsql?"
|
||||||
|
onClick={async () => {
|
||||||
|
await stop({
|
||||||
|
libsqlId: libsqlId,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Libsql stopped successfully");
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error stopping Libsql");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
isLoading={isStopping}
|
||||||
|
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Ban className="size-4 mr-1" />
|
||||||
|
Stop
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipContent sideOffset={5} className="z-[60]">
|
||||||
|
<p>Stop the currently running Libsql database</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</DialogAction>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
<DockerTerminalModal
|
||||||
|
appName={data?.appName || ""}
|
||||||
|
serverId={data?.serverId || ""}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Terminal className="size-4 mr-1" />
|
||||||
|
Open Terminal
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipContent sideOffset={5} className="z-[60]">
|
||||||
|
<p>Open a terminal to the Libsql container</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
</Tooltip>
|
||||||
|
</Button>
|
||||||
|
</DockerTerminalModal>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<DrawerLogs
|
||||||
|
isOpen={isDrawerOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsDrawerOpen(false);
|
||||||
|
setFilteredLogs([]);
|
||||||
|
setIsDeploying(false);
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
filteredLogs={filteredLogs}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
import { SelectGroup } from "@radix-ui/react-select";
|
||||||
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
libsqlId: string;
|
||||||
|
}
|
||||||
|
export const ShowInternalLibsqlCredentials = ({ libsqlId }: Props) => {
|
||||||
|
const { data } = api.libsql.one.useQuery({ libsqlId });
|
||||||
|
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>Sqld Node</Label>
|
||||||
|
<Select value={data?.sqldNode} disabled>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select Node type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{["primary", "replica"].map((node) => (
|
||||||
|
<SelectItem key={node} value={node}>
|
||||||
|
{node.charAt(0).toUpperCase() + node.slice(1)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Label>Password</Label>
|
||||||
|
<div className="flex flex-row gap-4">
|
||||||
|
<ToggleVisibilityInput
|
||||||
|
disabled
|
||||||
|
value={data?.databasePassword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<div className="w-full flex flex-col gap-2">
|
||||||
|
<Label>Internal Port (Container)</Label>
|
||||||
|
<Input disabled value="8080" />
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col gap-2">
|
||||||
|
<Label>Internal GRPC Port (Container)</Label>
|
||||||
|
<Input disabled value="5001" />
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col gap-2">
|
||||||
|
<Label>Internal Admin Port (Container)</Label>
|
||||||
|
<Input disabled value="5000" />
|
||||||
|
</div>
|
||||||
|
</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>Enable Namespaces</Label>
|
||||||
|
<Select
|
||||||
|
disabled
|
||||||
|
defaultValue={
|
||||||
|
data?.enableNamespaces
|
||||||
|
? String(data?.enableNamespaces)
|
||||||
|
: "false"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={"false"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{["false", "true"].map((node) => (
|
||||||
|
<SelectItem key={node} value={node}>
|
||||||
|
{node.charAt(0).toUpperCase() + node.slice(1)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
<Label>Internal Connection URL </Label>
|
||||||
|
<ToggleVisibilityInput
|
||||||
|
disabled
|
||||||
|
value={`http://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:8080`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
<Label>Internal Replication Connection URL </Label>
|
||||||
|
<ToggleVisibilityInput
|
||||||
|
disabled
|
||||||
|
value={`http://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:5001`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
|
import { PenBoxIcon } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
const updateLibsqlSchema = z.object({
|
||||||
|
name: z.string().min(1, {
|
||||||
|
message: "Name is required",
|
||||||
|
}),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type UpdateLibsql = z.infer<typeof updateLibsqlSchema>;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
libsqlId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateLibsql = ({ libsqlId }: Props) => {
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutateAsync, error, isError, isPending } =
|
||||||
|
api.libsql.update.useMutation();
|
||||||
|
const { data } = api.libsql.one.useQuery(
|
||||||
|
{
|
||||||
|
libsqlId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!libsqlId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const form = useForm<UpdateLibsql>({
|
||||||
|
defaultValues: {
|
||||||
|
description: data?.description ?? "",
|
||||||
|
name: data?.name ?? "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(updateLibsqlSchema),
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
form.reset({
|
||||||
|
description: data.description ?? "",
|
||||||
|
name: data.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data, form, form.reset]);
|
||||||
|
|
||||||
|
const onSubmit = async (formData: UpdateLibsql) => {
|
||||||
|
await mutateAsync({
|
||||||
|
name: formData.name,
|
||||||
|
libsqlId: libsqlId,
|
||||||
|
description: formData.description || "",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Libsql updated successfully");
|
||||||
|
utils.libsql.one.invalidate({
|
||||||
|
libsqlId: libsqlId,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error updating the Libsql");
|
||||||
|
})
|
||||||
|
.finally(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="group hover:bg-blue-500/10 "
|
||||||
|
>
|
||||||
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-lg">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Modify Libsql</DialogTitle>
|
||||||
|
<DialogDescription>Update the Libsql 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-libsql"
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
isLoading={isPending}
|
||||||
|
form="hook-form-update-libsql"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -42,6 +42,7 @@ export const ShowCustomCommand = ({ id, type }: Props) => {
|
|||||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||||
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
|
||||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||||
|
libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }),
|
||||||
mariadb: () =>
|
mariadb: () =>
|
||||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||||
application: () =>
|
application: () =>
|
||||||
@@ -56,6 +57,7 @@ export const ShowCustomCommand = ({ id, type }: Props) => {
|
|||||||
postgres: () => api.postgres.update.useMutation(),
|
postgres: () => api.postgres.update.useMutation(),
|
||||||
redis: () => api.redis.update.useMutation(),
|
redis: () => api.redis.update.useMutation(),
|
||||||
mysql: () => api.mysql.update.useMutation(),
|
mysql: () => api.mysql.update.useMutation(),
|
||||||
|
libsql: () => api.libsql.update.useMutation(),
|
||||||
mariadb: () => api.mariadb.update.useMutation(),
|
mariadb: () => api.mariadb.update.useMutation(),
|
||||||
application: () => api.application.update.useMutation(),
|
application: () => api.application.update.useMutation(),
|
||||||
mongo: () => api.mongo.update.useMutation(),
|
mongo: () => api.mongo.update.useMutation(),
|
||||||
@@ -84,7 +86,7 @@ export const ShowCustomCommand = ({ id, type }: Props) => {
|
|||||||
form.reset({
|
form.reset({
|
||||||
dockerImage: data.dockerImage,
|
dockerImage: data.dockerImage,
|
||||||
command: data.command || "",
|
command: data.command || "",
|
||||||
args: data.args?.map((arg) => ({ value: arg })) || [],
|
args: (data as any).args?.map((arg: string) => ({ value: arg })) || [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data, form]);
|
}, [data, form]);
|
||||||
@@ -95,6 +97,7 @@ export const ShowCustomCommand = ({ id, type }: Props) => {
|
|||||||
postgresId: id || "",
|
postgresId: id || "",
|
||||||
redisId: id || "",
|
redisId: id || "",
|
||||||
mysqlId: id || "",
|
mysqlId: id || "",
|
||||||
|
libsqlId: id || "",
|
||||||
mariadbId: id || "",
|
mariadbId: id || "",
|
||||||
dockerImage: formData?.dockerImage,
|
dockerImage: formData?.dockerImage,
|
||||||
command: formData?.command,
|
command: formData?.command,
|
||||||
@@ -144,7 +147,14 @@ export const ShowCustomCommand = ({ id, type }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Command</FormLabel>
|
<FormLabel>Command</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="/bin/sh" {...field} />
|
<Input
|
||||||
|
placeholder={
|
||||||
|
type === "libsql"
|
||||||
|
? "sqld --db-path iku.db --http-listen-addr 0.0.0.0:8080 --grpc-listen-addr 0.0.0.0:5001 --admin-listen-addr 0.0.0.0:5000"
|
||||||
|
: "Custom command"
|
||||||
|
}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
|
LibsqlIcon,
|
||||||
MariadbIcon,
|
MariadbIcon,
|
||||||
MongodbIcon,
|
MongodbIcon,
|
||||||
MysqlIcon,
|
MysqlIcon,
|
||||||
@@ -55,6 +56,7 @@ import { api } from "@/utils/api";
|
|||||||
type DbType = z.infer<typeof mySchema>["type"];
|
type DbType = z.infer<typeof mySchema>["type"];
|
||||||
|
|
||||||
const dockerImageDefaultPlaceholder: Record<DbType, string> = {
|
const dockerImageDefaultPlaceholder: Record<DbType, string> = {
|
||||||
|
libsql: "ghcr.io/tursodatabase/libsql-server:v0.24.32",
|
||||||
mongo: "mongo:7",
|
mongo: "mongo:7",
|
||||||
mariadb: "mariadb:11",
|
mariadb: "mariadb:11",
|
||||||
mysql: "mysql:8",
|
mysql: "mysql:8",
|
||||||
@@ -66,8 +68,9 @@ const databasesUserDefaultPlaceholder: Record<
|
|||||||
Exclude<DbType, "redis">,
|
Exclude<DbType, "redis">,
|
||||||
string
|
string
|
||||||
> = {
|
> = {
|
||||||
mongo: "mongo",
|
libsql: "libsql",
|
||||||
mariadb: "mariadb",
|
mariadb: "mariadb",
|
||||||
|
mongo: "mongo",
|
||||||
mysql: "mysql",
|
mysql: "mysql",
|
||||||
postgres: "postgres",
|
postgres: "postgres",
|
||||||
};
|
};
|
||||||
@@ -94,56 +97,88 @@ const baseDatabaseSchema = z.object({
|
|||||||
serverId: z.string().nullable(),
|
serverId: z.string().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mySchema = z.discriminatedUnion("type", [
|
const mySchema = z
|
||||||
z
|
.discriminatedUnion("type", [
|
||||||
.object({
|
z
|
||||||
type: z.literal("postgres"),
|
.object({
|
||||||
databaseName: z.string().default("postgres"),
|
type: z.literal("libsql"),
|
||||||
databaseUser: z.string().default("postgres"),
|
dockerImage: z
|
||||||
})
|
.string()
|
||||||
.merge(baseDatabaseSchema),
|
.default("ghcr.io/tursodatabase/libsql-server:v0.24.32"),
|
||||||
z
|
databaseUser: z.string().default("libsql"),
|
||||||
.object({
|
sqldNode: z.enum(["primary", "replica"]).default("primary"),
|
||||||
type: z.literal("mongo"),
|
sqldPrimaryUrl: z.string().optional(),
|
||||||
databaseUser: z.string().default("mongo"),
|
enableNamespaces: z.boolean().default(false),
|
||||||
replicaSets: z.boolean().default(false),
|
})
|
||||||
})
|
.merge(baseDatabaseSchema),
|
||||||
.merge(baseDatabaseSchema),
|
z
|
||||||
z
|
.object({
|
||||||
.object({
|
type: z.literal("mariadb"),
|
||||||
type: z.literal("redis"),
|
dockerImage: z.string().default("mariadb:4"),
|
||||||
})
|
databaseRootPassword: z
|
||||||
.merge(baseDatabaseSchema),
|
.string()
|
||||||
z
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
.object({
|
message:
|
||||||
type: z.literal("mysql"),
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
databaseRootPassword: z
|
})
|
||||||
.string()
|
.optional(),
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
databaseUser: z.string().default("mariadb"),
|
||||||
|
databaseName: z.string().default("mariadb"),
|
||||||
|
})
|
||||||
|
.merge(baseDatabaseSchema),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
type: z.literal("mongo"),
|
||||||
|
databaseUser: z.string().default("mongo"),
|
||||||
|
replicaSets: z.boolean().default(false),
|
||||||
|
})
|
||||||
|
.merge(baseDatabaseSchema),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
type: z.literal("mysql"),
|
||||||
|
databaseRootPassword: z
|
||||||
|
.string()
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
databaseUser: z.string().default("mysql"),
|
||||||
|
databaseName: z.string().default("mysql"),
|
||||||
|
})
|
||||||
|
.merge(baseDatabaseSchema),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
type: z.literal("postgres"),
|
||||||
|
databaseName: z.string().default("postgres"),
|
||||||
|
databaseUser: z.string().default("postgres"),
|
||||||
|
})
|
||||||
|
.merge(baseDatabaseSchema),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
type: z.literal("redis"),
|
||||||
|
})
|
||||||
|
.merge(baseDatabaseSchema),
|
||||||
|
])
|
||||||
|
.superRefine((data, ctx) => {
|
||||||
|
if (data.type === "libsql") {
|
||||||
|
if (data.sqldNode === "replica" && !data.sqldPrimaryUrl) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["sqldPrimaryUrl"],
|
||||||
|
message: "sqldPrimaryUrl is required when sqldNode is 'replica'.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.sqldNode !== "replica" && data.sqldPrimaryUrl) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["sqldPrimaryUrl"],
|
||||||
message:
|
message:
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
"sqldPrimaryUrl should not be provided when sqldNode is not 'replica'.",
|
||||||
})
|
});
|
||||||
.optional(),
|
}
|
||||||
databaseUser: z.string().default("mysql"),
|
}
|
||||||
databaseName: z.string().default("mysql"),
|
});
|
||||||
})
|
|
||||||
.merge(baseDatabaseSchema),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
type: z.literal("mariadb"),
|
|
||||||
dockerImage: z.string().default("mariadb:4"),
|
|
||||||
databaseRootPassword: z
|
|
||||||
.string()
|
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
databaseUser: z.string().default("mariadb"),
|
|
||||||
databaseName: z.string().default("mariadb"),
|
|
||||||
})
|
|
||||||
.merge(baseDatabaseSchema),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const databasesMap = {
|
const databasesMap = {
|
||||||
postgres: {
|
postgres: {
|
||||||
@@ -166,6 +201,10 @@ const databasesMap = {
|
|||||||
icon: <RedisIcon />,
|
icon: <RedisIcon />,
|
||||||
label: "Redis",
|
label: "Redis",
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
icon: <LibsqlIcon className="size-10" />,
|
||||||
|
label: "libSQL",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type AddDatabase = z.infer<typeof mySchema>;
|
type AddDatabase = z.infer<typeof mySchema>;
|
||||||
@@ -181,11 +220,12 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const postgresMutation = api.postgres.create.useMutation();
|
const libsqlMutation = api.libsql.create.useMutation();
|
||||||
const mongoMutation = api.mongo.create.useMutation();
|
|
||||||
const redisMutation = api.redis.create.useMutation();
|
|
||||||
const mariadbMutation = api.mariadb.create.useMutation();
|
const mariadbMutation = api.mariadb.create.useMutation();
|
||||||
|
const mongoMutation = api.mongo.create.useMutation();
|
||||||
const mysqlMutation = api.mysql.create.useMutation();
|
const mysqlMutation = api.mysql.create.useMutation();
|
||||||
|
const postgresMutation = api.postgres.create.useMutation();
|
||||||
|
const redisMutation = api.redis.create.useMutation();
|
||||||
|
|
||||||
// Get environment data to extract projectId
|
// Get environment data to extract projectId
|
||||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
||||||
@@ -210,13 +250,15 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
},
|
},
|
||||||
resolver: zodResolver(mySchema),
|
resolver: zodResolver(mySchema),
|
||||||
});
|
});
|
||||||
|
const sqldNode = form.watch("sqldNode");
|
||||||
const type = form.watch("type");
|
const type = form.watch("type");
|
||||||
const activeMutation = {
|
const activeMutation = {
|
||||||
postgres: postgresMutation,
|
libsql: libsqlMutation,
|
||||||
mongo: mongoMutation,
|
|
||||||
redis: redisMutation,
|
|
||||||
mariadb: mariadbMutation,
|
mariadb: mariadbMutation,
|
||||||
|
mongo: mongoMutation,
|
||||||
mysql: mysqlMutation,
|
mysql: mysqlMutation,
|
||||||
|
postgres: postgresMutation,
|
||||||
|
redis: redisMutation,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: AddDatabase) => {
|
const onSubmit = async (data: AddDatabase) => {
|
||||||
@@ -233,12 +275,23 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
description: data.description,
|
description: data.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.type === "postgres") {
|
if (data.type === "libsql") {
|
||||||
promise = postgresMutation.mutateAsync({
|
promise = libsqlMutation.mutateAsync({
|
||||||
|
...commonParams,
|
||||||
|
sqldNode: data.sqldNode,
|
||||||
|
sqldPrimaryUrl: data.sqldPrimaryUrl ?? null,
|
||||||
|
enableNamespaces: data.enableNamespaces,
|
||||||
|
databasePassword: data.databasePassword,
|
||||||
|
databaseUser:
|
||||||
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
|
});
|
||||||
|
} else if (data.type === "mariadb") {
|
||||||
|
promise = mariadbMutation.mutateAsync({
|
||||||
...commonParams,
|
...commonParams,
|
||||||
databasePassword: data.databasePassword,
|
databasePassword: data.databasePassword,
|
||||||
databaseName: data.databaseName || "postgres",
|
databaseRootPassword: data.databaseRootPassword || "",
|
||||||
|
databaseName: data.databaseName || "mariadb",
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
@@ -252,22 +305,6 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
replicaSets: data.replicaSets,
|
replicaSets: data.replicaSets,
|
||||||
});
|
});
|
||||||
} else if (data.type === "redis") {
|
|
||||||
promise = redisMutation.mutateAsync({
|
|
||||||
...commonParams,
|
|
||||||
databasePassword: data.databasePassword,
|
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
|
||||||
});
|
|
||||||
} else if (data.type === "mariadb") {
|
|
||||||
promise = mariadbMutation.mutateAsync({
|
|
||||||
...commonParams,
|
|
||||||
databasePassword: data.databasePassword,
|
|
||||||
databaseRootPassword: data.databaseRootPassword || "",
|
|
||||||
databaseName: data.databaseName || "mariadb",
|
|
||||||
databaseUser:
|
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
|
||||||
});
|
|
||||||
} else if (data.type === "mysql") {
|
} else if (data.type === "mysql") {
|
||||||
promise = mysqlMutation.mutateAsync({
|
promise = mysqlMutation.mutateAsync({
|
||||||
...commonParams,
|
...commonParams,
|
||||||
@@ -278,6 +315,21 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
databaseRootPassword: data.databaseRootPassword || "",
|
databaseRootPassword: data.databaseRootPassword || "",
|
||||||
});
|
});
|
||||||
|
} else if (data.type === "postgres") {
|
||||||
|
promise = postgresMutation.mutateAsync({
|
||||||
|
...commonParams,
|
||||||
|
databasePassword: data.databasePassword,
|
||||||
|
databaseName: data.databaseName || "postgres",
|
||||||
|
databaseUser:
|
||||||
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
|
});
|
||||||
|
} else if (data.type === "redis") {
|
||||||
|
promise = redisMutation.mutateAsync({
|
||||||
|
...commonParams,
|
||||||
|
databasePassword: data.databasePassword,
|
||||||
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise) {
|
if (promise) {
|
||||||
@@ -305,6 +357,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={visible} onOpenChange={setVisible}>
|
<Dialog open={visible} onOpenChange={setVisible}>
|
||||||
<DialogTrigger className="w-full">
|
<DialogTrigger className="w-full">
|
||||||
@@ -506,8 +559,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{(type === "mysql" ||
|
{(type === "mariadb" ||
|
||||||
type === "mariadb" ||
|
type === "mysql" ||
|
||||||
type === "postgres") && (
|
type === "postgres") && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -524,10 +577,101 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(type === "mysql" ||
|
|
||||||
|
{type === "libsql" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="sqldNode"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Sqld Node</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value || "primary"}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={"primary"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{["primary", "replica"].map((node) => (
|
||||||
|
<SelectItem key={node} value={node}>
|
||||||
|
{node.charAt(0).toUpperCase() + node.slice(1)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === "libsql" && sqldNode === "replica" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="sqldPrimaryUrl"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Sqld Primary URL</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={"https://<host>:<port>"}
|
||||||
|
autoComplete="off"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === "libsql" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="enableNamespaces"
|
||||||
|
render={({ field }) => {
|
||||||
|
console.log(field.value);
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Enable Namespaces</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) =>
|
||||||
|
field.onChange(Boolean(value))
|
||||||
|
}
|
||||||
|
defaultValue={
|
||||||
|
field.value ? String(field.value) : "false"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={"false"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{["false", "true"].map((node) => (
|
||||||
|
<SelectItem key={node} value={node}>
|
||||||
|
{node.charAt(0).toUpperCase() +
|
||||||
|
node.slice(1)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(type === "libsql" ||
|
||||||
type === "mariadb" ||
|
type === "mariadb" ||
|
||||||
type === "postgres" ||
|
type === "mongo" ||
|
||||||
type === "mongo") && (
|
type === "mysql" ||
|
||||||
|
type === "postgres") && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="databaseUser"
|
name="databaseUser"
|
||||||
@@ -568,7 +712,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{(type === "mysql" || type === "mariadb") && (
|
{(type === "mariadb" || type === "mysql") && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="databaseRootPassword"
|
name="databaseRootPassword"
|
||||||
|
|||||||
@@ -28,13 +28,14 @@ export type Services = {
|
|||||||
serverId?: string | null;
|
serverId?: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
type:
|
type:
|
||||||
| "mariadb"
|
|
||||||
| "application"
|
| "application"
|
||||||
| "postgres"
|
| "compose"
|
||||||
| "mysql"
|
| "libsql"
|
||||||
|
| "mariadb"
|
||||||
| "mongo"
|
| "mongo"
|
||||||
| "redis"
|
| "mysql"
|
||||||
| "compose";
|
| "postgres"
|
||||||
|
| "redis";
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { TimeBadge } from "@/components/ui/time-badge";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { useDebounce } from "@/utils/hooks/use-debounce";
|
import { useDebounce } from "@/utils/hooks/use-debounce";
|
||||||
import { HandleProject } from "./handle-project";
|
import { HandleProject } from "./handle-project";
|
||||||
@@ -199,11 +198,6 @@ export const ShowProjects = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isCloud && (
|
|
||||||
<div className="absolute top-4 right-4">
|
|
||||||
<TimeBadge />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<BreadcrumbSidebar
|
<BreadcrumbSidebar
|
||||||
list={[{ name: "Projects", href: "/dashboard/projects" }]}
|
list={[{ name: "Projects", href: "/dashboard/projects" }]}
|
||||||
/>
|
/>
|
||||||
@@ -300,26 +294,27 @@ export const ShowProjects = () => {
|
|||||||
.map(
|
.map(
|
||||||
(env) =>
|
(env) =>
|
||||||
env.applications.length === 0 &&
|
env.applications.length === 0 &&
|
||||||
|
env.compose.length === 0 &&
|
||||||
|
env.libsql.length === 0 &&
|
||||||
env.mariadb.length === 0 &&
|
env.mariadb.length === 0 &&
|
||||||
env.mongo.length === 0 &&
|
env.mongo.length === 0 &&
|
||||||
env.mysql.length === 0 &&
|
env.mysql.length === 0 &&
|
||||||
env.postgres.length === 0 &&
|
env.postgres.length === 0 &&
|
||||||
env.redis.length === 0 &&
|
env.redis.length === 0,
|
||||||
env.applications.length === 0 &&
|
|
||||||
env.compose.length === 0,
|
|
||||||
)
|
)
|
||||||
.every(Boolean);
|
.every(Boolean);
|
||||||
|
|
||||||
const totalServices = project?.environments
|
const totalServices = project?.environments
|
||||||
.map(
|
.map(
|
||||||
(env) =>
|
(env) =>
|
||||||
|
env.applications.length +
|
||||||
|
env.compose.length +
|
||||||
|
env.libsql.length +
|
||||||
env.mariadb.length +
|
env.mariadb.length +
|
||||||
env.mongo.length +
|
env.mongo.length +
|
||||||
env.mysql.length +
|
env.mysql.length +
|
||||||
env.postgres.length +
|
env.postgres.length +
|
||||||
env.redis.length +
|
env.redis.length,
|
||||||
env.applications.length +
|
|
||||||
env.compose.length,
|
|
||||||
)
|
)
|
||||||
.reduce((acc, curr) => acc + curr, 0);
|
.reduce((acc, curr) => acc + curr, 0);
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ export type Services = {
|
|||||||
| "mysql"
|
| "mysql"
|
||||||
| "mongo"
|
| "mongo"
|
||||||
| "redis"
|
| "redis"
|
||||||
| "compose";
|
| "compose"
|
||||||
|
| "libsql";
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -136,6 +137,18 @@ export const extractServices = (data: Environment | undefined) => {
|
|||||||
serverId: item.serverId,
|
serverId: item.serverId,
|
||||||
})) ?? []) as Services[];
|
})) ?? []) as Services[];
|
||||||
|
|
||||||
|
const libsql: Services[] =
|
||||||
|
data?.libsql?.map((item) => ({
|
||||||
|
appName: item.appName,
|
||||||
|
name: item.name,
|
||||||
|
type: "libsql" as const,
|
||||||
|
id: item.libsqlId,
|
||||||
|
createdAt: item.createdAt,
|
||||||
|
status: item.applicationStatus,
|
||||||
|
description: item.description,
|
||||||
|
serverId: item.serverId,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
applications.push(
|
applications.push(
|
||||||
...mysql,
|
...mysql,
|
||||||
...redis,
|
...redis,
|
||||||
@@ -143,6 +156,7 @@ export const extractServices = (data: Environment | undefined) => {
|
|||||||
...postgres,
|
...postgres,
|
||||||
...mariadb,
|
...mariadb,
|
||||||
...compose,
|
...compose,
|
||||||
|
...libsql,
|
||||||
);
|
);
|
||||||
|
|
||||||
applications.sort((a, b) => {
|
applications.sort((a, b) => {
|
||||||
|
|||||||
@@ -17,17 +17,18 @@ import { api } from "@/utils/api";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mysql" | "mariadb" | "mongo" | "redis";
|
type: "libsql" | "mariadb" | "mongo" | "mysql" | "postgres" | "redis";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RebuildDatabase = ({ id, type }: Props) => {
|
export const RebuildDatabase = ({ id, type }: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const mutationMap = {
|
const mutationMap = {
|
||||||
postgres: () => api.postgres.rebuild.useMutation(),
|
libsql: () => api.libsql.rebuild.useMutation(),
|
||||||
mysql: () => api.mysql.rebuild.useMutation(),
|
|
||||||
mariadb: () => api.mariadb.rebuild.useMutation(),
|
mariadb: () => api.mariadb.rebuild.useMutation(),
|
||||||
mongo: () => api.mongo.rebuild.useMutation(),
|
mongo: () => api.mongo.rebuild.useMutation(),
|
||||||
|
mysql: () => api.mysql.rebuild.useMutation(),
|
||||||
|
postgres: () => api.postgres.rebuild.useMutation(),
|
||||||
redis: () => api.redis.rebuild.useMutation(),
|
redis: () => api.redis.rebuild.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,10 +37,11 @@ export const RebuildDatabase = ({ id, type }: Props) => {
|
|||||||
const handleRebuild = async () => {
|
const handleRebuild = async () => {
|
||||||
try {
|
try {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
postgresId: type === "postgres" ? id : "",
|
libsqlId: type === "libsql" ? id : "",
|
||||||
mysqlId: type === "mysql" ? id : "",
|
|
||||||
mariadbId: type === "mariadb" ? id : "",
|
mariadbId: type === "mariadb" ? id : "",
|
||||||
mongoId: type === "mongo" ? id : "",
|
mongoId: type === "mongo" ? id : "",
|
||||||
|
mysqlId: type === "mysql" ? id : "",
|
||||||
|
postgresId: type === "postgres" ? id : "",
|
||||||
redisId: type === "redis" ? id : "",
|
redisId: type === "redis" ? id : "",
|
||||||
});
|
});
|
||||||
toast.success("Database rebuilt successfully");
|
toast.success("Database rebuilt successfully");
|
||||||
|
|||||||
@@ -6,14 +6,20 @@ import { RebuildDatabase } from "./rebuild-database";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
type: "postgres" | "mysql" | "mariadb" | "mongo" | "redis";
|
type: "libsql" | "mariadb" | "mongo" | "mysql" | "postgres" | "redis";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowDatabaseAdvancedSettings = ({ id, type }: Props) => {
|
export const ShowDatabaseAdvancedSettings = ({ id, type }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-5">
|
<div className="flex w-full flex-col gap-5">
|
||||||
<ShowCustomCommand id={id} type={type} />
|
<ShowCustomCommand id={id} type={type} />
|
||||||
<ShowClusterSettings id={id} type={type} />
|
{type === "mariadb" ||
|
||||||
|
type === "mongo" ||
|
||||||
|
type === "mysql" ||
|
||||||
|
type === "postgres" ||
|
||||||
|
type === "redis" ? (
|
||||||
|
<ShowClusterSettings id={id} type={type} />
|
||||||
|
) : null}
|
||||||
<ShowVolumes id={id} type={type} />
|
<ShowVolumes id={id} type={type} />
|
||||||
<ShowResources id={id} type={type} />
|
<ShowResources id={id} type={type} />
|
||||||
<RebuildDatabase id={id} type={type} />
|
<RebuildDatabase id={id} type={type} />
|
||||||
|
|||||||
@@ -156,6 +156,61 @@ export const RedisIcon = ({ className }: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LibsqlIcon = ({ className }: Props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-label="libsql"
|
||||||
|
height="1em"
|
||||||
|
width="1em"
|
||||||
|
viewBox="0 0 217.2 217.2"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#72f5cf", strokeWidth: "0px" }}
|
||||||
|
d="M118,87.2c.7,0,1.3,0,1.9.3.4.1.8.3,1.2.5.2.1.4.2.6.3.4.3.7.5,1.1.9l5.2,5.2c2.7,2.7,2.7,7,0,9.7l-95.1,95.1c-1.3,1.3-3.1,2-4.8,2s-3.5-.7-4.8-2l-5.2-5.2c-2.7-2.7-2.7-7,0-9.7l85.1-85.1,10-10c.3-.3.7-.6,1.1-.9.2-.1.4-.2.6-.3.4-.2.8-.4,1.2-.5.6-.2,1.3-.3,1.9-.3M118,71.2c-2.2,0-4.4.3-6.5.9-1.4.4-2.8,1-4.1,1.7-.7.4-1.3.7-2,1.2-1.3.8-2.5,1.8-3.6,2.9l-10,10L6.7,173c-8.9,8.9-8.9,23.4,0,32.3l5.2,5.2c4.3,4.3,10.1,6.7,16.2,6.7s11.8-2.4,16.2-6.7l95.1-95.1c4.3-4.3,6.7-10.1,6.7-16.2s-2.4-11.8-6.7-16.2l-5.2-5.2c-1.1-1.1-2.3-2.1-3.6-2.9-.6-.4-1.3-.8-1.9-1.2-1.3-.7-2.7-1.3-4.1-1.7-2.1-.6-4.3-.9-6.5-.9h0Z"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#72f5cf", strokeWidth: "0px" }}
|
||||||
|
d="M119.4,16c.3,0,.6,0,.9,0l66.5,3.8c5.8.3,10.3,4.9,10.7,10.7l3.8,66.5c.3,4.4-1.4,8.7-4.5,11.8l-79.7,79.7c-3,3-6.9,4.5-11,4.5s-3.3-.3-4.9-.8l-49.9-16.5c-4.7-1.5-8.3-5.2-9.8-9.8l-16.5-49.9c-1.8-5.6-.4-11.7,3.8-15.8L108.4,20.5c2.9-2.9,6.9-4.5,11-4.5M119.4,0c-8.4,0-16.3,3.3-22.3,9.2L17.4,88.9c-8.5,8.5-11.4,20.8-7.6,32.1l16.5,49.9c3.1,9.4,10.6,16.9,20,20l49.9,16.5c3.2,1.1,6.5,1.6,9.9,1.6,8.4,0,16.3-3.3,22.3-9.2l79.7-79.7c6.3-6.3,9.7-15.1,9.2-24.1l-3.8-66.5c-.8-13.9-11.9-24.9-25.7-25.7L121.2,0c-.6,0-1.2,0-1.8,0h0Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#a8f7d9", strokeWidth: "0px" }}
|
||||||
|
d="M24.9,116.1l16.5,49.9c1.5,4.7,5.2,8.3,9.8,9.8l49.9,16.5c5.6,1.8,11.7.4,15.8-3.8l79.7-79.7c3.1-3.1,4.8-7.4,4.5-11.8l-3.8-66.5c-.3-5.8-4.9-10.3-10.7-10.7l-66.5-3.8c-4.4-.3-8.7,1.4-11.8,4.5L28.7,100.3c-4.1,4.1-5.6,10.3-3.8,15.8Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#141b1f", strokeWidth: "0px" }}
|
||||||
|
d="M119.4,16c.3,0,.6,0,.9,0l66.5,3.8c5.8.3,10.3,4.9,10.7,10.7l3.8,66.5c.3,4.4-1.4,8.7-4.5,11.8l-79.7,79.7c-3,3-6.9,4.5-11,4.5s-3.3-.3-4.9-.8l-49.9-16.5c-4.7-1.5-8.3-5.2-9.8-9.8l-16.5-49.9c-1.8-5.6-.4-11.7,3.8-15.8L108.4,20.5c2.9-2.9,6.9-4.5,11-4.5M119.4,6c-6.8,0-13.2,2.7-18,7.5L21.6,93.2c-6.8,6.8-9.2,16.8-6.2,26l16.5,49.9c2.5,7.6,8.6,13.7,16.2,16.2l49.9,16.5c2.6.9,5.3,1.3,8,1.3,6.8,0,13.2-2.7,18-7.5l79.7-79.7c5.1-5.1,7.8-12.2,7.4-19.5l-3.8-66.5c-.6-10.8-9.3-19.5-20.1-20.1l-66.5-3.8c-.5,0-1,0-1.5,0h0Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#141b1f", strokeWidth: "0px" }}
|
||||||
|
d="M136.7,173.7l6.9-6.9c-.2-.1-.4-.2-.6-.3l-27.6-9.1-6.9,6.9,28.3,9.3Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#141b1f", strokeWidth: "0px" }}
|
||||||
|
d="M166.5,143.9l6.9-6.9c-.2-.1-.4-.2-.6-.3l-27.6-9.1-6.9,6.9,28.3,9.3Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#141b1f", strokeWidth: "0px" }}
|
||||||
|
d="M43.5,80.5l6.9-6.9c.1.2.2.4.3.6l9.1,27.6-6.9,6.9-9.3-28.3Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#141b1f", strokeWidth: "0px" }}
|
||||||
|
d="M73.3,50.7l6.9-6.9c.1.2.2.4.3.6l9.1,27.6-6.9,6.9-9.3-28.3Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#79ac91", strokeWidth: "0px" }}
|
||||||
|
d="M130.6,101.5l-97.7,97.7c-2.7,2.7-7,2.7-9.7,0l-5.2-5.2c-2.7-2.7-2.7-7,0-9.7l97.7-97.7c1.5-1.5,3.4-2.4,5.5-2.6l7.9-.8c2.8-.3,5.1,2.1,4.9,4.9l-.8,7.9c-.2,2.1-1.1,4-2.6,5.5Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style={{ fill: "#141b1f", strokeWidth: "0px" }}
|
||||||
|
d="M129.5,83.3c2.6,0,4.7,2.2,4.4,4.9l-.8,7.9c-.2,2.1-1.1,4-2.6,5.5l-97.7,97.7c-1.3,1.3-3.1,2-4.8,2s-3.5-.7-4.8-2l-5.2-5.2c-2.7-2.7-2.7-7,0-9.7l97.7-97.7c1.5-1.5,3.4-2.4,5.5-2.6l7.9-.8c.1,0,.3,0,.4,0M129.5,73.3h0c-.5,0-.9,0-1.4,0l-7.9.8c-4.4.4-8.5,2.4-11.5,5.5L10.9,177.3c-6.6,6.6-6.6,17.3,0,23.8l5.2,5.2c3.2,3.2,7.4,4.9,11.9,4.9s8.7-1.8,11.9-4.9l97.7-97.7c3.1-3.1,5-7.2,5.5-11.5l.8-7.9c.4-4-.9-8.1-3.7-11.1-2.7-3-6.6-4.7-10.7-4.7h0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const GitlabIcon = ({ className }: Props) => {
|
export const GitlabIcon = ({ className }: Props) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type ComponentType, useEffect, useMemo, useState } from "react";
|
import { type ComponentType, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
|
LibsqlIcon,
|
||||||
MariadbIcon,
|
MariadbIcon,
|
||||||
MongodbIcon,
|
MongodbIcon,
|
||||||
MysqlIcon,
|
MysqlIcon,
|
||||||
@@ -58,6 +59,7 @@ type EnvironmentServiceCollections = {
|
|||||||
mariadb: (NamedService & { mariadbId: string })[];
|
mariadb: (NamedService & { mariadbId: string })[];
|
||||||
redis: (NamedService & { redisId: string })[];
|
redis: (NamedService & { redisId: string })[];
|
||||||
mongo: (NamedService & { mongoId: string })[];
|
mongo: (NamedService & { mongoId: string })[];
|
||||||
|
libsql: (NamedService & { libsqlId: string })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ServiceCollections = Pick<
|
type ServiceCollections = Pick<
|
||||||
@@ -69,6 +71,7 @@ type ServiceCollections = Pick<
|
|||||||
| "mariadb"
|
| "mariadb"
|
||||||
| "redis"
|
| "redis"
|
||||||
| "mongo"
|
| "mongo"
|
||||||
|
| "libsql"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const SERVICE_COLLECTION_KEYS = [
|
const SERVICE_COLLECTION_KEYS = [
|
||||||
@@ -79,6 +82,7 @@ const SERVICE_COLLECTION_KEYS = [
|
|||||||
"mariadb",
|
"mariadb",
|
||||||
"redis",
|
"redis",
|
||||||
"mongo",
|
"mongo",
|
||||||
|
"libsql",
|
||||||
] as const satisfies ReadonlyArray<keyof ServiceCollections>;
|
] as const satisfies ReadonlyArray<keyof ServiceCollections>;
|
||||||
|
|
||||||
const SERVICE_QUERY_KEYS = [
|
const SERVICE_QUERY_KEYS = [
|
||||||
@@ -89,6 +93,7 @@ const SERVICE_QUERY_KEYS = [
|
|||||||
"mariadbId",
|
"mariadbId",
|
||||||
"redisId",
|
"redisId",
|
||||||
"mongoId",
|
"mongoId",
|
||||||
|
"libsqlId",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const SERVICE_ICONS: Record<
|
const SERVICE_ICONS: Record<
|
||||||
@@ -102,6 +107,7 @@ const SERVICE_ICONS: Record<
|
|||||||
mariadb: MariadbIcon,
|
mariadb: MariadbIcon,
|
||||||
redis: RedisIcon,
|
redis: RedisIcon,
|
||||||
mongo: MongodbIcon,
|
mongo: MongodbIcon,
|
||||||
|
libsql: LibsqlIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStringQueryParam = (value: string | string[] | undefined) =>
|
const getStringQueryParam = (value: string | string[] | undefined) =>
|
||||||
@@ -156,6 +162,7 @@ const extractServicesFromEnvironment = (
|
|||||||
...mapServices(servicesByType.mariadb, (item) => item.mariadbId, "mariadb"),
|
...mapServices(servicesByType.mariadb, (item) => item.mariadbId, "mariadb"),
|
||||||
...mapServices(servicesByType.redis, (item) => item.redisId, "redis"),
|
...mapServices(servicesByType.redis, (item) => item.redisId, "redis"),
|
||||||
...mapServices(servicesByType.mongo, (item) => item.mongoId, "mongo"),
|
...mapServices(servicesByType.mongo, (item) => item.mongoId, "mongo"),
|
||||||
|
...mapServices(servicesByType.libsql, (item) => item.libsqlId, "libsql"),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const CommandInput = React.forwardRef<
|
|||||||
<CommandPrimitive.Input
|
<CommandPrimitive.Input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 focus-visible:ring-inset",
|
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
CREATE TYPE "public"."sqldNode" AS ENUM('primary', 'replica');--> statement-breakpoint
|
||||||
|
ALTER TYPE "public"."databaseType" ADD VALUE 'libsql';--> statement-breakpoint
|
||||||
|
ALTER TYPE "public"."serviceType" ADD VALUE 'libsql';--> statement-breakpoint
|
||||||
|
CREATE TABLE "libsql" (
|
||||||
|
"libsqlId" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"appName" text NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"databaseUser" text NOT NULL,
|
||||||
|
"databasePassword" text NOT NULL,
|
||||||
|
"sqldNode" "sqldNode" DEFAULT 'primary' NOT NULL,
|
||||||
|
"sqldPrimaryUrl" text,
|
||||||
|
"enableNamespaces" boolean DEFAULT false NOT NULL,
|
||||||
|
"dockerImage" text NOT NULL,
|
||||||
|
"command" text,
|
||||||
|
"env" text,
|
||||||
|
"memoryReservation" text,
|
||||||
|
"memoryLimit" text,
|
||||||
|
"cpuReservation" text,
|
||||||
|
"cpuLimit" text,
|
||||||
|
"externalPort" integer,
|
||||||
|
"externalGRPCPort" integer,
|
||||||
|
"externalAdminPort" integer,
|
||||||
|
"applicationStatus" "applicationStatus" DEFAULT 'idle' NOT NULL,
|
||||||
|
"healthCheckSwarm" json,
|
||||||
|
"restartPolicySwarm" json,
|
||||||
|
"placementSwarm" json,
|
||||||
|
"updateConfigSwarm" json,
|
||||||
|
"rollbackConfigSwarm" json,
|
||||||
|
"modeSwarm" json,
|
||||||
|
"labelsSwarm" json,
|
||||||
|
"networkSwarm" json,
|
||||||
|
"stopGracePeriodSwarm" bigint,
|
||||||
|
"endpointSpecSwarm" json,
|
||||||
|
"replicas" integer DEFAULT 1 NOT NULL,
|
||||||
|
"createdAt" text NOT NULL,
|
||||||
|
"environmentId" text NOT NULL,
|
||||||
|
"serverId" text,
|
||||||
|
CONSTRAINT "libsql_appName_unique" UNIQUE("appName")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "backup" ADD COLUMN "libsqlId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mount" ADD COLUMN "libsqlId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "volume_backup" ADD COLUMN "libsqlId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "libsql" ADD CONSTRAINT "libsql_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "libsql" ADD CONSTRAINT "libsql_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "backup" ADD CONSTRAINT "backup_libsqlId_libsql_libsqlId_fk" FOREIGN KEY ("libsqlId") REFERENCES "public"."libsql"("libsqlId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "mount" ADD CONSTRAINT "mount_libsqlId_libsql_libsqlId_fk" FOREIGN KEY ("libsqlId") REFERENCES "public"."libsql"("libsqlId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "volume_backup" ADD CONSTRAINT "volume_backup_libsqlId_libsql_libsqlId_fk" FOREIGN KEY ("libsqlId") REFERENCES "public"."libsql"("libsqlId") ON DELETE cascade ON UPDATE no action;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1072,6 +1072,13 @@
|
|||||||
"when": 1773903778014,
|
"when": 1773903778014,
|
||||||
"tag": "0152_odd_firelord",
|
"tag": "0152_odd_firelord",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 153,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1774322599182,
|
||||||
|
"tag": "0153_motionless_mastermind",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,12 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
const PAGES = [
|
const PAGES = [
|
||||||
"compose",
|
"compose",
|
||||||
"application",
|
"application",
|
||||||
"postgres",
|
"libsql",
|
||||||
"redis",
|
|
||||||
"mysql",
|
|
||||||
"mariadb",
|
"mariadb",
|
||||||
"mongodb",
|
"mongodb",
|
||||||
|
"mysql",
|
||||||
|
"postgres",
|
||||||
|
"redis",
|
||||||
] as const;
|
] as const;
|
||||||
type Page = (typeof PAGES)[number];
|
type Page = (typeof PAGES)[number];
|
||||||
|
|
||||||
@@ -63,11 +64,12 @@ const REDIS_SHORTCUTS: Shortcuts = {
|
|||||||
const SHORTCUTS: ShortcutsDictionary = {
|
const SHORTCUTS: ShortcutsDictionary = {
|
||||||
application: APPLICATION_SHORTCUTS,
|
application: APPLICATION_SHORTCUTS,
|
||||||
compose: COMPOSE_SHORTCUTS,
|
compose: COMPOSE_SHORTCUTS,
|
||||||
postgres: POSTGRES_SHORTCUTS,
|
libsql: POSTGRES_SHORTCUTS,
|
||||||
redis: REDIS_SHORTCUTS,
|
|
||||||
mysql: POSTGRES_SHORTCUTS,
|
|
||||||
mariadb: POSTGRES_SHORTCUTS,
|
mariadb: POSTGRES_SHORTCUTS,
|
||||||
mongodb: POSTGRES_SHORTCUTS,
|
mongodb: POSTGRES_SHORTCUTS,
|
||||||
|
mysql: POSTGRES_SHORTCUTS,
|
||||||
|
postgres: POSTGRES_SHORTCUTS,
|
||||||
|
redis: REDIS_SHORTCUTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { DuplicateProject } from "@/components/dashboard/project/duplicate-proje
|
|||||||
import { EnvironmentVariables } from "@/components/dashboard/project/environment-variables";
|
import { EnvironmentVariables } from "@/components/dashboard/project/environment-variables";
|
||||||
import { ProjectEnvironment } from "@/components/dashboard/projects/project-environment";
|
import { ProjectEnvironment } from "@/components/dashboard/projects/project-environment";
|
||||||
import {
|
import {
|
||||||
|
LibsqlIcon,
|
||||||
MariadbIcon,
|
MariadbIcon,
|
||||||
MongodbIcon,
|
MongodbIcon,
|
||||||
MysqlIcon,
|
MysqlIcon,
|
||||||
@@ -111,7 +112,8 @@ export type Services = {
|
|||||||
| "mysql"
|
| "mysql"
|
||||||
| "mongo"
|
| "mongo"
|
||||||
| "redis"
|
| "redis"
|
||||||
| "compose";
|
| "compose"
|
||||||
|
| "libsql";
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -248,14 +250,27 @@ export const extractServicesFromEnvironment = (
|
|||||||
};
|
};
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
|
const libsql: Services[] =
|
||||||
|
environment.libsql?.map((item) => ({
|
||||||
|
name: item.name,
|
||||||
|
type: "libsql",
|
||||||
|
id: item.libsqlId,
|
||||||
|
createdAt: item.createdAt,
|
||||||
|
status: item.applicationStatus,
|
||||||
|
description: item.description,
|
||||||
|
serverId: item.serverId,
|
||||||
|
serverName: item?.server?.name || null,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
allServices.push(
|
allServices.push(
|
||||||
...applications,
|
...applications,
|
||||||
|
...compose,
|
||||||
|
...libsql,
|
||||||
...mysql,
|
...mysql,
|
||||||
...redis,
|
...redis,
|
||||||
...mongo,
|
...mongo,
|
||||||
...postgres,
|
...postgres,
|
||||||
...mariadb,
|
...mariadb,
|
||||||
...compose,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
allServices.sort((a, b) => {
|
allServices.sort((a, b) => {
|
||||||
@@ -383,7 +398,8 @@ const EnvironmentPage = (
|
|||||||
(currentEnvironment.postgres?.length || 0) === 0 &&
|
(currentEnvironment.postgres?.length || 0) === 0 &&
|
||||||
(currentEnvironment.redis?.length || 0) === 0 &&
|
(currentEnvironment.redis?.length || 0) === 0 &&
|
||||||
(currentEnvironment.applications?.length || 0) === 0 &&
|
(currentEnvironment.applications?.length || 0) === 0 &&
|
||||||
(currentEnvironment.compose?.length || 0) === 0);
|
(currentEnvironment.compose?.length || 0) === 0 &&
|
||||||
|
(currentEnvironment.libsql?.length || 0) === 0);
|
||||||
|
|
||||||
const applications = extractServicesFromEnvironment(currentEnvironment);
|
const applications = extractServicesFromEnvironment(currentEnvironment);
|
||||||
|
|
||||||
@@ -396,6 +412,7 @@ const EnvironmentPage = (
|
|||||||
{ value: "mysql", label: "MySQL", icon: MysqlIcon },
|
{ value: "mysql", label: "MySQL", icon: MysqlIcon },
|
||||||
{ value: "redis", label: "Redis", icon: RedisIcon },
|
{ value: "redis", label: "Redis", icon: RedisIcon },
|
||||||
{ value: "compose", label: "Compose", icon: CircuitBoard },
|
{ value: "compose", label: "Compose", icon: CircuitBoard },
|
||||||
|
{ value: "libsql", label: "Libsql", icon: LibsqlIcon },
|
||||||
];
|
];
|
||||||
|
|
||||||
const [selectedTypes, setSelectedTypes] = useState<string[]>([]);
|
const [selectedTypes, setSelectedTypes] = useState<string[]>([]);
|
||||||
@@ -1525,6 +1542,9 @@ const EnvironmentPage = (
|
|||||||
{service.type === "compose" && (
|
{service.type === "compose" && (
|
||||||
<CircuitBoard className="h-6 w-6" />
|
<CircuitBoard className="h-6 w-6" />
|
||||||
)}
|
)}
|
||||||
|
{service.type === "libsql" && (
|
||||||
|
<LibsqlIcon className="h-6 w-6" />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|||||||
+358
@@ -0,0 +1,358 @@
|
|||||||
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import { HelpCircle, ServerOff } from "lucide-react";
|
||||||
|
import type {
|
||||||
|
GetServerSidePropsContext,
|
||||||
|
InferGetServerSidePropsType,
|
||||||
|
} from "next";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { type ReactElement, useState } from "react";
|
||||||
|
import superjson from "superjson";
|
||||||
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||||
|
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||||
|
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||||
|
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||||
|
import { ShowExternalLibsqlCredentials } from "@/components/dashboard/libsql/general/show-external-libsql-credentials";
|
||||||
|
import { ShowGeneralLibsql } from "@/components/dashboard/libsql/general/show-general-libsql";
|
||||||
|
import { ShowInternalLibsqlCredentials } from "@/components/dashboard/libsql/general/show-internal-libsql-credentials";
|
||||||
|
import { UpdateLibsql } from "@/components/dashboard/libsql/update-libsql";
|
||||||
|
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||||
|
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||||
|
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
|
||||||
|
import { LibsqlIcon } from "@/components/icons/data-tools-icons";
|
||||||
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
|
import { AdvanceBreadcrumb } from "@/components/shared/advance-breadcrumb";
|
||||||
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { appRouter } from "@/server/api/root";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||||
|
|
||||||
|
const Libsql = (
|
||||||
|
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||||
|
) => {
|
||||||
|
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||||
|
|
||||||
|
const { libsqlId, activeTab } = props;
|
||||||
|
const router = useRouter();
|
||||||
|
const { projectId, environmentId } = router.query;
|
||||||
|
const [tab, setSab] = useState<TabState>(activeTab);
|
||||||
|
const { data } = api.libsql.one.useQuery({ libsqlId });
|
||||||
|
const { data: auth } = api.user.get.useQuery();
|
||||||
|
|
||||||
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pb-10">
|
||||||
|
<UseKeyboardNav forPage="libsql" />
|
||||||
|
<AdvanceBreadcrumb />
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Head>
|
||||||
|
<title>
|
||||||
|
Database: {data?.name} - {data?.environment?.project?.name} |
|
||||||
|
Dokploy
|
||||||
|
</title>
|
||||||
|
</Head>
|
||||||
|
<Card className="h-full bg-sidebar p-2.5 rounded-xl w-full">
|
||||||
|
<div className="rounded-xl bg-background shadow-md ">
|
||||||
|
<CardHeader className="flex flex-row justify-between items-center">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<CardTitle className="text-xl flex flex-row gap-2">
|
||||||
|
<div className="relative flex flex-row gap-4">
|
||||||
|
<div className="absolute -right-1 -top-2">
|
||||||
|
<StatusTooltip status={data?.applicationStatus} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LibsqlIcon className="h-6 w-6 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
{data?.name}
|
||||||
|
</CardTitle>
|
||||||
|
{data?.description && (
|
||||||
|
<CardDescription>{data?.description}</CardDescription>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{data?.appName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col h-fit w-fit gap-2">
|
||||||
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
|
<Badge
|
||||||
|
variant={
|
||||||
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
|
? "default"
|
||||||
|
: "destructive"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{data?.server?.name || "Dokploy Server"}
|
||||||
|
</Badge>
|
||||||
|
{data?.server?.serverStatus === "inactive" && (
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Label className="break-all w-fit flex flex-row gap-1 items-center">
|
||||||
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
|
</Label>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent
|
||||||
|
className="z-[999] w-[300px]"
|
||||||
|
align="start"
|
||||||
|
side="top"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
You cannot, deploy this application because the
|
||||||
|
server is inactive, please upgrade your plan to add
|
||||||
|
more servers.
|
||||||
|
</span>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-2 justify-end">
|
||||||
|
<UpdateLibsql libsqlId={libsqlId} />
|
||||||
|
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||||
|
<DeleteService id={libsqlId} type="libsql" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2 py-8 border-t">
|
||||||
|
{data?.server?.serverStatus === "inactive" ? (
|
||||||
|
<div className="flex h-[55vh] border-2 rounded-xl border-dashed p-4">
|
||||||
|
<div className="max-w-3xl mx-auto flex flex-col items-center justify-center self-center gap-3">
|
||||||
|
<ServerOff className="size-10 text-muted-foreground self-center" />
|
||||||
|
<span className="text-center text-base text-muted-foreground">
|
||||||
|
This service is hosted on the server {data.server.name},
|
||||||
|
but this server has been disabled because your current
|
||||||
|
plan doesn't include enough servers. Please purchase more
|
||||||
|
servers to regain access to this application.
|
||||||
|
</span>
|
||||||
|
<span className="text-center text-base text-muted-foreground">
|
||||||
|
Go to{" "}
|
||||||
|
<Link
|
||||||
|
href="/dashboard/settings/billing"
|
||||||
|
className="text-primary"
|
||||||
|
>
|
||||||
|
Billing
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Tabs
|
||||||
|
value={tab}
|
||||||
|
defaultValue="general"
|
||||||
|
className="w-full"
|
||||||
|
onValueChange={(e) => {
|
||||||
|
setSab(e as TabState);
|
||||||
|
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/libsql/${libsqlId}?tab=${e}`;
|
||||||
|
|
||||||
|
router.push(newPath, undefined, { shallow: true });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
||||||
|
<TabsList
|
||||||
|
className={cn(
|
||||||
|
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||||
|
isCloud && data?.serverId
|
||||||
|
? "md:grid-cols-6"
|
||||||
|
: data?.serverId
|
||||||
|
? "md:grid-cols-5"
|
||||||
|
: "md:grid-cols-6",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<TabsTrigger value="general">General</TabsTrigger>
|
||||||
|
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||||
|
<TabsTrigger value="logs">Logs</TabsTrigger>
|
||||||
|
{((data?.serverId && isCloud) || !data?.server) && (
|
||||||
|
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||||
|
)}
|
||||||
|
<TabsTrigger value="backups">Backups</TabsTrigger>
|
||||||
|
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TabsContent value="general">
|
||||||
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
|
<ShowGeneralLibsql libsqlId={libsqlId} />
|
||||||
|
<ShowInternalLibsqlCredentials libsqlId={libsqlId} />
|
||||||
|
<ShowExternalLibsqlCredentials libsqlId={libsqlId} />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="environment">
|
||||||
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
|
<ShowEnvironment id={libsqlId} type="libsql" />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="monitoring">
|
||||||
|
<div className="pt-2.5">
|
||||||
|
<div className="flex flex-col gap-4 border rounded-lg p-6">
|
||||||
|
{data?.serverId && isCloud ? (
|
||||||
|
<ContainerPaidMonitoring
|
||||||
|
appName={data?.appName || ""}
|
||||||
|
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||||
|
token={
|
||||||
|
data?.server?.metricsConfig?.server?.token || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* {monitoring?.enabledFeatures && (
|
||||||
|
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||||
|
<Label className="text-muted-foreground">
|
||||||
|
Change Monitoring
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
checked={toggleMonitoring}
|
||||||
|
onCheckedChange={setToggleMonitoring}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{toggleMonitoring ? (
|
||||||
|
<ContainerPaidMonitoring
|
||||||
|
appName={data?.appName || ""}
|
||||||
|
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||||
|
token={
|
||||||
|
monitoring?.metricsConfig?.server?.token || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div> */}
|
||||||
|
<ContainerFreeMonitoring
|
||||||
|
appName={data?.appName || ""}
|
||||||
|
/>
|
||||||
|
{/* </div> */}
|
||||||
|
{/* )} */}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="logs">
|
||||||
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
|
<ShowDockerLogs
|
||||||
|
serverId={data?.serverId || ""}
|
||||||
|
appName={data?.appName || ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="backups">
|
||||||
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
|
<ShowBackups
|
||||||
|
id={libsqlId}
|
||||||
|
databaseType="libsql"
|
||||||
|
backupType="database"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="advanced">
|
||||||
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
|
<ShowDatabaseAdvancedSettings
|
||||||
|
id={libsqlId}
|
||||||
|
type="libsql"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Libsql;
|
||||||
|
Libsql.getLayout = (page: ReactElement) => {
|
||||||
|
return <DashboardLayout>{page}</DashboardLayout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getServerSideProps(
|
||||||
|
ctx: GetServerSidePropsContext<{
|
||||||
|
libsqlId: string;
|
||||||
|
activeTab: TabState;
|
||||||
|
environmentId: string;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const { query, params, req, res } = ctx;
|
||||||
|
const activeTab = query.tab;
|
||||||
|
|
||||||
|
const { user, session } = await validateRequest(req);
|
||||||
|
if (!user) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Fetch data from external API
|
||||||
|
const helpers = createServerSideHelpers({
|
||||||
|
router: appRouter,
|
||||||
|
ctx: {
|
||||||
|
req: req as any,
|
||||||
|
res: res as any,
|
||||||
|
db: null as any,
|
||||||
|
session: session as any,
|
||||||
|
user: user as any,
|
||||||
|
},
|
||||||
|
transformer: superjson,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof params?.libsqlId === "string") {
|
||||||
|
try {
|
||||||
|
await helpers.libsql.one.fetch({
|
||||||
|
libsqlId: params?.libsqlId,
|
||||||
|
});
|
||||||
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
trpcState: helpers.dehydrate(),
|
||||||
|
libsqlId: params?.libsqlId,
|
||||||
|
activeTab: (activeTab || "general") as TabState,
|
||||||
|
environmentId: params?.environmentId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: "/dashboard/projects",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import { gitProviderRouter } from "./routers/git-provider";
|
|||||||
import { giteaRouter } from "./routers/gitea";
|
import { giteaRouter } from "./routers/gitea";
|
||||||
import { githubRouter } from "./routers/github";
|
import { githubRouter } from "./routers/github";
|
||||||
import { gitlabRouter } from "./routers/gitlab";
|
import { gitlabRouter } from "./routers/gitlab";
|
||||||
|
import { libsqlRouter } from "./routers/libsql";
|
||||||
import { mariadbRouter } from "./routers/mariadb";
|
import { mariadbRouter } from "./routers/mariadb";
|
||||||
import { mongoRouter } from "./routers/mongo";
|
import { mongoRouter } from "./routers/mongo";
|
||||||
import { mountRouter } from "./routers/mount";
|
import { mountRouter } from "./routers/mount";
|
||||||
@@ -54,39 +55,40 @@ import { volumeBackupsRouter } from "./routers/volume-backups";
|
|||||||
|
|
||||||
export const appRouter = createTRPCRouter({
|
export const appRouter = createTRPCRouter({
|
||||||
admin: adminRouter,
|
admin: adminRouter,
|
||||||
docker: dockerRouter,
|
|
||||||
project: projectRouter,
|
|
||||||
application: applicationRouter,
|
application: applicationRouter,
|
||||||
mysql: mysqlRouter,
|
|
||||||
postgres: postgresRouter,
|
|
||||||
redis: redisRouter,
|
|
||||||
mongo: mongoRouter,
|
|
||||||
mariadb: mariadbRouter,
|
|
||||||
compose: composeRouter,
|
|
||||||
user: userRouter,
|
|
||||||
domain: domainRouter,
|
|
||||||
destination: destinationRouter,
|
|
||||||
backup: backupRouter,
|
backup: backupRouter,
|
||||||
deployment: deploymentRouter,
|
|
||||||
previewDeployment: previewDeploymentRouter,
|
|
||||||
mounts: mountRouter,
|
|
||||||
certificates: certificateRouter,
|
|
||||||
settings: settingsRouter,
|
|
||||||
security: securityRouter,
|
|
||||||
redirects: redirectsRouter,
|
|
||||||
port: portRouter,
|
|
||||||
registry: registryRouter,
|
|
||||||
cluster: clusterRouter,
|
|
||||||
notification: notificationRouter,
|
|
||||||
sshKey: sshRouter,
|
|
||||||
gitProvider: gitProviderRouter,
|
|
||||||
gitea: giteaRouter,
|
|
||||||
bitbucket: bitbucketRouter,
|
bitbucket: bitbucketRouter,
|
||||||
gitlab: gitlabRouter,
|
certificates: certificateRouter,
|
||||||
|
cluster: clusterRouter,
|
||||||
|
compose: composeRouter,
|
||||||
|
deployment: deploymentRouter,
|
||||||
|
destination: destinationRouter,
|
||||||
|
docker: dockerRouter,
|
||||||
|
domain: domainRouter,
|
||||||
|
gitea: giteaRouter,
|
||||||
|
gitProvider: gitProviderRouter,
|
||||||
github: githubRouter,
|
github: githubRouter,
|
||||||
|
gitlab: gitlabRouter,
|
||||||
|
libsql: libsqlRouter,
|
||||||
|
mariadb: mariadbRouter,
|
||||||
|
mongo: mongoRouter,
|
||||||
|
mounts: mountRouter,
|
||||||
|
mysql: mysqlRouter,
|
||||||
|
notification: notificationRouter,
|
||||||
|
port: portRouter,
|
||||||
|
postgres: postgresRouter,
|
||||||
|
previewDeployment: previewDeploymentRouter,
|
||||||
|
project: projectRouter,
|
||||||
|
redirects: redirectsRouter,
|
||||||
|
redis: redisRouter,
|
||||||
|
registry: registryRouter,
|
||||||
|
security: securityRouter,
|
||||||
server: serverRouter,
|
server: serverRouter,
|
||||||
|
settings: settingsRouter,
|
||||||
|
sshKey: sshRouter,
|
||||||
stripe: stripeRouter,
|
stripe: stripeRouter,
|
||||||
swarm: swarmRouter,
|
swarm: swarmRouter,
|
||||||
|
user: userRouter,
|
||||||
ai: aiRouter,
|
ai: aiRouter,
|
||||||
organization: organizationRouter,
|
organization: organizationRouter,
|
||||||
licenseKey: licenseKeyRouter,
|
licenseKey: licenseKeyRouter,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import {
|
|||||||
findBackupById,
|
findBackupById,
|
||||||
findComposeByBackupId,
|
findComposeByBackupId,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
|
findLibsqlByBackupId,
|
||||||
|
findLibsqlById,
|
||||||
findMariadbByBackupId,
|
findMariadbByBackupId,
|
||||||
findMariadbById,
|
findMariadbById,
|
||||||
findMongoByBackupId,
|
findMongoByBackupId,
|
||||||
@@ -16,6 +18,7 @@ import {
|
|||||||
keepLatestNBackups,
|
keepLatestNBackups,
|
||||||
removeBackupById,
|
removeBackupById,
|
||||||
removeScheduleBackup,
|
removeScheduleBackup,
|
||||||
|
runLibsqlBackup,
|
||||||
runMariadbBackup,
|
runMariadbBackup,
|
||||||
runMongoBackup,
|
runMongoBackup,
|
||||||
runMySqlBackup,
|
runMySqlBackup,
|
||||||
@@ -36,6 +39,7 @@ import {
|
|||||||
} from "@dokploy/server/utils/process/execAsync";
|
} from "@dokploy/server/utils/process/execAsync";
|
||||||
import {
|
import {
|
||||||
restoreComposeBackup,
|
restoreComposeBackup,
|
||||||
|
restoreLibsqlBackup,
|
||||||
restoreMariadbBackup,
|
restoreMariadbBackup,
|
||||||
restoreMongoBackup,
|
restoreMongoBackup,
|
||||||
restoreMySqlBackup,
|
restoreMySqlBackup,
|
||||||
@@ -82,6 +86,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
input.mysqlId ||
|
input.mysqlId ||
|
||||||
input.mariadbId ||
|
input.mariadbId ||
|
||||||
input.mongoId ||
|
input.mongoId ||
|
||||||
|
input.libsqlId ||
|
||||||
input.composeId;
|
input.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -103,6 +108,8 @@ export const backupRouter = createTRPCRouter({
|
|||||||
serverId = backup.mongo.serverId;
|
serverId = backup.mongo.serverId;
|
||||||
} else if (databaseType === "mariadb" && backup.mariadb?.serverId) {
|
} else if (databaseType === "mariadb" && backup.mariadb?.serverId) {
|
||||||
serverId = backup.mariadb.serverId;
|
serverId = backup.mariadb.serverId;
|
||||||
|
} else if (databaseType === "libsql" && backup.libsql?.serverId) {
|
||||||
|
serverId = backup.libsql.serverId;
|
||||||
} else if (
|
} else if (
|
||||||
backup.backupType === "compose" &&
|
backup.backupType === "compose" &&
|
||||||
backup.compose?.serverId
|
backup.compose?.serverId
|
||||||
@@ -154,6 +161,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
backup.mysqlId ||
|
backup.mysqlId ||
|
||||||
backup.mariadbId ||
|
backup.mariadbId ||
|
||||||
backup.mongoId ||
|
backup.mongoId ||
|
||||||
|
backup.libsqlId ||
|
||||||
backup.composeId;
|
backup.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -173,6 +181,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
existing.mysqlId ||
|
existing.mysqlId ||
|
||||||
existing.mariadbId ||
|
existing.mariadbId ||
|
||||||
existing.mongoId ||
|
existing.mongoId ||
|
||||||
|
existing.libsqlId ||
|
||||||
existing.composeId;
|
existing.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -229,6 +238,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
backup.mysqlId ||
|
backup.mysqlId ||
|
||||||
backup.mariadbId ||
|
backup.mariadbId ||
|
||||||
backup.mongoId ||
|
backup.mongoId ||
|
||||||
|
backup.libsqlId ||
|
||||||
backup.composeId;
|
backup.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -400,6 +410,33 @@ export const backupRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
manualBackupLibsql: protectedProcedure
|
||||||
|
.input(apiFindOneBackup)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const backup = await findBackupById(input.backupId);
|
||||||
|
if (backup.libsqlId) {
|
||||||
|
await checkServicePermissionAndAccess(ctx, backup.libsqlId, {
|
||||||
|
backup: ["create"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const libsql = await findLibsqlByBackupId(backup.backupId);
|
||||||
|
await runLibsqlBackup(libsql, backup);
|
||||||
|
await keepLatestNBackups(backup, libsql?.serverId);
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "run",
|
||||||
|
resourceType: "backup",
|
||||||
|
resourceId: backup.backupId,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error running manual Libsql backup ",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
manualBackupWebServer: withPermission("backup", "create")
|
manualBackupWebServer: withPermission("backup", "create")
|
||||||
.input(apiFindOneBackup)
|
.input(apiFindOneBackup)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@@ -536,6 +573,12 @@ export const backupRouter = createTRPCRouter({
|
|||||||
queue.push(log);
|
queue.push(log);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (input.databaseType === "libsql") {
|
||||||
|
const libsql = await findLibsqlById(input.databaseId);
|
||||||
|
restoreLibsqlBackup(libsql, destination, input, (log) => {
|
||||||
|
queue.push(log);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (input.databaseType === "web-server") {
|
if (input.databaseType === "web-server") {
|
||||||
restoreWebServerBackup(destination, input.backupFile, (log) => {
|
restoreWebServerBackup(destination, input.backupFile, (log) => {
|
||||||
queue.push(log);
|
queue.push(log);
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ const filterEnvironmentServices = (
|
|||||||
applications: environment.applications.filter((app: any) =>
|
applications: environment.applications.filter((app: any) =>
|
||||||
accessedServices.includes(app.applicationId),
|
accessedServices.includes(app.applicationId),
|
||||||
),
|
),
|
||||||
|
compose: environment.compose.filter((comp: any) =>
|
||||||
|
accessedServices.includes(comp.composeId),
|
||||||
|
),
|
||||||
|
libsql: environment.libsql.filter((db: any) =>
|
||||||
|
accessedServices.includes(db.libsqlId),
|
||||||
|
),
|
||||||
mariadb: environment.mariadb.filter((db: any) =>
|
mariadb: environment.mariadb.filter((db: any) =>
|
||||||
accessedServices.includes(db.mariadbId),
|
accessedServices.includes(db.mariadbId),
|
||||||
),
|
),
|
||||||
@@ -53,9 +59,6 @@ const filterEnvironmentServices = (
|
|||||||
redis: environment.redis.filter((db: any) =>
|
redis: environment.redis.filter((db: any) =>
|
||||||
accessedServices.includes(db.redisId),
|
accessedServices.includes(db.redisId),
|
||||||
),
|
),
|
||||||
compose: environment.compose.filter((comp: any) =>
|
|
||||||
accessedServices.includes(comp.composeId),
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const environmentRouter = createTRPCRouter({
|
export const environmentRouter = createTRPCRouter({
|
||||||
|
|||||||
@@ -0,0 +1,453 @@
|
|||||||
|
import {
|
||||||
|
checkPortInUse,
|
||||||
|
createLibsql,
|
||||||
|
createMount,
|
||||||
|
deployLibsql,
|
||||||
|
findEnvironmentById,
|
||||||
|
findLibsqlById,
|
||||||
|
findProjectById,
|
||||||
|
IS_CLOUD,
|
||||||
|
rebuildDatabase,
|
||||||
|
removeLibsqlById,
|
||||||
|
removeService,
|
||||||
|
startService,
|
||||||
|
startServiceRemote,
|
||||||
|
stopService,
|
||||||
|
stopServiceRemote,
|
||||||
|
updateLibsqlById,
|
||||||
|
} from "@dokploy/server";
|
||||||
|
import {
|
||||||
|
addNewService,
|
||||||
|
checkServiceAccess,
|
||||||
|
checkServicePermissionAndAccess,
|
||||||
|
} from "@dokploy/server/services/permission";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||||
|
import { audit } from "@/server/api/utils/audit";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import {
|
||||||
|
apiChangeLibsqlStatus,
|
||||||
|
apiCreateLibsql,
|
||||||
|
apiDeployLibsql,
|
||||||
|
apiFindOneLibsql,
|
||||||
|
apiRebuildLibsql,
|
||||||
|
apiResetLibsql,
|
||||||
|
apiSaveEnvironmentVariablesLibsql,
|
||||||
|
apiSaveExternalPortsLibsql,
|
||||||
|
apiUpdateLibsql,
|
||||||
|
libsql as libsqlTable,
|
||||||
|
} from "@/server/db/schema";
|
||||||
|
export const libsqlRouter = createTRPCRouter({
|
||||||
|
create: protectedProcedure
|
||||||
|
.input(apiCreateLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const environment = await findEnvironmentById(input.environmentId);
|
||||||
|
const project = await findProjectById(environment.projectId);
|
||||||
|
|
||||||
|
await checkServiceAccess(ctx, project.projectId, "create");
|
||||||
|
|
||||||
|
if (IS_CLOUD && !input.serverId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You need to use a server to create a Libsql",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this project",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const newLibsql = await createLibsql({
|
||||||
|
...input,
|
||||||
|
});
|
||||||
|
await addNewService(ctx, newLibsql.libsqlId);
|
||||||
|
|
||||||
|
await createMount({
|
||||||
|
serviceId: newLibsql.libsqlId,
|
||||||
|
serviceType: "libsql",
|
||||||
|
volumeName: `${newLibsql.appName}-data`,
|
||||||
|
mountPath: "/var/lib/sqld",
|
||||||
|
type: "volume",
|
||||||
|
});
|
||||||
|
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "create",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: newLibsql.libsqlId,
|
||||||
|
resourceName: newLibsql.appName,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
one: protectedProcedure
|
||||||
|
.input(apiFindOneLibsql)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
await checkServiceAccess(ctx, input.libsqlId, "read");
|
||||||
|
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
if (
|
||||||
|
libsql.environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this Libsql",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return libsql;
|
||||||
|
}),
|
||||||
|
|
||||||
|
start: protectedProcedure
|
||||||
|
.input(apiFindOneLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
deployment: ["create"],
|
||||||
|
});
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
|
||||||
|
if (libsql.serverId) {
|
||||||
|
await startServiceRemote(libsql.serverId, libsql.appName);
|
||||||
|
} else {
|
||||||
|
await startService(libsql.appName);
|
||||||
|
}
|
||||||
|
await updateLibsqlById(input.libsqlId, {
|
||||||
|
applicationStatus: "done",
|
||||||
|
});
|
||||||
|
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "start",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsql.libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
return libsql;
|
||||||
|
}),
|
||||||
|
stop: protectedProcedure
|
||||||
|
.input(apiFindOneLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
deployment: ["create"],
|
||||||
|
});
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
|
||||||
|
if (libsql.serverId) {
|
||||||
|
await stopServiceRemote(libsql.serverId, libsql.appName);
|
||||||
|
} else {
|
||||||
|
await stopService(libsql.appName);
|
||||||
|
}
|
||||||
|
await updateLibsqlById(input.libsqlId, {
|
||||||
|
applicationStatus: "idle",
|
||||||
|
});
|
||||||
|
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "stop",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsql.libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
return libsql;
|
||||||
|
}),
|
||||||
|
saveExternalPorts: protectedProcedure
|
||||||
|
.input(apiSaveExternalPortsLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
service: ["create"],
|
||||||
|
});
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
|
||||||
|
if (libsql.sqldNode === "replica" && input.externalGRPCPort !== null) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "externalGRPCPort cannot be set when sqldNode is 'replica'",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const portsToCheck = [
|
||||||
|
{
|
||||||
|
port: input.externalPort,
|
||||||
|
name: "externalPort",
|
||||||
|
current: libsql.externalPort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
port: input.externalGRPCPort,
|
||||||
|
name: "externalGRPCPort",
|
||||||
|
current: libsql.externalGRPCPort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
port: input.externalAdminPort,
|
||||||
|
name: "externalAdminPort",
|
||||||
|
current: libsql.externalAdminPort,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { port, name, current } of portsToCheck) {
|
||||||
|
if (port && port !== current) {
|
||||||
|
const portCheck = await checkPortInUse(
|
||||||
|
port,
|
||||||
|
libsql.serverId || undefined,
|
||||||
|
);
|
||||||
|
if (portCheck.isInUse) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "CONFLICT",
|
||||||
|
message: `Port ${port} (${name}) is already in use by ${portCheck.conflictingContainer}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateLibsqlById(input.libsqlId, {
|
||||||
|
externalPort: input.externalPort,
|
||||||
|
externalGRPCPort: input.externalGRPCPort,
|
||||||
|
externalAdminPort: input.externalAdminPort,
|
||||||
|
});
|
||||||
|
await deployLibsql(input.libsqlId);
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "update",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsql.libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
return libsql;
|
||||||
|
}),
|
||||||
|
deploy: protectedProcedure
|
||||||
|
.input(apiDeployLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
deployment: ["create"],
|
||||||
|
});
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "deploy",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsql.libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
return deployLibsql(input.libsqlId);
|
||||||
|
}),
|
||||||
|
deployWithLogs: protectedProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
path: "/deploy/libsql-with-logs",
|
||||||
|
method: "POST",
|
||||||
|
override: true,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(apiDeployLibsql)
|
||||||
|
.subscription(async function* ({ input, ctx, signal }) {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
deployment: ["create"],
|
||||||
|
});
|
||||||
|
const queue: string[] = [];
|
||||||
|
const done = false;
|
||||||
|
|
||||||
|
deployLibsql(input.libsqlId, (log) => {
|
||||||
|
queue.push(log);
|
||||||
|
});
|
||||||
|
|
||||||
|
while (!done || queue.length > 0) {
|
||||||
|
if (queue.length > 0) {
|
||||||
|
yield queue.shift()!;
|
||||||
|
} else {
|
||||||
|
await new Promise((r) => setTimeout(r, 50));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal?.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
changeStatus: protectedProcedure
|
||||||
|
.input(apiChangeLibsqlStatus)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
deployment: ["create"],
|
||||||
|
});
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
await updateLibsqlById(input.libsqlId, {
|
||||||
|
applicationStatus: input.applicationStatus,
|
||||||
|
});
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "update",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsql.libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
return libsql;
|
||||||
|
}),
|
||||||
|
remove: protectedProcedure
|
||||||
|
.input(apiFindOneLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServiceAccess(ctx, input.libsqlId, "delete");
|
||||||
|
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
libsql.environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to delete this Libsql",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "delete",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsql.libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
const cleanupOperations = [
|
||||||
|
async () => await removeService(libsql?.appName, libsql.serverId),
|
||||||
|
async () => await removeLibsqlById(input.libsqlId),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const operation of cleanupOperations) {
|
||||||
|
try {
|
||||||
|
await operation();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libsql;
|
||||||
|
}),
|
||||||
|
saveEnvironment: protectedProcedure
|
||||||
|
.input(apiSaveEnvironmentVariablesLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
envVars: ["write"],
|
||||||
|
});
|
||||||
|
const service = await updateLibsqlById(input.libsqlId, {
|
||||||
|
env: input.env,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error adding environment variables",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "update",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: input.libsqlId,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
reload: protectedProcedure
|
||||||
|
.input(apiResetLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
deployment: ["create"],
|
||||||
|
});
|
||||||
|
const libsql = await findLibsqlById(input.libsqlId);
|
||||||
|
if (libsql.serverId) {
|
||||||
|
await stopServiceRemote(libsql.serverId, libsql.appName);
|
||||||
|
} else {
|
||||||
|
await stopService(libsql.appName);
|
||||||
|
}
|
||||||
|
await updateLibsqlById(input.libsqlId, {
|
||||||
|
applicationStatus: "idle",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (libsql.serverId) {
|
||||||
|
await startServiceRemote(libsql.serverId, libsql.appName);
|
||||||
|
} else {
|
||||||
|
await startService(libsql.appName);
|
||||||
|
}
|
||||||
|
await updateLibsqlById(input.libsqlId, {
|
||||||
|
applicationStatus: "done",
|
||||||
|
});
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "reload",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsql.libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
update: protectedProcedure
|
||||||
|
.input(apiUpdateLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { libsqlId, ...rest } = input;
|
||||||
|
await checkServicePermissionAndAccess(ctx, libsqlId, {
|
||||||
|
service: ["create"],
|
||||||
|
});
|
||||||
|
const libsql = await updateLibsqlById(libsqlId, {
|
||||||
|
...rest,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!libsql) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error updating Libsql",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "update",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: libsqlId,
|
||||||
|
resourceName: libsql.appName,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
move: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
libsqlId: z.string(),
|
||||||
|
targetEnvironmentId: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
service: ["create"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedLibsql = await db
|
||||||
|
.update(libsqlTable)
|
||||||
|
.set({
|
||||||
|
environmentId: input.targetEnvironmentId,
|
||||||
|
})
|
||||||
|
.where(eq(libsqlTable.libsqlId, input.libsqlId))
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0]);
|
||||||
|
|
||||||
|
if (!updatedLibsql) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "Failed to move libsql",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "move",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: updatedLibsql.libsqlId,
|
||||||
|
resourceName: updatedLibsql.appName,
|
||||||
|
});
|
||||||
|
return updatedLibsql;
|
||||||
|
}),
|
||||||
|
rebuild: protectedProcedure
|
||||||
|
.input(apiRebuildLibsql)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
await checkServicePermissionAndAccess(ctx, input.libsqlId, {
|
||||||
|
deployment: ["create"],
|
||||||
|
});
|
||||||
|
|
||||||
|
await rebuildDatabase(input.libsqlId, "libsql");
|
||||||
|
await audit(ctx, {
|
||||||
|
action: "rebuild",
|
||||||
|
resourceType: "service",
|
||||||
|
resourceId: input.libsqlId,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
deleteMount,
|
deleteMount,
|
||||||
findApplicationById,
|
findApplicationById,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
|
findLibsqlById,
|
||||||
findMariadbById,
|
findMariadbById,
|
||||||
findMongoById,
|
findMongoById,
|
||||||
findMountById,
|
findMountById,
|
||||||
@@ -13,13 +14,14 @@ import {
|
|||||||
getServiceContainer,
|
getServiceContainer,
|
||||||
updateMount,
|
updateMount,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
|
import type { ServiceType } from "@dokploy/server/db/schema/mount";
|
||||||
import {
|
import {
|
||||||
checkServiceAccess,
|
checkServiceAccess,
|
||||||
checkServicePermissionAndAccess,
|
checkServicePermissionAndAccess,
|
||||||
} from "@dokploy/server/services/permission";
|
} from "@dokploy/server/services/permission";
|
||||||
import type { ServiceType } from "@dokploy/server/db/schema/mount";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { audit } from "@/server/api/utils/audit";
|
||||||
import {
|
import {
|
||||||
apiCreateMount,
|
apiCreateMount,
|
||||||
apiFindMountByApplicationId,
|
apiFindMountByApplicationId,
|
||||||
@@ -28,7 +30,6 @@ import {
|
|||||||
apiUpdateMount,
|
apiUpdateMount,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
import { audit } from "@/server/api/utils/audit";
|
|
||||||
|
|
||||||
async function getServiceOrganizationId(
|
async function getServiceOrganizationId(
|
||||||
serviceId: string,
|
serviceId: string,
|
||||||
@@ -63,6 +64,10 @@ async function getServiceOrganizationId(
|
|||||||
const compose = await findComposeById(serviceId);
|
const compose = await findComposeById(serviceId);
|
||||||
return compose?.environment?.project?.organizationId ?? null;
|
return compose?.environment?.project?.organizationId ?? null;
|
||||||
}
|
}
|
||||||
|
case "libsql": {
|
||||||
|
const libsql = await findLibsqlById(serviceId);
|
||||||
|
return libsql?.environment?.project?.organizationId ?? null;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -95,6 +100,7 @@ export const mountRouter = createTRPCRouter({
|
|||||||
mount.mongoId ||
|
mount.mongoId ||
|
||||||
mount.mysqlId ||
|
mount.mysqlId ||
|
||||||
mount.redisId ||
|
mount.redisId ||
|
||||||
|
mount.libsqlId ||
|
||||||
mount.composeId;
|
mount.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -120,6 +126,7 @@ export const mountRouter = createTRPCRouter({
|
|||||||
mount.mongoId ||
|
mount.mongoId ||
|
||||||
mount.mysqlId ||
|
mount.mysqlId ||
|
||||||
mount.redisId ||
|
mount.redisId ||
|
||||||
|
mount.libsqlId ||
|
||||||
mount.composeId;
|
mount.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -139,6 +146,7 @@ export const mountRouter = createTRPCRouter({
|
|||||||
mount.mongoId ||
|
mount.mongoId ||
|
||||||
mount.mysqlId ||
|
mount.mysqlId ||
|
||||||
mount.redisId ||
|
mount.redisId ||
|
||||||
|
mount.libsqlId ||
|
||||||
mount.composeId;
|
mount.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -169,7 +177,6 @@ export const mountRouter = createTRPCRouter({
|
|||||||
listByServiceId: protectedProcedure
|
listByServiceId: protectedProcedure
|
||||||
.input(apiFindMountByApplicationId)
|
.input(apiFindMountByApplicationId)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
console.log("input", input);
|
|
||||||
await checkServiceAccess(ctx, input.serviceId, "read");
|
await checkServiceAccess(ctx, input.serviceId, "read");
|
||||||
const organizationId = await getServiceOrganizationId(
|
const organizationId = await getServiceOrganizationId(
|
||||||
input.serviceId,
|
input.serviceId,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
createBackup,
|
createBackup,
|
||||||
createCompose,
|
createCompose,
|
||||||
createDomain,
|
createDomain,
|
||||||
|
createLibsql,
|
||||||
createMariadb,
|
createMariadb,
|
||||||
createMongo,
|
createMongo,
|
||||||
createMount,
|
createMount,
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
findApplicationById,
|
findApplicationById,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
findEnvironmentById,
|
findEnvironmentById,
|
||||||
|
findLibsqlById,
|
||||||
findMariadbById,
|
findMariadbById,
|
||||||
findMongoById,
|
findMongoById,
|
||||||
findMySqlById,
|
findMySqlById,
|
||||||
@@ -28,6 +30,7 @@ import {
|
|||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
updateProjectById,
|
updateProjectById,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
addNewEnvironment,
|
addNewEnvironment,
|
||||||
addNewProject,
|
addNewProject,
|
||||||
@@ -35,17 +38,16 @@ import {
|
|||||||
checkProjectAccess,
|
checkProjectAccess,
|
||||||
findMemberByUserId,
|
findMemberByUserId,
|
||||||
} from "@dokploy/server/services/permission";
|
} from "@dokploy/server/services/permission";
|
||||||
import { db } from "@dokploy/server/db";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
||||||
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { audit } from "@/server/api/utils/audit";
|
|
||||||
import {
|
import {
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
withPermission,
|
withPermission,
|
||||||
} from "@/server/api/trpc";
|
} from "@/server/api/trpc";
|
||||||
|
import { audit } from "@/server/api/utils/audit";
|
||||||
import {
|
import {
|
||||||
apiCreateProject,
|
apiCreateProject,
|
||||||
apiFindOneProject,
|
apiFindOneProject,
|
||||||
@@ -54,6 +56,7 @@ import {
|
|||||||
applications,
|
applications,
|
||||||
compose,
|
compose,
|
||||||
environments,
|
environments,
|
||||||
|
libsql,
|
||||||
mariadb,
|
mariadb,
|
||||||
mongo,
|
mongo,
|
||||||
mysql,
|
mysql,
|
||||||
@@ -138,6 +141,9 @@ export const projectRouter = createTRPCRouter({
|
|||||||
accessedServices,
|
accessedServices,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
where: buildServiceFilter(libsql.libsqlId, accessedServices),
|
||||||
|
},
|
||||||
mariadb: {
|
mariadb: {
|
||||||
where: buildServiceFilter(
|
where: buildServiceFilter(
|
||||||
mariadb.mariadbId,
|
mariadb.mariadbId,
|
||||||
@@ -227,6 +233,14 @@ export const projectRouter = createTRPCRouter({
|
|||||||
applicationStatus: true,
|
applicationStatus: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
where: buildServiceFilter(libsql.libsqlId, accessedServices),
|
||||||
|
columns: {
|
||||||
|
libsqlId: true,
|
||||||
|
name: true,
|
||||||
|
applicationStatus: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
mariadb: {
|
mariadb: {
|
||||||
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
||||||
columns: {
|
columns: {
|
||||||
@@ -338,6 +352,11 @@ export const projectRouter = createTRPCRouter({
|
|||||||
composeStatus: true,
|
composeStatus: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
columns: {
|
||||||
|
libsqlId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
name: true,
|
name: true,
|
||||||
@@ -450,6 +469,17 @@ export const projectRouter = createTRPCRouter({
|
|||||||
serverId: true,
|
serverId: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
columns: {
|
||||||
|
libsqlId: true,
|
||||||
|
appName: true,
|
||||||
|
name: true,
|
||||||
|
createdAt: true,
|
||||||
|
applicationStatus: true,
|
||||||
|
description: true,
|
||||||
|
serverId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -622,12 +652,13 @@ export const projectRouter = createTRPCRouter({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
type: z.enum([
|
type: z.enum([
|
||||||
"application",
|
"application",
|
||||||
"postgres",
|
"compose",
|
||||||
|
"libsql",
|
||||||
"mariadb",
|
"mariadb",
|
||||||
"mongo",
|
"mongo",
|
||||||
"mysql",
|
"mysql",
|
||||||
|
"postgres",
|
||||||
"redis",
|
"redis",
|
||||||
"compose",
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -771,21 +802,27 @@ export const projectRouter = createTRPCRouter({
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "postgres": {
|
case "compose": {
|
||||||
const { postgresId, mounts, backups, appName, ...postgres } =
|
const {
|
||||||
await findPostgresById(id);
|
composeId,
|
||||||
|
mounts,
|
||||||
|
domains,
|
||||||
|
appName,
|
||||||
|
refreshToken,
|
||||||
|
...compose
|
||||||
|
} = await findComposeById(id);
|
||||||
|
|
||||||
const newAppName = appName.substring(
|
const newAppName = appName.substring(
|
||||||
0,
|
0,
|
||||||
appName.lastIndexOf("-"),
|
appName.lastIndexOf("-"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const newPostgres = await createPostgres({
|
const newCompose = await createCompose({
|
||||||
...postgres,
|
...compose,
|
||||||
appName: newAppName,
|
appName: newAppName,
|
||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${postgres.name} (copy)`
|
? `${compose.name} (copy)`
|
||||||
: postgres.name,
|
: compose.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
environmentId: targetProject?.environmentId || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -793,18 +830,49 @@ export const projectRouter = createTRPCRouter({
|
|||||||
const { mountId, ...rest } = mount;
|
const { mountId, ...rest } = mount;
|
||||||
await createMount({
|
await createMount({
|
||||||
...rest,
|
...rest,
|
||||||
serviceId: newPostgres.postgresId,
|
serviceId: newCompose.composeId,
|
||||||
serviceType: "postgres",
|
serviceType: "compose",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const backup of backups) {
|
for (const domain of domains) {
|
||||||
const { backupId, appName: _appName, ...rest } = backup;
|
const { domainId, ...rest } = domain;
|
||||||
await createBackup({
|
await createDomain({
|
||||||
...rest,
|
...rest,
|
||||||
postgresId: newPostgres.postgresId,
|
composeId: newCompose.composeId,
|
||||||
|
domainType: "compose",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "libsql": {
|
||||||
|
const { libsqlId, mounts, appName, ...libsql } =
|
||||||
|
await findLibsqlById(id);
|
||||||
|
|
||||||
|
const newAppName = appName.substring(
|
||||||
|
0,
|
||||||
|
appName.lastIndexOf("-"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const newLibsql = await createLibsql({
|
||||||
|
...libsql,
|
||||||
|
appName: newAppName,
|
||||||
|
name: input.duplicateInSameProject
|
||||||
|
? `${libsql.name} (copy)`
|
||||||
|
: libsql.name,
|
||||||
|
environmentId: targetProject?.environmentId || "",
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const mount of mounts) {
|
||||||
|
const { mountId, ...rest } = mount;
|
||||||
|
await createMount({
|
||||||
|
...rest,
|
||||||
|
serviceId: newLibsql.libsqlId,
|
||||||
|
serviceType: "libsql",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "mariadb": {
|
case "mariadb": {
|
||||||
@@ -915,6 +983,42 @@ export const projectRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "postgres": {
|
||||||
|
const { postgresId, mounts, backups, appName, ...postgres } =
|
||||||
|
await findPostgresById(id);
|
||||||
|
|
||||||
|
const newAppName = appName.substring(
|
||||||
|
0,
|
||||||
|
appName.lastIndexOf("-"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const newPostgres = await createPostgres({
|
||||||
|
...postgres,
|
||||||
|
appName: newAppName,
|
||||||
|
name: input.duplicateInSameProject
|
||||||
|
? `${postgres.name} (copy)`
|
||||||
|
: postgres.name,
|
||||||
|
environmentId: targetProject?.environmentId || "",
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const mount of mounts) {
|
||||||
|
const { mountId, ...rest } = mount;
|
||||||
|
await createMount({
|
||||||
|
...rest,
|
||||||
|
serviceId: newPostgres.postgresId,
|
||||||
|
serviceType: "postgres",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const backup of backups) {
|
||||||
|
const { backupId, ...rest } = backup;
|
||||||
|
await createBackup({
|
||||||
|
...rest,
|
||||||
|
postgresId: newPostgres.postgresId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "redis": {
|
case "redis": {
|
||||||
const { redisId, mounts, appName, ...redis } =
|
const { redisId, mounts, appName, ...redis } =
|
||||||
await findRedisById(id);
|
await findRedisById(id);
|
||||||
@@ -942,50 +1046,6 @@ export const projectRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "compose": {
|
|
||||||
const {
|
|
||||||
composeId,
|
|
||||||
mounts,
|
|
||||||
domains,
|
|
||||||
appName,
|
|
||||||
refreshToken,
|
|
||||||
...compose
|
|
||||||
} = await findComposeById(id);
|
|
||||||
|
|
||||||
const newAppName = appName.substring(
|
|
||||||
0,
|
|
||||||
appName.lastIndexOf("-"),
|
|
||||||
);
|
|
||||||
|
|
||||||
const newCompose = await createCompose({
|
|
||||||
...compose,
|
|
||||||
appName: newAppName,
|
|
||||||
name: input.duplicateInSameProject
|
|
||||||
? `${compose.name} (copy)`
|
|
||||||
: compose.name,
|
|
||||||
environmentId: targetProject?.environmentId || "",
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const mount of mounts) {
|
|
||||||
const { mountId, ...rest } = mount;
|
|
||||||
await createMount({
|
|
||||||
...rest,
|
|
||||||
serviceId: newCompose.composeId,
|
|
||||||
serviceType: "compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const domain of domains) {
|
|
||||||
const { domainId, ...rest } = domain;
|
|
||||||
await createDomain({
|
|
||||||
...rest,
|
|
||||||
composeId: newCompose.composeId,
|
|
||||||
domainType: "compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -648,6 +648,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
"postgres",
|
"postgres",
|
||||||
"redis",
|
"redis",
|
||||||
"mongo",
|
"mongo",
|
||||||
|
"libsql",
|
||||||
"mariadb",
|
"mariadb",
|
||||||
"sshRouter",
|
"sshRouter",
|
||||||
"gitProvider",
|
"gitProvider",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
updateVolumeBackupSchema,
|
updateVolumeBackupSchema,
|
||||||
volumeBackups,
|
volumeBackups,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
|
import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission";
|
||||||
import {
|
import {
|
||||||
execAsyncRemote,
|
execAsyncRemote,
|
||||||
execAsyncStream,
|
execAsyncStream,
|
||||||
@@ -25,7 +26,6 @@ import { desc, eq } from "drizzle-orm";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { audit } from "@/server/api/utils/audit";
|
import { audit } from "@/server/api/utils/audit";
|
||||||
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
||||||
import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission";
|
|
||||||
import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc";
|
import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc";
|
||||||
|
|
||||||
export const volumeBackupsRouter = createTRPCRouter({
|
export const volumeBackupsRouter = createTRPCRouter({
|
||||||
@@ -41,6 +41,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
"mongo",
|
"mongo",
|
||||||
"redis",
|
"redis",
|
||||||
"compose",
|
"compose",
|
||||||
|
"libsql",
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -58,6 +59,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
mongo: true,
|
mongo: true,
|
||||||
redis: true,
|
redis: true,
|
||||||
compose: true,
|
compose: true,
|
||||||
|
libsql: true,
|
||||||
},
|
},
|
||||||
orderBy: [desc(volumeBackups.createdAt)],
|
orderBy: [desc(volumeBackups.createdAt)],
|
||||||
});
|
});
|
||||||
@@ -72,6 +74,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
input.mariadbId ||
|
input.mariadbId ||
|
||||||
input.mongoId ||
|
input.mongoId ||
|
||||||
input.redisId ||
|
input.redisId ||
|
||||||
|
input.libsqlId ||
|
||||||
input.composeId;
|
input.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -113,6 +116,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
vb.mariadbId ||
|
vb.mariadbId ||
|
||||||
vb.mongoId ||
|
vb.mongoId ||
|
||||||
vb.redisId ||
|
vb.redisId ||
|
||||||
|
vb.libsqlId ||
|
||||||
vb.composeId;
|
vb.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -136,6 +140,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
vb.mariadbId ||
|
vb.mariadbId ||
|
||||||
vb.mongoId ||
|
vb.mongoId ||
|
||||||
vb.redisId ||
|
vb.redisId ||
|
||||||
|
vb.libsqlId ||
|
||||||
vb.composeId;
|
vb.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -161,6 +166,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
existingVb.mariadbId ||
|
existingVb.mariadbId ||
|
||||||
existingVb.mongoId ||
|
existingVb.mongoId ||
|
||||||
existingVb.redisId ||
|
existingVb.redisId ||
|
||||||
|
existingVb.libsqlId ||
|
||||||
existingVb.composeId;
|
existingVb.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
@@ -220,6 +226,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
|||||||
vb.mariadbId ||
|
vb.mariadbId ||
|
||||||
vb.mongoId ||
|
vb.mongoId ||
|
||||||
vb.redisId ||
|
vb.redisId ||
|
||||||
|
vb.libsqlId ||
|
||||||
vb.composeId;
|
vb.composeId;
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
keepLatestNBackups,
|
keepLatestNBackups,
|
||||||
runCommand,
|
runCommand,
|
||||||
runComposeBackup,
|
runComposeBackup,
|
||||||
|
runLibsqlBackup,
|
||||||
runMariadbBackup,
|
runMariadbBackup,
|
||||||
runMongoBackup,
|
runMongoBackup,
|
||||||
runMySqlBackup,
|
runMySqlBackup,
|
||||||
@@ -38,6 +39,7 @@ export const runJobs = async (job: QueueJob) => {
|
|||||||
mysql,
|
mysql,
|
||||||
mongo,
|
mongo,
|
||||||
mariadb,
|
mariadb,
|
||||||
|
libsql,
|
||||||
compose,
|
compose,
|
||||||
backupType,
|
backupType,
|
||||||
} = backup;
|
} = backup;
|
||||||
@@ -75,6 +77,14 @@ export const runJobs = async (job: QueueJob) => {
|
|||||||
}
|
}
|
||||||
await runMariadbBackup(mariadb, backup);
|
await runMariadbBackup(mariadb, backup);
|
||||||
await keepLatestNBackups(backup, server.serverId);
|
await keepLatestNBackups(backup, server.serverId);
|
||||||
|
} else if (databaseType === "libsql" && libsql) {
|
||||||
|
const server = await findServerById(libsql.serverId as string);
|
||||||
|
if (server.serverStatus === "inactive") {
|
||||||
|
logger.info("Server is inactive");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await runLibsqlBackup(libsql, backup);
|
||||||
|
await keepLatestNBackups(backup, server.serverId);
|
||||||
}
|
}
|
||||||
} else if (backupType === "compose" && compose) {
|
} else if (backupType === "compose" && compose) {
|
||||||
const server = await findServerById(compose.serverId as string);
|
const server = await findServerById(compose.serverId as string);
|
||||||
@@ -141,6 +151,7 @@ export const initializeJobs = async () => {
|
|||||||
mysql: true,
|
mysql: true,
|
||||||
postgres: true,
|
postgres: true,
|
||||||
mongo: true,
|
mongo: true,
|
||||||
|
libsql: true,
|
||||||
compose: true,
|
compose: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { generateAppName } from ".";
|
|||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
import { deployments } from "./deployment";
|
import { deployments } from "./deployment";
|
||||||
import { destinations } from "./destination";
|
import { destinations } from "./destination";
|
||||||
|
import { libsql } from "./libsql";
|
||||||
import { mariadb } from "./mariadb";
|
import { mariadb } from "./mariadb";
|
||||||
import { mongo } from "./mongo";
|
import { mongo } from "./mongo";
|
||||||
import { mysql } from "./mysql";
|
import { mysql } from "./mysql";
|
||||||
@@ -26,6 +27,7 @@ export const databaseType = pgEnum("databaseType", [
|
|||||||
"mysql",
|
"mysql",
|
||||||
"mongo",
|
"mongo",
|
||||||
"web-server",
|
"web-server",
|
||||||
|
"libsql",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const backupType = pgEnum("backupType", ["database", "compose"]);
|
export const backupType = pgEnum("backupType", ["database", "compose"]);
|
||||||
@@ -74,6 +76,9 @@ export const backups = pgTable("backup", {
|
|||||||
mongoId: text("mongoId").references((): AnyPgColumn => mongo.mongoId, {
|
mongoId: text("mongoId").references((): AnyPgColumn => mongo.mongoId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
|
libsqlId: text("libsqlId").references((): AnyPgColumn => libsql.libsqlId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
userId: text("userId").references(() => user.id),
|
userId: text("userId").references(() => user.id),
|
||||||
// Only for compose backups
|
// Only for compose backups
|
||||||
metadata: jsonb("metadata").$type<
|
metadata: jsonb("metadata").$type<
|
||||||
@@ -118,6 +123,10 @@ export const backupsRelations = relations(backups, ({ one, many }) => ({
|
|||||||
fields: [backups.mongoId],
|
fields: [backups.mongoId],
|
||||||
references: [mongo.mongoId],
|
references: [mongo.mongoId],
|
||||||
}),
|
}),
|
||||||
|
libsql: one(libsql, {
|
||||||
|
fields: [backups.libsqlId],
|
||||||
|
references: [libsql.libsqlId],
|
||||||
|
}),
|
||||||
user: one(user, {
|
user: one(user, {
|
||||||
fields: [backups.userId],
|
fields: [backups.userId],
|
||||||
references: [user.id],
|
references: [user.id],
|
||||||
@@ -137,11 +146,19 @@ const createSchema = createInsertSchema(backups, {
|
|||||||
database: z.string().min(1),
|
database: z.string().min(1),
|
||||||
schedule: z.string(),
|
schedule: z.string(),
|
||||||
keepLatestCount: z.number().optional(),
|
keepLatestCount: z.number().optional(),
|
||||||
databaseType: z.enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]),
|
databaseType: z.enum([
|
||||||
|
"postgres",
|
||||||
|
"mariadb",
|
||||||
|
"mysql",
|
||||||
|
"mongo",
|
||||||
|
"web-server",
|
||||||
|
"libsql",
|
||||||
|
]),
|
||||||
postgresId: z.string().optional(),
|
postgresId: z.string().optional(),
|
||||||
mariadbId: z.string().optional(),
|
mariadbId: z.string().optional(),
|
||||||
mysqlId: z.string().optional(),
|
mysqlId: z.string().optional(),
|
||||||
mongoId: z.string().optional(),
|
mongoId: z.string().optional(),
|
||||||
|
libsqlId: z.string().optional(),
|
||||||
userId: z.string().optional(),
|
userId: z.string().optional(),
|
||||||
metadata: z.any().optional(),
|
metadata: z.any().optional(),
|
||||||
});
|
});
|
||||||
@@ -157,6 +174,7 @@ export const apiCreateBackup = createSchema.pick({
|
|||||||
mysqlId: true,
|
mysqlId: true,
|
||||||
postgresId: true,
|
postgresId: true,
|
||||||
mongoId: true,
|
mongoId: true,
|
||||||
|
libsqlId: true,
|
||||||
databaseType: true,
|
databaseType: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
backupType: true,
|
backupType: true,
|
||||||
@@ -192,7 +210,14 @@ export const apiUpdateBackup = createSchema
|
|||||||
|
|
||||||
export const apiRestoreBackup = z.object({
|
export const apiRestoreBackup = z.object({
|
||||||
databaseId: z.string(),
|
databaseId: z.string(),
|
||||||
databaseType: z.enum(["postgres", "mysql", "mariadb", "mongo", "web-server"]),
|
databaseType: z.enum([
|
||||||
|
"postgres",
|
||||||
|
"mysql",
|
||||||
|
"mariadb",
|
||||||
|
"mongo",
|
||||||
|
"web-server",
|
||||||
|
"libsql",
|
||||||
|
]),
|
||||||
backupType: z.enum(["database", "compose"]),
|
backupType: z.enum(["database", "compose"]),
|
||||||
databaseName: z.string().min(1),
|
databaseName: z.string().min(1),
|
||||||
backupFile: z.string().min(1),
|
backupFile: z.string().min(1),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { nanoid } from "nanoid";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
|
import { libsql } from "./libsql";
|
||||||
import { mariadb } from "./mariadb";
|
import { mariadb } from "./mariadb";
|
||||||
import { mongo } from "./mongo";
|
import { mongo } from "./mongo";
|
||||||
import { mysql } from "./mysql";
|
import { mysql } from "./mysql";
|
||||||
@@ -36,12 +37,13 @@ export const environmentRelations = relations(
|
|||||||
references: [projects.projectId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
applications: many(applications),
|
applications: many(applications),
|
||||||
mariadb: many(mariadb),
|
|
||||||
postgres: many(postgres),
|
|
||||||
mysql: many(mysql),
|
|
||||||
redis: many(redis),
|
|
||||||
mongo: many(mongo),
|
|
||||||
compose: many(compose),
|
compose: many(compose),
|
||||||
|
libsql: many(libsql),
|
||||||
|
mariadb: many(mariadb),
|
||||||
|
mongo: many(mongo),
|
||||||
|
mysql: many(mysql),
|
||||||
|
postgres: many(postgres),
|
||||||
|
redis: many(redis),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export * from "./git-provider";
|
|||||||
export * from "./gitea";
|
export * from "./gitea";
|
||||||
export * from "./github";
|
export * from "./github";
|
||||||
export * from "./gitlab";
|
export * from "./gitlab";
|
||||||
|
export * from "./libsql";
|
||||||
export * from "./mariadb";
|
export * from "./mariadb";
|
||||||
export * from "./mongo";
|
export * from "./mongo";
|
||||||
export * from "./mount";
|
export * from "./mount";
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
import { relations } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
bigint,
|
||||||
|
boolean,
|
||||||
|
integer,
|
||||||
|
json,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { backups } from "./backups";
|
||||||
|
import { environments } from "./environment";
|
||||||
|
import { mounts } from "./mount";
|
||||||
|
import { server } from "./server";
|
||||||
|
import {
|
||||||
|
applicationStatus,
|
||||||
|
type EndpointSpecSwarm,
|
||||||
|
EndpointSpecSwarmSchema,
|
||||||
|
type HealthCheckSwarm,
|
||||||
|
HealthCheckSwarmSchema,
|
||||||
|
type LabelsSwarm,
|
||||||
|
LabelsSwarmSchema,
|
||||||
|
type NetworkSwarm,
|
||||||
|
NetworkSwarmSchema,
|
||||||
|
type PlacementSwarm,
|
||||||
|
PlacementSwarmSchema,
|
||||||
|
type RestartPolicySwarm,
|
||||||
|
RestartPolicySwarmSchema,
|
||||||
|
type ServiceModeSwarm,
|
||||||
|
ServiceModeSwarmSchema,
|
||||||
|
sqldNode,
|
||||||
|
type UpdateConfigSwarm,
|
||||||
|
UpdateConfigSwarmSchema,
|
||||||
|
} from "./shared";
|
||||||
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
|
export const libsql = pgTable("libsql", {
|
||||||
|
libsqlId: text("libsqlId")
|
||||||
|
.notNull()
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
appName: text("appName")
|
||||||
|
.notNull()
|
||||||
|
.$defaultFn(() => generateAppName("libsql"))
|
||||||
|
.unique(),
|
||||||
|
description: text("description"),
|
||||||
|
databaseUser: text("databaseUser").notNull(),
|
||||||
|
databasePassword: text("databasePassword").notNull(),
|
||||||
|
sqldNode: sqldNode("sqldNode").notNull().default("primary"),
|
||||||
|
sqldPrimaryUrl: text("sqldPrimaryUrl"),
|
||||||
|
enableNamespaces: boolean("enableNamespaces").notNull().default(false),
|
||||||
|
dockerImage: text("dockerImage").notNull(),
|
||||||
|
command: text("command"),
|
||||||
|
env: text("env"),
|
||||||
|
// RESOURCES
|
||||||
|
memoryReservation: text("memoryReservation"),
|
||||||
|
memoryLimit: text("memoryLimit"),
|
||||||
|
cpuReservation: text("cpuReservation"),
|
||||||
|
cpuLimit: text("cpuLimit"),
|
||||||
|
//
|
||||||
|
externalPort: integer("externalPort"),
|
||||||
|
externalGRPCPort: integer("externalGRPCPort"),
|
||||||
|
externalAdminPort: integer("externalAdminPort"),
|
||||||
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
|
.notNull()
|
||||||
|
.default("idle"),
|
||||||
|
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||||
|
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||||
|
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||||
|
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
|
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||||
|
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||||
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
|
createdAt: text("createdAt")
|
||||||
|
.notNull()
|
||||||
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
|
|
||||||
|
environmentId: text("environmentId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
||||||
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const libsqlRelations = relations(libsql, ({ one, many }) => ({
|
||||||
|
environment: one(environments, {
|
||||||
|
fields: [libsql.environmentId],
|
||||||
|
references: [environments.environmentId],
|
||||||
|
}),
|
||||||
|
backups: many(backups),
|
||||||
|
mounts: many(mounts),
|
||||||
|
server: one(server, {
|
||||||
|
fields: [libsql.serverId],
|
||||||
|
references: [server.serverId],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const createSchema = createInsertSchema(libsql, {
|
||||||
|
libsqlId: z.string(),
|
||||||
|
name: z.string().min(1),
|
||||||
|
appName: z.string().min(1),
|
||||||
|
createdAt: z.string(),
|
||||||
|
databaseUser: z.string().min(1),
|
||||||
|
databasePassword: z
|
||||||
|
.string()
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
|
sqldNode: z.enum(sqldNode.enumValues),
|
||||||
|
sqldPrimaryUrl: z.string().nullable(),
|
||||||
|
enableNamespaces: z.boolean().default(false),
|
||||||
|
dockerImage: z
|
||||||
|
.string()
|
||||||
|
.default("ghcr.io/tursodatabase/libsql-server:v0.24.32"),
|
||||||
|
command: z.string().optional(),
|
||||||
|
env: z.string().optional(),
|
||||||
|
memoryReservation: z.string().optional(),
|
||||||
|
memoryLimit: z.string().optional(),
|
||||||
|
cpuReservation: z.string().optional(),
|
||||||
|
cpuLimit: z.string().optional(),
|
||||||
|
environmentId: z.string(),
|
||||||
|
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||||
|
externalPort: z.number(),
|
||||||
|
externalGRPCPort: z.number(),
|
||||||
|
externalAdminPort: z.number(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
serverId: z.string().optional(),
|
||||||
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
|
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||||
|
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
|
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||||
|
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiCreateLibsql = createSchema
|
||||||
|
.pick({
|
||||||
|
name: true,
|
||||||
|
appName: true,
|
||||||
|
dockerImage: true,
|
||||||
|
environmentId: true,
|
||||||
|
description: true,
|
||||||
|
databaseUser: true,
|
||||||
|
databasePassword: true,
|
||||||
|
sqldNode: true,
|
||||||
|
sqldPrimaryUrl: true,
|
||||||
|
enableNamespaces: true,
|
||||||
|
serverId: true,
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
.superRefine((data, ctx) => {
|
||||||
|
if (data.sqldNode === "replica" && !data.sqldPrimaryUrl) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["sqldPrimaryUrl"],
|
||||||
|
message: "sqldPrimaryUrl is required when sqldNode is 'replica'.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.sqldNode !== "replica" && data.sqldPrimaryUrl) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["sqldPrimaryUrl"],
|
||||||
|
message:
|
||||||
|
"sqldPrimaryUrl should not be provided when sqldNode is not 'replica'.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiFindOneLibsql = createSchema
|
||||||
|
.pick({
|
||||||
|
libsqlId: true,
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export const apiChangeLibsqlStatus = createSchema
|
||||||
|
.pick({
|
||||||
|
libsqlId: true,
|
||||||
|
applicationStatus: true,
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export const apiSaveEnvironmentVariablesLibsql = createSchema
|
||||||
|
.pick({
|
||||||
|
libsqlId: true,
|
||||||
|
env: true,
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export const apiSaveExternalPortsLibsql = createSchema
|
||||||
|
.pick({
|
||||||
|
libsqlId: true,
|
||||||
|
externalPort: true,
|
||||||
|
externalGRPCPort: true,
|
||||||
|
externalAdminPort: true,
|
||||||
|
})
|
||||||
|
.required({ libsqlId: true })
|
||||||
|
.superRefine((data, ctx) => {
|
||||||
|
if (
|
||||||
|
data.externalPort === null &&
|
||||||
|
data.externalGRPCPort === null &&
|
||||||
|
data.externalAdminPort === null
|
||||||
|
) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message:
|
||||||
|
"Either externalPort, externalGRPCPort or externalAdminPort must be provided.",
|
||||||
|
path: ["externalPort", "externalGRPCPort", "externalAdminPort"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiDeployLibsql = createSchema
|
||||||
|
.pick({
|
||||||
|
libsqlId: true,
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export const apiResetLibsql = createSchema
|
||||||
|
.pick({
|
||||||
|
libsqlId: true,
|
||||||
|
appName: true,
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export const apiUpdateLibsql = createSchema
|
||||||
|
.partial()
|
||||||
|
.extend({
|
||||||
|
libsqlId: z.string().min(1),
|
||||||
|
})
|
||||||
|
.omit({ serverId: true });
|
||||||
|
|
||||||
|
export const apiRebuildLibsql = createSchema
|
||||||
|
.pick({
|
||||||
|
libsqlId: true,
|
||||||
|
})
|
||||||
|
.required();
|
||||||
@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
|
import { libsql } from "./libsql";
|
||||||
import { mariadb } from "./mariadb";
|
import { mariadb } from "./mariadb";
|
||||||
import { mongo } from "./mongo";
|
import { mongo } from "./mongo";
|
||||||
import { mysql } from "./mysql";
|
import { mysql } from "./mysql";
|
||||||
@@ -19,8 +20,11 @@ export const serviceType = pgEnum("serviceType", [
|
|||||||
"mongo",
|
"mongo",
|
||||||
"redis",
|
"redis",
|
||||||
"compose",
|
"compose",
|
||||||
|
"libsql",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export type ServiceType = (typeof serviceType.enumValues)[number];
|
||||||
|
|
||||||
export const mountType = pgEnum("mountType", ["bind", "volume", "file"]);
|
export const mountType = pgEnum("mountType", ["bind", "volume", "file"]);
|
||||||
|
|
||||||
export const mounts = pgTable("mount", {
|
export const mounts = pgTable("mount", {
|
||||||
@@ -39,7 +43,10 @@ export const mounts = pgTable("mount", {
|
|||||||
() => applications.applicationId,
|
() => applications.applicationId,
|
||||||
{ onDelete: "cascade" },
|
{ onDelete: "cascade" },
|
||||||
),
|
),
|
||||||
postgresId: text("postgresId").references(() => postgres.postgresId, {
|
composeId: text("composeId").references(() => compose.composeId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
|
libsqlId: text("libsqlId").references(() => libsql.libsqlId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
mariadbId: text("mariadbId").references(() => mariadb.mariadbId, {
|
mariadbId: text("mariadbId").references(() => mariadb.mariadbId, {
|
||||||
@@ -51,10 +58,10 @@ export const mounts = pgTable("mount", {
|
|||||||
mysqlId: text("mysqlId").references(() => mysql.mysqlId, {
|
mysqlId: text("mysqlId").references(() => mysql.mysqlId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
redisId: text("redisId").references(() => redis.redisId, {
|
postgresId: text("postgresId").references(() => postgres.postgresId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
composeId: text("composeId").references(() => compose.composeId, {
|
redisId: text("redisId").references(() => redis.redisId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -64,9 +71,13 @@ export const MountssRelations = relations(mounts, ({ one }) => ({
|
|||||||
fields: [mounts.applicationId],
|
fields: [mounts.applicationId],
|
||||||
references: [applications.applicationId],
|
references: [applications.applicationId],
|
||||||
}),
|
}),
|
||||||
postgres: one(postgres, {
|
compose: one(compose, {
|
||||||
fields: [mounts.postgresId],
|
fields: [mounts.composeId],
|
||||||
references: [postgres.postgresId],
|
references: [compose.composeId],
|
||||||
|
}),
|
||||||
|
libsql: one(libsql, {
|
||||||
|
fields: [mounts.libsqlId],
|
||||||
|
references: [libsql.libsqlId],
|
||||||
}),
|
}),
|
||||||
mariadb: one(mariadb, {
|
mariadb: one(mariadb, {
|
||||||
fields: [mounts.mariadbId],
|
fields: [mounts.mariadbId],
|
||||||
@@ -80,14 +91,14 @@ export const MountssRelations = relations(mounts, ({ one }) => ({
|
|||||||
fields: [mounts.mysqlId],
|
fields: [mounts.mysqlId],
|
||||||
references: [mysql.mysqlId],
|
references: [mysql.mysqlId],
|
||||||
}),
|
}),
|
||||||
|
postgres: one(postgres, {
|
||||||
|
fields: [mounts.postgresId],
|
||||||
|
references: [postgres.postgresId],
|
||||||
|
}),
|
||||||
redis: one(redis, {
|
redis: one(redis, {
|
||||||
fields: [mounts.redisId],
|
fields: [mounts.redisId],
|
||||||
references: [redis.redisId],
|
references: [redis.redisId],
|
||||||
}),
|
}),
|
||||||
compose: one(compose, {
|
|
||||||
fields: [mounts.composeId],
|
|
||||||
references: [compose.composeId],
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const createSchema = createInsertSchema(mounts, {
|
const createSchema = createInsertSchema(mounts, {
|
||||||
@@ -107,13 +118,10 @@ const createSchema = createInsertSchema(mounts, {
|
|||||||
"mongo",
|
"mongo",
|
||||||
"redis",
|
"redis",
|
||||||
"compose",
|
"compose",
|
||||||
|
"libsql",
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ServiceType = NonNullable<
|
|
||||||
z.infer<typeof createSchema>["serviceType"]
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const apiCreateMount = createSchema
|
export const apiCreateMount = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
type: true,
|
type: true,
|
||||||
@@ -121,8 +129,8 @@ export const apiCreateMount = createSchema
|
|||||||
volumeName: true,
|
volumeName: true,
|
||||||
content: true,
|
content: true,
|
||||||
mountPath: true,
|
mountPath: true,
|
||||||
serviceType: true,
|
|
||||||
filePath: true,
|
filePath: true,
|
||||||
|
serviceType: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
serviceId: z.string().min(1),
|
serviceId: z.string().min(1),
|
||||||
@@ -142,21 +150,12 @@ export const apiRemoveMount = createSchema
|
|||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiFindMountByApplicationId = createSchema
|
export const apiFindMountByApplicationId = createSchema
|
||||||
|
.pick({
|
||||||
|
serviceType: true,
|
||||||
|
})
|
||||||
|
.required()
|
||||||
.extend({
|
.extend({
|
||||||
serviceId: z.string().min(1),
|
serviceId: z.string().min(1),
|
||||||
serviceType: z.enum([
|
|
||||||
"application",
|
|
||||||
"postgres",
|
|
||||||
"mysql",
|
|
||||||
"mariadb",
|
|
||||||
"mongo",
|
|
||||||
"redis",
|
|
||||||
"compose",
|
|
||||||
]),
|
|
||||||
})
|
|
||||||
.pick({
|
|
||||||
serviceId: true,
|
|
||||||
serviceType: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiUpdateMount = createSchema.partial().extend({
|
export const apiUpdateMount = createSchema.partial().extend({
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { applications } from "./application";
|
|||||||
import { certificates } from "./certificate";
|
import { certificates } from "./certificate";
|
||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
import { deployments } from "./deployment";
|
import { deployments } from "./deployment";
|
||||||
|
import { libsql } from "./libsql";
|
||||||
import { mariadb } from "./mariadb";
|
import { mariadb } from "./mariadb";
|
||||||
import { mongo } from "./mongo";
|
import { mongo } from "./mongo";
|
||||||
import { mysql } from "./mysql";
|
import { mysql } from "./mysql";
|
||||||
@@ -116,6 +117,7 @@ export const serverRelations = relations(server, ({ one, many }) => ({
|
|||||||
relationName: "applicationBuildServer",
|
relationName: "applicationBuildServer",
|
||||||
}),
|
}),
|
||||||
compose: many(compose),
|
compose: many(compose),
|
||||||
|
libsql: many(libsql),
|
||||||
redis: many(redis),
|
redis: many(redis),
|
||||||
mariadb: many(mariadb),
|
mariadb: many(mariadb),
|
||||||
mongo: many(mongo),
|
mongo: many(mongo),
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export const certificateType = pgEnum("certificateType", [
|
|||||||
|
|
||||||
export const triggerType = pgEnum("triggerType", ["push", "tag"]);
|
export const triggerType = pgEnum("triggerType", ["push", "tag"]);
|
||||||
|
|
||||||
|
export const sqldNode = pgEnum("sqldNode", ["primary", "replica"]);
|
||||||
|
|
||||||
export interface HealthCheckSwarm {
|
export interface HealthCheckSwarm {
|
||||||
Test?: string[] | undefined;
|
Test?: string[] | undefined;
|
||||||
Interval?: number | undefined;
|
Interval?: number | undefined;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
|
import { serviceType } from "./mount";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -7,9 +8,9 @@ import { applications } from "./application";
|
|||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
import { deployments } from "./deployment";
|
import { deployments } from "./deployment";
|
||||||
import { destinations } from "./destination";
|
import { destinations } from "./destination";
|
||||||
|
import { libsql } from "./libsql";
|
||||||
import { mariadb } from "./mariadb";
|
import { mariadb } from "./mariadb";
|
||||||
import { mongo } from "./mongo";
|
import { mongo } from "./mongo";
|
||||||
import { serviceType } from "./mount";
|
|
||||||
import { mysql } from "./mysql";
|
import { mysql } from "./mysql";
|
||||||
import { postgres } from "./postgres";
|
import { postgres } from "./postgres";
|
||||||
import { redis } from "./redis";
|
import { redis } from "./redis";
|
||||||
@@ -53,6 +54,9 @@ export const volumeBackups = pgTable("volume_backup", {
|
|||||||
redisId: text("redisId").references(() => redis.redisId, {
|
redisId: text("redisId").references(() => redis.redisId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
|
libsqlId: text("libsqlId").references(() => libsql.libsqlId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
composeId: text("composeId").references(() => compose.composeId, {
|
composeId: text("composeId").references(() => compose.composeId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
@@ -93,6 +97,10 @@ export const volumeBackupsRelations = relations(
|
|||||||
fields: [volumeBackups.redisId],
|
fields: [volumeBackups.redisId],
|
||||||
references: [redis.redisId],
|
references: [redis.redisId],
|
||||||
}),
|
}),
|
||||||
|
libsql: one(libsql, {
|
||||||
|
fields: [volumeBackups.libsqlId],
|
||||||
|
references: [libsql.libsqlId],
|
||||||
|
}),
|
||||||
compose: one(compose, {
|
compose: one(compose, {
|
||||||
fields: [volumeBackups.composeId],
|
fields: [volumeBackups.composeId],
|
||||||
references: [compose.composeId],
|
references: [compose.composeId],
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
export type TemplateProps = {
|
export type TemplateProps = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
|
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb" | "libsql";
|
||||||
type: "error" | "success";
|
type: "error" | "success";
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
date: string;
|
date: string;
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ export type TemplateProps = {
|
|||||||
| "mongodb"
|
| "mongodb"
|
||||||
| "mariadb"
|
| "mariadb"
|
||||||
| "redis"
|
| "redis"
|
||||||
| "compose";
|
| "compose"
|
||||||
|
| "libsql";
|
||||||
type: "error" | "success";
|
type: "error" | "success";
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
backupSize?: string;
|
backupSize?: string;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export * from "./services/git-provider";
|
|||||||
export * from "./services/gitea";
|
export * from "./services/gitea";
|
||||||
export * from "./services/github";
|
export * from "./services/github";
|
||||||
export * from "./services/gitlab";
|
export * from "./services/gitlab";
|
||||||
|
export * from "./services/libsql";
|
||||||
export * from "./services/mariadb";
|
export * from "./services/mariadb";
|
||||||
export * from "./services/mongo";
|
export * from "./services/mongo";
|
||||||
export * from "./services/mount";
|
export * from "./services/mount";
|
||||||
@@ -67,6 +68,7 @@ export * from "./utils/access-log/types";
|
|||||||
export * from "./utils/access-log/utils";
|
export * from "./utils/access-log/utils";
|
||||||
export * from "./utils/backups/compose";
|
export * from "./utils/backups/compose";
|
||||||
export * from "./utils/backups/index";
|
export * from "./utils/backups/index";
|
||||||
|
export * from "./utils/backups/libsql";
|
||||||
export * from "./utils/backups/mariadb";
|
export * from "./utils/backups/mariadb";
|
||||||
export * from "./utils/backups/mongo";
|
export * from "./utils/backups/mongo";
|
||||||
export * from "./utils/backups/mysql";
|
export * from "./utils/backups/mysql";
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export const findBackupById = async (backupId: string) => {
|
|||||||
mysql: true,
|
mysql: true,
|
||||||
mariadb: true,
|
mariadb: true,
|
||||||
mongo: true,
|
mongo: true,
|
||||||
|
libsql: true,
|
||||||
destination: true,
|
destination: true,
|
||||||
compose: true,
|
compose: true,
|
||||||
},
|
},
|
||||||
@@ -72,7 +73,7 @@ export const removeBackupById = async (backupId: string) => {
|
|||||||
|
|
||||||
export const findBackupsByDbId = async (
|
export const findBackupsByDbId = async (
|
||||||
id: string,
|
id: string,
|
||||||
type: "postgres" | "mysql" | "mariadb" | "mongo",
|
type: "postgres" | "mysql" | "mariadb" | "mongo" | "libsql",
|
||||||
) => {
|
) => {
|
||||||
const result = await db.query.backups.findMany({
|
const result = await db.query.backups.findMany({
|
||||||
where: eq(backups[`${type}Id`], id),
|
where: eq(backups[`${type}Id`], id),
|
||||||
@@ -81,6 +82,7 @@ export const findBackupsByDbId = async (
|
|||||||
mysql: true,
|
mysql: true,
|
||||||
mariadb: true,
|
mariadb: true,
|
||||||
mongo: true,
|
mongo: true,
|
||||||
|
libsql: true,
|
||||||
destination: true,
|
destination: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -169,6 +169,24 @@ export const findEnvironmentById = async (environmentId: string) => {
|
|||||||
serverId: true,
|
serverId: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
with: {
|
||||||
|
server: {
|
||||||
|
columns: {
|
||||||
|
name: true,
|
||||||
|
serverId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
libsqlId: true,
|
||||||
|
name: true,
|
||||||
|
createdAt: true,
|
||||||
|
applicationStatus: true,
|
||||||
|
description: true,
|
||||||
|
serverId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
project: true,
|
project: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -193,6 +211,7 @@ export const findEnvironmentsByProjectId = async (projectId: string) => {
|
|||||||
postgres: true,
|
postgres: true,
|
||||||
redis: true,
|
redis: true,
|
||||||
compose: true,
|
compose: true,
|
||||||
|
libsql: true,
|
||||||
project: true,
|
project: true,
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
@@ -211,6 +230,7 @@ const environmentHasServices = (
|
|||||||
return (
|
return (
|
||||||
(env.applications?.length ?? 0) > 0 ||
|
(env.applications?.length ?? 0) > 0 ||
|
||||||
(env.compose?.length ?? 0) > 0 ||
|
(env.compose?.length ?? 0) > 0 ||
|
||||||
|
(env.libsql?.length ?? 0) > 0 ||
|
||||||
(env.mariadb?.length ?? 0) > 0 ||
|
(env.mariadb?.length ?? 0) > 0 ||
|
||||||
(env.mongo?.length ?? 0) > 0 ||
|
(env.mongo?.length ?? 0) > 0 ||
|
||||||
(env.mysql?.length ?? 0) > 0 ||
|
(env.mysql?.length ?? 0) > 0 ||
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { db } from "@dokploy/server/db";
|
||||||
|
import {
|
||||||
|
type apiCreateLibsql,
|
||||||
|
backups,
|
||||||
|
buildAppName,
|
||||||
|
libsql,
|
||||||
|
} from "@dokploy/server/db/schema";
|
||||||
|
import { generatePassword } from "@dokploy/server/templates";
|
||||||
|
import { buildLibsql } from "@dokploy/server/utils/databases/libsql";
|
||||||
|
import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||||
|
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { eq, getTableColumns } from "drizzle-orm";
|
||||||
|
import type { z } from "zod";
|
||||||
|
import { validUniqueServerAppName } from "./project";
|
||||||
|
|
||||||
|
export type Libsql = typeof libsql.$inferSelect;
|
||||||
|
|
||||||
|
export const createLibsql = async (input: z.infer<typeof apiCreateLibsql>) => {
|
||||||
|
const appName = buildAppName("libsql", input.appName);
|
||||||
|
|
||||||
|
const valid = await validUniqueServerAppName(input.appName);
|
||||||
|
if (!valid) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "CONFLICT",
|
||||||
|
message: "Service with this 'AppName' already exists",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newLibsql = await db
|
||||||
|
.insert(libsql)
|
||||||
|
.values({
|
||||||
|
...input,
|
||||||
|
databasePassword: input.databasePassword
|
||||||
|
? input.databasePassword
|
||||||
|
: generatePassword(),
|
||||||
|
appName,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newLibsql) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error input: Inserting libsql database",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newLibsql;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881
|
||||||
|
export const findLibsqlById = async (libsqlId: string) => {
|
||||||
|
const result = await db.query.libsql.findFirst({
|
||||||
|
where: eq(libsql.libsqlId, libsqlId),
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounts: true,
|
||||||
|
server: true,
|
||||||
|
backups: {
|
||||||
|
with: {
|
||||||
|
destination: true,
|
||||||
|
deployments: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Libsql not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateLibsqlById = async (
|
||||||
|
libsqlId: string,
|
||||||
|
libsqlData: Partial<Libsql>,
|
||||||
|
) => {
|
||||||
|
const { appName, ...rest } = libsqlData;
|
||||||
|
const result = await db
|
||||||
|
.update(libsql)
|
||||||
|
.set({
|
||||||
|
...rest,
|
||||||
|
})
|
||||||
|
.where(eq(libsql.libsqlId, libsqlId))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return result[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeLibsqlById = async (libsqlId: string) => {
|
||||||
|
const result = await db
|
||||||
|
.delete(libsql)
|
||||||
|
.where(eq(libsql.libsqlId, libsqlId))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return result[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findLibsqlByBackupId = async (backupId: string) => {
|
||||||
|
const result = await db
|
||||||
|
.select({
|
||||||
|
...getTableColumns(libsql),
|
||||||
|
})
|
||||||
|
.from(libsql)
|
||||||
|
.innerJoin(backups, eq(libsql.libsqlId, backups.libsqlId))
|
||||||
|
.where(eq(backups.backupId, backupId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!result || !result[0]) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Libsql not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deployLibsql = async (
|
||||||
|
libsqlId: string,
|
||||||
|
onData?: (data: any) => void,
|
||||||
|
) => {
|
||||||
|
const libsql = await findLibsqlById(libsqlId);
|
||||||
|
try {
|
||||||
|
await updateLibsqlById(libsqlId, {
|
||||||
|
applicationStatus: "running",
|
||||||
|
});
|
||||||
|
onData?.("Starting libsql deployment...");
|
||||||
|
if (libsql.serverId) {
|
||||||
|
await execAsyncRemote(
|
||||||
|
libsql.serverId,
|
||||||
|
`docker pull ${libsql.dockerImage}`,
|
||||||
|
onData,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await pullImage(libsql.dockerImage, onData);
|
||||||
|
}
|
||||||
|
|
||||||
|
await buildLibsql(libsql);
|
||||||
|
await updateLibsqlById(libsqlId, {
|
||||||
|
applicationStatus: "done",
|
||||||
|
});
|
||||||
|
onData?.("Deployment completed successfully!");
|
||||||
|
} catch (error) {
|
||||||
|
onData?.(`Error: ${error}`);
|
||||||
|
await updateLibsqlById(libsqlId, {
|
||||||
|
applicationStatus: "error",
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Error on deploy libsql${error}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return libsql;
|
||||||
|
};
|
||||||
@@ -32,8 +32,11 @@ export const createMount = async (input: z.infer<typeof apiCreateMount>) => {
|
|||||||
...(input.serviceType === "application" && {
|
...(input.serviceType === "application" && {
|
||||||
applicationId: serviceId,
|
applicationId: serviceId,
|
||||||
}),
|
}),
|
||||||
...(input.serviceType === "postgres" && {
|
...(input.serviceType === "compose" && {
|
||||||
postgresId: serviceId,
|
composeId: serviceId,
|
||||||
|
}),
|
||||||
|
...(input.serviceType === "libsql" && {
|
||||||
|
libsqlId: serviceId,
|
||||||
}),
|
}),
|
||||||
...(input.serviceType === "mariadb" && {
|
...(input.serviceType === "mariadb" && {
|
||||||
mariadbId: serviceId,
|
mariadbId: serviceId,
|
||||||
@@ -44,12 +47,12 @@ export const createMount = async (input: z.infer<typeof apiCreateMount>) => {
|
|||||||
...(input.serviceType === "mysql" && {
|
...(input.serviceType === "mysql" && {
|
||||||
mysqlId: serviceId,
|
mysqlId: serviceId,
|
||||||
}),
|
}),
|
||||||
|
...(input.serviceType === "postgres" && {
|
||||||
|
postgresId: serviceId,
|
||||||
|
}),
|
||||||
...(input.serviceType === "redis" && {
|
...(input.serviceType === "redis" && {
|
||||||
redisId: serviceId,
|
redisId: serviceId,
|
||||||
}),
|
}),
|
||||||
...(input.serviceType === "compose" && {
|
|
||||||
composeId: serviceId,
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
.then((value) => value[0]);
|
.then((value) => value[0]);
|
||||||
@@ -115,7 +118,16 @@ export const findMountById = async (mountId: string) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
postgres: {
|
compose: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
libsql: {
|
||||||
with: {
|
with: {
|
||||||
environment: {
|
environment: {
|
||||||
with: {
|
with: {
|
||||||
@@ -151,7 +163,7 @@ export const findMountById = async (mountId: string) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
redis: {
|
postgres: {
|
||||||
with: {
|
with: {
|
||||||
environment: {
|
environment: {
|
||||||
with: {
|
with: {
|
||||||
@@ -160,7 +172,7 @@ export const findMountById = async (mountId: string) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
compose: {
|
redis: {
|
||||||
with: {
|
with: {
|
||||||
environment: {
|
environment: {
|
||||||
with: {
|
with: {
|
||||||
@@ -186,8 +198,11 @@ export const findMountOrganizationId = async (mountId: string) => {
|
|||||||
if (mount.application) {
|
if (mount.application) {
|
||||||
return mount.application.environment.project.organizationId;
|
return mount.application.environment.project.organizationId;
|
||||||
}
|
}
|
||||||
if (mount.postgres) {
|
if (mount.compose) {
|
||||||
return mount.postgres.environment.project.organizationId;
|
return mount.compose.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
if (mount.libsql) {
|
||||||
|
return mount.libsql.environment.project.organizationId;
|
||||||
}
|
}
|
||||||
if (mount.mariadb) {
|
if (mount.mariadb) {
|
||||||
return mount.mariadb.environment.project.organizationId;
|
return mount.mariadb.environment.project.organizationId;
|
||||||
@@ -198,13 +213,13 @@ export const findMountOrganizationId = async (mountId: string) => {
|
|||||||
if (mount.mysql) {
|
if (mount.mysql) {
|
||||||
return mount.mysql.environment.project.organizationId;
|
return mount.mysql.environment.project.organizationId;
|
||||||
}
|
}
|
||||||
|
if (mount.postgres) {
|
||||||
|
return mount.postgres.environment.project.organizationId;
|
||||||
|
}
|
||||||
if (mount.redis) {
|
if (mount.redis) {
|
||||||
return mount.redis.environment.project.organizationId;
|
return mount.redis.environment.project.organizationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mount.compose) {
|
|
||||||
return mount.compose.environment.project.organizationId;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -248,8 +263,8 @@ export const findMountsByApplicationId = async (
|
|||||||
case "application":
|
case "application":
|
||||||
sqlChunks.push(eq(mounts.applicationId, serviceId));
|
sqlChunks.push(eq(mounts.applicationId, serviceId));
|
||||||
break;
|
break;
|
||||||
case "postgres":
|
case "libsql":
|
||||||
sqlChunks.push(eq(mounts.postgresId, serviceId));
|
sqlChunks.push(eq(mounts.libsqlId, serviceId));
|
||||||
break;
|
break;
|
||||||
case "mariadb":
|
case "mariadb":
|
||||||
sqlChunks.push(eq(mounts.mariadbId, serviceId));
|
sqlChunks.push(eq(mounts.mariadbId, serviceId));
|
||||||
@@ -260,6 +275,9 @@ export const findMountsByApplicationId = async (
|
|||||||
case "mysql":
|
case "mysql":
|
||||||
sqlChunks.push(eq(mounts.mysqlId, serviceId));
|
sqlChunks.push(eq(mounts.mysqlId, serviceId));
|
||||||
break;
|
break;
|
||||||
|
case "postgres":
|
||||||
|
sqlChunks.push(eq(mounts.postgresId, serviceId));
|
||||||
|
break;
|
||||||
case "redis":
|
case "redis":
|
||||||
sqlChunks.push(eq(mounts.redisId, serviceId));
|
sqlChunks.push(eq(mounts.redisId, serviceId));
|
||||||
break;
|
break;
|
||||||
@@ -362,6 +380,10 @@ export const getBaseFilesPath = async (mountId: string) => {
|
|||||||
const { COMPOSE_PATH } = paths(!!mount.compose.serverId);
|
const { COMPOSE_PATH } = paths(!!mount.compose.serverId);
|
||||||
appName = mount.compose.appName;
|
appName = mount.compose.appName;
|
||||||
absoluteBasePath = path.resolve(COMPOSE_PATH);
|
absoluteBasePath = path.resolve(COMPOSE_PATH);
|
||||||
|
} else if (mount.serviceType === "libsql" && mount.libsql) {
|
||||||
|
const { APPLICATIONS_PATH } = paths(!!mount.libsql.serverId);
|
||||||
|
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
|
||||||
|
appName = mount.libsql.appName;
|
||||||
}
|
}
|
||||||
directoryPath = path.join(absoluteBasePath, appName, "files");
|
directoryPath = path.join(absoluteBasePath, appName, "files");
|
||||||
|
|
||||||
@@ -391,6 +413,9 @@ export const getServerId = async (mount: MountNested) => {
|
|||||||
if (mount.serviceType === "compose" && mount?.compose?.serverId) {
|
if (mount.serviceType === "compose" && mount?.compose?.serverId) {
|
||||||
return mount.compose.serverId;
|
return mount.compose.serverId;
|
||||||
}
|
}
|
||||||
|
if (mount.serviceType === "libsql" && mount?.libsql?.serverId) {
|
||||||
|
return mount.libsql.serverId;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { db } from "@dokploy/server/db";
|
|||||||
import {
|
import {
|
||||||
type apiCreateProject,
|
type apiCreateProject,
|
||||||
applications,
|
applications,
|
||||||
|
libsql,
|
||||||
mariadb,
|
mariadb,
|
||||||
mongo,
|
mongo,
|
||||||
mysql,
|
mysql,
|
||||||
@@ -53,12 +54,13 @@ export const findProjectById = async (projectId: string) => {
|
|||||||
environments: {
|
environments: {
|
||||||
with: {
|
with: {
|
||||||
applications: true,
|
applications: true,
|
||||||
|
compose: true,
|
||||||
|
libsql: true,
|
||||||
mariadb: true,
|
mariadb: true,
|
||||||
mongo: true,
|
mongo: true,
|
||||||
mysql: true,
|
mysql: true,
|
||||||
postgres: true,
|
postgres: true,
|
||||||
redis: true,
|
redis: true,
|
||||||
compose: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
projectTags: {
|
projectTags: {
|
||||||
@@ -109,6 +111,9 @@ export const validUniqueServerAppName = async (appName: string) => {
|
|||||||
applications: {
|
applications: {
|
||||||
where: eq(applications.appName, appName),
|
where: eq(applications.appName, appName),
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
where: eq(libsql.appName, appName),
|
||||||
|
},
|
||||||
mariadb: {
|
mariadb: {
|
||||||
where: eq(mariadb.appName, appName),
|
where: eq(mariadb.appName, appName),
|
||||||
},
|
},
|
||||||
@@ -131,6 +136,7 @@ export const validUniqueServerAppName = async (appName: string) => {
|
|||||||
const nonEmptyProjects = query.filter(
|
const nonEmptyProjects = query.filter(
|
||||||
(project) =>
|
(project) =>
|
||||||
project.applications.length > 0 ||
|
project.applications.length > 0 ||
|
||||||
|
project.libsql.length > 0 ||
|
||||||
project.mariadb.length > 0 ||
|
project.mariadb.length > 0 ||
|
||||||
project.mongo.length > 0 ||
|
project.mongo.length > 0 ||
|
||||||
project.mysql.length > 0 ||
|
project.mysql.length > 0 ||
|
||||||
|
|||||||
@@ -80,11 +80,12 @@ export const haveActiveServices = async (serverId: string) => {
|
|||||||
with: {
|
with: {
|
||||||
applications: true,
|
applications: true,
|
||||||
compose: true,
|
compose: true,
|
||||||
redis: true,
|
libsql: true,
|
||||||
mariadb: true,
|
mariadb: true,
|
||||||
mongo: true,
|
mongo: true,
|
||||||
mysql: true,
|
mysql: true,
|
||||||
postgres: true,
|
postgres: true,
|
||||||
|
redis: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,11 +96,12 @@ export const haveActiveServices = async (serverId: string) => {
|
|||||||
const total =
|
const total =
|
||||||
currentServer?.applications?.length +
|
currentServer?.applications?.length +
|
||||||
currentServer?.compose?.length +
|
currentServer?.compose?.length +
|
||||||
currentServer?.redis?.length +
|
currentServer?.libsql?.length +
|
||||||
currentServer?.mariadb?.length +
|
currentServer?.mariadb?.length +
|
||||||
currentServer?.mongo?.length +
|
currentServer?.mongo?.length +
|
||||||
currentServer?.mysql?.length +
|
currentServer?.mysql?.length +
|
||||||
currentServer?.postgres?.length;
|
currentServer?.postgres?.length +
|
||||||
|
currentServer?.redis?.length;
|
||||||
|
|
||||||
if (total === 0) {
|
if (total === 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -75,6 +75,15 @@ export const findVolumeBackupById = async (volumeBackupId: string) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
libsql: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
destination: true,
|
destination: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export const initCronJobs = async () => {
|
|||||||
mariadb: true,
|
mariadb: true,
|
||||||
mysql: true,
|
mysql: true,
|
||||||
mongo: true,
|
mongo: true,
|
||||||
|
libsql: true,
|
||||||
user: true,
|
user: true,
|
||||||
compose: true,
|
compose: true,
|
||||||
},
|
},
|
||||||
@@ -116,7 +117,8 @@ const getServiceAppName = (backup: BackupSchedule): string => {
|
|||||||
backup.postgres?.appName ||
|
backup.postgres?.appName ||
|
||||||
backup.mysql?.appName ||
|
backup.mysql?.appName ||
|
||||||
backup.mariadb?.appName ||
|
backup.mariadb?.appName ||
|
||||||
backup.mongo?.appName;
|
backup.mongo?.appName ||
|
||||||
|
backup.libsql?.appName;
|
||||||
return serviceAppName || backup.appName;
|
return serviceAppName || backup.appName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import type { BackupSchedule } from "@dokploy/server/services/backup";
|
||||||
|
import {
|
||||||
|
createDeploymentBackup,
|
||||||
|
updateDeploymentStatus,
|
||||||
|
} from "@dokploy/server/services/deployment";
|
||||||
|
import { findEnvironmentById } from "@dokploy/server/services/environment";
|
||||||
|
import type { Libsql } from "@dokploy/server/services/libsql";
|
||||||
|
import { findProjectById } from "@dokploy/server/services/project";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||||
|
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||||
|
import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils";
|
||||||
|
|
||||||
|
export const runLibsqlBackup = async (
|
||||||
|
libsql: Libsql,
|
||||||
|
backup: BackupSchedule,
|
||||||
|
) => {
|
||||||
|
const { name, environmentId, appName } = libsql;
|
||||||
|
const environment = await findEnvironmentById(environmentId);
|
||||||
|
const project = await findProjectById(environment.projectId);
|
||||||
|
|
||||||
|
const deployment = await createDeploymentBackup({
|
||||||
|
backupId: backup.backupId,
|
||||||
|
title: "Initializing Backup",
|
||||||
|
description: "Initializing Backup",
|
||||||
|
});
|
||||||
|
const { prefix } = backup;
|
||||||
|
const destination = backup.destination;
|
||||||
|
const backupFileName = `${new Date().toISOString()}.sql.gz`;
|
||||||
|
const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`;
|
||||||
|
try {
|
||||||
|
const rcloneFlags = getS3Credentials(destination);
|
||||||
|
const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`;
|
||||||
|
|
||||||
|
const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
|
||||||
|
|
||||||
|
const backupCommand = getBackupCommand(
|
||||||
|
backup,
|
||||||
|
rcloneCommand,
|
||||||
|
deployment.logPath,
|
||||||
|
);
|
||||||
|
if (libsql.serverId) {
|
||||||
|
await execAsyncRemote(libsql.serverId, backupCommand);
|
||||||
|
} else {
|
||||||
|
await execAsync(backupCommand, {
|
||||||
|
shell: "/bin/bash",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "libsql",
|
||||||
|
type: "success",
|
||||||
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
|
} catch (error) {
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "libsql",
|
||||||
|
type: "error",
|
||||||
|
// @ts-ignore
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import type { Destination } from "@dokploy/server/services/destination";
|
|||||||
import { scheduledJobs, scheduleJob } from "node-schedule";
|
import { scheduledJobs, scheduleJob } from "node-schedule";
|
||||||
import { keepLatestNBackups } from ".";
|
import { keepLatestNBackups } from ".";
|
||||||
import { runComposeBackup } from "./compose";
|
import { runComposeBackup } from "./compose";
|
||||||
|
import { runLibsqlBackup } from "./libsql";
|
||||||
import { runMariadbBackup } from "./mariadb";
|
import { runMariadbBackup } from "./mariadb";
|
||||||
import { runMongoBackup } from "./mongo";
|
import { runMongoBackup } from "./mongo";
|
||||||
import { runMySqlBackup } from "./mysql";
|
import { runMySqlBackup } from "./mysql";
|
||||||
@@ -19,6 +20,7 @@ export const scheduleBackup = (backup: BackupSchedule) => {
|
|||||||
mysql,
|
mysql,
|
||||||
mongo,
|
mongo,
|
||||||
mariadb,
|
mariadb,
|
||||||
|
libsql,
|
||||||
compose,
|
compose,
|
||||||
} = backup;
|
} = backup;
|
||||||
scheduleJob(backupId, schedule, async () => {
|
scheduleJob(backupId, schedule, async () => {
|
||||||
@@ -35,6 +37,9 @@ export const scheduleBackup = (backup: BackupSchedule) => {
|
|||||||
} else if (databaseType === "mariadb" && mariadb) {
|
} else if (databaseType === "mariadb" && mariadb) {
|
||||||
await runMariadbBackup(mariadb, backup);
|
await runMariadbBackup(mariadb, backup);
|
||||||
await keepLatestNBackups(backup, mariadb.serverId);
|
await keepLatestNBackups(backup, mariadb.serverId);
|
||||||
|
} else if (databaseType === "libsql" && libsql) {
|
||||||
|
await runLibsqlBackup(libsql, backup);
|
||||||
|
await keepLatestNBackups(backup, libsql.serverId);
|
||||||
} else if (databaseType === "web-server") {
|
} else if (databaseType === "web-server") {
|
||||||
await runWebServerBackup(backup);
|
await runWebServerBackup(backup);
|
||||||
await keepLatestNBackups(backup);
|
await keepLatestNBackups(backup);
|
||||||
@@ -107,6 +112,10 @@ export const getMongoBackupCommand = (
|
|||||||
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase admin --gzip"`;
|
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase admin --gzip"`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLibsqlBackupCommand = (database: string) => {
|
||||||
|
return `docker exec -i $CONTAINER_ID sh -c "tar cf - -C /var/lib/sqld ${database} | gzip"`;
|
||||||
|
};
|
||||||
|
|
||||||
export const getServiceContainerCommand = (appName: string) => {
|
export const getServiceContainerCommand = (appName: string) => {
|
||||||
return `docker ps -q --filter "status=running" --filter "label=com.docker.swarm.service.name=${appName}" | head -n 1`;
|
return `docker ps -q --filter "status=running" --filter "label=com.docker.swarm.service.name=${appName}" | head -n 1`;
|
||||||
};
|
};
|
||||||
@@ -123,12 +132,24 @@ export const getComposeContainerCommand = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getContainerSearchCommand = (backup: BackupSchedule) => {
|
const getContainerSearchCommand = (backup: BackupSchedule) => {
|
||||||
const { backupType, postgres, mysql, mariadb, mongo, compose, serviceName } =
|
const {
|
||||||
backup;
|
backupType,
|
||||||
|
postgres,
|
||||||
|
mysql,
|
||||||
|
mariadb,
|
||||||
|
mongo,
|
||||||
|
libsql,
|
||||||
|
compose,
|
||||||
|
serviceName,
|
||||||
|
} = backup;
|
||||||
|
|
||||||
if (backupType === "database") {
|
if (backupType === "database") {
|
||||||
const appName =
|
const appName =
|
||||||
postgres?.appName || mysql?.appName || mariadb?.appName || mongo?.appName;
|
postgres?.appName ||
|
||||||
|
mysql?.appName ||
|
||||||
|
mariadb?.appName ||
|
||||||
|
mongo?.appName ||
|
||||||
|
libsql?.appName;
|
||||||
return getServiceContainerCommand(appName || "");
|
return getServiceContainerCommand(appName || "");
|
||||||
}
|
}
|
||||||
if (backupType === "compose") {
|
if (backupType === "compose") {
|
||||||
@@ -209,6 +230,12 @@ export const generateBackupCommand = (backup: BackupSchedule) => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "libsql": {
|
||||||
|
if (backupType === "database") {
|
||||||
|
return getLibsqlBackupCommand(backup.database);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Database type not supported: ${databaseType}`);
|
throw new Error(`Database type not supported: ${databaseType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
|
import type { CreateServiceOptions, PortConfig } from "dockerode";
|
||||||
|
import {
|
||||||
|
calculateResources,
|
||||||
|
generateBindMounts,
|
||||||
|
generateConfigContainer,
|
||||||
|
generateFileMounts,
|
||||||
|
generateVolumeMounts,
|
||||||
|
prepareEnvironmentVariables,
|
||||||
|
} from "../docker/utils";
|
||||||
|
import { getRemoteDocker } from "../servers/remote-docker";
|
||||||
|
|
||||||
|
export type LibsqlNested = InferResultType<
|
||||||
|
"libsql",
|
||||||
|
{
|
||||||
|
mounts: true;
|
||||||
|
environment: { with: { project: true } };
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
export const buildLibsql = async (libsql: LibsqlNested) => {
|
||||||
|
const {
|
||||||
|
appName,
|
||||||
|
env,
|
||||||
|
externalPort,
|
||||||
|
externalGRPCPort,
|
||||||
|
externalAdminPort,
|
||||||
|
memoryLimit,
|
||||||
|
memoryReservation,
|
||||||
|
databaseUser,
|
||||||
|
databasePassword,
|
||||||
|
sqldNode,
|
||||||
|
sqldPrimaryUrl,
|
||||||
|
cpuLimit,
|
||||||
|
cpuReservation,
|
||||||
|
command,
|
||||||
|
mounts,
|
||||||
|
enableNamespaces,
|
||||||
|
} = libsql;
|
||||||
|
|
||||||
|
const basicAuth = Buffer.from(
|
||||||
|
`${databaseUser}:${databasePassword}`,
|
||||||
|
"utf-8",
|
||||||
|
).toString("base64");
|
||||||
|
|
||||||
|
const defaultLibsqlEnv = `SQLD_NODE="${sqldNode}"\nSQLD_HTTP_AUTH="basic:${basicAuth}"${
|
||||||
|
env ? `\n${env}` : ""
|
||||||
|
}${sqldNode === "replica" ? `\nSQLD_PRIMARY_URL="${sqldPrimaryUrl}"` : ""}`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
HealthCheck,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
|
Labels,
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
|
UpdateConfig,
|
||||||
|
Networks,
|
||||||
|
} = generateConfigContainer(libsql);
|
||||||
|
const resources = calculateResources({
|
||||||
|
memoryLimit,
|
||||||
|
memoryReservation,
|
||||||
|
cpuLimit,
|
||||||
|
cpuReservation,
|
||||||
|
});
|
||||||
|
const envVariables = prepareEnvironmentVariables(
|
||||||
|
defaultLibsqlEnv,
|
||||||
|
libsql.environment.project.env,
|
||||||
|
libsql.environment.env,
|
||||||
|
);
|
||||||
|
const volumesMount = generateVolumeMounts(mounts);
|
||||||
|
const bindsMount = generateBindMounts(mounts);
|
||||||
|
const filesMount = generateFileMounts(appName, libsql);
|
||||||
|
|
||||||
|
const docker = await getRemoteDocker(libsql.serverId);
|
||||||
|
|
||||||
|
let finalCommand =
|
||||||
|
command ??
|
||||||
|
"sqld --db-path iku.db --http-listen-addr 0.0.0.0:8080 --grpc-listen-addr 0.0.0.0:5001 --admin-listen-addr 0.0.0.0:5000";
|
||||||
|
if (enableNamespaces) {
|
||||||
|
finalCommand += " --enable-namespaces";
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings: CreateServiceOptions = {
|
||||||
|
Name: appName,
|
||||||
|
TaskTemplate: {
|
||||||
|
ContainerSpec: {
|
||||||
|
HealthCheck,
|
||||||
|
Image: "ghcr.io/tursodatabase/libsql-server:v0.24.32",
|
||||||
|
Env: envVariables,
|
||||||
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
|
...(finalCommand
|
||||||
|
? {
|
||||||
|
Command: ["/bin/sh"],
|
||||||
|
Args: ["-c", finalCommand],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
Labels,
|
||||||
|
},
|
||||||
|
Networks,
|
||||||
|
RestartPolicy,
|
||||||
|
Placement,
|
||||||
|
Resources: {
|
||||||
|
...resources,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Mode,
|
||||||
|
RollbackConfig,
|
||||||
|
EndpointSpec: {
|
||||||
|
Mode: "dnsrr",
|
||||||
|
Ports: [
|
||||||
|
...(externalPort
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
Protocol: "tcp",
|
||||||
|
TargetPort: 8080,
|
||||||
|
PublishedPort: externalPort,
|
||||||
|
PublishMode: "host",
|
||||||
|
} as PortConfig,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(externalGRPCPort
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
Protocol: "tcp",
|
||||||
|
TargetPort: 5001,
|
||||||
|
PublishedPort: externalGRPCPort,
|
||||||
|
PublishMode: "host",
|
||||||
|
} as PortConfig,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(externalAdminPort
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
Protocol: "tcp",
|
||||||
|
TargetPort: 5000,
|
||||||
|
PublishedPort: externalAdminPort,
|
||||||
|
PublishMode: "host",
|
||||||
|
} as PortConfig,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
UpdateConfig,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const service = docker.getService(appName);
|
||||||
|
const inspect = await service.inspect();
|
||||||
|
await service.update({
|
||||||
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
|
...settings,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
await docker.createService(settings);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
|
libsql,
|
||||||
mariadb,
|
mariadb,
|
||||||
mongo,
|
mongo,
|
||||||
mysql,
|
mysql,
|
||||||
postgres,
|
postgres,
|
||||||
redis,
|
redis,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
|
import { deployLibsql } from "@dokploy/server/services/libsql";
|
||||||
import { deployMariadb } from "@dokploy/server/services/mariadb";
|
import { deployMariadb } from "@dokploy/server/services/mariadb";
|
||||||
import { deployMongo } from "@dokploy/server/services/mongo";
|
import { deployMongo } from "@dokploy/server/services/mongo";
|
||||||
import { deployMySql } from "@dokploy/server/services/mysql";
|
import { deployMySql } from "@dokploy/server/services/mysql";
|
||||||
@@ -15,7 +17,13 @@ import { eq } from "drizzle-orm";
|
|||||||
import { removeService } from "../docker/utils";
|
import { removeService } from "../docker/utils";
|
||||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||||
|
|
||||||
type DatabaseType = "postgres" | "mysql" | "mariadb" | "mongo" | "redis";
|
type DatabaseType =
|
||||||
|
| "libsql"
|
||||||
|
| "mariadb"
|
||||||
|
| "mongo"
|
||||||
|
| "mysql"
|
||||||
|
| "postgres"
|
||||||
|
| "redis";
|
||||||
|
|
||||||
export const rebuildDatabase = async (
|
export const rebuildDatabase = async (
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
@@ -41,31 +49,25 @@ export const rebuildDatabase = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "postgres") {
|
if (type === "libsql") {
|
||||||
await deployPostgres(databaseId);
|
await deployLibsql(databaseId);
|
||||||
} else if (type === "mysql") {
|
|
||||||
await deployMySql(databaseId);
|
|
||||||
} else if (type === "mariadb") {
|
} else if (type === "mariadb") {
|
||||||
await deployMariadb(databaseId);
|
await deployMariadb(databaseId);
|
||||||
} else if (type === "mongo") {
|
} else if (type === "mongo") {
|
||||||
await deployMongo(databaseId);
|
await deployMongo(databaseId);
|
||||||
|
} else if (type === "mysql") {
|
||||||
|
await deployMySql(databaseId);
|
||||||
|
} else if (type === "postgres") {
|
||||||
|
await deployPostgres(databaseId);
|
||||||
} else if (type === "redis") {
|
} else if (type === "redis") {
|
||||||
await deployRedis(databaseId);
|
await deployRedis(databaseId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findDatabaseById = async (databaseId: string, type: DatabaseType) => {
|
const findDatabaseById = async (databaseId: string, type: DatabaseType) => {
|
||||||
if (type === "postgres") {
|
if (type === "libsql") {
|
||||||
return await db.query.postgres.findFirst({
|
return await db.query.libsql.findFirst({
|
||||||
where: eq(postgres.postgresId, databaseId),
|
where: eq(libsql.libsqlId, databaseId),
|
||||||
with: {
|
|
||||||
mounts: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "mysql") {
|
|
||||||
return await db.query.mysql.findFirst({
|
|
||||||
where: eq(mysql.mysqlId, databaseId),
|
|
||||||
with: {
|
with: {
|
||||||
mounts: true,
|
mounts: true,
|
||||||
},
|
},
|
||||||
@@ -87,6 +89,22 @@ const findDatabaseById = async (databaseId: string, type: DatabaseType) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (type === "mysql") {
|
||||||
|
return await db.query.mysql.findFirst({
|
||||||
|
where: eq(mysql.mysqlId, databaseId),
|
||||||
|
with: {
|
||||||
|
mounts: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (type === "postgres") {
|
||||||
|
return await db.query.postgres.findFirst({
|
||||||
|
where: eq(postgres.postgresId, databaseId),
|
||||||
|
with: {
|
||||||
|
mounts: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
if (type === "redis") {
|
if (type === "redis") {
|
||||||
return await db.query.redis.findFirst({
|
return await db.query.redis.findFirst({
|
||||||
where: eq(redis.redisId, databaseId),
|
where: eq(redis.redisId, databaseId),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { ContainerInfo, ResourceRequirements } from "dockerode";
|
|||||||
import { parse } from "dotenv";
|
import { parse } from "dotenv";
|
||||||
import { quote } from "shell-quote";
|
import { quote } from "shell-quote";
|
||||||
import type { ApplicationNested } from "../builders";
|
import type { ApplicationNested } from "../builders";
|
||||||
|
import type { LibsqlNested } from "../databases/libsql";
|
||||||
import type { MariadbNested } from "../databases/mariadb";
|
import type { MariadbNested } from "../databases/mariadb";
|
||||||
import type { MongoNested } from "../databases/mongo";
|
import type { MongoNested } from "../databases/mongo";
|
||||||
import type { MysqlNested } from "../databases/mysql";
|
import type { MysqlNested } from "../databases/mysql";
|
||||||
@@ -610,6 +611,7 @@ export const generateFileMounts = (
|
|||||||
appName: string,
|
appName: string,
|
||||||
service:
|
service:
|
||||||
| ApplicationNested
|
| ApplicationNested
|
||||||
|
| LibsqlNested
|
||||||
| MongoNested
|
| MongoNested
|
||||||
| MariadbNested
|
| MariadbNested
|
||||||
| MysqlNested
|
| MysqlNested
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
}: {
|
}: {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
|
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb" | "libsql";
|
||||||
type: "error" | "success";
|
type: "error" | "success";
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ export const sendVolumeBackupNotifications = async ({
|
|||||||
| "mongodb"
|
| "mongodb"
|
||||||
| "mariadb"
|
| "mariadb"
|
||||||
| "redis"
|
| "redis"
|
||||||
| "compose";
|
| "compose"
|
||||||
|
| "libsql";
|
||||||
type: "error" | "success";
|
type: "error" | "success";
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const restoreComposeBackup = async (
|
|||||||
rcloneCommand = `rclone copy ${rcloneFlags.join(" ")} "${backupPath}"`;
|
rcloneCommand = `rclone copy ${rcloneFlags.join(" ")} "${backupPath}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let credentials: DatabaseCredentials;
|
let credentials: DatabaseCredentials = {};
|
||||||
|
|
||||||
switch (backupInput.databaseType) {
|
switch (backupInput.databaseType) {
|
||||||
case "postgres":
|
case "postgres":
|
||||||
@@ -62,7 +62,11 @@ export const restoreComposeBackup = async (
|
|||||||
const restoreCommand = getRestoreCommand({
|
const restoreCommand = getRestoreCommand({
|
||||||
appName: appName,
|
appName: appName,
|
||||||
serviceName: backupInput.metadata?.serviceName,
|
serviceName: backupInput.metadata?.serviceName,
|
||||||
type: backupInput.databaseType,
|
type: backupInput.databaseType as
|
||||||
|
| "postgres"
|
||||||
|
| "mariadb"
|
||||||
|
| "mysql"
|
||||||
|
| "mongo",
|
||||||
credentials: {
|
credentials: {
|
||||||
database: backupInput.databaseName,
|
database: backupInput.databaseName,
|
||||||
...credentials,
|
...credentials,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { restoreComposeBackup } from "./compose";
|
export { restoreComposeBackup } from "./compose";
|
||||||
|
export { restoreLibsqlBackup } from "./libsql";
|
||||||
export { restoreMariadbBackup } from "./mariadb";
|
export { restoreMariadbBackup } from "./mariadb";
|
||||||
export { restoreMongoBackup } from "./mongo";
|
export { restoreMongoBackup } from "./mongo";
|
||||||
export { restoreMySqlBackup } from "./mysql";
|
export { restoreMySqlBackup } from "./mysql";
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import type { apiRestoreBackup } from "@dokploy/server/db/schema";
|
||||||
|
import type { Destination } from "@dokploy/server/services/destination";
|
||||||
|
import type { Libsql } from "@dokploy/server/services/libsql";
|
||||||
|
import type { z } from "zod";
|
||||||
|
import { getS3Credentials, getServiceContainerCommand } from "../backups/utils";
|
||||||
|
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||||
|
|
||||||
|
export const restoreLibsqlBackup = async (
|
||||||
|
libsql: Libsql,
|
||||||
|
destination: Destination,
|
||||||
|
backupInput: z.infer<typeof apiRestoreBackup>,
|
||||||
|
emit: (log: string) => void,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const { appName, serverId } = libsql;
|
||||||
|
|
||||||
|
const rcloneFlags = getS3Credentials(destination);
|
||||||
|
const bucketPath = `:s3:${destination.bucket}`;
|
||||||
|
|
||||||
|
const backupPath = `${bucketPath}/${backupInput.backupFile}`;
|
||||||
|
|
||||||
|
const rcloneCommand = `rclone cat ${rcloneFlags.join(" ")} "${backupPath}"`;
|
||||||
|
|
||||||
|
emit("Starting restore...");
|
||||||
|
emit(`Backup path: ${backupPath}`);
|
||||||
|
|
||||||
|
const containerSearch = getServiceContainerCommand(appName);
|
||||||
|
const restoreCommand = `docker exec -i $CONTAINER_ID sh -c "tar xzf - -C /var/lib/sqld"`;
|
||||||
|
|
||||||
|
const command = `CONTAINER_ID=$(${containerSearch}) && ${rcloneCommand} | ${restoreCommand}`;
|
||||||
|
|
||||||
|
emit(`Executing command: ${command}`);
|
||||||
|
|
||||||
|
if (serverId) {
|
||||||
|
await execAsyncRemote(serverId, command);
|
||||||
|
} else {
|
||||||
|
await execAsync(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit("Restore completed successfully!");
|
||||||
|
} catch (error) {
|
||||||
|
emit(
|
||||||
|
`Error: ${
|
||||||
|
error instanceof Error ? error.message : "Error restoring libsql backup"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,7 +18,8 @@ export const getVolumeServiceAppName = (
|
|||||||
volumeBackup.mysql?.appName ||
|
volumeBackup.mysql?.appName ||
|
||||||
volumeBackup.mariadb?.appName ||
|
volumeBackup.mariadb?.appName ||
|
||||||
volumeBackup.mongo?.appName ||
|
volumeBackup.mongo?.appName ||
|
||||||
volumeBackup.redis?.appName;
|
volumeBackup.redis?.appName ||
|
||||||
|
volumeBackup.libsql?.appName;
|
||||||
return serviceAppName || volumeBackup.appName;
|
return serviceAppName || volumeBackup.appName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const getProjectName = (
|
|||||||
volumeBackup.mariadb,
|
volumeBackup.mariadb,
|
||||||
volumeBackup.mongo,
|
volumeBackup.mongo,
|
||||||
volumeBackup.redis,
|
volumeBackup.redis,
|
||||||
|
volumeBackup.libsql,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
@@ -48,6 +49,7 @@ const getOrganizationId = (
|
|||||||
volumeBackup.mariadb,
|
volumeBackup.mariadb,
|
||||||
volumeBackup.mongo,
|
volumeBackup.mongo,
|
||||||
volumeBackup.redis,
|
volumeBackup.redis,
|
||||||
|
volumeBackup.libsql,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
|
|||||||
Reference in New Issue
Block a user