"use client"; import { useState } from "react"; import { Accordion, Anchor, Badge, Card, Group, Image, Stack, Text, Tooltip } from "@mantine/core"; import { useLocalStorage } from "@mantine/hooks"; import { useTimeAgo } from "@homarr/common"; import { useScopedI18n } from "@homarr/translation/client"; import { ApplicationsSection } from "./applications-section"; import { getSafeApplicationUrl, SAFE_NEW_TAB_REL } from "../common/application-url"; import { buildServerResourceCounts, getBadgeColor, isCoolifyServerOnline, parseStatus } from "./coolify-utils"; import { ServersSection } from "./servers-section"; import { ServicesSection } from "./services-section"; import type { CoolifyOptions, InstanceData } from "./types"; import { COOLIFY_ICON_URL } from "./types"; interface InstanceCardProps { instance: InstanceData; options: CoolifyOptions; isTiny: boolean; isAdvanced: boolean; widgetKey: string; hideFooter: boolean; } export function InstanceCard({ instance, options, isTiny, isAdvanced, widgetKey, hideFooter }: InstanceCardProps) { const t = useScopedI18n("widget.coolify"); const tCommon = useScopedI18n("common"); const cardKey = `${widgetKey}-${instance.integrationId}`; const [showIp, setShowIp] = useLocalStorage({ key: `coolify-show-ip-${cardKey}`, defaultValue: false, }); const [openSections, setOpenSections] = useLocalStorage({ key: `coolify-sections-${cardKey}`, defaultValue: ["applications"], }); const [advancedOpenSections, setAdvancedOpenSections] = useState(["servers", "applications", "services"]); const serverResourceCounts = buildServerResourceCounts( instance.instanceInfo.servers, instance.instanceInfo.applications, instance.instanceInfo.services, ); const baseUrl = getSafeApplicationUrl(instance.integrationUrl)?.replace(/\/+$/, "") ?? ""; const displayUrl = baseUrl ? baseUrl.replace(/^https?:\/\//, "") : "—"; const onlineServers = instance.instanceInfo.servers.filter(isCoolifyServerOnline).length; const runningApps = instance.instanceInfo.applications.filter( (a) => parseStatus(a.status ?? "") === "running", ).length; const runningServices = instance.instanceInfo.services.filter( (s) => parseStatus(s.status ?? "") === "running", ).length; const healthyResources = (options.showServers ? onlineServers : 0) + (options.showApplications ? runningApps : 0) + (options.showServices ? runningServices : 0); const totalResources = (options.showServers ? instance.instanceInfo.servers.length : 0) + (options.showApplications ? instance.instanceInfo.applications.length : 0) + (options.showServices ? instance.instanceInfo.services.length : 0); const resourceSummary = [ options.showServers ? `${tCommon("servers")}: ${onlineServers}/${instance.instanceInfo.servers.length}` : null, options.showApplications ? `${tCommon("applications")}: ${runningApps}/${instance.instanceInfo.applications.length}` : null, options.showServices ? `${tCommon("services")}: ${runningServices}/${instance.instanceInfo.services.length}` : null, ] .filter(Boolean) .join(" · "); return ( Coolify {instance.integrationName} {isAdvanced && ( {t("source.url", { url: displayUrl })} )} {isTiny && totalResources > 0 ? ( {healthyResources}/{totalResources} ) : null} {!isTiny && options.showServers && ( {onlineServers}/{instance.instanceInfo.servers.length} )} {!isTiny && options.showApplications && ( {runningApps}/{instance.instanceInfo.applications.length} )} {!isTiny && options.showServices && ( {runningServices}/{instance.instanceInfo.services.length} )} {options.showServers && ( setShowIp((prev) => !prev)} /> )} {options.showApplications && ( )} {options.showServices && ( )} {!hideFooter && ( )} ); } const InstanceFooter = ({ version, updatedAt }: { version: string; updatedAt: Date }) => { const t = useScopedI18n("widget.coolify"); const relativeTime = useTimeAgo(updatedAt, 60_000); return ( v{version} {t("footer.updated", { when: relativeTime })} ); };