From 646095da654b3548bcb3855234af4f8973178626 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Sat, 20 Dec 2025 22:49:31 +0000 Subject: [PATCH] moduel setgins fixed --- .../modules/system/settings_views.py | 9 +- backend/igny8_core/modules/system/urls.py | 4 +- frontend/src/App.tsx | 9 +- frontend/src/layout/AppSidebar.tsx | 105 ++++++++++-------- frontend/src/services/api.ts | 19 ++++ frontend/src/store/moduleStore.ts | 59 ++++++++++ frontend/test-module-settings.html | 69 ++++++++++++ 7 files changed, 220 insertions(+), 54 deletions(-) create mode 100644 frontend/src/store/moduleStore.ts create mode 100644 frontend/test-module-settings.html diff --git a/backend/igny8_core/modules/system/settings_views.py b/backend/igny8_core/modules/system/settings_views.py index 313270ef..db4e68f1 100644 --- a/backend/igny8_core/modules/system/settings_views.py +++ b/backend/igny8_core/modules/system/settings_views.py @@ -289,17 +289,16 @@ class ModuleSettingsViewSet(AccountModelViewSet): @extend_schema_view( list=extend_schema(tags=['System']), retrieve=extend_schema(tags=['System']), - update=extend_schema(tags=['System']), - partial_update=extend_schema(tags=['System']), ) class ModuleEnableSettingsViewSet(viewsets.ViewSet): """ - ViewSet for GLOBAL module enable/disable settings (read-only). + ViewSet for GLOBAL module enable/disable settings (read-only, public). Returns platform-wide module availability from GlobalModuleSettings singleton. + No authentication required - these are public platform-wide settings. Only superadmin can modify via Django Admin at /admin/system/globalmodulesettings/. """ - authentication_classes = [JWTAuthentication] - permission_classes = [IsAuthenticatedAndActive, HasTenantAccess] + authentication_classes = [] + permission_classes = [] throttle_scope = 'system' throttle_classes = [DebugScopedRateThrottle] diff --git a/backend/igny8_core/modules/system/urls.py b/backend/igny8_core/modules/system/urls.py index 9c1508b2..aa2a08e4 100644 --- a/backend/igny8_core/modules/system/urls.py +++ b/backend/igny8_core/modules/system/urls.py @@ -52,11 +52,9 @@ integration_image_gen_settings_viewset = IntegrationSettingsViewSet.as_view({ # Custom view for module enable settings to avoid URL routing conflict with ModuleSettingsViewSet # This must be defined as a custom path BEFORE router.urls to ensure it matches first -# The update method handles pk=None correctly, so we can use as_view +# Read-only viewset - only GET is supported (modification via Django Admin only) module_enable_viewset = ModuleEnableSettingsViewSet.as_view({ 'get': 'list', - 'put': 'update', - 'patch': 'partial_update', }) urlpatterns = [ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ec8e292c..ba6e3b30 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import ProtectedRoute from "./components/auth/ProtectedRoute"; import GlobalErrorDisplay from "./components/common/GlobalErrorDisplay"; import LoadingStateMonitor from "./components/common/LoadingStateMonitor"; import { useAuthStore } from "./store/authStore"; +import { useModuleStore } from "./store/moduleStore"; // Auth pages - loaded immediately (needed for login) import SignIn from "./pages/AuthPages/SignIn"; @@ -111,7 +112,13 @@ const Components = lazy(() => import("./pages/Components")); export default function App() { - // All session validation removed - API interceptor handles authentication + const { isAuthenticated } = useAuthStore(); + const { loadModuleSettings } = useModuleStore(); + + // Load global module settings immediately on mount (public endpoint, no auth required) + useEffect(() => { + loadModuleSettings(); + }, [loadModuleSettings]); return ( <> diff --git a/frontend/src/layout/AppSidebar.tsx b/frontend/src/layout/AppSidebar.tsx index 4e4b8d41..804d6567 100644 --- a/frontend/src/layout/AppSidebar.tsx +++ b/frontend/src/layout/AppSidebar.tsx @@ -23,6 +23,7 @@ import SidebarWidget from "./SidebarWidget"; import { APP_VERSION } from "../config/version"; import { useAuthStore } from "../store/authStore"; import { useSettingsStore } from "../store/settingsStore"; +import { useModuleStore } from "../store/moduleStore"; type NavItem = { name: string; @@ -41,6 +42,7 @@ const AppSidebar: React.FC = () => { const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar(); const location = useLocation(); const { user, isAuthenticated } = useAuthStore(); + const { isModuleEnabled, settings: moduleSettings } = useModuleStore(); const [openSubmenu, setOpenSubmenu] = useState<{ sectionIndex: number; @@ -56,12 +58,9 @@ const AppSidebar: React.FC = () => { [location.pathname] ); - // Load module enable settings on mount (only once) - but only if user is authenticated - // REMOVED: Module enable settings functionality - all modules always shown - // Module enable/disable is now controlled ONLY via Django Admin (GlobalModuleSettings) - // Define menu sections with useMemo to prevent recreation on every render // New structure: Dashboard (standalone) → SETUP → WORKFLOW → SETTINGS + // Module visibility is controlled by GlobalModuleSettings (Django Admin only) const menuSections: MenuSection[] = useMemo(() => { // SETUP section items (single items, no dropdowns - submenus shown as in-page navigation) const setupItems: NavItem[] = [ @@ -70,57 +69,73 @@ const AppSidebar: React.FC = () => { name: "Add Keywords", path: "/setup/add-keywords", }, - { + ]; + + // Add Sites (Site Builder) if enabled + if (isModuleEnabled('site_builder')) { + setupItems.push({ icon: , name: "Sites", path: "/sites", // Submenus shown as in-page navigation - }, - ]; + }); + } - // Add Thinker (always shown) - setupItems.push({ - icon: , - name: "Thinker", - path: "/thinker/prompts", // Default to prompts, submenus shown as in-page navigation - }); + // Add Thinker if enabled + if (isModuleEnabled('thinker')) { + setupItems.push({ + icon: , + name: "Thinker", + path: "/thinker/prompts", // Default to prompts, submenus shown as in-page navigation + }); + } - // WORKFLOW section items (all modules always shown) + // WORKFLOW section items (conditionally shown based on global settings) const workflowItems: NavItem[] = []; - // Add Planner - workflowItems.push({ - icon: , - name: "Planner", - path: "/planner/keywords", // Default to keywords, submenus shown as in-page navigation - }); + // Add Planner if enabled + if (isModuleEnabled('planner')) { + workflowItems.push({ + icon: , + name: "Planner", + path: "/planner/keywords", // Default to keywords, submenus shown as in-page navigation + }); + } - // Add Writer - workflowItems.push({ - icon: , - name: "Writer", - path: "/writer/tasks", // Default to tasks, submenus shown as in-page navigation - }); + // Add Writer if enabled + if (isModuleEnabled('writer')) { + workflowItems.push({ + icon: , + name: "Writer", + path: "/writer/tasks", // Default to tasks, submenus shown as in-page navigation + }); + } - // Add Automation - workflowItems.push({ - icon: , - name: "Automation", - path: "/automation", - }); + // Add Automation if enabled + if (isModuleEnabled('automation')) { + workflowItems.push({ + icon: , + name: "Automation", + path: "/automation", + }); + } - // Add Linker - workflowItems.push({ - icon: , - name: "Linker", - path: "/linker/content", - }); + // Add Linker if enabled + if (isModuleEnabled('linker')) { + workflowItems.push({ + icon: , + name: "Linker", + path: "/linker/content", + }); + } - // Add Optimizer - workflowItems.push({ - icon: , - name: "Optimizer", - path: "/optimizer/content", - }); + // Add Optimizer if enabled + if (isModuleEnabled('optimizer')) { + workflowItems.push({ + icon: , + name: "Optimizer", + path: "/optimizer/content", + }); + } return [ // Dashboard is standalone (no section header) @@ -203,7 +218,7 @@ const AppSidebar: React.FC = () => { ], }, ]; - }, []); // No dependencies - always show all modules + }, [isModuleEnabled, moduleSettings]); // Re-run when settings change // Combine all sections const allSections = useMemo(() => { diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index a25f4858..bf5adedb 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1841,6 +1841,25 @@ export async function updateModuleSetting(moduleName: string, key: string, data: }); } +// Global Module Enable Settings (Platform-wide) +export interface GlobalModuleSettings { + id: number; + planner_enabled: boolean; + writer_enabled: boolean; + thinker_enabled: boolean; + automation_enabled: boolean; + site_builder_enabled: boolean; + linker_enabled: boolean; + optimizer_enabled: boolean; + publisher_enabled: boolean; + created_at: string | null; + updated_at: string | null; +} + +export async function fetchGlobalModuleSettings(): Promise { + return fetchAPI('/v1/system/settings/modules/enable/'); +} + // Billing API functions export interface CreditBalance { credits: number; diff --git a/frontend/src/store/moduleStore.ts b/frontend/src/store/moduleStore.ts new file mode 100644 index 00000000..49c1685f --- /dev/null +++ b/frontend/src/store/moduleStore.ts @@ -0,0 +1,59 @@ +/** + * Module Settings Store (Zustand) + * Manages global module enable/disable state (platform-wide) + * Settings are controlled via Django Admin only - NOT account-specific + * No persistence needed since this is the same for all users + */ +import { create } from 'zustand'; +import { fetchGlobalModuleSettings, GlobalModuleSettings } from '../services/api'; + +interface ModuleState { + settings: GlobalModuleSettings | null; + loading: boolean; + error: string | null; + + // Actions + loadModuleSettings: () => Promise; + isModuleEnabled: (moduleName: string) => boolean; + reset: () => void; +} + +export const useModuleStore = create()((set, get) => ({ + settings: null, + loading: false, + error: null, + + loadModuleSettings: async () => { + set({ loading: true, error: null }); + try { + const settings = await fetchGlobalModuleSettings(); + console.log('Loaded global module settings:', settings); + set({ settings, loading: false }); + } catch (error: any) { + console.error('Failed to load global module settings:', error); + set({ + error: error.message || 'Failed to load module settings', + loading: false + }); + } + }, + + isModuleEnabled: (moduleName: string): boolean => { + const { settings } = get(); + + // Default to true while settings are loading (better UX) + // Once settings load, they will control visibility + if (!settings) { + return true; + } + + const fieldName = `${moduleName.toLowerCase()}_enabled` as keyof GlobalModuleSettings; + const enabled = settings[fieldName] === true; + console.log(`Module check for '${moduleName}' (${fieldName}): ${enabled}`); + return enabled; + }, + + reset: () => { + set({ settings: null, loading: false, error: null }); + }, +})); diff --git a/frontend/test-module-settings.html b/frontend/test-module-settings.html new file mode 100644 index 00000000..49a83695 --- /dev/null +++ b/frontend/test-module-settings.html @@ -0,0 +1,69 @@ + + + + Module Settings Test + + + +

Module Settings API Test

+ +
+ + + +