feat(multi server): add env and toggle dashboard remote

This commit is contained in:
Mauricio Siu
2024-09-21 01:44:31 -06:00
parent 497d45129c
commit 8bf6a22db8
7 changed files with 102 additions and 52 deletions

View File

@@ -29,14 +29,13 @@ import { SetupServer } from "./setup-server";
import { UpdateServer } from "./update-server"; import { UpdateServer } from "./update-server";
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
import { ShowModalLogs } from "../web-server/show-modal-logs"; import { ShowModalLogs } from "../web-server/show-modal-logs";
import { ToggleTraefikDashboard } from "./toggle-traefik-dashboard";
import { EditTraefikEnv } from "../web-server/edit-traefik-env";
export const ShowServers = () => { export const ShowServers = () => {
const { data, refetch } = api.server.all.useQuery(); const { data, refetch } = api.server.all.useQuery();
const { mutateAsync } = api.server.remove.useMutation(); const { mutateAsync } = api.server.remove.useMutation();
const { data: sshKeys } = api.sshKey.all.useQuery(); const { data: sshKeys } = api.sshKey.all.useQuery();
const { mutateAsync: toggleDashboard, isLoading: toggleDashboardIsLoading } =
api.settings.toggleDashboard.useMutation();
const { const {
mutateAsync: cleanDockerBuilder, mutateAsync: cleanDockerBuilder,
isLoading: cleanDockerBuilderIsLoading, isLoading: cleanDockerBuilderIsLoading,
@@ -63,9 +62,6 @@ export const ShowServers = () => {
const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } = const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } =
api.settings.cleanAll.useMutation(); api.settings.cleanAll.useMutation();
const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } =
api.settings.haveTraefikDashboardPortEnabled.useQuery();
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<div className="space-y-2 flex flex-row justify-between items-end"> <div className="space-y-2 flex flex-row justify-between items-end">
@@ -226,35 +222,18 @@ export const ShowServers = () => {
> >
<span>Watch logs</span> <span>Watch logs</span>
</ShowModalLogs> </ShowModalLogs>
<EditTraefikEnv serverId={server.serverId}>
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
className="w-full cursor-pointer space-x-3"
>
<span>Modify Env</span>
</DropdownMenuItem>
</EditTraefikEnv>
<ToggleTraefikDashboard
serverId={server.serverId}
/>
{/* <DropdownMenuItem
onClick={async () => {
await toggleDashboard({
enableDashboard:
!haveTraefikDashboardPortEnabled,
serverId: server.serverId,
})
.then(async () => {
toast.success(
`${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
);
refetchDashboard();
})
.catch(() => {
toast.error(
`${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
);
});
}}
className="w-full cursor-pointer space-x-3"
>
<span>
{haveTraefikDashboardPortEnabled
? "Disable"
: "Enable"}{" "}
Dashboard
</span>
</DropdownMenuItem> */}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuLabel>Storage</DropdownMenuLabel> <DropdownMenuLabel>Storage</DropdownMenuLabel>

View File

@@ -0,0 +1,48 @@
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { api } from "@/utils/api";
import { toast } from "sonner";
interface Props {
serverId: string;
}
export const ToggleTraefikDashboard = ({ serverId }: Props) => {
const { mutateAsync: toggleDashboard, isLoading: toggleDashboardIsLoading } =
api.settings.toggleDashboard.useMutation();
const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } =
api.settings.haveTraefikDashboardPortEnabled.useQuery(
{
serverId,
},
{
enabled: !!serverId,
},
);
return (
<>
<DropdownMenuItem
onClick={async () => {
await toggleDashboard({
enableDashboard: !haveTraefikDashboardPortEnabled,
serverId: serverId,
})
.then(async () => {
toast.success(
`${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
);
refetchDashboard();
})
.catch(() => {
toast.error(
`${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
);
});
}}
className="w-full cursor-pointer space-x-3"
>
<span>
{haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard
</span>
</DropdownMenuItem>
</>
);
};

View File

@@ -65,6 +65,7 @@ export const EditTraefikEnv = ({ children, serverId }: Props) => {
const onSubmit = async (data: Schema) => { const onSubmit = async (data: Schema) => {
await mutateAsync({ await mutateAsync({
env: data.env, env: data.env,
serverId,
}) })
.then(async () => { .then(async () => {
toast.success("Traefik Env Updated"); toast.success("Traefik Env Updated");

View File

@@ -88,6 +88,7 @@ export const settingsRouter = createTRPCRouter({
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
await initializeTraefik({ await initializeTraefik({
enableDashboard: input.enableDashboard, enableDashboard: input.enableDashboard,
serverId: input.serverId,
}); });
return true; return true;
}), }),
@@ -357,6 +358,7 @@ export const settingsRouter = createTRPCRouter({
if (input?.serverId) { if (input?.serverId) {
const result = await execAsyncRemote(input.serverId, command); const result = await execAsyncRemote(input.serverId, command);
console.log(result);
return result.stdout.trim(); return result.stdout.trim();
} }
const result = await execAsync(command); const result = await execAsync(command);
@@ -369,25 +371,38 @@ export const settingsRouter = createTRPCRouter({
const envs = prepareEnvironmentVariables(input.env); const envs = prepareEnvironmentVariables(input.env);
await initializeTraefik({ await initializeTraefik({
env: envs, env: envs,
serverId: input.serverId,
}); });
return true; return true;
}), }),
haveTraefikDashboardPortEnabled: adminProcedure.query(async () => { haveTraefikDashboardPortEnabled: adminProcedure
const { stdout } = await execAsync( .input(apiStorage)
"docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik", .query(async ({ input }) => {
); const command = `docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik`;
const parsed: any[] = JSON.parse(stdout.trim()); let stdout = "";
if (input?.serverId) {
for (const port of parsed) { const result = await execAsyncRemote(input.serverId, command);
if (port.PublishedPort === 8080) { stdout = result.stdout;
return true; } else {
const result = await execAsync(
"docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik",
);
stdout = result.stdout;
} }
}
return false; const parsed: any[] = JSON.parse(stdout.trim());
}), console.log(parsed);
for (const port of parsed) {
if (port.PublishedPort === 8080) {
return true;
}
}
return false;
}),
readStatsLogs: adminProcedure.input(apiReadStatsLogs).query(({ input }) => { readStatsLogs: adminProcedure.input(apiReadStatsLogs).query(({ input }) => {
const rawConfig = readMonitoringConfig(); const rawConfig = readMonitoringConfig();

View File

@@ -135,7 +135,6 @@ export const getContainersByAppLabel = async (
let stderr = ""; let stderr = "";
const command = `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`; const command = `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`;
console.log(command);
if (serverId) { if (serverId) {
const result = await execAsyncRemote(serverId, command); const result = await execAsyncRemote(serverId, command);
stdout = result.stdout; stdout = result.stdout;

View File

@@ -2,10 +2,11 @@ import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path"; import path from "node:path";
import type { ContainerTaskSpec, CreateServiceOptions } from "dockerode"; import type { ContainerTaskSpec, CreateServiceOptions } from "dockerode";
import { dump } from "js-yaml"; import { dump } from "js-yaml";
import { docker, paths } from "../constants"; import { paths } from "../constants";
import { pullImage } from "../utils/docker/utils"; import { pullImage, pullRemoteImage } from "../utils/docker/utils";
import type { FileConfig } from "../utils/traefik/file-types"; import type { FileConfig } from "../utils/traefik/file-types";
import type { MainTraefikConfig } from "../utils/traefik/types"; import type { MainTraefikConfig } from "../utils/traefik/types";
import { getRemoteDocker } from "../utils/servers/remote-docker";
const TRAEFIK_SSL_PORT = const TRAEFIK_SSL_PORT =
Number.parseInt(process.env.TRAEFIK_SSL_PORT ?? "", 10) || 443; Number.parseInt(process.env.TRAEFIK_SSL_PORT ?? "", 10) || 443;
@@ -14,13 +15,15 @@ const TRAEFIK_PORT = Number.parseInt(process.env.TRAEFIK_PORT ?? "", 10) || 80;
interface TraefikOptions { interface TraefikOptions {
enableDashboard?: boolean; enableDashboard?: boolean;
env?: string[]; env?: string[];
serverId?: string;
} }
export const initializeTraefik = async ({ export const initializeTraefik = async ({
enableDashboard = false, enableDashboard = false,
env, env,
serverId,
}: TraefikOptions = {}) => { }: TraefikOptions = {}) => {
const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(); const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const imageName = "traefik:v3.1.2"; const imageName = "traefik:v3.1.2";
const containerName = "dokploy-traefik"; const containerName = "dokploy-traefik";
const settings: CreateServiceOptions = { const settings: CreateServiceOptions = {
@@ -84,8 +87,13 @@ export const initializeTraefik = async ({
], ],
}, },
}; };
const docker = await getRemoteDocker(serverId);
try { try {
await pullImage(imageName); if (serverId) {
await pullRemoteImage(imageName, serverId);
} else {
await pullImage(imageName);
}
const service = docker.getService(containerName); const service = docker.getService(containerName);
const inspect = await service.inspect(); const inspect = await service.inspect();

View File

@@ -3,7 +3,7 @@ import { docker } from "@/server/constants";
import Dockerode from "dockerode"; import Dockerode from "dockerode";
import { readSSHKey } from "../filesystem/ssh"; import { readSSHKey } from "../filesystem/ssh";
export const getRemoteDocker = async (serverId: string | null) => { export const getRemoteDocker = async (serverId?: string | null) => {
if (!serverId) return docker; if (!serverId) return docker;
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) return docker; if (!server.sshKeyId) return docker;