diff --git a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx
index 81345dac09f..781654ac2f8 100644
--- a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx
+++ b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx
@@ -95,7 +95,7 @@ describe("LicenseExpiryBanner", () => {
});
it("should show a warning banner when license expires within 14 days", async () => {
- // Now is 2026-03-15T12:00:00Z, exp is 2026-03-22T23:59:59 => ~7.5 days => ceil = 8
+ // Now is 2026-03-15T12:00:00Z, exp is 2026-03-22T23:59:59Z (UTC) => ~7.5 days => ceil = 8
mockGetLicenseInfo.mockResolvedValue(makeLicense("2026-03-22"));
render();
@@ -106,7 +106,7 @@ describe("LicenseExpiryBanner", () => {
});
it("should show singular 'day' when 1 day remains", async () => {
- // Now is 2026-03-15T12:00:00Z, exp is 2026-03-15T23:59:59 => ~0.5 days => ceil = 1
+ // Now is 2026-03-15T12:00:00Z, exp is 2026-03-15T23:59:59Z (UTC) => ~0.5 days => ceil = 1
mockGetLicenseInfo.mockResolvedValue(makeLicense("2026-03-15"));
render();
@@ -173,6 +173,16 @@ describe("LicenseExpiryBanner", () => {
});
});
+ it("should show a warning when getLicenseInfo API call fails", async () => {
+ mockGetLicenseInfo.mockRejectedValue(new Error("Network error"));
+
+ render();
+
+ expect(
+ await screen.findByText("Unable to verify enterprise license")
+ ).toBeInTheDocument();
+ });
+
it("should render nothing when expiration_date is unparseable", async () => {
mockGetLicenseInfo.mockResolvedValue(makeLicense("N/A"));
diff --git a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx
index b327c571991..615be1ff979 100644
--- a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx
+++ b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx
@@ -6,26 +6,55 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { getLicenseInfo, LicenseInfo } from "./networking";
const EXPIRY_WARNING_DAYS = 14;
+/** Re-fetch license info every 6 hours so long-lived sessions pick up expiry. */
+const REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
export const LicenseExpiryBanner: React.FC = () => {
const { accessToken } = useAuthorized();
const [licenseInfo, setLicenseInfo] = useState(null);
+ const [fetchError, setFetchError] = useState(false);
useEffect(() => {
if (!accessToken) return;
let cancelled = false;
- getLicenseInfo(accessToken)
- .then((info) => {
- if (!cancelled) setLicenseInfo(info);
- })
- .catch(() => null);
+
+ const fetchLicense = () => {
+ getLicenseInfo(accessToken)
+ .then((info) => {
+ if (!cancelled) {
+ setLicenseInfo(info);
+ setFetchError(false);
+ }
+ })
+ .catch(() => {
+ if (!cancelled) setFetchError(true);
+ });
+ };
+
+ fetchLicense();
+ const intervalId = setInterval(fetchLicense, REFRESH_INTERVAL_MS);
return () => {
cancelled = true;
+ clearInterval(intervalId);
};
}, [accessToken]);
+ if (fetchError) {
+ return (
+
+ );
+ }
+
if (!licenseInfo?.has_license || !licenseInfo.expiration_date) {
return null;
}