Preview
Full width desktop view
Code
api_test-4.tsx
1"use client";
2
3import React, { useState } from "react";
4import { motion } from "framer-motion";
5import { cn } from "@/lib/utils";
6import { Button } from "@/components/ui/button";
7import { Input } from "@/components/ui/input";
8import { Label } from "@/components/ui/label";
9import { Card } from "@/components/ui/card";
10import { Badge } from "@/components/ui/badge";
11import { Textarea } from "@/components/ui/textarea";
12import {
13 Server,
14 Terminal,
15 Play,
16 AlertCircle,
17 CheckCircle,
18 Loader2,
19 Copy,
20 Eye,
21 EyeOff,
22 Wifi,
23 WifiOff,
24 Activity,
25} from "lucide-react";
26
27const GlowEffect = ({
28 className,
29 style,
30 colors = ["#FF5733", "#33FF57", "#3357FF", "#F1C40F"],
31 mode = "rotate",
32 blur = "medium",
33 scale = 1,
34 duration = 5,
35}: {
36 className?: string;
37 style?: React.CSSProperties;
38 colors?: string[];
39 mode?:
40 | "rotate"
41 | "pulse"
42 | "breathe"
43 | "colorShift"
44 | "flowHorizontal"
45 | "static";
46 blur?:
47 | number
48 | "softest"
49 | "soft"
50 | "medium"
51 | "strong"
52 | "stronger"
53 | "strongest"
54 | "none";
55 scale?: number;
56 duration?: number;
57}) => {
58 const BASE_TRANSITION = {
59 repeat: Infinity,
60 duration: duration,
61 ease: "linear",
62 };
63
64 const animations = {
65 rotate: {
66 background: [
67 `conic-gradient(from 0deg at 50% 50%, ${colors.join(", ")})`,
68 `conic-gradient(from 360deg at 50% 50%, ${colors.join(", ")})`,
69 ],
70 transition: BASE_TRANSITION,
71 },
72 pulse: {
73 background: colors.map(
74 (color) =>
75 `radial-gradient(circle at 50% 50%, ${color} 0%, transparent 100%)`
76 ),
77 scale: [1 * scale, 1.1 * scale, 1 * scale],
78 opacity: [0.5, 0.8, 0.5],
79 transition: {
80 ...BASE_TRANSITION,
81 repeatType: "mirror",
82 },
83 },
84 breathe: {
85 background: [
86 ...colors.map(
87 (color) =>
88 `radial-gradient(circle at 50% 50%, ${color} 0%, transparent 100%)`
89 ),
90 ],
91 scale: [1 * scale, 1.05 * scale, 1 * scale],
92 transition: {
93 ...BASE_TRANSITION,
94 repeatType: "mirror",
95 },
96 },
97 colorShift: {
98 background: colors.map((color, index) => {
99 const nextColor = colors[(index + 1) % colors.length];
100 return `conic-gradient(from 0deg at 50% 50%, ${color} 0%, ${nextColor} 50%, ${color} 100%)`;
101 }),
102 transition: {
103 ...BASE_TRANSITION,
104 repeatType: "mirror",
105 },
106 },
107 flowHorizontal: {
108 background: colors.map((color) => {
109 const nextColor = colors[(colors.indexOf(color) + 1) % colors.length];
110 return `linear-gradient(to right, ${color}, ${nextColor})`;
111 }),
112 transition: {
113 ...BASE_TRANSITION,
114 repeatType: "mirror",
115 },
116 },
117 static: {
118 background: `linear-gradient(to right, ${colors.join(", ")})`,
119 },
120 };
121
122 // @ts-ignore
123 const getBlurClass = (blur: typeof blur) => {
124 if (typeof blur === "number") {
125 return `blur-[${blur}px]`;
126 }
127
128 const presets = {
129 softest: "blur-sm",
130 soft: "blur",
131 medium: "blur-md",
132 strong: "blur-lg",
133 stronger: "blur-xl",
134 strongest: "blur-xl",
135 none: "blur-none",
136 };
137
138 return presets[blur as keyof typeof presets];
139 };
140
141 return (
142 <motion.div
143 style={
144 {
145 ...style,
146 "--scale": scale,
147 willChange: "transform",
148 backfaceVisibility: "hidden",
149 } as React.CSSProperties
150 }
151 // @ts-ignore
152 animate={animations[mode]}
153 className={cn(
154 "pointer-events-none absolute inset-0 h-full w-full",
155 "scale-[var(--scale)] transform-gpu",
156 getBlurClass(blur),
157 className
158 )}
159 />
160 );
161};
162
163interface RCONResponse {
164 success: boolean;
165 response?: string;
166 error?: string;
167 timestamp: Date;
168}
169
170interface ConnectionStatus {
171 connected: boolean;
172 lastPing?: Date;
173 latency?: number;
174}
175
176const RCONDashboard = () => {
177 const [rconAddress, setRconAddress] = useState("localhost:25575");
178 const [password, setPassword] = useState("");
179 const [command, setCommand] = useState("list");
180 const [showPassword, setShowPassword] = useState(false);
181 const [isLoading, setIsLoading] = useState(false);
182 const [responses, setResponses] = useState<RCONResponse[]>([]);
183 const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>({
184 connected: false,
185 });
186 const [commandHistory, setCommandHistory] = useState<string[]>([
187 "list",
188 "time query daytime",
189 "weather query",
190 "difficulty",
191 ]);
192
193 const simulateRCONRequest = async (
194 address: string,
195 pass: string,
196 cmd: string
197 ): Promise<RCONResponse> => {
198 await new Promise((resolve) =>
199 setTimeout(resolve, 1000 + Math.random() * 2000)
200 );
201
202 const mockResponses: Record<string, string> = {
203 list: "There are 3 of a max of 20 players online: Steve, Alex, Notch",
204 "time query daytime": "The time is 6000",
205 "weather query": "The weather is clear",
206 difficulty: "The difficulty is Easy",
207 version:
208 "This server is running Paper version git-Paper-387 (MC: 1.20.1)",
209 "whitelist list":
210 "There are 5 whitelisted players: Steve, Alex, Notch, Herobrine, Jeb_",
211 help: "Available commands: list, time, weather, difficulty, version, whitelist, ban, kick, tp, give",
212 };
213
214 if (Math.random() < 0.1) {
215 return {
216 success: false,
217 error: "Connection timeout - server may be offline",
218 timestamp: new Date(),
219 };
220 }
221
222 const response =
223 mockResponses[cmd.toLowerCase()] || `Command executed: ${cmd}`;
224
225 return {
226 success: true,
227 response,
228 timestamp: new Date(),
229 };
230 };
231
232 const handleSubmit = async (e: React.FormEvent) => {
233 e.preventDefault();
234 if (!rconAddress || !password || !command) return;
235
236 setIsLoading(true);
237 setConnectionStatus({
238 connected: true,
239 lastPing: new Date(),
240 latency: Math.floor(Math.random() * 100) + 20,
241 });
242
243 try {
244 const result = await simulateRCONRequest(rconAddress, password, command);
245 setResponses((prev) => [result, ...prev].slice(0, 10));
246
247 if (!commandHistory.includes(command)) {
248 setCommandHistory((prev) => [command, ...prev].slice(0, 10));
249 }
250 } catch (error) {
251 setResponses((prev) =>
252 [
253 {
254 success: false,
255 error: "Failed to connect to RCON server",
256 timestamp: new Date(),
257 },
258 ...prev,
259 ].slice(0, 10)
260 );
261 setConnectionStatus({ connected: false });
262 } finally {
263 setIsLoading(false);
264 }
265 };
266
267 const copyToClipboard = (text: string) => {
268 navigator.clipboard.writeText(text);
269 };
270
271 const formatTimestamp = (date: Date) => {
272 return date.toLocaleTimeString("en-US", {
273 hour12: false,
274 hour: "2-digit",
275 minute: "2-digit",
276 second: "2-digit",
277 });
278 };
279
280 return (
281 <div className="min-h-screen bg-background relative overflow-hidden">
282 <GlowEffect
283 colors={["#0ea5e9", "#8b5cf6", "#06b6d4", "#3b82f6"]}
284 mode="breathe"
285 blur="strongest"
286 className="opacity-20"
287 />
288
289 <div className="relative z-10 container mx-auto px-4 py-8">
290 <motion.div
291 initial={{ opacity: 0, y: 20 }}
292 animate={{ opacity: 1, y: 0 }}
293 transition={{ duration: 0.6 }}
294 className="max-w-6xl mx-auto"
295 >
296 <div className="text-center mb-8">
297 <div className="flex items-center justify-center gap-3 mb-4">
298 <div className="p-3 bg-primary/10 rounded-xl">
299 <Terminal className="w-8 h-8 text-primary" />
300 </div>
301 <h1 className="text-4xl font-bold bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent">
302 RCON Dashboard
303 </h1>
304 </div>
305 <p className="text-muted-foreground text-lg">
306 Test and manage your Minecraft server remotely
307 </p>
308 </div>
309
310 <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
311 <div className="lg:col-span-2 space-y-6">
312 <Card className="p-6 relative overflow-hidden border-border/50 bg-card/50 backdrop-blur-sm">
313 <div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent" />
314 <div className="relative">
315 <div className="flex items-center gap-2 mb-6">
316 <Server className="w-5 h-5 text-primary" />
317 <h2 className="text-xl font-semibold">Server Connection</h2>
318 <Badge
319 variant={
320 connectionStatus.connected ? "default" : "secondary"
321 }
322 className="ml-auto"
323 >
324 {connectionStatus.connected ? (
325 <>
326 <Wifi className="w-3 h-3 mr-1" /> Connected
327 </>
328 ) : (
329 <>
330 <WifiOff className="w-3 h-3 mr-1" /> Disconnected
331 </>
332 )}
333 </Badge>
334 </div>
335
336 <form onSubmit={handleSubmit} className="space-y-4">
337 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
338 <div className="space-y-2">
339 <Label htmlFor="address">RCON Address</Label>
340 <Input
341 id="address"
342 placeholder="localhost:25575"
343 value={rconAddress}
344 onChange={(e) => setRconAddress(e.target.value)}
345 className="bg-background/50"
346 />
347 </div>
348 <div className="space-y-2">
349 <Label htmlFor="password">Password</Label>
350 <div className="relative">
351 <Input
352 id="password"
353 type={showPassword ? "text" : "password"}
354 placeholder="Enter RCON password"
355 value={password}
356 onChange={(e) => setPassword(e.target.value)}
357 className="bg-background/50 pr-10"
358 />
359 <Button
360 type="button"
361 variant="ghost"
362 size="sm"
363 className="absolute right-0 top-0 h-full px-3"
364 onClick={() => setShowPassword(!showPassword)}
365 >
366 {showPassword ? (
367 <EyeOff className="w-4 h-4" />
368 ) : (
369 <Eye className="w-4 h-4" />
370 )}
371 </Button>
372 </div>
373 </div>
374 </div>
375
376 <div className="space-y-2">
377 <Label htmlFor="command">Command</Label>
378 <div className="flex gap-2">
379 <Input
380 id="command"
381 placeholder="Enter Minecraft command (e.g., list, time query daytime)"
382 value={command}
383 onChange={(e) => setCommand(e.target.value)}
384 className="bg-background/50 flex-1"
385 />
386 <Button
387 type="submit"
388 disabled={isLoading}
389 className="px-6"
390 >
391 {isLoading ? (
392 <Loader2 className="w-4 h-4 animate-spin" />
393 ) : (
394 <Play className="w-4 h-4" />
395 )}
396 {isLoading ? "Executing..." : "Execute"}
397 </Button>
398 </div>
399 </div>
400 </form>
401 </div>
402 </Card>
403
404 <Card className="p-6 relative overflow-hidden border-border/50 bg-card/50 backdrop-blur-sm">
405 <div className="absolute inset-0 bg-gradient-to-br from-green-500/5 to-transparent" />
406 <div className="relative">
407 <div className="flex items-center gap-2 mb-4">
408 <Activity className="w-5 h-5 text-green-600" />
409 <h3 className="text-lg font-semibold">Command Response</h3>
410 </div>
411
412 <div className="space-y-3 max-h-96 overflow-y-auto">
413 {responses.length === 0 ? (
414 <div className="text-center py-8 text-muted-foreground">
415 <Terminal className="w-12 h-12 mx-auto mb-3 opacity-50" />
416 <p>No commands executed yet</p>
417 <p className="text-sm">
418 Execute a command to see the response here
419 </p>
420 </div>
421 ) : (
422 responses.map((response, index) => (
423 <motion.div
424 key={index}
425 initial={{ opacity: 0, y: 10 }}
426 animate={{ opacity: 1, y: 0 }}
427 transition={{ delay: index * 0.1 }}
428 className={cn(
429 "p-4 rounded-lg border relative group",
430 response.success
431 ? "bg-green-50 border-green-200 dark:bg-green-950/20 dark:border-green-800"
432 : "bg-red-50 border-red-200 dark:bg-red-950/20 dark:border-red-800"
433 )}
434 >
435 <div className="flex items-start gap-3">
436 {response.success ? (
437 <CheckCircle className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
438 ) : (
439 <AlertCircle className="w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" />
440 )}
441 <div className="flex-1 min-w-0">
442 <div className="flex items-center justify-between mb-2">
443 <span className="text-xs text-muted-foreground">
444 {formatTimestamp(response.timestamp)}
445 </span>
446 <Button
447 variant="ghost"
448 size="sm"
449 className="opacity-0 group-hover:opacity-100 transition-opacity h-6 w-6 p-0"
450 onClick={() =>
451 copyToClipboard(
452 response.response || response.error || ""
453 )
454 }
455 >
456 <Copy className="w-3 h-3" />
457 </Button>
458 </div>
459 <Textarea
460 value={
461 response.response || response.error || ""
462 }
463 readOnly
464 className={cn(
465 "min-h-[60px] resize-none border-0 p-0 bg-transparent focus-visible:ring-0",
466 response.success
467 ? "text-green-800 dark:text-green-200"
468 : "text-red-800 dark:text-red-200"
469 )}
470 />
471 </div>
472 </div>
473 </motion.div>
474 ))
475 )}
476 </div>
477 </div>
478 </Card>
479 </div>
480
481 <div className="space-y-6">
482 <Card className="p-6 relative overflow-hidden border-border/50 bg-card/50 backdrop-blur-sm">
483 <div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-transparent" />
484 <div className="relative">
485 <h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
486 <Terminal className="w-5 h-5 text-blue-600" />
487 Quick Commands
488 </h3>
489 <div className="space-y-2">
490 {[
491 { cmd: "list", desc: "List online players" },
492 { cmd: "time query daytime", desc: "Get current time" },
493 { cmd: "weather query", desc: "Check weather" },
494 { cmd: "difficulty", desc: "Get difficulty" },
495 { cmd: "version", desc: "Server version" },
496 { cmd: "whitelist list", desc: "Show whitelist" },
497 ].map((item, index) => (
498 <Button
499 key={index}
500 variant="ghost"
501 className="w-full justify-start text-left h-auto p-3"
502 onClick={() => setCommand(item.cmd)}
503 >
504 <div>
505 <div className="font-mono text-sm text-primary">
506 {item.cmd}
507 </div>
508 <div className="text-xs text-muted-foreground">
509 {item.desc}
510 </div>
511 </div>
512 </Button>
513 ))}
514 </div>
515 </div>
516 </Card>
517
518 <Card className="p-6 relative overflow-hidden border-border/50 bg-card/50 backdrop-blur-sm">
519 <div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 to-transparent" />
520 <div className="relative">
521 <h3 className="text-lg font-semibold mb-4">
522 Connection Status
523 </h3>
524 <div className="space-y-3">
525 <div className="flex justify-between items-center">
526 <span className="text-sm text-muted-foreground">
527 Status
528 </span>
529 <Badge
530 variant={
531 connectionStatus.connected ? "default" : "secondary"
532 }
533 >
534 {connectionStatus.connected
535 ? "Connected"
536 : "Disconnected"}
537 </Badge>
538 </div>
539 {connectionStatus.connected && (
540 <>
541 <div className="flex justify-between items-center">
542 <span className="text-sm text-muted-foreground">
543 Latency
544 </span>
545 <span className="text-sm font-mono">
546 {connectionStatus.latency}ms
547 </span>
548 </div>
549 <div className="flex justify-between items-center">
550 <span className="text-sm text-muted-foreground">
551 Last Ping
552 </span>
553 <span className="text-sm font-mono">
554 {connectionStatus.lastPing
555 ? formatTimestamp(connectionStatus.lastPing)
556 : "N/A"}
557 </span>
558 </div>
559 </>
560 )}
561 </div>
562 </div>
563 </Card>
564
565 {commandHistory.length > 0 && (
566 <Card className="p-6 relative overflow-hidden border-border/50 bg-card/50 backdrop-blur-sm">
567 <div className="absolute inset-0 bg-gradient-to-br from-orange-500/5 to-transparent" />
568 <div className="relative">
569 <h3 className="text-lg font-semibold mb-4">
570 Command History
571 </h3>
572 <div className="space-y-1">
573 {commandHistory.slice(0, 5).map((cmd, index) => (
574 <Button
575 key={index}
576 variant="ghost"
577 className="w-full justify-start font-mono text-sm h-8"
578 onClick={() => setCommand(cmd)}
579 >
580 {cmd}
581 </Button>
582 ))}
583 </div>
584 </div>
585 </Card>
586 )}
587 </div>
588 </div>
589 </motion.div>
590 </div>
591 </div>
592 );
593};
594
595export default RCONDashboard;
Dependencies
External Libraries
framer-motionlucide-reactreact
Shadcn/UI Components
badgebuttoncardinputlabeltextarea
Local Components
/lib/utils