This commit is contained in:
IGNY8 VPS (Salman)
2025-12-04 22:43:25 +00:00
parent 1521f3ff8c
commit 8b895dbdc7
18 changed files with 1569 additions and 172 deletions

View File

@@ -147,15 +147,13 @@ const AdminBilling: React.FC = () => {
Admin controls for credits, pricing, and user billing
</p>
</div>
<a
href="/admin/igny8_core/"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700"
<Button
variant="outline"
startIcon={<PlugInIcon className="w-4 h-4" />}
onClick={() => window.open('/admin/igny8_core/', '_blank')}
>
<PlugInIcon className="w-4 h-4 mr-2" />
Django Admin
</a>
</Button>
</div>
{/* System Stats */}
@@ -163,30 +161,26 @@ const AdminBilling: React.FC = () => {
<EnhancedMetricCard
title="Total Users"
value={stats?.total_users || 0}
icon={UserIcon}
color="blue"
iconColor="text-blue-500"
icon={<UserIcon />}
accentColor="blue"
/>
<EnhancedMetricCard
title="Active Users"
value={stats?.active_users || 0}
icon={CheckCircleIcon}
color="green"
iconColor="text-green-500"
icon={<CheckCircleIcon />}
accentColor="green"
/>
<EnhancedMetricCard
title="Credits Issued"
value={stats?.total_credits_issued || 0}
icon={DollarLineIcon}
color="amber"
iconColor="text-amber-500"
icon={<DollarLineIcon />}
accentColor="orange"
/>
<EnhancedMetricCard
title="Credits Used"
value={stats?.total_credits_used || 0}
icon={BoltIcon}
color="purple"
iconColor="text-purple-500"
icon={<BoltIcon />}
accentColor="purple"
/>
</div>
@@ -231,28 +225,28 @@ const AdminBilling: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<ComponentCard title="Quick Actions">
<div className="space-y-3">
<Button
variant="primary"
<Button
variant="primary"
fullWidth
startIcon={<UserIcon className="w-4 h-4" />}
onClick={() => setActiveTab('users')}
>
<UserIcon className="w-4 h-4 mr-2" />
Manage User Credits
</Button>
<Button
variant="secondary"
<Button
variant="secondary"
fullWidth
startIcon={<DollarLineIcon className="w-4 h-4" />}
onClick={() => setActiveTab('pricing')}
>
<DollarLineIcon className="w-4 h-4 mr-2" />
Update Credit Costs
</Button>
<Button
variant="outline"
<Button
variant="outline"
fullWidth
startIcon={<PlugInIcon className="w-4 h-4" />}
onClick={() => window.open('/admin/igny8_core/creditcostconfig/', '_blank')}
>
<PlugInIcon className="w-4 h-4 mr-2" />
Full Admin Panel
</Button>
</div>
@@ -309,7 +303,7 @@ const AdminBilling: React.FC = () => {
</div>
</td>
<td className="px-4 py-4 whitespace-nowrap">
<Badge variant="info">{user.subscription_plan || 'Free'}</Badge>
<Badge tone="info">{user.subscription_plan || 'Free'}</Badge>
</td>
<td className="px-4 py-4 whitespace-nowrap text-right font-bold text-amber-600 dark:text-amber-400">
{user.credits}
@@ -432,7 +426,7 @@ const AdminBilling: React.FC = () => {
{config.cost}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<Badge variant={config.is_active ? 'success' : 'warning'}>
<Badge tone={config.is_active ? 'success' : 'warning'}>
{config.is_active ? 'Active' : 'Inactive'}
</Badge>
</td>

View File

@@ -54,6 +54,7 @@ const AutomationPage: React.FC = () => {
const [pipelineOverview, setPipelineOverview] = useState<PipelineStage[]>([]);
const [metrics, setMetrics] = useState<any>(null);
const [showConfigModal, setShowConfigModal] = useState(false);
const [showProcessingCard, setShowProcessingCard] = useState<boolean>(true);
const [loading, setLoading] = useState(true);
const [estimate, setEstimate] = useState<{ estimated_credits: number; current_balance: number; sufficient: boolean } | null>(null);
@@ -147,6 +148,10 @@ const AutomationPage: React.FC = () => {
setCurrentRun(runData.run);
setEstimate(estimateData);
setPipelineOverview(pipelineData.stages);
// show processing card when there's a current run
if (runData.run) {
setShowProcessingCard(true);
}
} catch (error: any) {
toast.error('Failed to load automation data');
console.error(error);
@@ -160,6 +165,8 @@ const AutomationPage: React.FC = () => {
try {
const data = await automationService.getCurrentRun(activeSite.id);
setCurrentRun(data.run);
// ensure processing card is visible when a run exists
if (data.run) setShowProcessingCard(true);
} catch (error) {
console.error('Failed to poll current run', error);
}
@@ -251,22 +258,27 @@ const AutomationPage: React.FC = () => {
};
const handlePause = async () => {
if (!currentRun) return;
if (!currentRun || !activeSite) return;
try {
await automationService.pause(currentRun.run_id);
await automationService.pause(activeSite.id, currentRun.run_id);
toast.success('Automation paused');
loadCurrentRun();
// refresh run and pipeline/metrics
await loadCurrentRun();
await loadPipelineOverview();
await loadMetrics();
} catch (error) {
toast.error('Failed to pause automation');
}
};
const handleResume = async () => {
if (!currentRun) return;
if (!currentRun || !activeSite) return;
try {
await automationService.resume(currentRun.run_id);
await automationService.resume(activeSite.id, currentRun.run_id);
toast.success('Automation resumed');
loadCurrentRun();
await loadCurrentRun();
await loadPipelineOverview();
await loadMetrics();
} catch (error) {
toast.error('Failed to resume automation');
}
@@ -278,8 +290,13 @@ const AutomationPage: React.FC = () => {
await automationService.updateConfig(activeSite.id, newConfig);
toast.success('Configuration saved');
setShowConfigModal(false);
loadData();
// Optimistically update config locally and refresh data
setConfig((prev) => ({ ...(prev as AutomationConfig), ...newConfig } as AutomationConfig));
await loadPipelineOverview();
await loadMetrics();
await loadCurrentRun();
} catch (error) {
console.error('Failed to save config:', error);
toast.error('Failed to save configuration');
}
};
@@ -665,18 +682,21 @@ const AutomationPage: React.FC = () => {
</div>
{/* Current Processing Card - Shows real-time automation progress */}
{currentRun && (currentRun.status === 'running' || currentRun.status === 'paused') && activeSite && (
{currentRun && showProcessingCard && activeSite && (
<CurrentProcessingCard
runId={currentRun.run_id}
siteId={activeSite.id}
currentRun={currentRun}
onUpdate={() => {
// Refresh current run status
loadCurrentRun();
pipelineOverview={pipelineOverview}
onUpdate={async () => {
// Refresh current run status, pipeline overview and metrics (no full page reload)
await loadCurrentRun();
await loadPipelineOverview();
await loadMetrics();
}}
onClose={() => {
// Card will remain in DOM but user acknowledged it
// Can add state here to minimize it if needed
// hide the processing card until next run
setShowProcessingCard(false);
}}
/>
)}
@@ -692,7 +712,8 @@ const AutomationPage: React.FC = () => {
const isComplete = currentRun && currentRun.current_stage > stage.number;
const result = currentRun ? (currentRun[`stage_${stage.number}_result` as keyof AutomationRun] as any) : null;
const processed = result ? Object.values(result).reduce((sum: number, val) => typeof val === 'number' ? sum + val : sum, 0) : 0;
const progressPercent = stage.pending > 0 ? Math.round((processed / (processed + stage.pending)) * 100) : 0;
const total = (stage.pending ?? 0) + processed;
const progressPercent = total > 0 ? Math.round((processed / total) * 100) : 0;
return (
<div
@@ -787,7 +808,8 @@ const AutomationPage: React.FC = () => {
const isComplete = currentRun && currentRun.current_stage > stage.number;
const result = currentRun ? (currentRun[`stage_${stage.number}_result` as keyof AutomationRun] as any) : null;
const processed = result ? Object.values(result).reduce((sum: number, val) => typeof val === 'number' ? sum + val : sum, 0) : 0;
const progressPercent = stage.pending > 0 ? Math.round((processed / (processed + stage.pending)) * 100) : 0;
const total = (stage.pending ?? 0) + processed;
const progressPercent = total > 0 ? Math.round((processed / total) * 100) : 0;
return (
<div

View File

@@ -113,11 +113,14 @@ const CreditsAndBilling: React.FC = () => {
Manage your credits, view transactions, and monitor usage
</p>
</div>
<Button variant="primary" onClick={() => {
// TODO: Link to purchase credits page
toast?.info('Purchase credits feature coming soon');
}}>
<DollarLineIcon className="w-4 h-4 mr-2" />
<Button
variant="primary"
startIcon={<DollarLineIcon className="w-4 h-4" />}
onClick={() => {
// TODO: Link to purchase credits page
toast?.info('Purchase credits feature coming soon');
}}
>
Purchase Credits
</Button>
</div>
@@ -127,31 +130,27 @@ const CreditsAndBilling: React.FC = () => {
<EnhancedMetricCard
title="Current Balance"
value={balance?.credits || 0}
icon={BoltIcon}
color="amber"
iconColor="text-amber-500"
icon={<BoltIcon />}
accentColor="orange"
/>
<EnhancedMetricCard
title="Monthly Included"
value={balance?.monthly_credits_included || 0}
subtitle={balance?.subscription_plan || 'Free'}
icon={CheckCircleIcon}
color="green"
iconColor="text-green-500"
icon={<CheckCircleIcon />}
accentColor="green"
/>
<EnhancedMetricCard
title="Bonus Credits"
value={balance?.bonus_credits || 0}
icon={DollarLineIcon}
color="blue"
iconColor="text-blue-500"
icon={<DollarLineIcon />}
accentColor="blue"
/>
<EnhancedMetricCard
title="Total This Month"
value={usageLogs.reduce((sum, log) => sum + log.credits_used, 0)}
icon={TimeIcon}
color="purple"
iconColor="text-purple-500"
icon={<TimeIcon />}
accentColor="purple"
/>
</div>
@@ -201,7 +200,7 @@ const CreditsAndBilling: React.FC = () => {
<div key={transaction.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex-1">
<div className="flex items-center gap-2">
<Badge variant={getTransactionTypeColor(transaction.transaction_type)}>
<Badge tone={getTransactionTypeColor(transaction.transaction_type) as any}>
{transaction.transaction_type}
</Badge>
<span className="text-sm text-gray-900 dark:text-white">
@@ -290,7 +289,7 @@ const CreditsAndBilling: React.FC = () => {
{new Date(transaction.created_at).toLocaleDateString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<Badge variant={getTransactionTypeColor(transaction.transaction_type)}>
<Badge tone={getTransactionTypeColor(transaction.transaction_type) as any}>
{transaction.transaction_type}
</Badge>
</td>