From 961259cd39a3c0c077381504dd5790b6019fb8b7 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sun, 19 Apr 2026 05:25:41 +0300 Subject: [PATCH] feat: Add automation and billing pages with workflow management, invoice generation, and payment tracking - Add "use client" directive to automation and billing pages for client-side rendering - Add useState/useEffect hooks for Automation/AutomationRun/Invoice/Payment state management with loading/error/actionLoading states - Add automation page: loadData with Promise.all for api.automation.list/logs, handleCreateAutomation with JSON config parsing, handleRunAutomation/handleScheduleAutomation with api --- .../web-dashboard/src/app/automation/page.tsx | 429 ++++++++++++++++++ apps/web-dashboard/src/app/billing/page.tsx | 387 ++++++++++++++++ apps/web-dashboard/src/app/config/page.tsx | 394 ++++++++++++++++ apps/web-dashboard/src/app/deploy/page.tsx | 328 +++++++++++++ apps/web-dashboard/src/app/esim/page.tsx | 77 +++- apps/web-dashboard/src/app/health/page.tsx | 135 +++++- apps/web-dashboard/src/app/page.tsx | 137 ++++-- apps/web-dashboard/src/app/payments/page.tsx | 133 +++++- 8 files changed, 1947 insertions(+), 73 deletions(-) create mode 100644 apps/web-dashboard/src/app/automation/page.tsx create mode 100644 apps/web-dashboard/src/app/billing/page.tsx create mode 100644 apps/web-dashboard/src/app/config/page.tsx create mode 100644 apps/web-dashboard/src/app/deploy/page.tsx diff --git a/apps/web-dashboard/src/app/automation/page.tsx b/apps/web-dashboard/src/app/automation/page.tsx new file mode 100644 index 0000000..a30fb01 --- /dev/null +++ b/apps/web-dashboard/src/app/automation/page.tsx @@ -0,0 +1,429 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Bot, Play, Clock, CheckCircle, XCircle, AlertCircle, History, Plus, Calendar } from "lucide-react"; +import { api, Automation, AutomationRun } from "@/lib/api"; + +export default function AutomationPage() { + const [automations, setAutomations] = useState([]); + const [runs, setRuns] = useState([]); + const [selectedAutomation, setSelectedAutomation] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [showCreateForm, setShowCreateForm] = useState(false); + const [actionLoading, setActionLoading] = useState(null); + const [createForm, setCreateForm] = useState({ + name: "", + description: "", + trigger: "", + schedule: "", + config: "{}" + }); + + useEffect(() => { + loadData(); + }, []); + + const loadData = async () => { + try { + setLoading(true); + const [automationsResponse, runsResponse] = await Promise.all([ + api.automation.list(), + api.automation.logs() + ]); + + setAutomations(automationsResponse.automations); + setRuns(runsResponse.data); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load automation data"); + } finally { + setLoading(false); + } + }; + + const handleCreateAutomation = async () => { + try { + setActionLoading('create'); + const config = createForm.config ? JSON.parse(createForm.config) : {}; + await api.automation.create({ + name: createForm.name, + description: createForm.description, + trigger: createForm.trigger, + schedule: createForm.schedule, + config, + enabled: true + }); + + // Reset form + setCreateForm({ name: "", description: "", trigger: "", schedule: "", config: "{}" }); + setShowCreateForm(false); + await loadData(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to create automation"); + } finally { + setActionLoading(null); + } + }; + + const handleRunAutomation = async (automationId: number) => { + try { + setActionLoading(`run-${automationId}`); + await api.automation.run(automationId); + await loadData(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to run automation"); + } finally { + setActionLoading(null); + } + }; + + const handleScheduleAutomation = async (automationId: number, schedule: string) => { + try { + setActionLoading(`schedule-${automationId}`); + await api.automation.schedule(automationId, schedule); + await loadData(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to schedule automation"); + } finally { + setActionLoading(null); + } + }; + + const loadAutomationRuns = async (automationId: number) => { + try { + const runsResponse = await api.automation.logs(automationId); + setRuns(runsResponse.data); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load automation runs"); + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'completed': return ; + case 'failed': return ; + case 'running': return ; + case 'pending': return ; + default: return ; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'completed': return 'success'; + case 'failed': return 'destructive'; + case 'running': return 'default'; + case 'pending': return 'secondary'; + default: return 'secondary'; + } + }; + + const formatDuration = (started: string, completed?: string) => { + const start = new Date(started); + const end = completed ? new Date(completed) : new Date(); + const duration = end.getTime() - start.getTime(); + const minutes = Math.floor(duration / 60000); + const seconds = Math.floor((duration % 60000) / 1000); + return `${minutes}m ${seconds}s`; + }; + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(4)].map((_, i) => ( +
+
+
+ ))} +
+
+ ); + } + + if (error) { + return ( +
+
+

Error loading automation data

+

{error}

+
+
+ ); + } + + return ( +
+
+
+ +
+

Automation

+

Workflow automation and scheduling.

+
+
+ +
+ + {/* Create Automation Form */} + {showCreateForm && ( + + + Create New Automation + + +
+
+ + setCreateForm({ ...createForm, name: e.target.value })} + placeholder="e.g., Daily Backup" + className="mt-1 block w-full h-9 rounded-lg border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring/50" + /> +
+
+ + setCreateForm({ ...createForm, description: e.target.value })} + placeholder="e.g., Backup database daily at midnight" + className="mt-1 block w-full h-9 rounded-lg border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring/50" + /> +
+
+ + setCreateForm({ ...createForm, trigger: e.target.value })} + placeholder="e.g., manual, schedule, webhook" + className="mt-1 block w-full h-9 rounded-lg border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring/50" + /> +
+
+ + setCreateForm({ ...createForm, schedule: e.target.value })} + placeholder="e.g., 0 0 * * * (daily at midnight)" + className="mt-1 block w-full h-9 rounded-lg border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring/50" + /> +
+
+ +