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
+
+
+
+
+
+