diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index faee27ef..bc8929b2 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -308,7 +308,7 @@ export const AddTemplate = ({ projectId }: Props) => { {/* Create Button */}
{ href={`/dashboard/project/${project.projectId}`} > - - + {project.applications.length > 0 || + project.compose.length > 0 ? ( + + + + + e.stopPropagation()} + > + {project.applications.length > 0 && ( + + + Applications + + {project.applications.map((app) => ( +
+ + + + {app.name} + + + {app.domains.map((domain) => ( + + + {domain.host} + + + + ))} + +
+ ))} +
+ )} + {project.compose.length > 0 && ( + + + Compose + + {project.compose.map((comp) => ( +
+ + + + {comp.name} + + + {comp.domains.map((domain) => ( + + + {domain.host} + + + + ))} + +
+ ))} +
+ )} +
+
+ ) : null} @@ -182,7 +261,10 @@ export const ShowProjects = () => { - + e.stopPropagation()} + > Actions diff --git a/apps/dokploy/components/dashboard/settings/ssh-keys/handle-ssh-keys.tsx b/apps/dokploy/components/dashboard/settings/ssh-keys/handle-ssh-keys.tsx index f100979e..fc48134a 100644 --- a/apps/dokploy/components/dashboard/settings/ssh-keys/handle-ssh-keys.tsx +++ b/apps/dokploy/components/dashboard/settings/ssh-keys/handle-ssh-keys.tsx @@ -22,7 +22,7 @@ import { Textarea } from "@/components/ui/textarea"; import { sshKeyCreate, type sshKeyType } from "@/server/db/validations"; import { api } from "@/utils/api"; 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 { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -111,6 +111,26 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => { 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 ( @@ -245,7 +265,41 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => { )} /> - + +
+ {form.watch("privateKey") && ( + + )} + {form.watch("publicKey") && ( + + )} +
diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx index e45b73d2..dccdec00 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef } from "react"; import { FitAddon } from "xterm-addon-fit"; import "@xterm/xterm/css/xterm.css"; import { AttachAddon } from "@xterm/addon-attach"; +import { ClipboardAddon } from "@xterm/addon-clipboard"; import { useTheme } from "next-themes"; import { getLocalServerData } from "./local-server-config"; @@ -37,6 +38,7 @@ export const Terminal: React.FC = ({ id, serverId }) => { foreground: "currentColor", }, }); + const addonFit = new FitAddon(); const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; @@ -54,6 +56,8 @@ export const Terminal: React.FC = ({ id, serverId }) => { const ws = new WebSocket(wsUrl); const addonAttach = new AttachAddon(ws); + const clipboardAddon = new ClipboardAddon(); + term.loadAddon(clipboardAddon); // @ts-ignore term.open(termRef.current); @@ -68,7 +72,7 @@ export const Terminal: React.FC = ({ id, serverId }) => { return (
-
+
diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx index c711b862..559fe182 100644 --- a/apps/dokploy/components/layouts/side.tsx +++ b/apps/dokploy/components/layouts/side.tsx @@ -783,7 +783,7 @@ export default function Page({ children }: Props) { ))} - {!isCloud && ( + {!isCloud && auth?.rol === "admin" && ( diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 5166c2f9..23be9df3 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -75,6 +75,7 @@ "@uiw/react-codemirror": "^4.22.1", "@xterm/addon-attach": "0.10.0", "@xterm/xterm": "^5.4.0", + "@xterm/addon-clipboard": "0.1.0", "adm-zip": "^0.5.14", "bcrypt": "5.1.1", "bullmq": "5.4.2", diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index ff7a9221..761c3866 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -182,8 +182,9 @@ export default async function handler( } } else if (req.headers["x-github-event"] === "pull_request") { const prId = githubBody?.pull_request?.id; + const action = githubBody?.action; - if (githubBody?.action === "closed") { + if (action === "closed") { const previewDeploymentResult = await findPreviewDeploymentsByPullRequestId(prId); @@ -201,79 +202,86 @@ export default async function handler( res.status(200).json({ message: "Preview Deployment Closed" }); return; } + // opened or synchronize or reopened - const repository = githubBody?.repository?.name; - const deploymentHash = githubBody?.pull_request?.head?.sha; - const branch = githubBody?.pull_request?.base?.ref; - const owner = githubBody?.repository?.owner?.login; + if ( + action === "opened" || + action === "synchronize" || + action === "reopened" + ) { + const repository = githubBody?.repository?.name; + const deploymentHash = githubBody?.pull_request?.head?.sha; + const branch = githubBody?.pull_request?.base?.ref; + const owner = githubBody?.repository?.owner?.login; - const apps = await db.query.applications.findMany({ - where: and( - eq(applications.sourceType, "github"), - eq(applications.repository, repository), - eq(applications.branch, branch), - eq(applications.isPreviewDeploymentsActive, true), - eq(applications.owner, owner), - ), - with: { - previewDeployments: true, - }, - }); - - const prBranch = githubBody?.pull_request?.head?.ref; - - const prNumber = githubBody?.pull_request?.number; - const prTitle = githubBody?.pull_request?.title; - const prURL = githubBody?.pull_request?.html_url; - - for (const app of apps) { - const previewLimit = app?.previewLimit || 0; - if (app?.previewDeployments?.length > previewLimit) { - continue; - } - const previewDeploymentResult = - await findPreviewDeploymentByApplicationId(app.applicationId, prId); - - let previewDeploymentId = - previewDeploymentResult?.previewDeploymentId || ""; - - if (!previewDeploymentResult) { - const previewDeployment = await createPreviewDeployment({ - applicationId: app.applicationId as string, - branch: prBranch, - pullRequestId: prId, - pullRequestNumber: prNumber, - pullRequestTitle: prTitle, - pullRequestURL: prURL, - }); - previewDeploymentId = previewDeployment.previewDeploymentId; - } - - const jobData: DeploymentJob = { - applicationId: app.applicationId as string, - titleLog: "Preview Deployment", - descriptionLog: `Hash: ${deploymentHash}`, - type: "deploy", - applicationType: "application-preview", - server: !!app.serverId, - previewDeploymentId, - }; - - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.repository, repository), + eq(applications.branch, branch), + eq(applications.isPreviewDeploymentsActive, true), + eq(applications.owner, owner), + ), + with: { + previewDeployments: true, }, - ); + }); + + const prBranch = githubBody?.pull_request?.head?.ref; + + const prNumber = githubBody?.pull_request?.number; + const prTitle = githubBody?.pull_request?.title; + const prURL = githubBody?.pull_request?.html_url; + + for (const app of apps) { + const previewLimit = app?.previewLimit || 0; + if (app?.previewDeployments?.length > previewLimit) { + continue; + } + const previewDeploymentResult = + await findPreviewDeploymentByApplicationId(app.applicationId, prId); + + let previewDeploymentId = + previewDeploymentResult?.previewDeploymentId || ""; + + if (!previewDeploymentResult) { + const previewDeployment = await createPreviewDeployment({ + applicationId: app.applicationId as string, + branch: prBranch, + pullRequestId: prId, + pullRequestNumber: prNumber, + pullRequestTitle: prTitle, + pullRequestURL: prURL, + }); + previewDeploymentId = previewDeployment.previewDeploymentId; + } + + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: "Preview Deployment", + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application-preview", + server: !!app.serverId, + previewDeploymentId, + }; + + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + continue; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } + return res.status(200).json({ message: "Apps Deployed" }); } - return res.status(200).json({ message: "Apps Deployed" }); } return res.status(400).json({ message: "No Actions matched" }); diff --git a/apps/dokploy/public/locales/ru/settings.json b/apps/dokploy/public/locales/ru/settings.json index 1e71d710..0d87ed15 100644 --- a/apps/dokploy/public/locales/ru/settings.json +++ b/apps/dokploy/public/locales/ru/settings.json @@ -1,5 +1,6 @@ { "settings.common.save": "Сохранить", + "settings.common.enterTerminal": "Открыть терминал", "settings.server.domain.title": "Домен сервера", "settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.", "settings.server.domain.form.domain": "Домен", @@ -7,18 +8,26 @@ "settings.server.domain.form.certificate.label": "Сертификат", "settings.server.domain.form.certificate.placeholder": "Выберите сертификат", "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.description": "Перезагрузка или очистка веб-сервера.", - "settings.server.webServer.server.label": "Сервер", - "settings.server.webServer.traefik.label": "Traefik", - "settings.server.webServer.storage.label": "Дисковое пространство", "settings.server.webServer.actions": "Действия", "settings.server.webServer.reload": "Перезагрузить", "settings.server.webServer.watchLogs": "Просмотр логов", "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.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.cleanUnusedVolumes": "Очистить неиспользуемые тома", "settings.server.webServer.storage.cleanStoppedContainers": "Очистить остановленные контейнеры", @@ -40,5 +49,10 @@ "settings.appearance.themes.dark": "Темная", "settings.appearance.themes.system": "Системная", "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": "Имя пользователя" } diff --git a/apps/dokploy/public/templates/alist.svg b/apps/dokploy/public/templates/alist.svg new file mode 100644 index 00000000..37d5fdcd --- /dev/null +++ b/apps/dokploy/public/templates/alist.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/dokploy/public/templates/maybe.svg b/apps/dokploy/public/templates/maybe.svg new file mode 100644 index 00000000..a4a87736 --- /dev/null +++ b/apps/dokploy/public/templates/maybe.svg @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/apps/dokploy/public/templates/spacedrive.png b/apps/dokploy/public/templates/spacedrive.png new file mode 100644 index 00000000..a10fffd5 Binary files /dev/null and b/apps/dokploy/public/templates/spacedrive.png differ diff --git a/apps/dokploy/styles/globals.css b/apps/dokploy/styles/globals.css index 7b7977b9..74a1d276 100644 --- a/apps/dokploy/styles/globals.css +++ b/apps/dokploy/styles/globals.css @@ -4,6 +4,7 @@ @layer base { :root { + --terminal-paste: rgba(0, 0, 0, 0.2); --background: 0 0% 100%; --foreground: 240 10% 3.9%; @@ -51,6 +52,7 @@ } .dark { + --terminal-paste: rgba(255, 255, 255, 0.2); --background: 0 0% 0%; --foreground: 0 0% 98%; @@ -235,3 +237,8 @@ background-color: hsl(var(--muted-foreground) / 0.5); } } + +.xterm-bg-257.xterm-fg-257 { + background-color: var(--terminal-paste) !important; + color: currentColor !important; +} diff --git a/apps/dokploy/templates/alist/docker-compose.yml b/apps/dokploy/templates/alist/docker-compose.yml new file mode 100644 index 00000000..9ff67c94 --- /dev/null +++ b/apps/dokploy/templates/alist/docker-compose.yml @@ -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: \ No newline at end of file diff --git a/apps/dokploy/templates/alist/index.ts b/apps/dokploy/templates/alist/index.ts new file mode 100644 index 00000000..2a27f570 --- /dev/null +++ b/apps/dokploy/templates/alist/index.ts @@ -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, + }; +} diff --git a/apps/dokploy/templates/maybe/docker-compose.yml b/apps/dokploy/templates/maybe/docker-compose.yml new file mode 100644 index 00000000..017ca29b --- /dev/null +++ b/apps/dokploy/templates/maybe/docker-compose.yml @@ -0,0 +1,37 @@ +services: + app: + image: ghcr.io/maybe-finance/maybe:sha-68c570eed8810fd59b5b33cca51bbad5eabb4cb4 + restart: unless-stopped + volumes: + - ../files/uploads:/app/uploads + environment: + DATABASE_URL: postgresql://maybe:maybe@db:5432/maybe + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + SELF_HOSTED: true + SYNTH_API_KEY: ${SYNTH_API_KEY} + RAILS_FORCE_SSL: "false" + RAILS_ASSUME_SSL: "false" + GOOD_JOB_EXECUTION_MODE: async + depends_on: + db: + condition: service_healthy + + db: + image: postgres:16 + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - dokploy-network + volumes: + - db-data:/var/lib/postgresql/data + environment: + POSTGRES_DB: maybe + POSTGRES_USER: maybe + POSTGRES_PASSWORD: maybe + +volumes: + db-data: diff --git a/apps/dokploy/templates/maybe/index.ts b/apps/dokploy/templates/maybe/index.ts new file mode 100644 index 00000000..5eaf7a81 --- /dev/null +++ b/apps/dokploy/templates/maybe/index.ts @@ -0,0 +1,43 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateBase64, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainDomain = generateRandomDomain(schema); + const secretKeyBase = generateBase64(64); + const synthApiKey = generateBase64(32); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 3000, + serviceName: "app", + }, + ]; + + const envs = [ + `SECRET_KEY_BASE=${secretKeyBase}`, + "SELF_HOSTED=true", + `SYNTH_API_KEY=${synthApiKey}`, + "RAILS_FORCE_SSL=false", + "RAILS_ASSUME_SSL=false", + "GOOD_JOB_EXECUTION_MODE=async", + ]; + + const mounts: Template["mounts"] = [ + { + filePath: "./uploads", + content: "This is where user uploads will be stored", + }, + ]; + + return { + envs, + mounts, + domains, + }; +} diff --git a/apps/dokploy/templates/spacedrive/docker-compose.yml b/apps/dokploy/templates/spacedrive/docker-compose.yml new file mode 100644 index 00000000..b98d55ab --- /dev/null +++ b/apps/dokploy/templates/spacedrive/docker-compose.yml @@ -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 diff --git a/apps/dokploy/templates/spacedrive/index.ts b/apps/dokploy/templates/spacedrive/index.ts new file mode 100644 index 00000000..15db4b19 --- /dev/null +++ b/apps/dokploy/templates/spacedrive/index.ts @@ -0,0 +1,28 @@ +import { + type DomainSchema, + type Schema, + type Template, + generatePassword, + generateRandomDomain, +} 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, + }; +} diff --git a/apps/dokploy/templates/superset/docker-compose.yml b/apps/dokploy/templates/superset/docker-compose.yml index 1766b86b..e3fb3454 100644 --- a/apps/dokploy/templates/superset/docker-compose.yml +++ b/apps/dokploy/templates/superset/docker-compose.yml @@ -13,8 +13,11 @@ services: image: amancevice/superset restart: always depends_on: - - db - - redis + - superset_postgres + - superset_redis + volumes: + # This superset_config.py can be edited in Dokploy's UI Advanced -> Volume Mount + - ../files/superset/superset_config.py:/etc/superset/superset_config.py environment: SECRET_KEY: ${SECRET_KEY} MAPBOX_API_KEY: ${MAPBOX_API_KEY} @@ -22,11 +25,11 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} REDIS_PASSWORD: ${REDIS_PASSWORD} - volumes: - # Note: superset_config.py can be edited in Dokploy's UI Volume Mount - - ../files/superset/superset_config.py:/etc/superset/superset_config.py + # Ensure the hosts matches your service names below. + POSTGRES_HOST: superset_postgres + REDIS_HOST: superset_redis - db: + superset_postgres: image: postgres restart: always environment: @@ -34,7 +37,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - - postgres:/var/lib/postgresql/data + - superset_postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 30s @@ -43,11 +46,11 @@ services: networks: - dokploy-network - redis: + superset_redis: image: redis restart: always volumes: - - redis:/data + - superset_redis_data:/data command: redis-server --requirepass ${REDIS_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] @@ -58,5 +61,5 @@ services: - dokploy-network volumes: - postgres: - redis: + superset_postgres_data: + superset_redis_data: diff --git a/apps/dokploy/templates/superset/index.ts b/apps/dokploy/templates/superset/index.ts index 6132f978..4994b639 100644 --- a/apps/dokploy/templates/superset/index.ts +++ b/apps/dokploy/templates/superset/index.ts @@ -47,13 +47,13 @@ CACHE_CONFIG = { "CACHE_REDIS_HOST": "redis", "CACHE_REDIS_PORT": 6379, "CACHE_REDIS_DB": 1, - "CACHE_REDIS_URL": f"redis://:{os.getenv('REDIS_PASSWORD')}@redis:6379/1", + "CACHE_REDIS_URL": f"redis://:{os.getenv('REDIS_PASSWORD')}@{os.getenv('REDIS_HOST')}:6379/1", } FILTER_STATE_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_filter_"} EXPLORE_FORM_DATA_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_explore_form_"} -SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@db:5432/{os.getenv('POSTGRES_DB')}" +SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}:5432/{os.getenv('POSTGRES_DB')}" SQLALCHEMY_TRACK_MODIFICATIONS = True `.trim(), }, diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 86867532..be04033d 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -538,7 +538,7 @@ export const templates: TemplateData[] = [ website: "https://filebrowser.org/", docs: "https://filebrowser.org/", }, - tags: ["file", "manager"], + tags: ["file-manager", "storage"], load: () => import("./filebrowser/index").then((m) => m.generate), }, { @@ -834,7 +834,7 @@ export const templates: TemplateData[] = [ website: "https://nextcloud.com/", docs: "https://docs.nextcloud.com/", }, - tags: ["file", "sync"], + tags: ["file-manager", "sync"], load: () => import("./nextcloud-aio/index").then((m) => m.generate), }, { @@ -1363,4 +1363,49 @@ export const templates: TemplateData[] = [ ], load: () => import("./erpnext/index").then((m) => m.generate), }, + { + id: "maybe", + name: "Maybe", + version: "latest", + description: + "Maybe is a self-hosted finance tracking application designed to simplify budgeting and expenses.", + logo: "maybe.svg", + links: { + github: "https://github.com/maybe-finance/maybe", + website: "https://maybe.finance/", + docs: "https://docs.maybe.finance/", + }, + tags: ["finance", "self-hosted"], + 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"], + load: () => import("./spacedrive/index").then((m) => m.generate), + }, + { + 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), + }, ]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6341be34..c2167677 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -217,6 +217,9 @@ importers: '@xterm/addon-attach': specifier: 0.10.0 version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/addon-clipboard': + specifier: 0.1.0 + version: 0.1.0(@xterm/xterm@5.5.0) '@xterm/xterm': specifier: ^5.4.0 version: 5.5.0 @@ -3503,6 +3506,11 @@ packages: peerDependencies: '@xterm/xterm': ^5.0.0 + '@xterm/addon-clipboard@0.1.0': + resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==} + peerDependencies: + '@xterm/xterm': ^5.4.0 + '@xterm/xterm@5.5.0': resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} @@ -5011,6 +5019,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + js-beautify@1.15.1: resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==} engines: {node: '>=14'} @@ -9894,6 +9905,11 @@ snapshots: dependencies: '@xterm/xterm': 5.5.0 + '@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + js-base64: 3.7.7 + '@xterm/xterm@5.5.0': {} '@xtuc/ieee754@1.2.0': {} @@ -11410,6 +11426,8 @@ snapshots: joycon@3.1.1: {} + js-base64@3.7.7: {} + js-beautify@1.15.1: dependencies: config-chain: 1.1.13