Pre luanch plan phase 1 complete
This commit is contained in:
@@ -3,11 +3,12 @@ import PageMeta from '../../components/common/PageMeta';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
|
||||
export default function AccountSettings() {
|
||||
const toast = useToast();
|
||||
const [settings, setSettings] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
@@ -15,34 +16,28 @@ export default function AccountSettings() {
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
startLoading('Loading account settings...');
|
||||
const response = await fetchAPI('/v1/system/settings/account/');
|
||||
setSettings(response.results || []);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load account settings: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
stopLoading();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Account Settings" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Account Settings</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage your account preferences and profile</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="p-6">
|
||||
<p className="text-gray-600 dark:text-gray-400">Account settings management interface coming soon.</p>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
<Card className="p-6">
|
||||
<p className="text-gray-600 dark:text-gray-400">Account settings management interface coming soon.</p>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,18 +66,18 @@ const CreditsAndBilling: React.FC = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Usage & Billing" description="View your usage and billing" />
|
||||
<div className="text-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading billing data...</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Usage & Billing" description="View your usage and billing" />
|
||||
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
@@ -243,7 +243,7 @@ const CreditsAndBilling: React.FC = () => {
|
||||
</div>
|
||||
</ComponentCard>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import PageMeta from "../../components/common/PageMeta";
|
||||
import ComponentCard from "../../components/common/ComponentCard";
|
||||
import { Card } from "../../components/ui/card";
|
||||
import { DownloadIcon, UploadIcon, DatabaseIcon, FileArchiveIcon, CheckCircleIcon } from '../../icons';
|
||||
|
||||
export default function ImportExport() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Import/Export - IGNY8" description="Data management" />
|
||||
|
||||
<Card className="p-8">
|
||||
<div className="text-center py-8 max-w-3xl mx-auto">
|
||||
<div className="mb-6 flex justify-center">
|
||||
<div className="p-4 bg-brand-100 dark:bg-brand-900/30 rounded-full">
|
||||
<DatabaseIcon className="w-12 h-12 text-brand-600 dark:text-brand-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-3">
|
||||
Coming Soon: Manage Your Data
|
||||
</h1>
|
||||
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8">
|
||||
Import and Export Your Content - Backup your keywords, articles, and settings. Move your content to other platforms. Download everything safely.
|
||||
</p>
|
||||
|
||||
<div className="bg-gradient-to-br from-brand-50 to-purple-50 dark:from-brand-900/20 dark:to-purple-900/20 rounded-lg p-6 border border-brand-200 dark:border-brand-800">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
What will be available:
|
||||
</h2>
|
||||
<div className="space-y-3 text-left">
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-gray-700 dark:text-gray-300">
|
||||
<strong>Export your keywords as a file</strong> (backup or share)
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-gray-700 dark:text-gray-300">
|
||||
<strong>Export all your articles</strong> in different formats
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-gray-700 dark:text-gray-300">
|
||||
<strong>Import keywords from other sources</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-gray-700 dark:text-gray-300">
|
||||
<strong>Backup and restore</strong> your entire account
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-gray-700 dark:text-gray-300">
|
||||
<strong>Download your settings</strong> and configurations
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
import { fetchIndustries, Industry } from '../../services/api';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
|
||||
export default function Industries() {
|
||||
const toast = useToast();
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
const [industries, setIndustries] = useState<Industry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadIndustries();
|
||||
@@ -16,49 +17,43 @@ export default function Industries() {
|
||||
|
||||
const loadIndustries = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
startLoading('Loading industries...');
|
||||
const response = await fetchIndustries();
|
||||
setIndustries(response.industries || []);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load industries: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
stopLoading();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Industries" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Industries</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage global industry templates (Admin Only)</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{industries.map((industry) => (
|
||||
<Card key={industry.id} className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{industry.name}</h3>
|
||||
<Badge variant="light" color={industry.is_active ? 'success' : 'dark'}>
|
||||
{industry.is_active ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
</div>
|
||||
{industry.description && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">{industry.description}</p>
|
||||
)}
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Sectors: {industry.sectors_count || 0}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{industries.map((industry) => (
|
||||
<Card key={industry.id} className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{industry.name}</h3>
|
||||
<Badge variant="light" color={industry.is_active ? 'success' : 'dark'}>
|
||||
{industry.is_active ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
</div>
|
||||
{industry.description && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">{industry.description}</p>
|
||||
)}
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Sectors: {industry.sectors_count || 0}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import { PricingPlan } from '../../components/ui/pricing-table';
|
||||
import PricingTable1 from '../../components/ui/pricing-table/pricing-table-1';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
|
||||
interface Plan {
|
||||
id: number;
|
||||
@@ -110,8 +111,8 @@ const getPlanDescription = (plan: Plan): string => {
|
||||
|
||||
export default function Plans() {
|
||||
const toast = useToast();
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
const [plans, setPlans] = useState<Plan[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadPlans();
|
||||
@@ -119,7 +120,7 @@ export default function Plans() {
|
||||
|
||||
const loadPlans = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
startLoading('Loading plans...');
|
||||
const response: PlanResponse = await fetchAPI('/v1/auth/plans/');
|
||||
// Filter only active plans and sort by price
|
||||
const activePlans = (response.results || [])
|
||||
@@ -133,7 +134,7 @@ export default function Plans() {
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load plans: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
stopLoading();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,7 +149,7 @@ export default function Plans() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Plans" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Plans</h1>
|
||||
@@ -157,11 +158,7 @@ export default function Plans() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading plans...</div>
|
||||
</div>
|
||||
) : pricingPlans.length === 0 ? (
|
||||
{pricingPlans.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">No active plans available</div>
|
||||
</div>
|
||||
@@ -183,6 +180,6 @@ export default function Plans() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Button from '../../components/ui/button/Button';
|
||||
import Checkbox from '../../components/form/input/Checkbox';
|
||||
@@ -16,7 +17,7 @@ import { fetchAPI } from '../../services/api';
|
||||
|
||||
export default function Publishing() {
|
||||
const toast = useToast();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [defaultDestinations, setDefaultDestinations] = useState<string[]>(['sites']);
|
||||
const [autoPublishEnabled, setAutoPublishEnabled] = useState(false);
|
||||
@@ -31,7 +32,7 @@ export default function Publishing() {
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
startLoading('Loading publishing settings...');
|
||||
// TODO: Load from backend API when endpoint is available
|
||||
// For now, use defaults
|
||||
setDefaultDestinations(['sites']);
|
||||
@@ -43,7 +44,7 @@ export default function Publishing() {
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load settings: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
stopLoading();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,19 +74,8 @@ export default function Publishing() {
|
||||
{ value: 'shopify', label: 'Publish to Shopify (your Shopify store)' },
|
||||
];
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<PageMeta title="Publishing Settings" />
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Publishing Settings - IGNY8" />
|
||||
|
||||
<div className="mb-6">
|
||||
@@ -230,7 +220,7 @@ export default function Publishing() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import PageMeta from "../../components/common/PageMeta";
|
||||
import ComponentCard from "../../components/common/ComponentCard";
|
||||
import { fetchAPI } from "../../services/api";
|
||||
|
||||
interface SystemStatus {
|
||||
timestamp: string;
|
||||
system: {
|
||||
cpu: { usage_percent: number; cores: number; status: string };
|
||||
memory: { total_gb: number; used_gb: number; available_gb: number; usage_percent: number; status: string };
|
||||
disk: { total_gb: number; used_gb: number; free_gb: number; usage_percent: number; status: string };
|
||||
};
|
||||
database: {
|
||||
connected: boolean;
|
||||
version: string;
|
||||
size: string;
|
||||
active_connections: number;
|
||||
status: string;
|
||||
};
|
||||
redis: {
|
||||
connected: boolean;
|
||||
status: string;
|
||||
};
|
||||
celery: {
|
||||
workers: string[];
|
||||
worker_count: number;
|
||||
tasks: { active: number; scheduled: number; reserved: number };
|
||||
status: string;
|
||||
};
|
||||
processes: {
|
||||
by_stack: {
|
||||
[key: string]: { count: number; cpu: number; memory_mb: number };
|
||||
};
|
||||
};
|
||||
modules: {
|
||||
planner: { keywords: number; clusters: number; content_ideas: number };
|
||||
writer: { tasks: number; images: number };
|
||||
};
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'healthy': return 'text-success-600 dark:text-success-400';
|
||||
case 'warning': return 'text-warning-600 dark:text-warning-400';
|
||||
case 'critical': return 'text-error-600 dark:text-error-400';
|
||||
default: return 'text-gray-600 dark:text-gray-400';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case 'healthy': return 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-400';
|
||||
case 'warning': return 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-400';
|
||||
case 'critical': return 'bg-error-100 text-error-800 dark:bg-error-900/30 dark:text-error-400';
|
||||
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400';
|
||||
}
|
||||
};
|
||||
|
||||
export default function Status() {
|
||||
const [status, setStatus] = useState<SystemStatus | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchStatus = async () => {
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So response IS the data object
|
||||
const response = await fetchAPI('/v1/system/status/');
|
||||
setStatus(response);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchStatus();
|
||||
const interval = setInterval(fetchStatus, 30000); // Refresh every 30 seconds
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="System Status - IGNY8" description="System monitoring" />
|
||||
<ComponentCard title="System Status" desc="Loading system information...">
|
||||
<div className="text-center py-8">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 dark:border-white mx-auto"></div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !status) {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="System Status - IGNY8" description="System monitoring" />
|
||||
<ComponentCard title="System Status" desc="Error loading system information">
|
||||
<div className="text-center py-8 text-error-600 dark:text-error-400">
|
||||
{error || 'Failed to load system status'}
|
||||
</div>
|
||||
</ComponentCard>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="System Status - IGNY8" description="System monitoring" />
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* System Resources */}
|
||||
<ComponentCard title="System Resources" desc="CPU, Memory, and Disk Usage">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{/* CPU */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">CPU</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.system?.cpu?.status || 'unknown')}`}>
|
||||
{status.system?.cpu?.status || 'unknown'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4">
|
||||
<div
|
||||
className={`h-4 rounded-full ${
|
||||
(status.system?.cpu?.usage_percent || 0) < 80 ? 'bg-success-500' :
|
||||
(status.system?.cpu?.usage_percent || 0) < 95 ? 'bg-warning-500' : 'bg-error-500'
|
||||
}`}
|
||||
style={{ width: `${status.system?.cpu?.usage_percent || 0}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{status.system?.cpu?.usage_percent?.toFixed(1)}% used ({status.system?.cpu?.cores} cores)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Memory */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Memory</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.system?.memory?.status || 'unknown')}`}>
|
||||
{status.system?.memory?.status || 'unknown'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4">
|
||||
<div
|
||||
className={`h-4 rounded-full ${
|
||||
(status.system?.memory?.usage_percent || 0) < 80 ? 'bg-success-500' :
|
||||
(status.system?.memory?.usage_percent || 0) < 95 ? 'bg-warning-500' : 'bg-error-500'
|
||||
}`}
|
||||
style={{ width: `${status.system?.memory?.usage_percent || 0}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{status.system?.memory?.used_gb?.toFixed(1)} GB / {status.system?.memory?.total_gb?.toFixed(1)} GB
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Disk */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Disk</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.system?.disk?.status || 'unknown')}`}>
|
||||
{status.system?.disk?.status || 'unknown'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4">
|
||||
<div
|
||||
className={`h-4 rounded-full ${
|
||||
(status.system?.disk?.usage_percent || 0) < 80 ? 'bg-success-500' :
|
||||
(status.system?.disk?.usage_percent || 0) < 95 ? 'bg-warning-500' : 'bg-error-500'
|
||||
}`}
|
||||
style={{ width: `${status.system?.disk?.usage_percent || 0}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{status.system?.disk?.used_gb?.toFixed(1)} GB / {status.system?.disk?.total_gb?.toFixed(1)} GB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
|
||||
{/* Services Status */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{/* Database */}
|
||||
<ComponentCard title="Database" desc="PostgreSQL Status">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Status</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.database?.status || 'unknown')}`}>
|
||||
{status.database?.connected ? 'Connected' : 'Disconnected'}
|
||||
</span>
|
||||
</div>
|
||||
{status.database?.version && (
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Version:</span>
|
||||
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.database.version.split(',')[0]}</span>
|
||||
</div>
|
||||
)}
|
||||
{status.database?.size && (
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Size:</span>
|
||||
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.database.size}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Active Connections:</span>
|
||||
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.database?.active_connections || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
|
||||
{/* Redis */}
|
||||
<ComponentCard title="Redis" desc="Cache & Message Broker">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Status</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.redis?.status || 'unknown')}`}>
|
||||
{status.redis?.connected ? 'Connected' : 'Disconnected'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
|
||||
{/* Celery */}
|
||||
<ComponentCard title="Celery" desc="Task Queue Workers">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Workers</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.celery?.status || 'unknown')}`}>
|
||||
{status.celery?.worker_count || 0} active
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Active Tasks:</span>
|
||||
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.celery?.tasks?.active || 0}</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Scheduled:</span>
|
||||
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.celery?.tasks?.scheduled || 0}</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Reserved:</span>
|
||||
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.celery?.tasks?.reserved || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
</div>
|
||||
|
||||
{/* Process Monitoring by Stack */}
|
||||
<ComponentCard title="Process Monitoring" desc="Resource usage by technology stack">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Stack</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Processes</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">CPU %</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Memory (MB)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{Object.entries(status.processes?.by_stack || {}).map(([stack, stats]) => (
|
||||
<tr key={stack}>
|
||||
<td className="px-4 py-3 text-sm font-medium text-gray-800 dark:text-gray-200 capitalize">{stack}</td>
|
||||
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">{stats.count}</td>
|
||||
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">{stats.cpu.toFixed(2)}%</td>
|
||||
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">{stats.memory_mb.toFixed(2)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
|
||||
{/* Module Statistics */}
|
||||
<ComponentCard title="Module Statistics" desc="Data counts by module">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Planner Module */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-gray-800 dark:text-gray-200">Planner Module</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Keywords:</span>
|
||||
<span className="text-gray-800 dark:text-gray-200">{status.modules?.planner?.keywords?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Clusters:</span>
|
||||
<span className="text-gray-800 dark:text-gray-200">{status.modules?.planner?.clusters?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Content Ideas:</span>
|
||||
<span className="text-gray-800 dark:text-gray-200">{status.modules?.planner?.content_ideas?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Writer Module */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-gray-800 dark:text-gray-200">Writer Module</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Tasks:</span>
|
||||
<span className="text-gray-800 dark:text-gray-200">{status.modules?.writer?.tasks?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">Images:</span>
|
||||
<span className="text-gray-800 dark:text-gray-200">{status.modules?.writer?.images?.toLocaleString() || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
|
||||
{/* Last Updated */}
|
||||
<div className="text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Last updated: {new Date(status.timestamp).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
@@ -15,8 +16,8 @@ interface Subscription {
|
||||
|
||||
export default function Subscriptions() {
|
||||
const toast = useToast();
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadSubscriptions();
|
||||
@@ -24,13 +25,13 @@ export default function Subscriptions() {
|
||||
|
||||
const loadSubscriptions = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
startLoading('Loading subscriptions...');
|
||||
const response = await fetchAPI('/v1/auth/subscriptions/');
|
||||
setSubscriptions(response.results || []);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load subscriptions: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
stopLoading();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,52 +49,46 @@ export default function Subscriptions() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Subscriptions" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Subscriptions</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage account subscriptions</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="p-6">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Account</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period Start</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period End</th>
|
||||
<Card className="p-6">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Account</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period Start</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period End</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{subscriptions.map((subscription) => (
|
||||
<tr key={subscription.id} className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{subscription.account_name}</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="light" color={getStatusColor(subscription.status) as any}>
|
||||
{subscription.status}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{new Date(subscription.current_period_start).toLocaleDateString()}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{new Date(subscription.current_period_end).toLocaleDateString()}
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{subscriptions.map((subscription) => (
|
||||
<tr key={subscription.id} className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{subscription.account_name}</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="light" color={getStatusColor(subscription.status) as any}>
|
||||
{subscription.status}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{new Date(subscription.current_period_start).toLocaleDateString()}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{new Date(subscription.current_period_end).toLocaleDateString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@ import PageMeta from '../../components/common/PageMeta';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
|
||||
export default function SystemSettings() {
|
||||
const toast = useToast();
|
||||
const [settings, setSettings] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
@@ -15,34 +16,28 @@ export default function SystemSettings() {
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
startLoading('Loading system settings...');
|
||||
const response = await fetchAPI('/v1/system/settings/system/');
|
||||
setSettings(response.results || []);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load system settings: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
stopLoading();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="System Settings" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">System Settings</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">Global platform-wide settings</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="p-6">
|
||||
<p className="text-gray-600 dark:text-gray-400">System settings management interface coming soon.</p>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
<Card className="p-6">
|
||||
<p className="text-gray-600 dark:text-gray-400">System settings management interface coming soon.</p>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { usePageLoading } from '../../context/PageLoadingContext';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
@@ -15,8 +16,8 @@ interface User {
|
||||
|
||||
export default function Users() {
|
||||
const toast = useToast();
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
@@ -24,61 +25,55 @@ export default function Users() {
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
startLoading('Loading users...');
|
||||
const response = await fetchAPI('/v1/auth/users/');
|
||||
setUsers(response.results || []);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load users: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
stopLoading();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<>
|
||||
<PageMeta title="Users" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Users</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage account users and permissions</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="p-6">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Email</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Username</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Role</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
|
||||
<Card className="p-6">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Email</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Username</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Role</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id} className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{user.email}</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">{user.username}</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="light" color="primary">{user.role}</Badge>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="light" color={user.is_active ? 'success' : 'dark'}>
|
||||
{user.is_active ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id} className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{user.email}</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">{user.username}</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="light" color="primary">{user.role}</Badge>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="light" color={user.is_active ? 'success' : 'dark'}>
|
||||
{user.is_active ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user