Merge branch 'canary' into canary

This commit is contained in:
Mauricio Siu 2025-01-26 14:30:11 -06:00 committed by GitHub
commit b96103247a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 284 additions and 19 deletions

View File

@ -308,7 +308,7 @@ export const AddTemplate = ({ projectId }: Props) => {
{/* Create Button */} {/* Create Button */}
<div <div
className={cn( className={cn(
"flex-none px-6 pb-6 pt-3 mt-auto", "flex-none px-6 py-3 mt-auto",
viewMode === "detailed" viewMode === "detailed"
? "flex items-center justify-between bg-muted/30 border-t" ? "flex items-center justify-between bg-muted/30 border-t"
: "flex justify-center", : "flex justify-center",

View File

@ -23,8 +23,10 @@ import {
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -149,6 +151,10 @@ export const ShowProjects = () => {
href={`/dashboard/project/${project.projectId}`} href={`/dashboard/project/${project.projectId}`}
> >
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border"> <Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
{project.applications.length > 0 ||
project.compose.length > 0 ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button <Button
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100" className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
size="sm" size="sm"
@ -156,7 +162,80 @@ export const ShowProjects = () => {
> >
<ExternalLinkIcon className="size-3.5" /> <ExternalLinkIcon className="size-3.5" />
</Button> </Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[200px] space-y-2 overflow-y-auto max-h-[400px]"
onClick={(e) => e.stopPropagation()}
>
{project.applications.length > 0 && (
<DropdownMenuGroup>
<DropdownMenuLabel>
Applications
</DropdownMenuLabel>
{project.applications.map((app) => (
<div key={app.applicationId}>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel className="font-normal capitalize text-xs">
{app.name}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{app.domains.map((domain) => (
<DropdownMenuItem
key={domain.domainId}
asChild
>
<Link
className="space-x-4 text-xs cursor-pointer justify-between"
target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
>
<span>{domain.host}</span>
<ExternalLinkIcon className="size-4 shrink-0" />
</Link>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</div>
))}
</DropdownMenuGroup>
)}
{project.compose.length > 0 && (
<DropdownMenuGroup>
<DropdownMenuLabel>
Compose
</DropdownMenuLabel>
{project.compose.map((comp) => (
<div key={comp.composeId}>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel className="font-normal capitalize text-xs">
{comp.name}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{comp.domains.map((domain) => (
<DropdownMenuItem
key={domain.domainId}
asChild
>
<Link
className="space-x-4 text-xs cursor-pointer justify-between"
target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
>
<span>{domain.host}</span>
<ExternalLinkIcon className="size-4 shrink-0" />
</Link>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</div>
))}
</DropdownMenuGroup>
)}
</DropdownMenuContent>
</DropdownMenu>
) : null}
<CardHeader> <CardHeader>
<CardTitle className="flex items-center justify-between gap-2"> <CardTitle className="flex items-center justify-between gap-2">
<span className="flex flex-col gap-1.5"> <span className="flex flex-col gap-1.5">
@ -182,7 +261,10 @@ export const ShowProjects = () => {
<MoreHorizontalIcon className="size-5" /> <MoreHorizontalIcon className="size-5" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-[200px] space-y-2"> <DropdownMenuContent
className="w-[200px] space-y-2 overflow-y-auto max-h-[280px]"
onClick={(e) => e.stopPropagation()}
>
<DropdownMenuLabel className="font-normal"> <DropdownMenuLabel className="font-normal">
Actions Actions
</DropdownMenuLabel> </DropdownMenuLabel>

View File

@ -22,7 +22,7 @@ import { Textarea } from "@/components/ui/textarea";
import { sshKeyCreate, type sshKeyType } from "@/server/db/validations"; import { sshKeyCreate, type sshKeyType } from "@/server/db/validations";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, PlusIcon } from "lucide-react"; import { DownloadIcon, PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
@ -111,6 +111,26 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
toast.error("Error generating the SSH Key"); toast.error("Error generating the SSH Key");
}); });
const downloadKey = (
content: string,
defaultFilename: string,
keyType: "private" | "public",
) => {
const keyName = form.watch("name");
const filename = keyName
? `${keyName}${sshKeyId ? `_${sshKeyId}` : ""}_${keyType}_${defaultFilename}`
: `${keyType}_${defaultFilename}`;
const blob = new Blob([content], { type: "text/plain" });
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
return ( return (
<Dialog open={isOpen} onOpenChange={setIsOpen}> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger className="" asChild> <DialogTrigger className="" asChild>
@ -245,7 +265,41 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
<DialogFooter> <DialogFooter className="flex items-center justify-between">
<div className="flex items-center gap-4">
{form.watch("privateKey") && (
<Button
type="button"
variant="outline"
size="default"
onClick={() =>
downloadKey(form.watch("privateKey"), "id_rsa", "private")
}
className="flex items-center gap-2"
>
<DownloadIcon className="h-4 w-4" />
Private Key
</Button>
)}
{form.watch("publicKey") && (
<Button
type="button"
variant="outline"
size="default"
onClick={() =>
downloadKey(
form.watch("publicKey"),
"id_rsa.pub",
"public",
)
}
className="flex items-center gap-2"
>
<DownloadIcon className="h-4 w-4" />
Public Key
</Button>
)}
</div>
<Button isLoading={isLoading} type="submit"> <Button isLoading={isLoading} type="submit">
{sshKeyId ? "Update" : "Create"} {sshKeyId ? "Update" : "Create"}
</Button> </Button>

View File

@ -1,5 +1,6 @@
{ {
"settings.common.save": "Сохранить", "settings.common.save": "Сохранить",
"settings.common.enterTerminal": "Открыть терминал",
"settings.server.domain.title": "Домен сервера", "settings.server.domain.title": "Домен сервера",
"settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.", "settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.",
"settings.server.domain.form.domain": "Домен", "settings.server.domain.form.domain": "Домен",
@ -7,18 +8,26 @@
"settings.server.domain.form.certificate.label": "Сертификат", "settings.server.domain.form.certificate.label": "Сертификат",
"settings.server.domain.form.certificate.placeholder": "Выберите сертификат", "settings.server.domain.form.certificate.placeholder": "Выберите сертификат",
"settings.server.domain.form.certificateOptions.none": "Нет", "settings.server.domain.form.certificateOptions.none": "Нет",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (По умолчанию)", "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
"settings.server.webServer.title": "Веб-сервер", "settings.server.webServer.title": "Веб-сервер",
"settings.server.webServer.description": "Перезагрузка или очистка веб-сервера.", "settings.server.webServer.description": "Перезагрузка или очистка веб-сервера.",
"settings.server.webServer.server.label": "Сервер",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.storage.label": "Дисковое пространство",
"settings.server.webServer.actions": "Действия", "settings.server.webServer.actions": "Действия",
"settings.server.webServer.reload": "Перезагрузить", "settings.server.webServer.reload": "Перезагрузить",
"settings.server.webServer.watchLogs": "Просмотр логов", "settings.server.webServer.watchLogs": "Просмотр логов",
"settings.server.webServer.updateServerIp": "Изменить IP адрес", "settings.server.webServer.updateServerIp": "Изменить IP адрес",
"settings.server.webServer.server.label": "Сервер",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "Изменить переменные окружения", "settings.server.webServer.traefik.modifyEnv": "Изменить переменные окружения",
"settings.server.webServer.traefik.managePorts": "Назначение портов",
"settings.server.webServer.traefik.managePortsDescription": "Добавить или удалить дополнительные порты для Traefik",
"settings.server.webServer.traefik.targetPort": "Внутренний порт",
"settings.server.webServer.traefik.publishedPort": "Внешний порт",
"settings.server.webServer.traefik.addPort": "Добавить порт",
"settings.server.webServer.traefik.portsUpdated": "Порты успешно обновлены",
"settings.server.webServer.traefik.portsUpdateError": "Не удалось обновить порты",
"settings.server.webServer.traefik.publishMode": "Режим сопоставления",
"settings.server.webServer.storage.label": "Дисковое пространство",
"settings.server.webServer.storage.cleanUnusedImages": "Очистить неиспользуемые образы", "settings.server.webServer.storage.cleanUnusedImages": "Очистить неиспользуемые образы",
"settings.server.webServer.storage.cleanUnusedVolumes": "Очистить неиспользуемые тома", "settings.server.webServer.storage.cleanUnusedVolumes": "Очистить неиспользуемые тома",
"settings.server.webServer.storage.cleanStoppedContainers": "Очистить остановленные контейнеры", "settings.server.webServer.storage.cleanStoppedContainers": "Очистить остановленные контейнеры",
@ -40,5 +49,10 @@
"settings.appearance.themes.dark": "Темная", "settings.appearance.themes.dark": "Темная",
"settings.appearance.themes.system": "Системная", "settings.appearance.themes.system": "Системная",
"settings.appearance.language": "Язык", "settings.appearance.language": "Язык",
"settings.appearance.languageDescription": "Select a language for your dashboard" "settings.appearance.languageDescription": "Выберите язык для панели управления",
"settings.terminal.connectionSettings": "Настройки подключения",
"settings.terminal.ipAddress": "IP адрес",
"settings.terminal.port": "Порт",
"settings.terminal.username": "Имя пользователя"
} }

View File

@ -0,0 +1,10 @@
<svg width="1252" height="1252" xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<g id="#70c6beff">
<path id="svg_2" d="m634.37,138.38c11.88,-1.36 24.25,1.3 34.18,8.09c14.96,9.66 25.55,24.41 34.49,39.51c40.59,68.03 81.45,135.91 122.02,203.96c54.02,90.99 108.06,181.97 161.94,273.06c37.28,63 74.65,125.96 112.18,188.82c24.72,41.99 50.21,83.54 73.84,126.16c10.18,17.84 15.77,38.44 14.93,59.03c-0.59,15.92 -3.48,32.28 -11.84,46.08c-11.73,19.46 -31.39,33.2 -52.71,40.36c-11.37,4.09 -23.3,6.87 -35.43,6.89c-132.32,-0.05 -264.64,0.04 -396.95,0.03c-11.38,-0.29 -22.95,-1.6 -33.63,-5.72c-7.81,-3.33 -15.5,-7.43 -21.61,-13.42c-10.43,-10.32 -17.19,-24.96 -15.38,-39.83c0.94,-10.39 3.48,-20.64 7.76,-30.16c4.15,-9.77 9.99,-18.67 15.06,-27.97c22.13,-39.47 45.31,-78.35 69.42,-116.65c7.72,-12.05 14.44,-25.07 25.12,-34.87c11.35,-10.39 25.6,-18.54 41.21,-19.6c12.55,-0.52 24.89,3.82 35.35,10.55c11.8,6.92 21.09,18.44 24.2,31.88c4.49,17.01 -0.34,34.88 -7.55,50.42c-8.09,17.65 -19.62,33.67 -25.81,52.18c-1.13,4.21 -2.66,9.52 0.48,13.23c3.19,3 7.62,4.18 11.77,5.22c12,2.67 24.38,1.98 36.59,2.06c45,-0.01 90,0 135,0c8.91,-0.15 17.83,0.3 26.74,-0.22c6.43,-0.74 13.44,-1.79 18.44,-6.28c3.3,-2.92 3.71,-7.85 2.46,-11.85c-2.74,-8.86 -7.46,-16.93 -12.12,-24.89c-119.99,-204.91 -239.31,-410.22 -360.56,-614.4c-3.96,-6.56 -7.36,-13.68 -13.03,-18.98c-2.8,-2.69 -6.95,-4.22 -10.77,-3.11c-3.25,1.17 -5.45,4.03 -7.61,6.57c-5.34,6.81 -10.12,14.06 -14.51,21.52c-20.89,33.95 -40.88,68.44 -61.35,102.64c-117.9,198.43 -235.82,396.85 -353.71,595.29c-7.31,13.46 -15.09,26.67 -23.57,39.43c-7.45,10.96 -16.49,21.23 -28.14,27.83c-13.73,7.94 -30.69,11.09 -46.08,6.54c-11.23,-3.47 -22.09,-9.12 -30.13,-17.84c-10.18,-10.08 -14.69,-24.83 -14.17,-38.94c0.52,-14.86 5.49,-29.34 12.98,-42.1c71.58,-121.59 143.62,-242.92 215.93,-364.09c37.2,-62.8 74.23,-125.69 111.64,-188.36c37.84,-63.5 75.77,-126.94 113.44,-190.54c21.02,-35.82 42.19,-71.56 64.28,-106.74c6.79,-11.15 15.58,-21.15 26.16,-28.85c8.68,-5.92 18.42,-11 29.05,-11.94z" fill="#70c6be"/>
</g>
<g id="#1ba0d8ff">
<path id="svg_3" d="m628.35,608.38c17.83,-2.87 36.72,1.39 51.5,11.78c11.22,8.66 19.01,21.64 21.26,35.65c1.53,10.68 0.49,21.75 -3.44,31.84c-3.02,8.73 -7.35,16.94 -12.17,24.81c-68.76,115.58 -137.5,231.17 -206.27,346.75c-8.8,14.47 -16.82,29.47 -26.96,43.07c-7.37,9.11 -16.58,16.85 -27.21,21.89c-22.47,11.97 -51.79,4.67 -68.88,-13.33c-8.66,-8.69 -13.74,-20.63 -14.4,-32.84c-0.98,-12.64 1.81,-25.42 7.53,-36.69c5.03,-10.96 10.98,-21.45 17.19,-31.77c30.22,-50.84 60.17,-101.84 90.3,-152.73c41.24,-69.98 83.16,-139.55 124.66,-209.37c4.41,-7.94 9.91,-15.26 16.09,-21.9c8.33,-8.46 18.9,-15.3 30.8,-17.16z" fill="#1ba0d8"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

View File

@ -0,0 +1,14 @@
version: '3.3'
services:
alist:
image: xhofe/alist:v3.41.0
volumes:
- alist-data:/opt/alist/data
environment:
- PUID=0
- PGID=0
- UMASK=022
restart: unless-stopped
volumes:
alist-data:

View File

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

View File

@ -0,0 +1,9 @@
services:
server:
image: ghcr.io/spacedriveapp/spacedrive/server:latest
ports:
- 8080
environment:
- SD_AUTH=${SD_USERNAME}:${SD_PASSWORD}
volumes:
- /var/spacedrive:/var/spacedrive

View File

@ -0,0 +1,31 @@
import {
type DomainSchema,
type Schema,
type Template,
generateRandomDomain,
generatePassword,
} from "../utils";
export function generate(schema: Schema): Template {
const randomDomain = generateRandomDomain(schema);
const secretKey = generatePassword();
const randomUsername = "admin"; // Default username
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 8080,
serviceName: "server",
},
];
const envs = [
`SD_USERNAME=${randomUsername}`,
`SD_PASSWORD=${secretKey}`,
];
return {
envs,
domains,
};
}

View File

@ -538,7 +538,7 @@ export const templates: TemplateData[] = [
website: "https://filebrowser.org/", website: "https://filebrowser.org/",
docs: "https://filebrowser.org/", docs: "https://filebrowser.org/",
}, },
tags: ["file", "manager"], tags: ["file-manager","storage"],
load: () => import("./filebrowser/index").then((m) => m.generate), load: () => import("./filebrowser/index").then((m) => m.generate),
}, },
{ {
@ -834,7 +834,7 @@ export const templates: TemplateData[] = [
website: "https://nextcloud.com/", website: "https://nextcloud.com/",
docs: "https://docs.nextcloud.com/", docs: "https://docs.nextcloud.com/",
}, },
tags: ["file", "sync"], tags: ["file-manager", "sync"],
load: () => import("./nextcloud-aio/index").then((m) => m.generate), load: () => import("./nextcloud-aio/index").then((m) => m.generate),
}, },
{ {
@ -1356,4 +1356,33 @@ export const templates: TemplateData[] = [
tags: ["finance", "self-hosted"], tags: ["finance", "self-hosted"],
load: () => import("./maybe/index").then((m) => m.generate), load: () => import("./maybe/index").then((m) => m.generate),
}, },
{
id: "spacedrive",
name: "Spacedrive",
version: "latest",
description:
"Spacedrive is a cross-platform file manager. It connects your devices together to help you organize files from anywhere. powered by a virtual distributed filesystem (VDFS) written in Rust. Organize files across many devices in one place.",
links: {
github: "https://github.com/spacedriveapp/spacedrive",
website: "https://spacedrive.com/",
docs: "https://www.spacedrive.com/docs/product/getting-started/introduction",
},
logo: "spacedrive.png",
tags: ["file-manager", "vdfs", "storage"],
},
{
id: "alist",
name: "AList",
version: "v3.41.0",
description:
"🗂A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs.",
logo: "alist.svg",
links: {
github: "https://github.com/AlistGo/alist",
website: "https://alist.nn.ci",
docs: "https://alist.nn.ci/guide/install/docker.html",
},
tags: ["file", "webdav", "storage"],
load: () => import("./alist/index").then((m) => m.generate),
},
]; ];