moduel setgins fixed

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-20 22:49:31 +00:00
parent 5c9ef81aba
commit 646095da65
7 changed files with 220 additions and 54 deletions

View File

@@ -289,17 +289,16 @@ class ModuleSettingsViewSet(AccountModelViewSet):
@extend_schema_view( @extend_schema_view(
list=extend_schema(tags=['System']), list=extend_schema(tags=['System']),
retrieve=extend_schema(tags=['System']), retrieve=extend_schema(tags=['System']),
update=extend_schema(tags=['System']),
partial_update=extend_schema(tags=['System']),
) )
class ModuleEnableSettingsViewSet(viewsets.ViewSet): 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. 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/. Only superadmin can modify via Django Admin at /admin/system/globalmodulesettings/.
""" """
authentication_classes = [JWTAuthentication] authentication_classes = []
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess] permission_classes = []
throttle_scope = 'system' throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle] throttle_classes = [DebugScopedRateThrottle]

View File

@@ -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 # 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 # 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({ module_enable_viewset = ModuleEnableSettingsViewSet.as_view({
'get': 'list', 'get': 'list',
'put': 'update',
'patch': 'partial_update',
}) })
urlpatterns = [ urlpatterns = [

View File

@@ -7,6 +7,7 @@ import ProtectedRoute from "./components/auth/ProtectedRoute";
import GlobalErrorDisplay from "./components/common/GlobalErrorDisplay"; import GlobalErrorDisplay from "./components/common/GlobalErrorDisplay";
import LoadingStateMonitor from "./components/common/LoadingStateMonitor"; import LoadingStateMonitor from "./components/common/LoadingStateMonitor";
import { useAuthStore } from "./store/authStore"; import { useAuthStore } from "./store/authStore";
import { useModuleStore } from "./store/moduleStore";
// Auth pages - loaded immediately (needed for login) // Auth pages - loaded immediately (needed for login)
import SignIn from "./pages/AuthPages/SignIn"; import SignIn from "./pages/AuthPages/SignIn";
@@ -111,7 +112,13 @@ const Components = lazy(() => import("./pages/Components"));
export default function App() { 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 ( return (
<> <>

View File

@@ -23,6 +23,7 @@ import SidebarWidget from "./SidebarWidget";
import { APP_VERSION } from "../config/version"; import { APP_VERSION } from "../config/version";
import { useAuthStore } from "../store/authStore"; import { useAuthStore } from "../store/authStore";
import { useSettingsStore } from "../store/settingsStore"; import { useSettingsStore } from "../store/settingsStore";
import { useModuleStore } from "../store/moduleStore";
type NavItem = { type NavItem = {
name: string; name: string;
@@ -41,6 +42,7 @@ const AppSidebar: React.FC = () => {
const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar(); const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar();
const location = useLocation(); const location = useLocation();
const { user, isAuthenticated } = useAuthStore(); const { user, isAuthenticated } = useAuthStore();
const { isModuleEnabled, settings: moduleSettings } = useModuleStore();
const [openSubmenu, setOpenSubmenu] = useState<{ const [openSubmenu, setOpenSubmenu] = useState<{
sectionIndex: number; sectionIndex: number;
@@ -56,12 +58,9 @@ const AppSidebar: React.FC = () => {
[location.pathname] [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 // Define menu sections with useMemo to prevent recreation on every render
// New structure: Dashboard (standalone) → SETUP → WORKFLOW → SETTINGS // New structure: Dashboard (standalone) → SETUP → WORKFLOW → SETTINGS
// Module visibility is controlled by GlobalModuleSettings (Django Admin only)
const menuSections: MenuSection[] = useMemo(() => { const menuSections: MenuSection[] = useMemo(() => {
// SETUP section items (single items, no dropdowns - submenus shown as in-page navigation) // SETUP section items (single items, no dropdowns - submenus shown as in-page navigation)
const setupItems: NavItem[] = [ const setupItems: NavItem[] = [
@@ -70,57 +69,73 @@ const AppSidebar: React.FC = () => {
name: "Add Keywords", name: "Add Keywords",
path: "/setup/add-keywords", path: "/setup/add-keywords",
}, },
{ ];
// Add Sites (Site Builder) if enabled
if (isModuleEnabled('site_builder')) {
setupItems.push({
icon: <GridIcon />, icon: <GridIcon />,
name: "Sites", name: "Sites",
path: "/sites", // Submenus shown as in-page navigation path: "/sites", // Submenus shown as in-page navigation
}, });
]; }
// Add Thinker (always shown) // Add Thinker if enabled
setupItems.push({ if (isModuleEnabled('thinker')) {
icon: <BoltIcon />, setupItems.push({
name: "Thinker", icon: <BoltIcon />,
path: "/thinker/prompts", // Default to prompts, submenus shown as in-page navigation 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[] = []; const workflowItems: NavItem[] = [];
// Add Planner // Add Planner if enabled
workflowItems.push({ if (isModuleEnabled('planner')) {
icon: <ListIcon />, workflowItems.push({
name: "Planner", icon: <ListIcon />,
path: "/planner/keywords", // Default to keywords, submenus shown as in-page navigation name: "Planner",
}); path: "/planner/keywords", // Default to keywords, submenus shown as in-page navigation
});
}
// Add Writer // Add Writer if enabled
workflowItems.push({ if (isModuleEnabled('writer')) {
icon: <TaskIcon />, workflowItems.push({
name: "Writer", icon: <TaskIcon />,
path: "/writer/tasks", // Default to tasks, submenus shown as in-page navigation name: "Writer",
}); path: "/writer/tasks", // Default to tasks, submenus shown as in-page navigation
});
}
// Add Automation // Add Automation if enabled
workflowItems.push({ if (isModuleEnabled('automation')) {
icon: <BoltIcon />, workflowItems.push({
name: "Automation", icon: <BoltIcon />,
path: "/automation", name: "Automation",
}); path: "/automation",
});
}
// Add Linker // Add Linker if enabled
workflowItems.push({ if (isModuleEnabled('linker')) {
icon: <PlugInIcon />, workflowItems.push({
name: "Linker", icon: <PlugInIcon />,
path: "/linker/content", name: "Linker",
}); path: "/linker/content",
});
}
// Add Optimizer // Add Optimizer if enabled
workflowItems.push({ if (isModuleEnabled('optimizer')) {
icon: <BoltIcon />, workflowItems.push({
name: "Optimizer", icon: <BoltIcon />,
path: "/optimizer/content", name: "Optimizer",
}); path: "/optimizer/content",
});
}
return [ return [
// Dashboard is standalone (no section header) // 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 // Combine all sections
const allSections = useMemo(() => { const allSections = useMemo(() => {

View File

@@ -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<GlobalModuleSettings> {
return fetchAPI('/v1/system/settings/modules/enable/');
}
// Billing API functions // Billing API functions
export interface CreditBalance { export interface CreditBalance {
credits: number; credits: number;

View File

@@ -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<void>;
isModuleEnabled: (moduleName: string) => boolean;
reset: () => void;
}
export const useModuleStore = create<ModuleState>()((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 });
},
}));

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<title>Module Settings Test</title>
<style>
body { font-family: Arial; padding: 20px; }
.success { color: green; }
.error { color: red; }
.info { color: blue; }
pre { background: #f5f5f5; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Module Settings API Test</h1>
<button onclick="testAPI()">Test API Endpoint</button>
<div id="result"></div>
<script>
async function testAPI() {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '<p class="info">Testing...</p>';
try {
const response = await fetch('https://api.igny8.com/v1/system/settings/modules/enable/', {
headers: {
'Authorization': 'Bearer YOUR_TOKEN_HERE' // Replace with actual token
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
resultDiv.innerHTML = `
<h2 class="success">✓ Success!</h2>
<h3>Raw Response:</h3>
<pre>${JSON.stringify(data, null, 2)}</pre>
<h3>Module Status:</h3>
<ul>
<li>Planner: ${data.data?.planner_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
<li>Writer: ${data.data?.writer_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
<li>Thinker: ${data.data?.thinker_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
<li>Automation: ${data.data?.automation_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
<li>Site Builder: ${data.data?.site_builder_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
<li>Linker: ${data.data?.linker_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
<li>Optimizer: ${data.data?.optimizer_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
<li>Publisher: ${data.data?.publisher_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
</ul>
`;
} catch (error) {
resultDiv.innerHTML = `
<h2 class="error">✗ Error</h2>
<p>${error.message}</p>
<p class="info">Note: This test requires authentication. Open browser console in your app and run:</p>
<pre>
// In browser console on your app:
fetch('/v1/system/settings/modules/enable/')
.then(r => r.json())
.then(data => console.log(data))
</pre>
`;
}
}
</script>
</body>
</html>