Skip to content

Commit 11f37bf

Browse files
committed
feat: implement light mode and theme toggle functionality
- Added light mode CSS variables to globals.css for improved UI flexibility. - Introduced ThemeProvider context to manage theme state across the application. - Integrated ThemeToggle component for users to switch between light and dark modes. - Updated layout and components to support theme changes, ensuring consistent styling. - Enhanced GraphTemplateBuilder and GraphVisualization components to reflect theme changes. These changes enhance user experience by providing a customizable interface that adapts to user preferences. auth: @nk-ag
1 parent 0f92411 commit 11f37bf

20 files changed

Lines changed: 1545 additions & 888 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
3+
const API_BASE_URL = process.env.EXOSPHERE_STATE_MANAGER_URI || 'http://localhost:8000';
4+
const API_KEY = process.env.EXOSPHERE_API_KEY;
5+
6+
export async function POST(request: NextRequest) {
7+
try {
8+
const { searchParams } = new URL(request.url);
9+
const namespace = searchParams.get('namespace');
10+
const stateId = searchParams.get('stateId');
11+
12+
if (!namespace || !stateId) {
13+
return NextResponse.json({ error: 'Namespace and stateId are required' }, { status: 400 });
14+
}
15+
16+
if (!API_KEY) {
17+
return NextResponse.json({ error: 'API key not configured' }, { status: 500 });
18+
}
19+
20+
const body = await request.json();
21+
22+
if (!body.fanout_id) {
23+
return NextResponse.json({ error: 'fanout_id is required in request body' }, { status: 400 });
24+
}
25+
26+
const response = await fetch(`${API_BASE_URL}/v0/namespace/${namespace}/state/${stateId}/manual-retry`, {
27+
method: 'POST',
28+
headers: {
29+
'X-API-Key': API_KEY,
30+
'Content-Type': 'application/json',
31+
},
32+
body: JSON.stringify(body),
33+
});
34+
35+
if (!response.ok) {
36+
const errorText = await response.text();
37+
throw new Error(`State manager API error: ${response.status} ${response.statusText} - ${errorText}`);
38+
}
39+
40+
const data = await response.json();
41+
return NextResponse.json(data);
42+
} catch (error) {
43+
console.error('Error retrying state:', error);
44+
return NextResponse.json(
45+
{ error: 'Failed to retry state' },
46+
{ status: 500 }
47+
);
48+
}
49+
}

dashboard/src/app/globals.css

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
--input: #1a2a5a; /* Navy input background */
3939
--ring: #87ceeb; /* Sky blue focus ring */
4040
--chart-1: #87ceeb; /* Sky blue */
41-
--chart-2: #4ade80; /* Green accent */
42-
--chart-3: #fbbf24; /* Yellow accent */
41+
--chart-2: #66d1b5; /* Green accent */
42+
--chart-3: #ffed9e; /* Yellow accent */
4343
--chart-4: #ff6b8a; /* Pink accent */
4444
--chart-5: #a78bfa; /* Purple accent */
4545
--sidebar: #0a1a4a;
@@ -52,6 +52,44 @@
5252
--sidebar-ring: #87ceeb;
5353
}
5454

55+
/* Light Mode Variables */
56+
.light {
57+
--background: #ffffff; /* White background */
58+
--foreground: #031035; /* Dark navy text */
59+
--card: #f2f7fb; /* Light card background */
60+
--card-foreground: #031035;
61+
--popover: #ffffff;
62+
--popover-foreground: #031035;
63+
--primary: #031035; /* Dark navy primary (keeping dark blue accent) */
64+
--primary-foreground: #ffffff;
65+
--secondary: #f1f5f9; /* Light gray secondary */
66+
--secondary-foreground: #031035;
67+
--muted: #f1f5f9; /* Light muted background */
68+
--muted-foreground: #64748b; /* Medium gray text */
69+
--accent: #031035; /* Keep dark blue accent */
70+
--accent-light: #0a1a4a; /* Keep dark blue accent */
71+
--accent-lighter: #1a2a5a; /* Keep dark blue accent */
72+
--accent-lightest: #2a3a6a; /* Keep dark blue accent */
73+
--accent-foreground: #ffffff;
74+
--destructive: #dc2626; /* Red for errors in light mode */
75+
--border: #e2e8f0; /* Light border */
76+
--input: #ffffff; /* White input background */
77+
--ring: #87ceeb; /* Keep sky blue focus ring */
78+
--chart-1: #87ceeb; /* Sky blue */
79+
--chart-2: #4ade80; /* Green accent */
80+
--chart-3: #cca301; /* Yellow accent */
81+
--chart-4: #ff6b8a; /* Pink accent */
82+
--chart-5: #a78bfa; /* Purple accent */
83+
--sidebar: #f8fafc;
84+
--sidebar-foreground: #031035;
85+
--sidebar-primary: #031035; /* Dark navy for sidebar primary in light mode */
86+
--sidebar-primary-foreground: #ffffff;
87+
--sidebar-accent: #f1f5f9;
88+
--sidebar-accent-foreground: #031035;
89+
--sidebar-border: #e2e8f0;
90+
--sidebar-ring: #87ceeb;
91+
}
92+
5593
/* Custom Scrollbar Styling */
5694
::-webkit-scrollbar {
5795
width: 8px;
@@ -113,7 +151,18 @@
113151
}
114152
}
115153

116-
154+
/* Light mode react-flow nodes */
155+
.light .react-flow__node{
156+
background-color: var(--card);
157+
color: var(--card-foreground);
158+
border: 1px solid var(--border);
159+
&:hover{
160+
background-color: var(--muted);
161+
}
162+
&:active{
163+
background-color: var(--muted);
164+
}
165+
}
117166

118167
@theme inline {
119168
--color-background: var(--background);
@@ -169,9 +218,13 @@
169218
@apply bg-background text-foreground font-sans;
170219
}
171220

172-
/* Custom select dropdown styling for better dark theme support */
221+
/* Custom select dropdown styling for better theme support */
173222
select {
174-
color-scheme: dark;
223+
color-scheme: light dark;
224+
}
225+
226+
.light select {
227+
color-scheme: light;
175228
}
176229

177230
select option {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use client';
2+
3+
import React, { useState, useCallback } from 'react';
4+
import { useParams, useRouter } from 'next/navigation';
5+
import { GraphVisualization } from '@/components/GraphVisualization';
6+
import { GraphTemplateDetail } from '@/components/GraphTemplateDetail';
7+
import { ThemeToggle } from '@/components/ThemeToggle';
8+
import { Button } from '@/components/ui/button';
9+
import { ArrowLeft } from 'lucide-react';
10+
import { clientApiService } from '@/services/clientApi';
11+
import { UpsertGraphTemplateResponse } from '@/types/state-manager';
12+
13+
export default function GraphPage() {
14+
const router = useRouter();
15+
const params = useParams();
16+
17+
const namespace = params?.namespace as string;
18+
const runId = params?.runId as string;
19+
20+
// Graph template state
21+
const [graphTemplate, setGraphTemplate] = useState<UpsertGraphTemplateResponse | null>(null);
22+
const [isLoadingTemplate, setIsLoadingTemplate] = useState(false);
23+
const [templateError, setTemplateError] = useState<string | null>(null);
24+
25+
const handleBack = () => {
26+
// Go back to the previous page or close the tab if opened from external link
27+
if (window.history.length > 1) {
28+
router.back();
29+
} else {
30+
window.close();
31+
}
32+
};
33+
34+
const handleOpenGraphTemplate = useCallback(async (graphName: string) => {
35+
if (!graphName || !namespace) return;
36+
37+
try {
38+
setIsLoadingTemplate(true);
39+
setTemplateError(null);
40+
const template = await clientApiService.getGraphTemplate(namespace, graphName);
41+
// Add name and namespace to the template
42+
template.name = graphName;
43+
template.namespace = namespace;
44+
setGraphTemplate(template);
45+
} catch (err) {
46+
setTemplateError(err instanceof Error ? err.message : 'Failed to load graph template');
47+
} finally {
48+
setIsLoadingTemplate(false);
49+
}
50+
}, [namespace]);
51+
52+
const handleCloseGraphTemplate = () => {
53+
setGraphTemplate(null);
54+
setTemplateError(null);
55+
};
56+
57+
if (!namespace || !runId) {
58+
return (
59+
<div className="min-h-screen bg-background flex items-center justify-center">
60+
<div className="text-center">
61+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
62+
<p className="text-muted-foreground">Loading...</p>
63+
</div>
64+
</div>
65+
);
66+
}
67+
68+
return (
69+
<div className="min-h-screen bg-background">
70+
{/* Header */}
71+
<header className="border-b border-border bg-card/50 backdrop-blur-sm sticky top-0 z-10">
72+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
73+
<div className="flex items-center justify-between h-16">
74+
<div className="flex items-center space-x-4">
75+
<Button
76+
onClick={handleBack}
77+
variant="ghost"
78+
size="sm"
79+
className="flex items-center space-x-2"
80+
>
81+
<ArrowLeft className="w-4 h-4" />
82+
<span>Back</span>
83+
</Button>
84+
<div className="h-6 w-px bg-border" />
85+
<div>
86+
<h1 className="text-xl font-semibold text-foreground">
87+
Graph Visualization
88+
</h1>
89+
<p className="text-sm text-muted-foreground">
90+
Namespace: {namespace} | Run: {runId}
91+
</p>
92+
</div>
93+
</div>
94+
<ThemeToggle />
95+
</div>
96+
</div>
97+
</header>
98+
99+
{/* Main Content */}
100+
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
101+
<GraphVisualization
102+
namespace={namespace}
103+
runId={runId}
104+
onGraphTemplateRequest={handleOpenGraphTemplate}
105+
/>
106+
</main>
107+
108+
{/* Graph Template Detail Modal - Inline at bottom */}
109+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-8">
110+
{templateError && (
111+
<div className="mb-4 p-4 bg-destructive/10 border border-destructive/20 rounded-lg">
112+
<p className="text-sm text-destructive">{templateError}</p>
113+
</div>
114+
)}
115+
116+
{isLoadingTemplate && (
117+
<div className="mb-4 p-4 bg-muted rounded-lg">
118+
<div className="flex items-center">
119+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary mr-2"></div>
120+
<p className="text-sm text-muted-foreground">Loading graph template...</p>
121+
</div>
122+
</div>
123+
)}
124+
125+
<GraphTemplateDetail
126+
graphTemplate={graphTemplate}
127+
isOpen={!!graphTemplate}
128+
onClose={handleCloseGraphTemplate}
129+
/>
130+
</div>
131+
</div>
132+
);
133+
}

dashboard/src/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Metadata } from "next";
22
import { Geist, Geist_Mono } from "next/font/google";
3+
import { ThemeProvider } from "@/contexts/ThemeContext";
34
import "./globals.css";
45

56
const geistSans = Geist({
@@ -27,7 +28,9 @@ export default function RootLayout({
2728
<body
2829
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
2930
>
30-
{children}
31+
<ThemeProvider>
32+
{children}
33+
</ThemeProvider>
3134
</body>
3235
</html>
3336
);

dashboard/src/app/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { GraphTemplateBuilder } from '@/components/GraphTemplateBuilder';
66
import { NamespaceOverview } from '@/components/NamespaceOverview';
77
import { RunsTable } from '@/components/RunsTable';
88
import { NodeDetailModal } from '@/components/NodeDetailModal';
9-
import { GraphTemplateDetailModal } from '@/components/GraphTemplateDetailModal';
9+
import { GraphTemplateDetailModal} from '@/components/GraphTemplateDetailModal';
10+
import { ThemeToggle } from '@/components/ThemeToggle';
1011
import { clientApiService } from '@/services/clientApi';
1112
import {
1213
NodeRegistration,
@@ -168,6 +169,7 @@ export default function Dashboard() {
168169
)}
169170
</Select>
170171
</div>
172+
<ThemeToggle />
171173
</div>
172174
</div>
173175
</div>

dashboard/src/components/GraphTemplateBuilder.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({
7979
return (
8080
<div className="w-full max-w-6xl mx-auto p-6">
8181
<div className="flex items-center justify-between mb-6">
82-
<h2 className="text-2xl font-bold text-gray-800">Graph Template Builder</h2>
82+
<h2 className="text-2xl font-bold text-foreground">Graph Template Builder</h2>
8383
{!readOnly && (
8484
<button
8585
onClick={handleSave}
86-
className="px-4 py-2 bg-[#031035] text-white rounded-lg hover:bg-[#0a1a4a] transition-colors"
86+
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
8787
>
8888
Save Template
8989
</button>
@@ -93,11 +93,11 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({
9393
{/* Nodes Section */}
9494
<div className="mb-8">
9595
<div className="flex items-center justify-between mb-4">
96-
<h3 className="text-lg font-semibold text-gray-700">Workflow Nodes</h3>
96+
<h3 className="text-lg font-semibold text-foreground">Workflow Nodes</h3>
9797
{!readOnly && (
9898
<button
9999
onClick={addNode}
100-
className="flex items-center space-x-2 px-3 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
100+
className="flex items-center space-x-2 px-3 py-2 bg-chart-1 text-primary-foreground rounded-md hover:bg-chart-1/90 transition-colors"
101101
>
102102
<Plus className="w-4 h-4" />
103103
<span>Add Node</span>
@@ -107,9 +107,9 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({
107107

108108
<div className="space-y-4">
109109
{nodes.map((node, index) => (
110-
<div key={index} className="bg-white border border-gray-200 rounded-lg p-4">
110+
<div key={index} className="bg-card border border-border rounded-lg p-4">
111111
<div className="flex items-center justify-between mb-4">
112-
<h4 className="text-md font-medium text-gray-800">Node {index + 1}</h4>
112+
<h4 className="text-md font-medium text-card-foreground">Node {index + 1}</h4>
113113
{!readOnly && (
114114
<button
115115
onClick={() => removeNode(index)}
@@ -122,15 +122,15 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({
122122

123123
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
124124
<div>
125-
<label className="block text-sm font-medium text-gray-700 mb-1">
125+
<label className="block text-sm font-medium text-card-foreground mb-1">
126126
Node Name
127127
</label>
128128
<input
129129
type="text"
130130
value={node.node_name}
131131
onChange={(e) => updateNode(index, { node_name: e.target.value })}
132132
disabled={readOnly}
133-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100"
133+
className="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent disabled:bg-muted bg-input text-foreground"
134134
placeholder="Enter node name"
135135
/>
136136
</div>

0 commit comments

Comments
 (0)