Compare commits

...

46 Commits

Author SHA1 Message Date
Mauricio Siu
a6de744f91 fix(2fa): improve error handling and display for two-factor authentication setup
- Handle potential errors when enabling two-factor authentication
- Provide more detailed error messages for password verification
- Update client-side error handling to display specific error messages
- Add base URL configuration for non-cloud environments
2025-03-08 12:37:50 -06:00
Mauricio Siu
b64ddf1119 Merge pull request #1392 from ali-issa/feature/project-view-tab-reorg
feat: reorganize project view tabs into logical workflow groups #1261
2025-03-08 12:36:46 -06:00
Mauricio Siu
2f074ac734 Merge pull request #1405 from frostming/patch-1
feat: fallback to openai compatible provider if url host doesn't match
2025-03-07 00:56:33 -06:00
Mauricio Siu
96e3721b4b chore(ai): remove debug logging in model fetching 2025-03-07 00:56:12 -06:00
Mauricio Siu
b8e5cae88f feat(ai): improve model fetching and error handling
- Add server-side model fetching endpoint with flexible provider support
- Refactor client-side AI settings component to use new API query
- Implement dynamic header generation for different AI providers
- Enhance error handling and toast notifications
- Remove local model fetching logic in favor of server-side implementation
2025-03-07 00:55:11 -06:00
Mauricio Siu
fa20444a14 Merge pull request #1414 from gentslava/fix/template-superset
fix(template): superset
2025-03-07 00:12:43 -06:00
Mauricio Siu
668ccabec8 Merge pull request #1390 from hexaaagon/feat/zipline
feat(zipline): update zipline version
2025-03-07 00:12:19 -06:00
Mauricio Siu
aa07a0c574 Merge pull request #1417 from vinumzz/vinumzz-remove-shadow-monitoring
refactor: remove unnecessary extra shadow from monitoring page
2025-03-07 00:10:22 -06:00
Mauricio Siu
0b64b43376 Merge pull request #1415 from gentslava/feature/template-datalens
feat(template): DataLens
2025-03-07 00:10:03 -06:00
Mauricio Siu
5c65dc9a21 Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:20 -06:00
Mauricio Siu
58262606d4 Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:16 -06:00
Mauricio Siu
f73959db41 Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:10 -06:00
Mauricio Siu
e6c664e65f Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:06 -06:00
Mauricio Siu
36cc157566 Merge pull request #1355 from drudge/templates/hoarder
feat: add Hoarder template
2025-03-07 00:03:42 -06:00
Mauricio Siu
7e070623cc Merge pull request #1411 from gentslava/canary
fix: breadcrumbs UX
2025-03-06 23:58:42 -06:00
Mauricio Siu
b2c0a685f8 fix(destinations): validate server selection for cloud destinations 2025-03-06 23:57:39 -06:00
Mauricio Siu
c14528886d Merge pull request #1424 from Dokploy/1382-automated-postgres-backup-always-empty
feat(destinations): add createdAt timestamp and display creation date
2025-03-06 23:54:03 -06:00
Mauricio Siu
29eb490e2d feat(destinations): add createdAt timestamp and display creation date 2025-03-06 23:46:21 -06:00
Mauricio Siu
6166963b00 fix(gitlab): remove debug console logs from connection testing 2025-03-06 22:27:30 -06:00
Mauricio Siu
f544efed35 Merge pull request #1422 from Dokploy/1418-fetching-gitlab-repositories-0-found
fix(gitlab): update repository filtering and connection testing
2025-03-06 22:17:34 -06:00
Mauricio Siu
598d095241 fix(gitlab): update repository filtering and connection testing
- Change repository filtering to use 'user' kind instead of 'member'
- Add console logging for debugging GitLab provider and repository connection
- Ensure consistent filtering logic in both getGitlabRepositories and testGitlabConnection
2025-03-06 22:17:20 -06:00
Mauricio Siu
457a8e05fd chore(issue-template): update bug report label emoji 2025-03-06 22:05:31 -06:00
Mauricio Siu
3ca057c44a chore(issue-template): update bug report label and add cloud version option 2025-03-06 22:04:46 -06:00
Nicholas Penree
ad3a0198e9 feat: add Hoarder template 2025-03-06 08:23:49 -05:00
Peter Vinum
ab5f62604c refactor: remove unnecessary extra shadow from monitoring page 2025-03-06 14:08:23 +01:00
Vyacheslav Shcherbinin
bf9e886b9a Disable demo 2025-03-06 14:14:01 +07:00
Vyacheslav Shcherbinin
f5cd0fbdd8 Restart policy 2025-03-06 11:32:13 +07:00
Vyacheslav Scherbinin
8859cc97b4 fix: superset docker-compose 2025-03-06 10:46:10 +07:00
Vyacheslav Scherbinin
3bdd5e4dd0 Template DataLens 2025-03-06 10:34:13 +07:00
Vyacheslav Scherbinin
b0c710aa92 Tab instead space 2025-03-05 21:25:11 +07:00
Vyacheslav Scherbinin
c83d0a95b7 Remove ending separator 2025-03-05 20:53:38 +07:00
Vyacheslav Scherbinin
71ca5babfd Remove duplicate breadcrumb 2025-03-05 20:51:09 +07:00
Vyacheslav Scherbinin
f342613503 Text format 2025-03-05 20:27:16 +07:00
Frost Ming
efd176451f fix: default case correct 2025-03-05 16:19:28 +08:00
Frost Ming
a7fd64e019 fix: use a named case 2025-03-05 16:17:50 +08:00
Frost Ming
21c8b98f9c feat: fallback to openai compatible provider if url host doesn't match 2025-03-05 16:12:46 +08:00
Mauricio Siu
6ff06576d0 fix(dockerfile): update Railpack installation script to use bash 2025-03-05 00:36:20 -06:00
Mauricio Siu
24cc08a1ac Merge pull request #1402 from Dokploy/feat/add-railpack
feat(application): add Railpack as a new build type
2025-03-05 00:24:37 -06:00
Mauricio Siu
e039826d50 chore(version): bump project version to v0.19.1 2025-03-05 00:21:36 -06:00
Mauricio Siu
a947b4915a Merge pull request #1398 from shaunjanssens/feature/bitbucket-branches-length
feat: Bitbucket branch length
2025-03-05 00:19:25 -06:00
Mauricio Siu
fc1d9ad202 Merge pull request #1399 from nktnet1/profile-moved-to-top
feat(ui): move profile to top of account dropdown
2025-03-05 00:18:43 -06:00
Mauricio Siu
5489e3b0a5 feat(application): add Railpack as a new build type
- Introduce Railpack as a new build method for applications
- Update database schema to include 'railpack' in buildType enum
- Add Railpack installation and validation scripts for servers
- Implement Railpack build and command generation utilities
- Update UI to include Railpack as a build option with a 'New' badge
2025-03-05 00:18:10 -06:00
Khiet Tam Nguyen
e43b907a8d feat(ui): move profile to first item in account dropdown 2025-03-04 22:42:04 +11:00
Shaun Janssens
62fae661a1 feat: bitbucket branch length 2025-03-04 10:50:42 +01:00
Ali Issa
6846e0e5a3 feat: reorganize project view tabs into logical workflow groups #1261
Restructure the project view tabs to follow a more intuitive user journey:
- Group tabs into "Initial Setup", "Deployment", and "Monitoring" sections
- Maintain "Advanced" as a standalone option
- Order tabs to match typical project workflow (configuration → deployment → monitoring)

This reorganization reduces cognitive load by grouping related functions,
minimizes tab switching during common tasks, and provides a clearer
mental model of the platform's workflow for new users.
2025-03-03 06:45:36 -05:00
Hexaa
49d4cea06f feat(zipline): update zipline version 2025-03-03 12:17:42 +07:00
49 changed files with 10929 additions and 137 deletions

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Create a bug report
labels: ["bug"]
labels: ["needs-triage🔍"]
body:
- type: markdown
attributes:
@@ -62,6 +62,7 @@ body:
- "Docker"
- "Remote server"
- "Local Development"
- "Cloud Version"
validations:
required: true
- type: dropdown

View File

@@ -138,11 +138,18 @@ curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& ./install.sh
```
```bash
# Install Railpack
curl -sSL https://railpack.com/install.sh | sh
```
```bash
# Install Buildpacks
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
```
## Pull Request
- The `main` branch is the source of truth and should always reflect the latest stable release.

View File

@@ -55,6 +55,10 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& ./install.sh \
&& pnpm install -g tsx
# Install Railpack
ARG RAILPACK_VERSION=0.0.37
RUN curl -sSL https://railpack.com/install.sh | bash
# Install buildpacks
COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack

View File

@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
@@ -25,6 +26,7 @@ enum BuildType {
paketo_buildpacks = "paketo_buildpacks",
nixpacks = "nixpacks",
static = "static",
railpack = "railpack",
}
const mySchema = z.discriminatedUnion("buildType", [
@@ -53,6 +55,9 @@ const mySchema = z.discriminatedUnion("buildType", [
z.object({
buildType: z.literal("static"),
}),
z.object({
buildType: z.literal("railpack"),
}),
]);
type AddTemplate = z.infer<typeof mySchema>;
@@ -173,6 +178,15 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
Dockerfile
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="railpack" />
</FormControl>
<FormLabel className="font-normal">
Railpack{" "}
<Badge className="ml-1 text-xs px-1">New</Badge>
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="nixpacks" />

View File

@@ -200,7 +200,7 @@ export const ContainerFreeMonitoring = ({
}, [appName]);
return (
<div className="rounded-xl bg-background shadow-md flex flex-col gap-4">
<div className="rounded-xl bg-background flex flex-col gap-4">
<header className="flex items-center justify-between">
<div className="space-y-1">
<h1 className="text-2xl font-semibold tracking-tight">Monitoring</h1>

View File

@@ -383,9 +383,8 @@ export const AddTemplate = ({ projectId }: Props) => {
side="top"
>
<span>
If ot server is selected, the application
will be deployed on the server where the
user is logged in.
If no server is selected, the application will be
deployed on the server where the user is logged in.
</span>
</TooltipContent>
</Tooltip>

View File

@@ -39,12 +39,12 @@ import { S3_PROVIDERS } from "./constants";
const addDestination = z.object({
name: z.string().min(1, "Name is required"),
provider: z.string().optional(),
accessKeyId: z.string(),
secretAccessKey: z.string(),
bucket: z.string(),
provider: z.string().min(1, "Provider is required"),
accessKeyId: z.string().min(1, "Access Key Id is required"),
secretAccessKey: z.string().min(1, "Secret Access Key is required"),
bucket: z.string().min(1, "Bucket is required"),
region: z.string(),
endpoint: z.string(),
endpoint: z.string().min(1, "Endpoint is required"),
serverId: z.string().optional(),
});
@@ -129,6 +129,63 @@ export const HandleDestinations = ({ destinationId }: Props) => {
);
});
};
const handleTestConnection = async (serverId?: string) => {
const result = await form.trigger([
"provider",
"accessKeyId",
"secretAccessKey",
"bucket",
"endpoint",
]);
if (!result) {
const errors = form.formState.errors;
const errorFields = Object.entries(errors)
.map(([field, error]) => `${field}: ${error?.message}`)
.filter(Boolean)
.join("\n");
toast.error("Please fill all required fields", {
description: errorFields,
});
return;
}
if (isCloud && !serverId) {
toast.error("Please select a server");
return;
}
const provider = form.getValues("provider");
const accessKey = form.getValues("accessKeyId");
const secretKey = form.getValues("secretAccessKey");
const bucket = form.getValues("bucket");
const endpoint = form.getValues("endpoint");
const region = form.getValues("region");
const connectionString = `:s3,provider=${provider},access_key_id=${accessKey},secret_access_key=${secretKey},endpoint=${endpoint}${region ? `,region=${region}` : ""}:${bucket}`;
await testConnection({
provider,
accessKey,
bucket,
endpoint,
name: "Test",
region,
secretAccessKey: secretKey,
serverId,
})
.then(() => {
toast.success("Connection Success");
})
.catch((e) => {
toast.error("Error connecting to provider", {
description: `${e.message}\n\nTry manually: rclone ls ${connectionString}`,
});
});
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger className="" asChild>
@@ -349,26 +406,9 @@ export const HandleDestinations = ({ destinationId }: Props) => {
<Button
type="button"
variant={"secondary"}
isLoading={isLoading}
isLoading={isLoadingConnection}
onClick={async () => {
await testConnection({
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),
name: "Test",
region: form.getValues("region"),
secretAccessKey: form.getValues("secretAccessKey"),
serverId: form.getValues("serverId"),
})
.then(async () => {
toast.success("Connection Success");
})
.catch((e) => {
toast.error("Error connecting the provider", {
description: e.message,
});
});
await handleTestConnection(form.getValues("serverId"));
}}
>
Test Connection
@@ -380,21 +420,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
type="button"
variant="secondary"
onClick={async () => {
await testConnection({
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),
name: "Test",
region: form.getValues("region"),
secretAccessKey: form.getValues("secretAccessKey"),
})
.then(async () => {
toast.success("Connection Success");
})
.catch(() => {
toast.error("Error connecting the provider");
});
await handleTestConnection();
}}
>
Test connection

View File

@@ -56,9 +56,17 @@ export const ShowDestinations = () => {
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
>
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
<span className="text-sm">
{index + 1}. {destination.name}
</span>
<div className="flex flex-col gap-1">
<span className="text-sm">
{index + 1}. {destination.name}
</span>
<span className="text-xs text-muted-foreground">
Created at:{" "}
{new Date(
destination.createdAt,
).toLocaleDateString()}
</span>
</div>
<div className="flex flex-row gap-1">
<HandleDestinations
destinationId={destination.destinationId}

View File

@@ -45,21 +45,12 @@ const Schema = z.object({
type Schema = z.infer<typeof Schema>;
interface Model {
id: string;
object: string;
created: number;
owned_by: string;
}
interface Props {
aiId?: string;
}
export const HandleAi = ({ aiId }: Props) => {
const [models, setModels] = useState<Model[]>([]);
const utils = api.useUtils();
const [isLoadingModels, setIsLoadingModels] = useState(false);
const [error, setError] = useState<string | null>(null);
const [open, setOpen] = useState(false);
const { data, refetch } = api.ai.one.useQuery(
@@ -73,6 +64,7 @@ export const HandleAi = ({ aiId }: Props) => {
const { mutateAsync, isLoading } = aiId
? api.ai.update.useMutation()
: api.ai.create.useMutation();
const form = useForm<Schema>({
resolver: zodResolver(Schema),
defaultValues: {
@@ -94,50 +86,33 @@ export const HandleAi = ({ aiId }: Props) => {
});
}, [aiId, form, data]);
const fetchModels = async (apiUrl: string, apiKey: string) => {
setIsLoadingModels(true);
setError(null);
try {
const response = await fetch(`${apiUrl}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
if (!response.ok) {
throw new Error("Failed to fetch models");
}
const res = await response.json();
setModels(res.data);
const apiUrl = form.watch("apiUrl");
const apiKey = form.watch("apiKey");
// Set default model to gpt-4 if present
const defaultModel = res.data.find(
(model: Model) => model.id === "gpt-4",
);
if (defaultModel) {
form.setValue("model", defaultModel.id);
return defaultModel.id;
}
} catch (error) {
setError("Failed to fetch models. Please check your API URL and Key.");
setModels([]);
} finally {
setIsLoadingModels(false);
}
};
const { data: models, isLoading: isLoadingServerModels } =
api.ai.getModels.useQuery(
{
apiUrl: apiUrl ?? "",
apiKey: apiKey ?? "",
},
{
enabled: !!apiUrl && !!apiKey,
onError: (error) => {
setError(`Failed to fetch models: ${error.message}`);
},
},
);
useEffect(() => {
const apiUrl = form.watch("apiUrl");
const apiKey = form.watch("apiKey");
if (apiUrl && apiKey) {
form.setValue("model", "");
fetchModels(apiUrl, apiKey);
}
}, [form.watch("apiUrl"), form.watch("apiKey")]);
const onSubmit = async (data: Schema) => {
try {
console.log("Form data:", data);
console.log("Current model value:", form.getValues("model"));
await mutateAsync({
...data,
aiId: aiId || "",
@@ -148,8 +123,9 @@ export const HandleAi = ({ aiId }: Props) => {
refetch();
setOpen(false);
} catch (error) {
console.error("Submit error:", error);
toast.error("Failed to save AI settings");
toast.error("Failed to save AI settings", {
description: error instanceof Error ? error.message : "Unknown error",
});
}
};
@@ -232,13 +208,13 @@ export const HandleAi = ({ aiId }: Props) => {
)}
/>
{isLoadingModels && (
{isLoadingServerModels && (
<span className="text-sm text-muted-foreground">
Loading models...
</span>
)}
{!isLoadingModels && models.length > 0 && (
{!isLoadingServerModels && models && models.length > 0 && (
<FormField
control={form.control}
name="model"

View File

@@ -64,12 +64,12 @@ export const Enable2FA = () => {
const handlePasswordSubmit = async (formData: PasswordForm) => {
setIsPasswordLoading(true);
try {
const { data: enableData } = await authClient.twoFactor.enable({
const { data: enableData, error } = await authClient.twoFactor.enable({
password: formData.password,
});
if (!enableData) {
throw new Error("No data received from server");
throw new Error(error?.message || "Unknown error");
}
if (enableData.backupCodes) {
@@ -95,7 +95,7 @@ export const Enable2FA = () => {
error instanceof Error ? error.message : "Error setting up 2FA",
);
passwordForm.setError("password", {
message: "Error verifying password",
message: `Error verifying password: ${error instanceof Error ? error.message : "Unknown error"}`,
});
} finally {
setIsPasswordLoading(false);

View File

@@ -139,6 +139,15 @@ export const ValidateServer = ({ serverId }: Props) => {
: "Not Created"
}
/>
<StatusRow
label="Railpack Installed"
isEnabled={data?.railpack?.enabled}
description={
data?.railpack?.enabled
? `Installed: ${data?.railpack?.version}`
: undefined
}
/>
</div>
</div>
</div>

View File

@@ -1055,10 +1055,6 @@ export default function Page({ children }: Props) {
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="block" />
<BreadcrumbItem>
<BreadcrumbPage>{activeItem?.title}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>

View File

@@ -72,6 +72,14 @@ export const UserNav = () => {
</div>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
router.push("/dashboard/settings/profile");
}}
>
Profile
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
@@ -126,14 +134,6 @@ export const UserNav = () => {
</>
) : (
<>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
router.push("/dashboard/settings/profile");
}}
>
Profile
</DropdownMenuItem>
{data?.role === "owner" && (
<DropdownMenuItem
className="cursor-pointer"

View File

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

View File

@@ -0,0 +1 @@
ALTER TYPE "public"."buildType" ADD VALUE 'railpack';

View File

@@ -0,0 +1 @@
ALTER TABLE "destination" ADD COLUMN "createdAt" timestamp DEFAULT now() NOT NULL;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -484,6 +484,20 @@
"when": 1740897756774,
"tag": "0068_complex_rhino",
"breakpoints": true
},
{
"idx": 69,
"version": "7",
"when": 1741152916611,
"tag": "0069_legal_bill_hollister",
"breakpoints": true
},
{
"idx": 70,
"version": "7",
"when": 1741322697251,
"tag": "0070_useful_serpent_society",
"breakpoints": true
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.19.0",
"version": "v0.19.1",
"private": true,
"license": "Apache-2.0",
"type": "module",

View File

@@ -228,15 +228,15 @@ const Service = (
>
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="environment">Environment</TabsTrigger>
{((data?.serverId && isCloud) || !data?.server) && (
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
)}
<TabsTrigger value="logs">Logs</TabsTrigger>
<TabsTrigger value="deployments">Deployments</TabsTrigger>
<TabsTrigger value="domains">Domains</TabsTrigger>
<TabsTrigger value="preview-deployments">
Preview Deployments
</TabsTrigger>
<TabsTrigger value="domains">Domains</TabsTrigger>
<TabsTrigger value="deployments">Deployments</TabsTrigger>
<TabsTrigger value="logs">Logs</TabsTrigger>
{((data?.serverId && isCloud) || !data?.server) && (
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
)}
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
</div>

View File

@@ -224,12 +224,12 @@ const Service = (
>
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="environment">Environment</TabsTrigger>
<TabsTrigger value="domains">Domains</TabsTrigger>
<TabsTrigger value="deployments">Deployments</TabsTrigger>
<TabsTrigger value="logs">Logs</TabsTrigger>
{((data?.serverId && isCloud) || !data?.server) && (
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
)}
<TabsTrigger value="logs">Logs</TabsTrigger>
<TabsTrigger value="deployments">Deployments</TabsTrigger>
<TabsTrigger value="domains">Domains</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
</div>

View File

@@ -197,11 +197,11 @@ const Mariadb = (
>
<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="logs">Logs</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
</div>

View File

@@ -198,11 +198,11 @@ const Mongo = (
>
<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="logs">Logs</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
</div>

View File

@@ -200,13 +200,13 @@ const MySql = (
<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="logs">Logs</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
</div>

View File

@@ -197,11 +197,11 @@ const Postgresql = (
>
<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="logs">Logs</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
</div>

View File

@@ -197,10 +197,10 @@ const Redis = (
>
<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="logs">Logs</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
</div>

View File

@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" width="163" height="32" fill="none" viewBox="0 0 163 32">
<g clip-path="url(#a)">
<rect width="32" height="32" fill="#FF6928" rx="16" />
<path fill="#fff" fill-rule="evenodd" d="M7.556 29.778C-3 29.778-11.556 23.609-11.556 16c0-7.61 8.557-13.778 19.112-13.778 11.17 0 19.11 5.905 19.11 13.778s-7.94 13.778-19.11 13.778Zm0-8c-10.17 0-17.334-2.587-17.334-5.778 0-3.191 7.163-5.778 17.334-5.778 10.17 0 17.333 2.128 17.333 5.778 0 3.65-7.163 5.778-17.333 5.778Z" clip-rule="evenodd" />
<g filter="url(#b)" opacity=".3">
<path fill="#FF6928" d="M1.893 11.25C4.192 6.285 5.753 3.667 10.495.177L7.9-5.684-5.308-2.17l-.438 15.1 7.639-1.678Z" />
</g>
<g filter="url(#c)" opacity=".3">
<path fill="#FF6928" d="M12.434 29.6c-4.607-2.955-6.988-4.86-9.798-10.033l-6.16 1.773 1.679 13.563 14.898 2.493-.62-7.796Z" />
</g>
</g>
<path fill="currentColor" d="M46.18 22.416c4.02 0 6.03-2.171 6.03-6.514v-.227c0-2.143-.493-3.745-1.48-4.807-.966-1.081-2.502-1.622-4.607-1.622h-1.82v13.17h1.877ZM39.694 5.662h6.656c3.47 0 6.144.892 8.022 2.674 1.763 1.764 2.645 4.19 2.645 7.282v.227c0 3.13-.891 5.585-2.674 7.367C52.485 25.071 49.811 26 46.322 26h-6.628V5.662Zm23.788 20.65c-1.441 0-2.608-.35-3.499-1.052-.986-.777-1.479-1.905-1.479-3.384 0-1.65.72-2.883 2.162-3.698 1.29-.72 3.148-1.081 5.575-1.081h1.678V16.5c0-.949-.18-1.64-.54-2.077-.342-.436-.968-.654-1.878-.654-1.46 0-2.304.701-2.531 2.105h-3.897c.114-1.669.806-2.958 2.076-3.869 1.157-.815 2.693-1.223 4.608-1.223 1.916 0 3.414.417 4.494 1.252 1.157.91 1.736 2.332 1.736 4.266V26h-4.011v-1.792c-1.005 1.403-2.503 2.105-4.494 2.105Zm1.223-2.872c.93 0 1.697-.237 2.304-.711.607-.474.91-1.119.91-1.934v-1.252h-1.593c-1.251 0-2.2.161-2.844.484-.626.322-.939.863-.939 1.621 0 1.195.72 1.792 2.162 1.792Zm15.734 2.844c-3.204 0-4.807-1.564-4.807-4.693v-7.538h-1.905v-2.93h1.905V7.91h4.096v3.215h3.13v2.93h-3.13v7.167c0 1.176.55 1.764 1.65 1.764.607 0 1.129-.095 1.565-.285v3.186c-.759.266-1.593.398-2.504.398Zm8.92.029c-1.442 0-2.608-.35-3.5-1.053-.985-.777-1.478-1.905-1.478-3.384 0-1.65.72-2.883 2.161-3.698 1.29-.72 3.148-1.081 5.576-1.081h1.678V16.5c0-.949-.18-1.64-.54-2.077-.342-.436-.968-.654-1.878-.654-1.46 0-2.304.701-2.532 2.105H84.95c.114-1.669.806-2.958 2.077-3.869 1.157-.815 2.693-1.223 4.608-1.223 1.915 0 3.413.417 4.494 1.252 1.157.91 1.735 2.332 1.735 4.266V26h-4.01v-1.792c-1.005 1.403-2.503 2.105-4.495 2.105Zm1.223-2.873c.929 0 1.697-.237 2.303-.711.607-.474.91-1.119.91-1.934v-1.252h-1.592c-1.252 0-2.2.161-2.845.484-.625.322-.938.863-.938 1.621 0 1.195.72 1.792 2.162 1.792Zm10.671-17.778h4.608v16.726h8.277V26h-12.885V5.662Zm21.632 20.65c-2.313 0-4.162-.672-5.547-2.019-1.479-1.365-2.218-3.214-2.218-5.546v-.228c0-2.313.739-4.19 2.218-5.632 1.442-1.403 3.252-2.105 5.433-2.105 2.067 0 3.755.598 5.063 1.792 1.46 1.328 2.191 3.252 2.191 5.775v1.137h-10.724c.057 1.252.398 2.219 1.024 2.902.645.663 1.536.995 2.674.995 1.782 0 2.816-.692 3.1-2.076h3.897c-.246 1.612-.986 2.854-2.219 3.726-1.213.853-2.844 1.28-4.892 1.28Zm3.129-9.357c-.133-2.219-1.214-3.328-3.243-3.328-.929 0-1.697.294-2.304.881-.588.57-.957 1.385-1.109 2.447h6.656Zm6.19-5.831h4.125v2.36c.929-1.801 2.541-2.702 4.835-2.702 1.498 0 2.703.455 3.613 1.366.929.986 1.394 2.446 1.394 4.38V26h-4.125v-8.875c0-1.024-.209-1.773-.626-2.247-.417-.493-1.081-.74-1.991-.74-.929 0-1.678.285-2.247.854-.569.55-.853 1.356-.853 2.418V26h-4.125V11.124Zm22.385 15.189c-2.01 0-3.575-.427-4.694-1.28-1.118-.853-1.716-2.086-1.792-3.698h3.84c.114.72.361 1.252.74 1.593.379.341 1.005.512 1.877.512 1.555 0 2.333-.54 2.333-1.621 0-.512-.228-.892-.683-1.138-.455-.266-1.233-.474-2.332-.626-2.01-.322-3.414-.806-4.21-1.45-.853-.664-1.28-1.726-1.28-3.186s.607-2.627 1.82-3.499c1.043-.758 2.399-1.138 4.068-1.138 1.725 0 3.119.342 4.181 1.024 1.119.759 1.773 1.953 1.963 3.584h-3.783c-.114-.626-.351-1.08-.711-1.365-.361-.284-.901-.427-1.622-.427-.663 0-1.185.143-1.564.427a1.35 1.35 0 0 0-.541 1.11c0 .473.2.824.598 1.052.417.227 1.175.417 2.275.569 1.953.265 3.385.71 4.295 1.337.986.739 1.48 1.848 1.48 3.328 0 1.592-.55 2.806-1.65 3.64-1.081.835-2.617 1.252-4.608 1.252Z" />
<defs>
<filter id="b" width="37.575" height="39.945" x="-16.413" y="-16.35" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur result="effect1_foregroundBlur_219_14417" stdDeviation="5.333" />
</filter>
<filter id="c" width="37.91" height="39.162" x="-14.19" y="8.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur result="effect1_foregroundBlur_219_14417" stdDeviation="5.333" />
</filter>
<clipPath id="a">
<rect width="32" height="32" fill="#fff" rx="16" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 355 354" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-232,-118)">
<path d="M565.33,118.79L253.02,118.79C241.46,118.79 232.09,128.16 232.09,139.72L232.09,450.44C232.09,462 241.46,471.37 253.02,471.37L565.33,471.37C576.89,471.37 586.26,462 586.26,450.44L586.26,139.72C586.26,128.16 576.89,118.79 565.33,118.79ZM386.85,419.57C386.85,422.58 384.4,425.03 381.39,425.03L285.11,425.03C282.1,425.03 279.65,422.58 279.65,419.57L279.65,169.43C279.65,166.42 282.1,163.96 285.11,163.96L379.87,163.96C382.88,163.96 385.33,166.41 385.33,169.43L385.33,264.64C385.33,264.64 384.81,305.61 386.85,337.76L386.85,419.58L386.85,419.57ZM537.19,419.57C537.19,423.92 532.36,426.52 528.75,424.14L484.85,395.44C482.95,394.18 480.5,394.25 478.64,395.59L440.16,423.4C438.56,424.59 436.67,424.66 435.07,424.07C433.66,423.07 432.73,421.43 432.73,419.57L432.73,225.34C437.94,224.34 443.73,223.78 450.43,223.78C483.29,223.78 537.2,242.37 537.2,294.78L537.2,419.58L537.19,419.57Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -25,6 +25,10 @@ import {
addNewService,
checkServiceAccess,
} from "@dokploy/server/services/user";
import {
getProviderHeaders,
type Model,
} from "@dokploy/server/utils/ai/select-ai-provider";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
@@ -41,6 +45,58 @@ export const aiRouter = createTRPCRouter({
}
return aiSetting;
}),
getModels: protectedProcedure
.input(z.object({ apiUrl: z.string().min(1), apiKey: z.string().min(1) }))
.query(async ({ input }) => {
try {
const headers = getProviderHeaders(input.apiUrl, input.apiKey);
const response = await fetch(`${input.apiUrl}/models`, { headers });
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to fetch models: ${errorText}`);
}
const res = await response.json();
if (Array.isArray(res)) {
return res.map((model) => ({
id: model.id || model.name,
object: "model",
created: Date.now(),
owned_by: "provider",
}));
}
if (res.models) {
return res.models.map((model: any) => ({
id: model.id || model.name,
object: "model",
created: Date.now(),
owned_by: "provider",
})) as Model[];
}
if (res.data) {
return res.data as Model[];
}
const possibleModels =
(Object.values(res).find(Array.isArray) as any[]) || [];
return possibleModels.map((model) => ({
id: model.id || model.name,
object: "model",
created: Date.now(),
owned_by: "provider",
})) as Model[];
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error?.message : `Error: ${error}`,
});
}
}),
create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.session.activeOrganizationId, input);
}),

View File

@@ -21,7 +21,7 @@ import {
updateDestinationById,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { eq, desc } from "drizzle-orm";
export const destinationRouter = createTRPCRouter({
create: adminProcedure
@@ -98,6 +98,7 @@ export const destinationRouter = createTRPCRouter({
all: protectedProcedure.query(async ({ ctx }) => {
return await db.query.destinations.findMany({
where: eq(destinations.organizationId, ctx.session.activeOrganizationId),
orderBy: [desc(destinations.createdAt)],
});
}),
remove: adminProcedure

View File

@@ -206,6 +206,10 @@ export const serverRouter = createTRPCRouter({
enabled: boolean;
version: string;
};
railpack: {
enabled: boolean;
version: string;
};
isDokployNetworkInstalled: boolean;
isSwarmInstalled: boolean;
isMainDirectoryInstalled: boolean;

View File

@@ -0,0 +1,96 @@
services:
pg-compeng:
image: postgres:16-alpine
restart: always
environment:
POSTGRES_PASSWORD: "postgres"
POSTGRES_DB: postgres
POSTGRES_USER: postgres
control-api:
image: ghcr.io/datalens-tech/datalens-control-api:0.2192.0
restart: always
environment:
BI_API_UWSGI_WORKERS_COUNT: 4
CONNECTOR_AVAILABILITY_VISIBLE: "clickhouse,postgres,chyt,ydb,mysql,greenplum,mssql,appmetrica_api,metrika_api"
RQE_FORCE_OFF: 1
DL_CRY_ACTUAL_KEY_ID: key_1
DL_CRY_KEY_VAL_ID_key_1: "h1ZpilcYLYRdWp7Nk8X1M1kBPiUi8rdjz9oBfHyUKIk="
RQE_SECRET_KEY: ""
US_HOST: "http://us:8083"
US_MASTER_TOKEN: "fake-us-master-token"
depends_on:
- us
data-api:
container_name: datalens-data-api
image: ghcr.io/datalens-tech/datalens-data-api:0.2192.0
restart: always
environment:
GUNICORN_WORKERS_COUNT: 5
RQE_FORCE_OFF: 1
CACHES_ON: 0
MUTATIONS_CACHES_ON: 0
RQE_SECRET_KEY: ""
DL_CRY_ACTUAL_KEY_ID: key_1
DL_CRY_KEY_VAL_ID_key_1: "h1ZpilcYLYRdWp7Nk8X1M1kBPiUi8rdjz9oBfHyUKIk="
BI_COMPENG_PG_ON: 1
BI_COMPENG_PG_URL: "postgresql://postgres:postgres@pg-compeng:5432/postgres"
US_HOST: "http://us:8083"
US_MASTER_TOKEN: "fake-us-master-token"
depends_on:
- us
- pg-compeng
pg-us:
container_name: datalens-pg-us
image: postgres:16-alpine
restart: always
environment:
POSTGRES_DB: us-db-ci_purgeable
POSTGRES_USER: us
POSTGRES_PASSWORD: us
volumes:
- ${VOLUME_US:-./metadata}:/var/lib/postgresql/data
us:
image: ghcr.io/datalens-tech/datalens-us:0.310.0
restart: always
depends_on:
- pg-us
environment:
APP_INSTALLATION: "opensource"
APP_ENV: "prod"
MASTER_TOKEN: "fake-us-master-token"
POSTGRES_DSN_LIST: ${METADATA_POSTGRES_DSN_LIST:-postgres://us:us@pg-us:5432/us-db-ci_purgeable}
SKIP_INSTALL_DB_EXTENSIONS: ${METADATA_SKIP_INSTALL_DB_EXTENSIONS:-0}
USE_DEMO_DATA: ${USE_DEMO_DATA:-0}
HC: ${HC:-0}
NODE_EXTRA_CA_CERTS: /certs/root.crt
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./certs:/certs
datalens:
image: ghcr.io/datalens-tech/datalens-ui:0.2601.0
restart: always
ports:
- ${UI_PORT:-8080}:8080
depends_on:
- us
- control-api
- data-api
environment:
APP_MODE: "full"
APP_ENV: "production"
APP_INSTALLATION: "opensource"
AUTH_POLICY: "disabled"
US_ENDPOINT: "http://us:8083"
BI_API_ENDPOINT: "http://control-api:8080"
BI_DATA_ENDPOINT: "http://data-api:8080"
US_MASTER_TOKEN: "fake-us-master-token"
NODE_EXTRA_CA_CERTS: "/usr/local/share/ca-certificates/cert.pem"
HC: ${HC:-0}
YANDEX_MAP_ENABLED: ${YANDEX_MAP_ENABLED:-0}
YANDEX_MAP_TOKEN: ${YANDEX_MAP_TOKEN:-0}

View File

@@ -0,0 +1,23 @@
import {
type DomainSchema,
type Schema,
type Template,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const domains: DomainSchema[] = [
{
host: generateRandomDomain(schema),
port: 8080,
serviceName: "datalens",
},
];
const envs = ["HC=1"];
return {
envs,
domains,
};
}

View File

@@ -0,0 +1,45 @@
services:
web:
image: ghcr.io/hoarder-app/hoarder:0.22.0
restart: unless-stopped
volumes:
- hoarder-data:/data
ports:
- 3000
environment:
- DISABLE_SIGNUPS
- NEXTAUTH_URL
- NEXTAUTH_SECRET
- MEILI_ADDR=http://meilisearch:7700
- BROWSER_WEB_URL=http://chrome:9222
- DATA_DIR=/data
chrome:
image: gcr.io/zenika-hub/alpine-chrome:124
restart: unless-stopped
command:
- --no-sandbox
- --disable-gpu
- --disable-dev-shm-usage
- --remote-debugging-address=0.0.0.0
- --remote-debugging-port=9222
- --hide-scrollbars
meilisearch:
image: getmeili/meilisearch:v1.6
restart: unless-stopped
environment:
- MEILI_MASTER_KEY
- MEILI_NO_ANALYTICS="true"
volumes:
- meilisearch-data:/meili_data
healthcheck:
test:
- CMD
- curl
- '-f'
- 'http://127.0.0.1:7700/health'
interval: 2s
timeout: 10s
retries: 15
volumes:
meilisearch-data:
hoarder-data:

View File

@@ -0,0 +1,34 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const postgresPassword = generatePassword();
const nextSecret = generateBase64(32);
const meiliMasterKey = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "web",
},
];
const envs = [
`NEXTAUTH_SECRET=${nextSecret}`,
`MEILI_MASTER_KEY=${meiliMasterKey}`,
`NEXTAUTH_URL=http://${mainDomain}`,
];
return {
domains,
envs,
};
}

View File

@@ -1,7 +1,7 @@
# This is an UNOFFICIAL production docker image build for Superset:
# - https://github.com/amancevice/docker-superset
# ## SETUP INSTRUCTIONS
#
# After deploying this image, you will need to run one of the two
@@ -10,7 +10,7 @@
# $ superset-init # Initialise database only
#
# You will be prompted to enter the credentials for the admin user.
# ## NETWORK INSTRUCTIONS
#
@@ -66,7 +66,7 @@ services:
timeout: 10s
retries: 3
superset_redis:
superset_redis:
image: redis
restart: always
volumes:

View File

@@ -108,6 +108,20 @@ export const templates: TemplateData[] = [
tags: ["monitoring"],
load: () => import("./grafana/index").then((m) => m.generate),
},
{
id: "datalens",
name: "DataLens",
version: "1.23.0",
description: "A modern, scalable business intelligence and data visualization system.",
logo: "datalens.svg",
links: {
github: "https://github.com/datalens-tech/datalens",
website: "https://datalens.tech/",
docs: "https://datalens.tech/docs/",
},
tags: ["analytics", "self-hosted", "bi", "monitoring"],
load: () => import("./datalens/index").then((m) => m.generate),
},
{
id: "directus",
name: "Directus",
@@ -707,6 +721,21 @@ export const templates: TemplateData[] = [
tags: ["self-hosted", "open-source", "manager"],
load: () => import("./hi-events/index").then((m) => m.generate),
},
{
id: "hoarder",
name: "Hoarder",
version: "0.22.0",
description:
'Hoarder is an open source "Bookmark Everything" app that uses AI for automatically tagging the content you throw at it.',
logo: "hoarder.svg",
links: {
github: "https://github.com/hoarder/hoarder",
website: "https://hoarder.app/",
docs: "https://docs.hoarder.app/",
},
tags: ["self-hosted", "bookmarks", "link-sharing"],
load: () => import("./hoarder/index").then((m) => m.generate),
},
{
id: "windows",
name: "Windows (dockerized)",

View File

@@ -17,15 +17,17 @@ services:
retries: 5
zipline:
image: ghcr.io/diced/zipline:3.7.9
image: ghcr.io/diced/zipline:4
restart: unless-stopped
environment:
- CORE_RETURN_HTTPS=${ZIPLINE_RETURN_HTTPS}
- CORE_SECRET=${ZIPLINE_SECRET}
- CORE_HOST=0.0.0.0
- CORE_HOSTNAME=0.0.0.0
- CORE_PORT=${ZIPLINE_PORT}
- CORE_DATABASE_URL=postgres://postgres:postgres@postgres/postgres
- DATABASE_URL=postgres://postgres:postgres@postgres/postgres
- CORE_LOGGER=${ZIPLINE_LOGGER}
- DATASOURCE_TYPE=local
- DATASOURCE_LOCAL_DIRECTORY=./uploads
volumes:
- "../files/uploads:/zipline/uploads"
- "../files/public:/zipline/public"

View File

@@ -42,6 +42,7 @@ export const buildType = pgEnum("buildType", [
"paketo_buildpacks",
"nixpacks",
"static",
"railpack",
]);
export interface HealthCheckSwarm {
@@ -383,6 +384,7 @@ const createSchema = createInsertSchema(applications, {
"paketo_buildpacks",
"nixpacks",
"static",
"railpack",
]),
herokuVersion: z.string().optional(),
publishDirectory: z.string().optional(),

View File

@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm";
import { pgTable, text } from "drizzle-orm/pg-core";
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -21,6 +21,7 @@ export const destinations = pgTable("destination", {
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export const destinationsRelations = relations(

View File

@@ -14,6 +14,9 @@ const { handler, api } = betterAuth({
provider: "pg",
schema: schema,
}),
...(!IS_CLOUD && {
baseURL: "http://localhost:3000",
}),
logger: {
disabled: process.env.NODE_ENV === "production",
},

View File

@@ -170,6 +170,9 @@ ${installNixpacks()}
echo -e "12. Installing Buildpacks"
${installBuildpacks()}
echo -e "13. Installing Railpack"
${installRailpack()}
`;
return bashCommand;
@@ -573,6 +576,16 @@ const installNixpacks = () => `
fi
`;
const installRailpack = () => `
if command_exists railpack; then
echo "Railpack already installed ✅"
else
export RAILPACK_VERSION=0.0.37
bash -c "$(curl -fsSL https://railpack.com/install.sh)"
echo "Railpack version $RAILPACK_VERSION installed ✅"
fi
`;
const installBuildpacks = () => `
SUFFIX=""
if [ "$SYS_ARCH" = "aarch64" ] || [ "$SYS_ARCH" = "arm64" ]; then

View File

@@ -38,6 +38,18 @@ export const validateNixpacks = () => `
fi
`;
export const validateRailpack = () => `
if command_exists railpack; then
version=$(railpack --version | awk '{print $3}')
if [ -n "$version" ]; then
echo "$version true"
else
echo "0.0.0 false"
fi
else
echo "0.0.0 false"
fi
`;
export const validateBuildpacks = () => `
if command_exists pack; then
version=$(pack --version | awk '{print $1}')
@@ -86,7 +98,7 @@ export const serverValidate = async (serverId: string) => {
rcloneVersionEnabled=$(${validateRClone()})
nixpacksVersionEnabled=$(${validateNixpacks()})
buildpacksVersionEnabled=$(${validateBuildpacks()})
railpackVersionEnabled=$(${validateRailpack()})
dockerVersion=$(echo $dockerVersionEnabled | awk '{print $1}')
dockerEnabled=$(echo $dockerVersionEnabled | awk '{print $2}')
@@ -96,6 +108,9 @@ export const serverValidate = async (serverId: string) => {
nixpacksVersion=$(echo $nixpacksVersionEnabled | awk '{print $1}')
nixpacksEnabled=$(echo $nixpacksVersionEnabled | awk '{print $2}')
railpackVersion=$(echo $railpackVersionEnabled | awk '{print $1}')
railpackEnabled=$(echo $railpackVersionEnabled | awk '{print $2}')
buildpacksVersion=$(echo $buildpacksVersionEnabled | awk '{print $1}')
buildpacksEnabled=$(echo $buildpacksVersionEnabled | awk '{print $2}')
@@ -103,7 +118,7 @@ export const serverValidate = async (serverId: string) => {
isSwarmInstalled=$(${validateSwarm()})
isMainDirectoryInstalled=$(${validateMainDirectory()})
echo "{\\"docker\\": {\\"version\\": \\"$dockerVersion\\", \\"enabled\\": $dockerEnabled}, \\"rclone\\": {\\"version\\": \\"$rcloneVersion\\", \\"enabled\\": $rcloneEnabled}, \\"nixpacks\\": {\\"version\\": \\"$nixpacksVersion\\", \\"enabled\\": $nixpacksEnabled}, \\"buildpacks\\": {\\"version\\": \\"$buildpacksVersion\\", \\"enabled\\": $buildpacksEnabled}, \\"isDokployNetworkInstalled\\": $isDokployNetworkInstalled, \\"isSwarmInstalled\\": $isSwarmInstalled, \\"isMainDirectoryInstalled\\": $isMainDirectoryInstalled}"
echo "{\\"docker\\": {\\"version\\": \\"$dockerVersion\\", \\"enabled\\": $dockerEnabled}, \\"rclone\\": {\\"version\\": \\"$rcloneVersion\\", \\"enabled\\": $rcloneEnabled}, \\"nixpacks\\": {\\"version\\": \\"$nixpacksVersion\\", \\"enabled\\": $nixpacksEnabled}, \\"buildpacks\\": {\\"version\\": \\"$buildpacksVersion\\", \\"enabled\\": $buildpacksEnabled}, \\"railpack\\": {\\"version\\": \\"$railpackVersion\\", \\"enabled\\": $railpackEnabled}, \\"isDokployNetworkInstalled\\": $isDokployNetworkInstalled, \\"isSwarmInstalled\\": $isSwarmInstalled, \\"isMainDirectoryInstalled\\": $isMainDirectoryInstalled}"
`;
client.exec(bashCommand, (err, stream) => {
if (err) {

View File

@@ -17,7 +17,7 @@ function getProviderName(apiUrl: string) {
if (apiUrl.includes("localhost:11434") || apiUrl.includes("ollama"))
return "ollama";
if (apiUrl.includes("api.deepinfra.com")) return "deepinfra";
throw new Error(`Unsupported AI provider for URL: ${apiUrl}`);
return "custom";
}
export function selectAIProvider(config: { apiUrl: string; apiKey: string }) {
@@ -67,7 +67,46 @@ export function selectAIProvider(config: { apiUrl: string; apiKey: string }) {
baseURL: config.apiUrl,
apiKey: config.apiKey,
});
case "custom":
return createOpenAICompatible({
name: "custom",
baseURL: config.apiUrl,
headers: {
Authorization: `Bearer ${config.apiKey}`,
},
});
default:
throw new Error(`Unsupported AI provider: ${providerName}`);
}
}
export const getProviderHeaders = (
apiUrl: string,
apiKey: string,
): Record<string, string> => {
// Anthropic
if (apiUrl.includes("anthropic")) {
return {
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
};
}
// Mistral
if (apiUrl.includes("mistral")) {
return {
Authorization: apiKey,
};
}
// Default (OpenAI style)
return {
Authorization: `Bearer ${apiKey}`,
};
};
export interface Model {
id: string;
object: string;
created: number;
owned_by: string;
}

View File

@@ -16,6 +16,7 @@ import { buildCustomDocker, getDockerCommand } from "./docker-file";
import { buildHeroku, getHerokuCommand } from "./heroku";
import { buildNixpacks, getNixpacksCommand } from "./nixpacks";
import { buildPaketo, getPaketoCommand } from "./paketo";
import { buildRailpack, getRailpackCommand } from "./railpack";
import { buildStatic, getStaticCommand } from "./static";
// NIXPACKS codeDirectory = where is the path of the code directory
@@ -55,6 +56,8 @@ export const buildApplication = async (
await buildCustomDocker(application, writeStream);
} else if (buildType === "static") {
await buildStatic(application, writeStream);
} else if (buildType === "railpack") {
await buildRailpack(application, writeStream);
}
if (application.registryId) {
@@ -96,6 +99,9 @@ export const getBuildCommand = (
case "dockerfile":
command = getDockerCommand(application, logPath);
break;
case "railpack":
command = getRailpackCommand(application, logPath);
break;
}
if (registry) {
command += uploadImageRemoteCommand(application, logPath);

View File

@@ -0,0 +1,87 @@
import type { WriteStream } from "node:fs";
import type { ApplicationNested } from ".";
import { prepareEnvironmentVariables } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
import { execAsync } from "../process/execAsync";
export const buildRailpack = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const { env, appName } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
);
try {
// Ensure buildkit container is running, create if it doesn't exist
await execAsync(
"docker container inspect buildkit >/dev/null 2>&1 || docker run --rm --privileged -d --name buildkit moby/buildkit",
);
// Build the application using railpack
const args = ["build", buildAppDirectory, "--name", appName];
// Add environment variables
for (const env of envVariables) {
args.push("--env", env);
}
await spawnAsync(
"railpack",
args,
(data) => {
if (writeStream.writable) {
writeStream.write(data);
}
},
{
env: {
...process.env,
BUILDKIT_HOST: "docker-container://buildkit",
},
},
);
return true;
} catch (e) {
throw e;
}
};
export const getRailpackCommand = (
application: ApplicationNested,
logPath: string,
) => {
const { env, appName } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
);
// Build the application using railpack
const args = ["build", buildAppDirectory, "--name", appName];
// Add environment variables
for (const env of envVariables) {
args.push("--env", env);
}
const command = `railpack ${args.join(" ")}`;
const bashCommand = `
echo "Building with Railpack..." >> "${logPath}";
docker container inspect buildkit >/dev/null 2>&1 || docker run --rm --privileged -d --name buildkit moby/buildkit;
export BUILDKIT_HOST=docker-container://buildkit;
${command} >> ${logPath} 2>> ${logPath} || {
echo "❌ Railpack build failed" >> ${logPath};
exit 1;
}
echo "✅ Railpack build completed." >> ${logPath};
`;
return bashCommand;
};

View File

@@ -275,7 +275,7 @@ export const getBitbucketBranches = async (
}
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
const { owner, repo } = input;
const url = `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/refs/branches`;
const url = `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/refs/branches?pagelen=100`;
try {
const response = await fetch(url, {

View File

@@ -266,7 +266,7 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
return kind === "member";
return kind === "user";
});
const mappedRepositories = filteredRepos.map((repo: any) => {
return {
@@ -433,7 +433,7 @@ export const testGitlabConnection = async (
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
return kind === "member";
return kind === "user";
});
return filteredRepos.length;