ShadCN Vaults

Back to Blocks

Enhanced RCON Dashboard

RCON Dashboards Block

RCON DashboardsComponent

Enhanced RCON Dashboard

Feature-rich RCON dashboard with glow effects, connection status, and command history.

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

LICENSE

MIT License

Copyright (c) 2025 Aldhaneka

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Shadcn Vaults Project (CC BY-NC 4.0 with Internal Use Exception)

All user-submitted components in the '/blocks' directory are licensed under the Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0),
with the following clarification and exception:

You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material

Under these conditions:
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
- NonCommercial — You may NOT use the material for commercial redistribution, resale, or monetization.

🚫 You MAY NOT:
- Sell or redistribute the components individually or as part of a product (e.g. a UI kit, template marketplace, SaaS component library)
- Offer the components or derivative works in any paid tool, theme pack, or design system

✅ You MAY:
- Use the components in internal company tools, dashboards, or applications that are not sold as products
- Remix or adapt components for private or enterprise projects
- Use them in open-source non-commercial projects

This license encourages sharing, learning, and internal innovation — but prohibits using these components as a basis for monetized products.

Full license text: https://creativecommons.org/licenses/by-nc/4.0/

By submitting a component, contributors agree to these terms.

For questions about licensing, please contact the project maintainers.