diff --git a/apps/api/plane/license/api/views/instance.py b/apps/api/plane/license/api/views/instance.py index 23eeebec1c1..fed0c5e17e6 100644 --- a/apps/api/plane/license/api/views/instance.py +++ b/apps/api/plane/license/api/views/instance.py @@ -175,6 +175,7 @@ def get(self, request): data["app_base_url"] = settings.APP_BASE_URL data["instance_changelog_url"] = settings.INSTANCE_CHANGELOG_URL + data["is_self_managed"] = settings.IS_SELF_MANAGED instance_data = serializer.data instance_data["workspaces_exist"] = Workspace.objects.count() >= 1 diff --git a/apps/api/plane/settings/common.py b/apps/api/plane/settings/common.py index 41780521668..a9e9925c28c 100644 --- a/apps/api/plane/settings/common.py +++ b/apps/api/plane/settings/common.py @@ -25,6 +25,9 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = int(os.environ.get("DEBUG", "0")) +# Self-hosted mode +IS_SELF_MANAGED = True + # Allowed Hosts ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "*").split(",") @@ -69,9 +72,7 @@ # Rest Framework settings REST_FRAMEWORK = { - "DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework.authentication.SessionAuthentication", - ), + "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework.authentication.SessionAuthentication",), "DEFAULT_THROTTLE_CLASSES": ("rest_framework.throttling.AnonRateThrottle",), "DEFAULT_THROTTLE_RATES": { "anon": "30/minute", diff --git a/apps/web/ce/components/global/index.ts b/apps/web/ce/components/global/index.ts index c87c8ae0273..08b85c764c0 100644 --- a/apps/web/ce/components/global/index.ts +++ b/apps/web/ce/components/global/index.ts @@ -1,2 +1 @@ export * from "./version-number"; -export * from "./product-updates-header"; diff --git a/apps/web/ce/components/global/product-updates/changelog.tsx b/apps/web/ce/components/global/product-updates/changelog.tsx new file mode 100644 index 00000000000..672b7490bc3 --- /dev/null +++ b/apps/web/ce/components/global/product-updates/changelog.tsx @@ -0,0 +1,83 @@ +import { useState, useEffect, useRef } from "react"; +import { observer } from "mobx-react"; +// hooks +import { Loader } from "@plane/ui"; +import { ProductUpdatesFallback } from "@/components/global/product-updates/fallback"; +import { useInstance } from "@/hooks/store/use-instance"; + +export const ProductUpdatesChangelog = observer(function ProductUpdatesChangelog() { + // refs + const isLoadingRef = useRef(true); + // states + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + // store hooks + const { config } = useInstance(); + // derived values + const changeLogUrl = config?.instance_changelog_url; + const shouldShowFallback = !changeLogUrl || changeLogUrl === "" || hasError; + + // timeout fallback - if iframe doesn't load within 15 seconds, show error + useEffect(() => { + if (!changeLogUrl || changeLogUrl === "") { + setIsLoading(false); + isLoadingRef.current = false; + return; + } + + setIsLoading(true); + setHasError(false); + isLoadingRef.current = true; + + const timeoutId = setTimeout(() => { + if (isLoadingRef.current) { + setHasError(true); + setIsLoading(false); + isLoadingRef.current = false; + } + }, 15000); // 15 second timeout + + return () => { + clearTimeout(timeoutId); + }; + }, [changeLogUrl]); + + const handleIframeLoad = () => { + setTimeout(() => { + isLoadingRef.current = false; + setIsLoading(false); + }, 1000); + }; + + const handleIframeError = () => { + isLoadingRef.current = false; + setHasError(true); + setIsLoading(false); + }; + + // Show fallback if URL is missing, empty, or iframe failed to load + if (shouldShowFallback) { + return ( + + ); + } + + return ( +
+ {isLoading && ( + + + + )} +