Merge pull request #5993 from SebZar/feat/certificate-details-tooltip

feat: show certificate details in host tables
This commit is contained in:
jc21 authored and GitHub committed 2026-11-05 08:42:57 +10:00
commit bd34c94222
1 file changed
+87 -10
@@ -1,18 +1,95 @@
import cn from "classnames";
import { differenceInDays, isPast } from "date-fns";
import OverlayTrigger from "react-bootstrap/OverlayTrigger";
import Popover from "react-bootstrap/Popover";
import type { Certificate } from "src/api/backend";
import { T } from "src/locale";
import { DateFormatter } from "src/components";
import { parseDate, T } from "src/locale";
const getProviderLabel = (provider: string) => {
if (provider === "letsencrypt") {
return "lets-encrypt";
}
if (provider === "other") {
return "certificates.custom";
}
return provider;
};
interface Props {
certificate?: Certificate;
}
export function CertificateFormatter({ certificate }: Props) {
let translation = "http-only";
if (certificate) {
translation = certificate.provider;
if (translation === "letsencrypt") {
translation = "lets-encrypt";
} else if (translation === "other") {
translation = "certificates.custom";
}
if (!certificate) {
return <T id="http-only" />;
}
return <T id={translation} />;
const translation = getProviderLabel(certificate.provider);
const expires = certificate.expiresOn ? parseDate(certificate.expiresOn) : null;
const expired = expires ? isPast(expires) : false;
const daysLeft = expires ? differenceInDays(expires, new Date()) : null;
const labelClass = cn({
"text-danger": expired,
"text-warning": !expired && daysLeft !== null && daysLeft <= 30,
});
const name = certificate.niceName || certificate.domainNames?.[0] || `#${certificate.id}`;
const popover = (
<Popover id={`popover-certificate-${certificate.id}`}>
<Popover.Body>
<div className="mb-2">
<strong>{name}</strong>
<span className="text-secondary ms-1">#{certificate.id}</span>
</div>
<div>
<strong>
<T id="column.provider" />
</strong>
</div>
<div className="ms-1 mb-2">
<T id={translation} />
</div>
{certificate.domainNames?.length ? (
<>
<div>
<strong>
<T id="domain-names" />
</strong>
</div>
{certificate.domainNames.map((domain) => (
<div key={domain} className="ms-1">
{domain}
</div>
))}
</>
) : null}
{certificate.expiresOn ? (
<>
<div className="mt-2">
<strong>
<T id="column.expires" />
</strong>
</div>
<div className="ms-1">
<DateFormatter value={certificate.expiresOn} highlightPast highlistNearlyExpired />
</div>
</>
) : null}
</Popover.Body>
</Popover>
);
return (
<OverlayTrigger trigger={["hover", "click", "focus"]} placement="bottom" overlay={popover}>
<div style={{ cursor: "help" }}>
<span className={labelClass}>
<T id={translation} />
</span>
{certificate.provider === "other" && certificate.niceName ? (
<div className="text-secondary small">{certificate.niceName}</div>
) : null}
</div>
</OverlayTrigger>
);
}