Feat/monitoring (#1267) Cloud Version

* feat: add start monitoring remote servers

* reafctor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor:

* refactor: add metrics

* feat: add disk monitoring

* refactor: translate to english

* refacotor: add stats

* refactor: remove color

* feat: add log server metrics

* refactor: remove unused deps

* refactor: add origin

* refactor: add logs

* refactor: update

* feat: add series monitoring

* refactor: add system monitoring

* feat: add benchmark to optimize data

* refactor: update fn

* refactor: remove comments

* refactor: update

* refactor: exclude items

* feat: add refresh rate

* feat: add monitoring remote servers

* refactor: update

* refactor: remove unsued volumes

* refactor: update monitoring

* refactor: add more presets

* feat: add container metrics

* feat: add docker monitoring

* refactor: update conversion

* refactor: remove unused code

* refactor: update

* refactor: add docker compose logs

* refactor: add docker cli

* refactor: add install curl

* refactor: add get update

* refactor: add monitoring remote servers

* refactor: add containers config

* feat: add container specification

* refactor: update path

* refactor: add server filter

* refactor: simplify logic

* fix: verify if file exist before get stats

* refactor: update

* refactor: remove unused deps

* test: add test for containers

* refactor: update

* refactor add memory collector

* refactor: update

* refactor: update

* refactor: update

* refactor: remove

* refactor: add memory

* refactor: add server memory usage

* refactor: change memory

* refactor: update

* refactor: update

* refactor: add container metrics

* refactor: comment code

* refactor: mount proc bind

* refactor: change interval with node cron

* refactor: remove opening file

* refactor: use streams

* refactor: remove unused ws

* refactor: disable live when is all

* refactor: add sqlite

* refactor: update

* feat: add golang benchmark

* refactor: update go

* refactor: update dockerfile

* refactor: update db

* refactor: add env

* refactor: separate logic

* refactor: split logic

* refactor: update logs

* refactor: update dockerfile

* refactor: hide .env

* refactor: update

* chore: hide ,.ebnv

* refactor: add end angle

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update monitoring

* refactor: add mount db

* refactor: add metrics and url callback

* refactor: add middleware

* refactor: add threshold property

* feat: add memory and cpu threshold notification

* feat: send notifications to the server

* feat: add metrics for dokploy server

* refactor: add dokploy server to monitoring

* refactor: update methods

* refactor: add admin to useeffect

* refactor: stop monitoring containers if elements are 0

* refactor: cancel request if appName is empty

* refactor: reuse methods

* chore; add feat monitoring

* refactor: set base url

* refactor: adjust monitoring

* refactor: delete migrations

* feat: add columns

* fix: add missing flag

* refactor: add free metrics

* refactor: add paid monitoring

* refactor: update methods

* feat: improve ui

* feat: add container stats

* refactor: add all container metrics

* refactor: add color primary

* refactor: change default rate limiting refresher

* refactor: update retention days

* refactor: use json instead of individual properties

* refactor: lint

* refactor: pass json env

* refactor: update

* refactor: delete

* refactor: update

* refactor: fix types

* refactor: add retention days

* chore: add license

* refactor: create db

* refactor: update path

* refactor: update setup

* refactor: update

* refactor: create files

* refactor: update

* refactor: delete

* refactor: update

* refactor: update token metrics

* fix: typechecks

* refactor: setup web server

* refactor: update error handling and add monitoring

* refactor: add local storage save

* refactor: add spacing

* refactor: update

* refactor: upgrade drizzle

* refactor: delete

* refactor: uppgrade drizzle kit

* refactor: update search with jsonB

* chore: upgrade drizzle

* chore: update packages

* refactor: add missing type

* refactor: add serverType

* refactor: update url

* refactor: update

* refactor: update

* refactor: hide monitoring on self hosted

* refactor: update server

* refactor: update

* refactor: update

* refactor: pin node version
This commit is contained in:
Mauricio Siu
2025-02-02 14:08:06 -06:00
committed by GitHub
parent 8c69d2a085
commit 74a0f5e992
150 changed files with 36173 additions and 11538 deletions

View File

@@ -49,6 +49,7 @@ const notificationBaseSchema = z.object({
databaseBackup: z.boolean().default(false),
dokployRestart: z.boolean().default(false),
dockerCleanup: z.boolean().default(false),
serverThreshold: z.boolean().default(false),
});
export const notificationSchema = z.discriminatedUnion("type", [
@@ -204,6 +205,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
channel: notification.slack?.channel || "",
name: notification.name,
type: notification.notificationType,
serverThreshold: notification.serverThreshold,
});
} else if (notification.notificationType === "telegram") {
form.reset({
@@ -216,6 +218,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
type: notification.notificationType,
name: notification.name,
dockerCleanup: notification.dockerCleanup,
serverThreshold: notification.serverThreshold,
});
} else if (notification.notificationType === "discord") {
form.reset({
@@ -228,6 +231,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
decoration: notification.discord?.decoration || undefined,
name: notification.name,
dockerCleanup: notification.dockerCleanup,
serverThreshold: notification.serverThreshold,
});
} else if (notification.notificationType === "email") {
form.reset({
@@ -244,6 +248,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
fromAddress: notification.email?.fromAddress,
name: notification.name,
dockerCleanup: notification.dockerCleanup,
serverThreshold: notification.serverThreshold,
});
} else if (notification.notificationType === "gotify") {
form.reset({
@@ -280,6 +285,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
dokployRestart,
databaseBackup,
dockerCleanup,
serverThreshold,
} = data;
let promise: Promise<unknown> | null = null;
if (data.type === "slack") {
@@ -294,6 +300,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
dockerCleanup: dockerCleanup,
slackId: notification?.slackId || "",
notificationId: notificationId || "",
serverThreshold: serverThreshold,
});
} else if (data.type === "telegram") {
promise = telegramMutation.mutateAsync({
@@ -307,6 +314,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
dockerCleanup: dockerCleanup,
notificationId: notificationId || "",
telegramId: notification?.telegramId || "",
serverThreshold: serverThreshold,
});
} else if (data.type === "discord") {
promise = discordMutation.mutateAsync({
@@ -320,6 +328,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
dockerCleanup: dockerCleanup,
notificationId: notificationId || "",
discordId: notification?.discordId || "",
serverThreshold: serverThreshold,
});
} else if (data.type === "email") {
promise = emailMutation.mutateAsync({
@@ -337,6 +346,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
dockerCleanup: dockerCleanup,
notificationId: notificationId || "",
emailId: notification?.emailId || "",
serverThreshold: serverThreshold,
});
} else if (data.type === "gotify") {
promise = gotifyMutation.mutateAsync({
@@ -955,6 +965,30 @@ export const HandleNotifications = ({ notificationId }: Props) => {
)}
/>
)}
{isCloud && (
<FormField
control={form.control}
name="serverThreshold"
render={({ field }) => (
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
<div className="space-y-0.5">
<FormLabel>Server Threshold</FormLabel>
<FormDescription>
Trigger the action when the server threshold is
reached.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
)}
</div>
</div>
</form>

View File

@@ -0,0 +1,636 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input, NumberInput } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { extractServices } from "@/pages/dashboard/project/[projectId]";
import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod";
import { Eye, EyeOff, LayoutDashboardIcon, RefreshCw } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
interface Props {
serverId?: string;
}
const Schema = z.object({
metricsConfig: z.object({
server: z.object({
refreshRate: z.number().min(2, {
message: "Server Refresh Rate is required",
}),
port: z.number().min(1, {
message: "Port is required",
}),
token: z.string(),
urlCallback: z.string(),
retentionDays: z.number().min(1, {
message: "Retention days must be at least 1",
}),
thresholds: z.object({
cpu: z.number().min(0),
memory: z.number().min(0),
}),
cronJob: z.string().min(1, {
message: "Cron Job is required",
}),
}),
containers: z.object({
refreshRate: z.number().min(2, {
message: "Container Refresh Rate is required",
}),
services: z.object({
include: z.array(z.string()).optional(),
exclude: z.array(z.string()).optional(),
}),
}),
}),
});
type Schema = z.infer<typeof Schema>;
export const SetupMonitoring = ({ serverId }: Props) => {
const { data, isLoading } = serverId
? api.server.one.useQuery(
{
serverId: serverId || "",
},
{
enabled: !!serverId,
},
)
: api.admin.one.useQuery();
const url = useUrl();
const { data: projects } = api.project.all.useQuery();
const extractServicesFromProjects = (projects: any[] | undefined) => {
if (!projects) return [];
const allServices = projects.flatMap((project) => {
const services = extractServices(project);
return serverId
? services
.filter((service) => service.serverId === serverId)
.map((service) => service.appName)
: services.map((service) => service.appName);
});
return [...new Set(allServices)];
};
const services = extractServicesFromProjects(projects);
const form = useForm<Schema>({
resolver: zodResolver(Schema),
defaultValues: {
metricsConfig: {
server: {
refreshRate: 20,
port: 4500,
token: "",
urlCallback: `${url}/api/trpc/notification.receiveNotification`,
retentionDays: 7,
thresholds: {
cpu: 0,
memory: 0,
},
cronJob: "",
},
containers: {
refreshRate: 20,
services: {
include: [],
exclude: [],
},
},
},
},
});
useEffect(() => {
if (data) {
form.reset({
metricsConfig: {
server: {
refreshRate: data?.metricsConfig?.server?.refreshRate,
port: data?.metricsConfig?.server?.port,
token: data?.metricsConfig?.server?.token || generateToken(),
urlCallback:
data?.metricsConfig?.server?.urlCallback ||
`${url}/api/trpc/notification.receiveNotification`,
retentionDays: data?.metricsConfig?.server?.retentionDays || 5,
thresholds: {
cpu: data?.metricsConfig?.server?.thresholds?.cpu,
memory: data?.metricsConfig?.server?.thresholds?.memory,
},
cronJob: data?.metricsConfig?.server?.cronJob || "0 0 * * *",
},
containers: {
refreshRate: data?.metricsConfig?.containers?.refreshRate,
services: {
include: data?.metricsConfig?.containers?.services?.include,
exclude: data?.metricsConfig?.containers?.services?.exclude,
},
},
},
});
}
}, [data, url]);
const [search, setSearch] = useState("");
const [searchExclude, setSearchExclude] = useState("");
const [showToken, setShowToken] = useState(false);
const availableServices = services?.filter(
(service) =>
!form
.watch("metricsConfig.containers.services.include")
?.some((s) => s === service) &&
!form
.watch("metricsConfig.containers.services.exclude")
?.includes(service) &&
service.toLowerCase().includes(search.toLowerCase()),
);
const availableServicesToExclude = [
...(services?.filter(
(service) =>
!form
.watch("metricsConfig.containers.services.exclude")
?.includes(service) &&
!form
.watch("metricsConfig.containers.services.include")
?.some((s) => s === service) &&
service.toLowerCase().includes(searchExclude.toLowerCase()),
) ?? []),
...(!form.watch("metricsConfig.containers.services.exclude")?.includes("*")
? ["*"]
: []),
];
const { mutateAsync } = serverId
? api.server.setupMonitoring.useMutation()
: api.admin.setupMonitoring.useMutation();
const generateToken = () => {
const array = new Uint8Array(64);
crypto.getRandomValues(array);
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
"",
);
};
const onSubmit = async (values: Schema) => {
await mutateAsync({
serverId: serverId || "",
metricsConfig: values.metricsConfig,
})
.then(() => {
toast.success("Server updated successfully");
})
.catch(() => {
toast.error("Error updating the server");
});
};
return (
<>
<CardHeader className="">
<CardTitle className="text-xl flex flex-row gap-2">
<LayoutDashboardIcon className="size-6 text-muted-foreground self-center" />
Monitoring
</CardTitle>
<CardDescription>
Monitor your servers and containers in realtime with notifications
when they reach their thresholds.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 py-6 border-t">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full flex-col gap-4"
>
<AlertBlock>
Using a lower refresh rate will make your CPU and memory usage
higher, we recommend 30-60 seconds
</AlertBlock>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="metricsConfig.server.refreshRate"
render={({ field }) => (
<FormItem className="flex flex-col justify-center max-sm:items-center">
<FormLabel>Server Refresh Rate</FormLabel>
<FormControl>
<NumberInput placeholder="10" {...field} />
</FormControl>
<FormDescription>
Please set the refresh rate for the server in seconds
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.containers.refreshRate"
render={({ field }) => (
<FormItem className="flex flex-col justify-center max-sm:items-center">
<FormLabel>Container Refresh Rate</FormLabel>
<FormControl>
<NumberInput placeholder="10" {...field} />
</FormControl>
<FormDescription>
Please set the refresh rate for the containers in seconds
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.server.cronJob"
render={({ field }) => (
<FormItem>
<FormLabel>Cron Job</FormLabel>
<FormControl>
<Input {...field} placeholder="0 0 * * *" />
</FormControl>
<FormDescription>
Cron job for cleaning up metrics
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.server.retentionDays"
render={({ field }) => (
<FormItem>
<FormLabel>Server Retention Days</FormLabel>
<FormControl>
<NumberInput {...field} />
</FormControl>
<FormDescription>
Number of days to retain server metrics data
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.server.port"
render={({ field }) => (
<FormItem className="flex flex-col justify-center max-sm:items-center">
<FormLabel>Port</FormLabel>
<FormControl>
<NumberInput placeholder="4500" {...field} />
</FormControl>
<FormDescription>
Please set the port for the metrics server
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.containers.services.include"
render={({ field }) => (
<FormItem>
<FormLabel>Include Services</FormLabel>
<FormControl>
<div className="flex flex-col gap-4">
<div className="flex gap-2">
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Add Service</Button>
</PopoverTrigger>
<PopoverContent
className="w-[300px] p-0"
align="start"
>
<Command>
<CommandInput
placeholder="Search service..."
value={search}
onValueChange={setSearch}
/>
{availableServices?.length === 0 ? (
<div className="p-4 text-sm text-muted-foreground">
No services available.
</div>
) : (
<>
<CommandEmpty>
No service found.
</CommandEmpty>
<CommandGroup>
{availableServices?.map((service) => (
<CommandItem
key={service}
value={service}
onSelect={() => {
field.onChange([
...(field.value ?? []),
service,
]);
setSearch("");
}}
>
{service}
</CommandItem>
))}
</CommandGroup>
</>
)}
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex flex-wrap gap-2">
{field.value?.map((service) => (
<Badge
key={service}
variant="secondary"
className="flex items-center gap-2"
>
{service}
<Button
type="button"
variant="ghost"
size="icon"
className="h-4 w-4 p-0"
onClick={() => {
field.onChange(
field.value?.filter((s) => s !== service),
);
}}
>
×
</Button>
</Badge>
))}
<FormDescription>
Services to monitor.
</FormDescription>
</div>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.containers.services.exclude"
render={({ field }) => (
<FormItem>
<FormLabel>Exclude Services</FormLabel>
<FormControl>
<div className="flex flex-col gap-4">
<div className="flex gap-2">
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Add Service</Button>
</PopoverTrigger>
<PopoverContent
className="w-[300px] p-0"
align="start"
>
<Command>
<CommandInput
placeholder="Search service..."
value={searchExclude}
onValueChange={setSearchExclude}
/>
{availableServicesToExclude?.length === 0 ? (
<div className="p-4 text-sm text-muted-foreground">
No services available.
</div>
) : (
<>
<CommandEmpty>
No service found.
</CommandEmpty>
<CommandGroup>
{availableServicesToExclude.map(
(service) => (
<CommandItem
key={service}
value={service}
onSelect={() => {
field.onChange([
...(field.value ?? []),
service,
]);
setSearchExclude("");
}}
>
{service}
</CommandItem>
),
)}
</CommandGroup>
</>
)}
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex flex-wrap gap-2">
{field.value?.map((service, index) => (
<Badge
key={service}
variant="secondary"
className="flex items-center gap-2"
>
{service}
<Button
type="button"
variant="ghost"
size="icon"
className="h-4 w-4 p-0"
onClick={() => {
field.onChange(
field.value?.filter((_, i) => i !== index),
);
}}
>
×
</Button>
</Badge>
))}
<FormDescription>
Services to exclude from monitoring
</FormDescription>
</div>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.server.thresholds.cpu"
render={({ field }) => (
<FormItem>
<FormLabel>CPU Threshold (%)</FormLabel>
<FormControl>
<NumberInput {...field} />
</FormControl>
<FormDescription>
Alert when CPU usage exceeds this percentage
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.server.thresholds.memory"
render={({ field }) => (
<FormItem>
<FormLabel>Memory Threshold (%)</FormLabel>
<FormControl>
<NumberInput {...field} />
</FormControl>
<FormDescription>
Alert when memory usage exceeds this percentage
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.server.token"
render={({ field }) => (
<FormItem>
<FormLabel>Metrics Token</FormLabel>
<FormControl>
<div className="flex gap-2">
<div className="relative flex-1">
<Input
type={showToken ? "text" : "password"}
placeholder="Enter your metrics token"
{...field}
/>
<Button
type="button"
variant="secondary"
size="icon"
className="absolute right-0 top-1/2 -translate-y-1/2"
onClick={() => setShowToken(!showToken)}
title={showToken ? "Hide token" : "Show token"}
>
{showToken ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => {
const newToken = generateToken();
form.setValue(
"metricsConfig.server.token",
newToken,
);
toast.success("Token generated successfully");
}}
title="Generate new token"
>
<RefreshCw className="h-4 w-4" />
</Button>
</div>
</FormControl>
<FormDescription>
Token for authenticating metrics requests
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="metricsConfig.server.urlCallback"
render={({ field }) => (
<FormItem>
<FormLabel>Metrics Callback URL</FormLabel>
<FormControl>
<Input
placeholder="https://your-callback-url.com"
{...field}
/>
</FormControl>
<FormDescription>
URL where metrics will be sent
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex items-center justify-end gap-2">
<Button type="submit" isLoading={form.formState.isSubmitting}>
Save changes
</Button>
</div>
</form>
</Form>
</CardContent>
</>
);
};

View File

@@ -19,6 +19,7 @@ import {
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import copy from "copy-to-clipboard";
import { CopyIcon, ExternalLinkIcon, ServerIcon } from "lucide-react";
@@ -30,6 +31,7 @@ import { type LogLine, parseLogs } from "../../docker/logs/utils";
import { EditScript } from "./edit-script";
import { GPUSupport } from "./gpu-support";
import { SecurityAudit } from "./security-audit";
import { SetupMonitoring } from "./setup-monitoring";
import { ValidateServer } from "./validate-server";
interface Props {
@@ -48,7 +50,7 @@ export const SetupServer = ({ serverId }: Props) => {
);
const [activeLog, setActiveLog] = useState<string | null>(null);
const { data: isCloud } = api.settings.isCloud.useQuery();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
const [isDeploying, setIsDeploying] = useState(false);
@@ -112,11 +114,19 @@ export const SetupServer = ({ serverId }: Props) => {
</AlertBlock>
<Tabs defaultValue="ssh-keys">
<TabsList className="grid grid-cols-5 w-[700px]">
<TabsList
className={cn(
"grid w-[700px]",
isCloud ? "grid-cols-6" : "grid-cols-5",
)}
>
<TabsTrigger value="ssh-keys">SSH Keys</TabsTrigger>
<TabsTrigger value="deployments">Deployments</TabsTrigger>
<TabsTrigger value="validate">Validate</TabsTrigger>
<TabsTrigger value="audit">Security</TabsTrigger>
{isCloud && (
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
)}
<TabsTrigger value="gpu-setup">GPU Setup</TabsTrigger>
</TabsList>
<TabsContent
@@ -309,6 +319,16 @@ export const SetupServer = ({ serverId }: Props) => {
<SecurityAudit serverId={serverId} />
</div>
</TabsContent>
<TabsContent
value="monitoring"
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
>
<div className="flex flex-col gap-2 text-sm pt-3">
<div className="rounded-xl bg-background shadow-md border">
<SetupMonitoring serverId={serverId} />
</div>
</div>
</TabsContent>
<TabsContent
value="gpu-setup"
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"

View File

@@ -0,0 +1,31 @@
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { useState } from "react";
import { ShowPaidMonitoring } from "../../monitoring/paid/servers/show-paid-monitoring";
interface Props {
url: string;
token: string;
}
export const ShowMonitoringModal = ({ url, token }: Props) => {
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<DropdownMenuItem
className="w-full cursor-pointer "
onSelect={(e) => e.preventDefault()}
>
Show Monitoring
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="sm:max-w-7xl overflow-y-auto max-h-screen ">
<div className="flex gap-4 py-4 w-full">
<ShowPaidMonitoring BASE_URL={url} token={token} />
</div>
</DialogContent>
</Dialog>
);
};

View File

@@ -38,6 +38,7 @@ import { ShowServerActions } from "./actions/show-server-actions";
import { HandleServers } from "./handle-servers";
import { SetupServer } from "./setup-server";
import { ShowDockerContainersModal } from "./show-docker-containers-modal";
import { ShowMonitoringModal } from "./show-monitoring-modal";
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
@@ -314,6 +315,16 @@ export const ShowServers = () => {
<ShowDockerContainersModal
serverId={server.serverId}
/>
{isCloud && (
<ShowMonitoringModal
url={`http://${server.ipAddress}:${server?.metricsConfig?.server?.port}/metrics`}
token={
server?.metricsConfig?.server
?.token
}
/>
)}
<ShowSwarmOverviewModal
serverId={server.serverId}
/>