nitrostack 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/studio/README.md +140 -0
- package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
- package/src/studio/app/api/auth/register-client/route.ts +67 -0
- package/src/studio/app/api/chat/route.ts +123 -0
- package/src/studio/app/api/health/checks/route.ts +42 -0
- package/src/studio/app/api/health/route.ts +13 -0
- package/src/studio/app/api/init/route.ts +85 -0
- package/src/studio/app/api/ping/route.ts +13 -0
- package/src/studio/app/api/prompts/[name]/route.ts +21 -0
- package/src/studio/app/api/prompts/route.ts +13 -0
- package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
- package/src/studio/app/api/resources/route.ts +13 -0
- package/src/studio/app/api/roots/route.ts +13 -0
- package/src/studio/app/api/sampling/route.ts +14 -0
- package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
- package/src/studio/app/api/tools/route.ts +23 -0
- package/src/studio/app/api/widget-examples/route.ts +44 -0
- package/src/studio/app/auth/callback/page.tsx +160 -0
- package/src/studio/app/auth/page.tsx +543 -0
- package/src/studio/app/chat/page.tsx +530 -0
- package/src/studio/app/chat/page.tsx.backup +390 -0
- package/src/studio/app/globals.css +410 -0
- package/src/studio/app/health/page.tsx +177 -0
- package/src/studio/app/layout.tsx +48 -0
- package/src/studio/app/page.tsx +337 -0
- package/src/studio/app/page.tsx.backup +346 -0
- package/src/studio/app/ping/page.tsx +204 -0
- package/src/studio/app/prompts/page.tsx +228 -0
- package/src/studio/app/resources/page.tsx +313 -0
- package/src/studio/components/EnlargeModal.tsx +116 -0
- package/src/studio/components/Sidebar.tsx +133 -0
- package/src/studio/components/ToolCard.tsx +108 -0
- package/src/studio/components/WidgetRenderer.tsx +99 -0
- package/src/studio/lib/api.ts +207 -0
- package/src/studio/lib/llm-service.ts +361 -0
- package/src/studio/lib/mcp-client.ts +168 -0
- package/src/studio/lib/store.ts +192 -0
- package/src/studio/lib/theme-provider.tsx +50 -0
- package/src/studio/lib/types.ts +107 -0
- package/src/studio/lib/widget-loader.ts +90 -0
- package/src/studio/middleware.ts +27 -0
- package/src/studio/next.config.js +16 -0
- package/src/studio/package-lock.json +2696 -0
- package/src/studio/package.json +34 -0
- package/src/studio/postcss.config.mjs +10 -0
- package/src/studio/tailwind.config.ts +67 -0
- package/src/studio/tsconfig.json +41 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
/* Professional Light Theme */
|
|
8
|
+
--background: 249 250 251;
|
|
9
|
+
--foreground: 17 24 39;
|
|
10
|
+
--card: 255 255 255;
|
|
11
|
+
--card-foreground: 17 24 39;
|
|
12
|
+
--popover: 255 255 255;
|
|
13
|
+
--popover-foreground: 17 24 39;
|
|
14
|
+
--primary: 212 175 55; /* Gold #D4AF37 */
|
|
15
|
+
--primary-foreground: 17 24 39;
|
|
16
|
+
--secondary: 243 244 246;
|
|
17
|
+
--secondary-foreground: 17 24 39;
|
|
18
|
+
--muted: 243 244 246;
|
|
19
|
+
--muted-foreground: 107 114 128;
|
|
20
|
+
--accent: 212 175 55;
|
|
21
|
+
--accent-foreground: 17 24 39;
|
|
22
|
+
--destructive: 239 68 68;
|
|
23
|
+
--destructive-foreground: 255 255 255;
|
|
24
|
+
--border: 229 231 235;
|
|
25
|
+
--input: 229 231 235;
|
|
26
|
+
--ring: 212 175 55;
|
|
27
|
+
--radius: 0.75rem;
|
|
28
|
+
--success: 34 197 94;
|
|
29
|
+
--warning: 251 191 36;
|
|
30
|
+
--info: 59 130 246;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.dark {
|
|
34
|
+
/* Professional Dark Theme */
|
|
35
|
+
--background: 9 9 11;
|
|
36
|
+
--foreground: 250 250 250;
|
|
37
|
+
--card: 24 24 27;
|
|
38
|
+
--card-foreground: 250 250 250;
|
|
39
|
+
--popover: 24 24 27;
|
|
40
|
+
--popover-foreground: 250 250 250;
|
|
41
|
+
--primary: 212 175 55; /* Gold #D4AF37 */
|
|
42
|
+
--primary-foreground: 9 9 11;
|
|
43
|
+
--secondary: 39 39 42;
|
|
44
|
+
--secondary-foreground: 250 250 250;
|
|
45
|
+
--muted: 39 39 42;
|
|
46
|
+
--muted-foreground: 161 161 170;
|
|
47
|
+
--accent: 212 175 55;
|
|
48
|
+
--accent-foreground: 9 9 11;
|
|
49
|
+
--destructive: 239 68 68;
|
|
50
|
+
--destructive-foreground: 255 255 255;
|
|
51
|
+
--border: 39 39 42;
|
|
52
|
+
--input: 39 39 42;
|
|
53
|
+
--ring: 212 175 55;
|
|
54
|
+
--success: 34 197 94;
|
|
55
|
+
--warning: 251 191 36;
|
|
56
|
+
--info: 59 130 246;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
* {
|
|
60
|
+
@apply border-border;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body {
|
|
64
|
+
@apply bg-background text-foreground;
|
|
65
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
66
|
+
font-feature-settings: 'rlig' 1, 'calt' 1, 'ss01' 1, 'ss02' 1;
|
|
67
|
+
-webkit-font-smoothing: antialiased;
|
|
68
|
+
-moz-osx-font-smoothing: grayscale;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
code, pre, .font-mono {
|
|
72
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
h1, h2, h3, h4, h5, h6 {
|
|
76
|
+
@apply font-semibold tracking-tight;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
h1 {
|
|
80
|
+
@apply text-4xl font-bold;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
h2 {
|
|
84
|
+
@apply text-3xl font-bold;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
h3 {
|
|
88
|
+
@apply text-2xl font-semibold;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Professional Scrollbar */
|
|
93
|
+
::-webkit-scrollbar {
|
|
94
|
+
width: 10px;
|
|
95
|
+
height: 10px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
::-webkit-scrollbar-track {
|
|
99
|
+
background: hsl(var(--muted) / 0.3);
|
|
100
|
+
border-radius: 10px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
::-webkit-scrollbar-thumb {
|
|
104
|
+
background: hsl(var(--primary) / 0.4);
|
|
105
|
+
border-radius: 10px;
|
|
106
|
+
border: 2px solid transparent;
|
|
107
|
+
background-clip: content-box;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
::-webkit-scrollbar-thumb:hover {
|
|
111
|
+
background: hsl(var(--primary) / 0.6);
|
|
112
|
+
background-clip: content-box;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Smooth Transitions */
|
|
116
|
+
* {
|
|
117
|
+
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
|
|
118
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
119
|
+
transition-duration: 150ms;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Professional Card Styles */
|
|
123
|
+
@layer components {
|
|
124
|
+
.card {
|
|
125
|
+
@apply rounded-xl border bg-card text-card-foreground;
|
|
126
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.card-hover {
|
|
130
|
+
@apply card hover:shadow-lg hover:border-primary/50;
|
|
131
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.card-hover:hover {
|
|
135
|
+
transform: translateY(-2px);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.card-interactive {
|
|
139
|
+
@apply card-hover cursor-pointer;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Professional Button System */
|
|
143
|
+
.btn {
|
|
144
|
+
@apply inline-flex items-center justify-center gap-2 rounded-lg text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50;
|
|
145
|
+
padding: 0.625rem 1.25rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.btn-primary {
|
|
149
|
+
background: hsl(var(--primary));
|
|
150
|
+
color: hsl(var(--primary-foreground));
|
|
151
|
+
@apply shadow-sm hover:opacity-90;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.btn-secondary {
|
|
155
|
+
background: hsl(var(--secondary));
|
|
156
|
+
color: hsl(var(--secondary-foreground));
|
|
157
|
+
@apply border hover:opacity-80;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.btn-ghost {
|
|
161
|
+
background: transparent;
|
|
162
|
+
@apply hover:bg-accent;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.btn-ghost:hover {
|
|
166
|
+
color: hsl(var(--accent-foreground));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.btn-outline {
|
|
170
|
+
background: hsl(var(--background));
|
|
171
|
+
@apply border border-input hover:bg-accent;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.btn-outline:hover {
|
|
175
|
+
color: hsl(var(--accent-foreground));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.btn-sm {
|
|
179
|
+
@apply text-xs;
|
|
180
|
+
padding: 0.5rem 0.875rem;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.btn-lg {
|
|
184
|
+
@apply text-base;
|
|
185
|
+
padding: 0.75rem 1.5rem;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Professional Badge System */
|
|
189
|
+
.badge {
|
|
190
|
+
@apply inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-semibold transition-colors;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.badge-primary {
|
|
194
|
+
@apply bg-primary/10 text-primary ring-1 ring-inset ring-primary/20;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.badge-secondary {
|
|
198
|
+
background: hsl(var(--secondary));
|
|
199
|
+
color: hsl(var(--secondary-foreground));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.badge-success {
|
|
203
|
+
@apply bg-emerald-50 text-emerald-700 ring-1 ring-inset ring-emerald-600/20;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.dark .badge-success {
|
|
207
|
+
@apply bg-emerald-500/10 text-emerald-400 ring-emerald-500/20;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.badge-warning {
|
|
211
|
+
@apply bg-amber-50 text-amber-700 ring-1 ring-inset ring-amber-600/20;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.dark .badge-warning {
|
|
215
|
+
@apply bg-amber-500/10 text-amber-400 ring-amber-500/20;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.badge-error {
|
|
219
|
+
@apply bg-rose-50 text-rose-700 ring-1 ring-inset ring-rose-600/20;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.dark .badge-error {
|
|
223
|
+
@apply bg-rose-500/10 text-rose-400 ring-rose-500/20;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Professional Input System */
|
|
227
|
+
.input {
|
|
228
|
+
@apply flex h-10 w-full rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.textarea {
|
|
232
|
+
@apply input min-h-[80px] resize-y;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Professional Status Indicators */
|
|
236
|
+
.status-dot {
|
|
237
|
+
@apply relative flex h-3 w-3 rounded-full;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.status-dot::before {
|
|
241
|
+
content: '';
|
|
242
|
+
@apply absolute inset-0 rounded-full animate-ping opacity-75;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.status-connected {
|
|
246
|
+
@apply bg-emerald-500;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.status-connected::before {
|
|
250
|
+
@apply bg-emerald-500;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.status-connecting {
|
|
254
|
+
@apply bg-amber-500;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.status-connecting::before {
|
|
258
|
+
@apply bg-amber-500;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.status-disconnected {
|
|
262
|
+
@apply bg-rose-500;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.status-disconnected::before {
|
|
266
|
+
@apply bg-rose-500;
|
|
267
|
+
animation: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Professional Loading States */
|
|
271
|
+
.skeleton {
|
|
272
|
+
@apply animate-pulse rounded-md bg-muted;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.shimmer {
|
|
276
|
+
background: linear-gradient(
|
|
277
|
+
90deg,
|
|
278
|
+
hsl(var(--muted)) 0%,
|
|
279
|
+
hsl(var(--muted) / 0.5) 50%,
|
|
280
|
+
hsl(var(--muted)) 100%
|
|
281
|
+
);
|
|
282
|
+
background-size: 200% 100%;
|
|
283
|
+
animation: shimmer 1.5s ease-in-out infinite;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@keyframes shimmer {
|
|
287
|
+
0% {
|
|
288
|
+
background-position: -200% 0;
|
|
289
|
+
}
|
|
290
|
+
100% {
|
|
291
|
+
background-position: 200% 0;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Professional Animations */
|
|
297
|
+
@keyframes fadeIn {
|
|
298
|
+
from {
|
|
299
|
+
opacity: 0;
|
|
300
|
+
transform: translateY(8px);
|
|
301
|
+
}
|
|
302
|
+
to {
|
|
303
|
+
opacity: 1;
|
|
304
|
+
transform: translateY(0);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
@keyframes slideIn {
|
|
309
|
+
from {
|
|
310
|
+
transform: translateX(-16px);
|
|
311
|
+
opacity: 0;
|
|
312
|
+
}
|
|
313
|
+
to {
|
|
314
|
+
transform: translateX(0);
|
|
315
|
+
opacity: 1;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@keyframes scaleIn {
|
|
320
|
+
from {
|
|
321
|
+
opacity: 0;
|
|
322
|
+
transform: scale(0.95);
|
|
323
|
+
}
|
|
324
|
+
to {
|
|
325
|
+
opacity: 1;
|
|
326
|
+
transform: scale(1);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.animate-fade-in {
|
|
331
|
+
animation: fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.animate-slide-in {
|
|
335
|
+
animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.animate-scale-in {
|
|
339
|
+
animation: scaleIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* Gold Accent Effects */
|
|
343
|
+
.gold-glow {
|
|
344
|
+
box-shadow: 0 0 24px rgba(212, 175, 55, 0.15),
|
|
345
|
+
0 0 12px rgba(212, 175, 55, 0.1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.dark .gold-glow {
|
|
349
|
+
box-shadow: 0 0 32px rgba(212, 175, 55, 0.25),
|
|
350
|
+
0 0 16px rgba(212, 175, 55, 0.15);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Glassmorphism Effect */
|
|
354
|
+
.glass {
|
|
355
|
+
@apply backdrop-blur-xl bg-background/80 border;
|
|
356
|
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.dark .glass {
|
|
360
|
+
@apply bg-card/60;
|
|
361
|
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Professional Hover Effects */
|
|
365
|
+
.hover-lift {
|
|
366
|
+
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
367
|
+
box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.hover-lift:hover {
|
|
371
|
+
transform: translateY(-4px);
|
|
372
|
+
box-shadow: 0 12px 24px -4px rgba(0, 0, 0, 0.12),
|
|
373
|
+
0 8px 16px -4px rgba(0, 0, 0, 0.08);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Focus Visible Styles */
|
|
377
|
+
.focus-ring {
|
|
378
|
+
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* Prose/Content Styles */
|
|
382
|
+
.prose {
|
|
383
|
+
@apply text-foreground;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.prose :where(code):not(:where([class~="not-prose"] *)) {
|
|
387
|
+
@apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.prose :where(code):not(:where([class~="not-prose"] *))::before,
|
|
391
|
+
.prose :where(code):not(:where([class~="not-prose"] *))::after {
|
|
392
|
+
content: '';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Empty State */
|
|
396
|
+
.empty-state {
|
|
397
|
+
@apply flex flex-col items-center justify-center py-12 text-center;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.empty-state-icon {
|
|
401
|
+
@apply w-16 h-16 text-muted-foreground/50 mb-4;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.empty-state-title {
|
|
405
|
+
@apply text-lg font-semibold text-foreground mb-2;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.empty-state-description {
|
|
409
|
+
@apply text-sm text-muted-foreground max-w-md;
|
|
410
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { api } from '@/lib/api';
|
|
5
|
+
import { Activity, RefreshCw, CheckCircle2, AlertTriangle, XCircle, HelpCircle } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
interface HealthCheck {
|
|
8
|
+
name: string;
|
|
9
|
+
status: 'up' | 'down' | 'degraded';
|
|
10
|
+
message?: string;
|
|
11
|
+
details?: any;
|
|
12
|
+
timestamp?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function HealthPage() {
|
|
16
|
+
const [healthChecks, setHealthChecks] = useState<HealthCheck[]>([]);
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
loadHealth();
|
|
21
|
+
const interval = setInterval(loadHealth, 30000); // Refresh every 30s
|
|
22
|
+
return () => clearInterval(interval);
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const loadHealth = async () => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
try {
|
|
28
|
+
const data = await api.getHealth();
|
|
29
|
+
setHealthChecks(data.checks || []);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Failed to load health checks:', error);
|
|
32
|
+
} finally {
|
|
33
|
+
setLoading(false);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const overallStatus =
|
|
38
|
+
healthChecks.length === 0
|
|
39
|
+
? 'unknown'
|
|
40
|
+
: healthChecks.every((c) => c.status === 'up')
|
|
41
|
+
? 'healthy'
|
|
42
|
+
: healthChecks.some((c) => c.status === 'down')
|
|
43
|
+
? 'unhealthy'
|
|
44
|
+
: 'degraded';
|
|
45
|
+
|
|
46
|
+
const getStatusIcon = (status: string) => {
|
|
47
|
+
switch (status) {
|
|
48
|
+
case 'up':
|
|
49
|
+
case 'healthy':
|
|
50
|
+
return <CheckCircle2 className="w-12 h-12 text-emerald-500" />;
|
|
51
|
+
case 'degraded':
|
|
52
|
+
return <AlertTriangle className="w-12 h-12 text-amber-500" />;
|
|
53
|
+
case 'down':
|
|
54
|
+
case 'unhealthy':
|
|
55
|
+
return <XCircle className="w-12 h-12 text-rose-500" />;
|
|
56
|
+
default:
|
|
57
|
+
return <HelpCircle className="w-12 h-12 text-muted-foreground" />;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="min-h-screen bg-background p-8">
|
|
63
|
+
{/* Header */}
|
|
64
|
+
<div className="mb-8">
|
|
65
|
+
<div className="flex items-center justify-between mb-6">
|
|
66
|
+
<div className="flex items-center gap-3">
|
|
67
|
+
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center">
|
|
68
|
+
<Activity className="w-6 h-6 text-white" />
|
|
69
|
+
</div>
|
|
70
|
+
<div>
|
|
71
|
+
<h1 className="text-3xl font-bold text-foreground">Health Checks</h1>
|
|
72
|
+
<p className="text-muted-foreground mt-1">System health monitoring</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
<button onClick={loadHealth} className="btn btn-primary gap-2" disabled={loading}>
|
|
76
|
+
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
|
77
|
+
{loading ? 'Checking...' : 'Refresh'}
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Overall Status */}
|
|
82
|
+
<div className="card p-8 bg-gradient-to-br from-card to-muted/20">
|
|
83
|
+
<div className="flex items-center gap-6">
|
|
84
|
+
{getStatusIcon(overallStatus)}
|
|
85
|
+
<div>
|
|
86
|
+
<h2 className="text-3xl font-bold capitalize text-foreground">{overallStatus}</h2>
|
|
87
|
+
<p className="text-muted-foreground mt-1">
|
|
88
|
+
{healthChecks.length} health check{healthChecks.length !== 1 ? 's' : ''} configured
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Health Checks Grid */}
|
|
96
|
+
{loading && healthChecks.length === 0 ? (
|
|
97
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
98
|
+
{[1, 2].map((i) => (
|
|
99
|
+
<div key={i} className="card skeleton h-48"></div>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
) : healthChecks.length === 0 ? (
|
|
103
|
+
<div className="empty-state">
|
|
104
|
+
<Activity className="empty-state-icon" />
|
|
105
|
+
<p className="empty-state-title">No health checks configured</p>
|
|
106
|
+
<p className="empty-state-description">
|
|
107
|
+
Add health checks using the @HealthCheck decorator
|
|
108
|
+
</p>
|
|
109
|
+
</div>
|
|
110
|
+
) : (
|
|
111
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
112
|
+
{healthChecks.map((check) => (
|
|
113
|
+
<div key={check.name} className="card card-hover p-6 animate-fade-in">
|
|
114
|
+
<div className="flex items-start justify-between mb-4">
|
|
115
|
+
<div className="flex items-center gap-3">
|
|
116
|
+
<div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
|
|
117
|
+
check.status === 'up'
|
|
118
|
+
? 'bg-emerald-500/10'
|
|
119
|
+
: check.status === 'degraded'
|
|
120
|
+
? 'bg-amber-500/10'
|
|
121
|
+
: 'bg-rose-500/10'
|
|
122
|
+
}`}>
|
|
123
|
+
{check.status === 'up' ? (
|
|
124
|
+
<CheckCircle2 className="w-6 h-6 text-emerald-500" />
|
|
125
|
+
) : check.status === 'degraded' ? (
|
|
126
|
+
<AlertTriangle className="w-6 h-6 text-amber-500" />
|
|
127
|
+
) : (
|
|
128
|
+
<XCircle className="w-6 h-6 text-rose-500" />
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
<div>
|
|
132
|
+
<h3 className="font-semibold capitalize text-foreground">{check.name}</h3>
|
|
133
|
+
<span
|
|
134
|
+
className={`badge text-xs mt-1 ${
|
|
135
|
+
check.status === 'up'
|
|
136
|
+
? 'badge-success'
|
|
137
|
+
: check.status === 'degraded'
|
|
138
|
+
? 'badge-warning'
|
|
139
|
+
: 'badge-error'
|
|
140
|
+
}`}
|
|
141
|
+
>
|
|
142
|
+
{check.status}
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{check.message && (
|
|
149
|
+
<p className="text-sm text-muted-foreground mb-3">{check.message}</p>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{check.details && (
|
|
153
|
+
<div className="mt-3 p-4 bg-muted/30 rounded-lg border border-border">
|
|
154
|
+
<p className="text-xs font-semibold text-muted-foreground mb-3 uppercase tracking-wide">Details</p>
|
|
155
|
+
<div className="space-y-2">
|
|
156
|
+
{Object.entries(check.details).map(([key, value]) => (
|
|
157
|
+
<div key={key} className="flex justify-between text-sm">
|
|
158
|
+
<span className="text-muted-foreground capitalize">{key}:</span>
|
|
159
|
+
<span className="text-foreground font-mono font-medium">{String(value)}</span>
|
|
160
|
+
</div>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{check.timestamp && (
|
|
167
|
+
<p className="text-xs text-muted-foreground mt-3">
|
|
168
|
+
Last check: {new Date(check.timestamp).toLocaleString()}
|
|
169
|
+
</p>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import './globals.css';
|
|
3
|
+
import '@fontsource/inter/400.css';
|
|
4
|
+
import '@fontsource/inter/500.css';
|
|
5
|
+
import '@fontsource/inter/600.css';
|
|
6
|
+
import '@fontsource/inter/700.css';
|
|
7
|
+
import '@fontsource/jetbrains-mono/400.css';
|
|
8
|
+
import '@fontsource/jetbrains-mono/500.css';
|
|
9
|
+
import { Sidebar } from '@/components/Sidebar';
|
|
10
|
+
import { EnlargeModal } from '@/components/EnlargeModal';
|
|
11
|
+
|
|
12
|
+
export const metadata: Metadata = {
|
|
13
|
+
title: 'NitroStack Studio',
|
|
14
|
+
description: 'Professional development environment for NitroStack servers',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default function RootLayout({
|
|
18
|
+
children,
|
|
19
|
+
}: {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<html lang="en" suppressHydrationWarning className="antialiased">
|
|
24
|
+
<head>
|
|
25
|
+
<script
|
|
26
|
+
dangerouslySetInnerHTML={{
|
|
27
|
+
__html: `
|
|
28
|
+
// Force dark mode
|
|
29
|
+
document.documentElement.className = 'dark antialiased';
|
|
30
|
+
`,
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
</head>
|
|
34
|
+
<body className="min-h-screen font-sans">
|
|
35
|
+
<div className="flex min-h-screen bg-gradient-to-br from-background via-background to-muted/20">
|
|
36
|
+
<Sidebar />
|
|
37
|
+
<main className="flex-1 ml-64">
|
|
38
|
+
<div className="min-h-screen p-8">
|
|
39
|
+
{children}
|
|
40
|
+
</div>
|
|
41
|
+
</main>
|
|
42
|
+
</div>
|
|
43
|
+
{/* Global Modal - Available on all pages */}
|
|
44
|
+
<EnlargeModal />
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
47
|
+
);
|
|
48
|
+
}
|