ShadcnUI Vaults

Back to Blocks

Minecraft RCON Dashboard

Unknown Block

UnknownComponent

Minecraft RCON Dashboard

Advanced RCON dashboard for Minecraft server management with command history and validation.

Preview

Full width desktop view

Code

api_test-1.tsx
1"use client";
2import React, { useState, useRef, useEffect } from "react";
3import { Button } from "@/components/ui/button";
4import { Input } from "@/components/ui/input";
5import { Label } from "@/components/ui/label";
6import { Card } from "@/components/ui/card";
7import { Badge } from "@/components/ui/badge";
8import { Separator } from "@/components/ui/separator";
9import { ScrollArea } from "@/components/ui/scroll-area";
10import { Alert } from "@/components/ui/alert";
11import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
12import { Switch } from "@/components/ui/switch";
13import {
14  Terminal,
15  Send,
16  Loader2,
17  Eye,
18  EyeOff,
19  Server,
20  History,
21  CheckCircle,
22  XCircle,
23  Copy,
24  Trash2,
25  Settings,
26  AlertTriangle,
27} from "lucide-react";
28
29interface RCONCommand {
30  id: string;
31  command: string;
32  timestamp: Date;
33  response?: string;
34  status: "success" | "error" | "pending";
35  duration?: number;
36}
37
38interface RCONConfig {
39  address: string;
40  port: string;
41  password: string;
42}
43
44interface ValidationErrors {
45  address?: string;
46  port?: string;
47  password?: string;
48  command?: string;
49}
50
51const MinecraftRCONDashboard: React.FC = () => {
52  const [config, setConfig] = useState<RCONConfig>({
53    address: "localhost",
54    port: "25575",
55    password: "your-rcon-password",
56  });
57
58  const [command, setCommand] = useState<string>("list");
59  const [isConnected, setIsConnected] = useState<boolean>(false);
60  const [isLoading, setIsLoading] = useState<boolean>(false);
61  const [showPassword, setShowPassword] = useState<boolean>(false);
62  const [commandHistory, setCommandHistory] = useState<RCONCommand[]>([]);
63  const [validationErrors, setValidationErrors] = useState<ValidationErrors>(
64    {}
65  );
66  const [autoScroll, setAutoScroll] = useState<boolean>(true);
67  const [syntaxHighlight, setSyntaxHighlight] = useState<boolean>(true);
68
69  const responseAreaRef = useRef<HTMLDivElement>(null);
70  const commandInputRef = useRef<HTMLInputElement>(null);
71
72  const validateConfig = (): boolean => {
73    const errors: ValidationErrors = {};
74
75    if (!config.address.trim()) {
76      errors.address = "Server address is required";
77    } else if (!/^[\w.-]+$/.test(config.address)) {
78      errors.address = "Invalid server address format";
79    }
80
81    const portNum = parseInt(config.port);
82    if (!config.port.trim()) {
83      errors.port = "Port is required";
84    } else if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
85      errors.port = "Port must be between 1 and 65535";
86    }
87
88    if (!config.password.trim()) {
89      errors.password = "RCON password is required";
90    } else if (config.password.length < 4) {
91      errors.password = "Password must be at least 4 characters";
92    }
93
94    if (!command.trim()) {
95      errors.command = "Command is required";
96    }
97
98    setValidationErrors(errors);
99    return Object.keys(errors).length === 0;
100  };
101
102  const simulateRCONCommand = async (
103    cmd: string
104  ): Promise<{ response: string; status: "success" | "error" }> => {
105    // Simulate network delay
106    await new Promise((resolve) =>
107      setTimeout(resolve, Math.random() * 2000 + 500)
108    );
109
110    // Simulate different responses based on command
111    const responses: Record<string, string> = {
112      list: "There are 3 of a max of 20 players online: Steve, Alex, Notch",
113      "time query daytime": "The time is 6000",
114      "weather clear": "Set the weather to clear",
115      "gamemode creative Steve": "Set Steve's game mode to Creative Mode",
116      "tp Steve Alex": "Teleported Steve to Alex",
117      "give Steve diamond 64": "Gave 64 [Diamond] to Steve",
118      ban: "Error: Usage: /ban <player> [reason]",
119      invalid: 'Unknown command. Type "/help" for help.',
120    };
121
122    const lowerCmd = cmd.toLowerCase();
123    let response = responses[lowerCmd] || `Executed: ${cmd}`;
124    let status: "success" | "error" = "success";
125
126    // Simulate errors for certain commands
127    if (lowerCmd.includes("ban") && lowerCmd.split(" ").length < 2) {
128      status = "error";
129    } else if (lowerCmd === "invalid" || lowerCmd.includes("unknown")) {
130      status = "error";
131    } else if (Math.random() < 0.1) {
132      // 10% chance of connection error
133      response = "Connection timeout - failed to execute command";
134      status = "error";
135    }
136
137    return { response, status };
138  };
139
140  const executeCommand = async () => {
141    if (!validateConfig()) return;
142
143    const commandId = Date.now().toString();
144    const startTime = Date.now();
145
146    const newCommand: RCONCommand = {
147      id: commandId,
148      command: command.trim(),
149      timestamp: new Date(),
150      status: "pending",
151    };
152
153    setCommandHistory((prev) => [newCommand, ...prev]);
154    setIsLoading(true);
155
156    try {
157      const result = await simulateRCONCommand(command.trim());
158      const duration = Date.now() - startTime;
159
160      setCommandHistory((prev) =>
161        prev.map((cmd) =>
162          cmd.id === commandId
163            ? {
164                ...cmd,
165                response: result.response,
166                status: result.status,
167                duration,
168              }
169            : cmd
170        )
171      );
172
173      if (result.status === "success") {
174        setIsConnected(true);
175      }
176    } catch (error) {
177      setCommandHistory((prev) =>
178        prev.map((cmd) =>
179          cmd.id === commandId
180            ? {
181                ...cmd,
182                response: "Failed to connect to server",
183                status: "error" as const,
184              }
185            : cmd
186        )
187      );
188    } finally {
189      setIsLoading(false);
190    }
191  };
192
193  const clearHistory = () => {
194    setCommandHistory([]);
195  };
196
197  const copyResponse = (response: string) => {
198    navigator.clipboard.writeText(response);
199  };
200
201  const formatResponse = (response: string, highlight: boolean) => {
202    if (!highlight) return response;
203
204    // Simple syntax highlighting for Minecraft responses
205    return response
206      .replace(/(\d+)/g, '<span class="text-blue-400">$1</span>')
207      .replace(/(Steve|Alex|Notch)/g, '<span class="text-green-400">$1</span>')
208      .replace(
209        /(Error|Failed|Unknown)/g,
210        '<span class="text-red-400">$1</span>'
211      )
212      .replace(
213        /(Success|Gave|Set|Teleported)/g,
214        '<span class="text-emerald-400">$1</span>'
215      );
216  };
217
218  useEffect(() => {
219    if (autoScroll && responseAreaRef.current) {
220      responseAreaRef.current.scrollTop = 0;
221    }
222  }, [commandHistory, autoScroll]);
223
224  const handleKeyPress = (e: React.KeyboardEvent) => {
225    if (e.key === "Enter" && !isLoading) {
226      executeCommand();
227    }
228  };
229
230  return (
231    <div className="min-h-screen bg-background p-4 md:p-6">
232      <div className="max-w-7xl mx-auto space-y-6">
233        {/* Header */}
234        <div className="flex items-center justify-between">
235          <div className="flex items-center space-x-3">
236            <div className="p-2 bg-primary/10 rounded-lg">
237              <Terminal className="h-6 w-6 text-primary" />
238            </div>
239            <div>
240              <h1 className="text-2xl font-bold text-foreground">
241                Minecraft RCON Dashboard
242              </h1>
243              <p className="text-muted-foreground">
244                Execute commands on your Minecraft server
245              </p>
246            </div>
247          </div>
248          <div className="flex items-center space-x-2">
249            <Badge
250              variant={isConnected ? "default" : "secondary"}
251              className="flex items-center space-x-1"
252            >
253              <Server className="h-3 w-3" />
254              <span>{isConnected ? "Connected" : "Disconnected"}</span>
255            </Badge>
256          </div>
257        </div>
258
259        <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
260          {/* Configuration Panel */}
261          <div className="lg:col-span-1">
262            <Card className="p-6">
263              <div className="flex items-center space-x-2 mb-4">
264                <Settings className="h-5 w-5 text-primary" />
265                <h2 className="text-lg font-semibold">Server Configuration</h2>
266              </div>
267
268              <div className="space-y-4">
269                <div className="space-y-2">
270                  <Label htmlFor="address">Server Address</Label>
271                  <Input
272                    id="address"
273                    value={config.address}
274                    onChange={(e) =>
275                      setConfig((prev) => ({
276                        ...prev,
277                        address: e.target.value,
278                      }))
279                    }
280                    placeholder="localhost or IP address"
281                    className={validationErrors.address ? "border-red-500" : ""}
282                  />
283                  {validationErrors.address && (
284                    <p className="text-sm text-red-500">
285                      {validationErrors.address}
286                    </p>
287                  )}
288                </div>
289
290                <div className="space-y-2">
291                  <Label htmlFor="port">RCON Port</Label>
292                  <Input
293                    id="port"
294                    value={config.port}
295                    onChange={(e) =>
296                      setConfig((prev) => ({ ...prev, port: e.target.value }))
297                    }
298                    placeholder="25575"
299                    type="number"
300                    className={validationErrors.port ? "border-red-500" : ""}
301                  />
302                  {validationErrors.port && (
303                    <p className="text-sm text-red-500">
304                      {validationErrors.port}
305                    </p>
306                  )}
307                </div>
308
309                <div className="space-y-2">
310                  <Label htmlFor="password">RCON Password</Label>
311                  <div className="relative">
312                    <Input
313                      id="password"
314                      type={showPassword ? "text" : "password"}
315                      value={config.password}
316                      onChange={(e) =>
317                        setConfig((prev) => ({
318                          ...prev,
319                          password: e.target.value,
320                        }))
321                      }
322                      placeholder="Enter RCON password"
323                      className={
324                        validationErrors.password
325                          ? "border-red-500 pr-10"
326                          : "pr-10"
327                      }
328                    />
329                    <Button
330                      type="button"
331                      variant="ghost"
332                      size="sm"
333                      className="absolute right-0 top-0 h-full px-3"
334                      onClick={() => setShowPassword(!showPassword)}
335                    >
336                      {showPassword ? (
337                        <EyeOff className="h-4 w-4" />
338                      ) : (
339                        <Eye className="h-4 w-4" />
340                      )}
341                    </Button>
342                  </div>
343                  {validationErrors.password && (
344                    <p className="text-sm text-red-500">
345                      {validationErrors.password}
346                    </p>
347                  )}
348                </div>
349              </div>
350
351              <Separator className="my-4" />
352
353              <div className="space-y-3">
354                <div className="flex items-center justify-between">
355                  <Label htmlFor="auto-scroll">Auto-scroll responses</Label>
356                  <Switch
357                    id="auto-scroll"
358                    checked={autoScroll}
359                    onCheckedChange={setAutoScroll}
360                  />
361                </div>
362                <div className="flex items-center justify-between">
363                  <Label htmlFor="syntax-highlight">Syntax highlighting</Label>
364                  <Switch
365                    id="syntax-highlight"
366                    checked={syntaxHighlight}
367                    onCheckedChange={setSyntaxHighlight}
368                  />
369                </div>
370              </div>
371            </Card>
372          </div>
373
374          {/* Command Execution and Response */}
375          <div className="lg:col-span-2 space-y-6">
376            {/* Command Input */}
377            <Card className="p-6">
378              <div className="flex items-center space-x-2 mb-4">
379                <Terminal className="h-5 w-5 text-primary" />
380                <h2 className="text-lg font-semibold">Execute Command</h2>
381              </div>
382
383              <div className="flex space-x-2">
384                <div className="flex-1">
385                  <Input
386                    ref={commandInputRef}
387                    value={command}
388                    onChange={(e) => setCommand(e.target.value)}
389                    onKeyPress={handleKeyPress}
390                    placeholder="Enter Minecraft command (e.g., list, time query daytime)"
391                    className={validationErrors.command ? "border-red-500" : ""}
392                    disabled={isLoading}
393                  />
394                  {validationErrors.command && (
395                    <p className="text-sm text-red-500 mt-1">
396                      {validationErrors.command}
397                    </p>
398                  )}
399                </div>
400                <Button
401                  onClick={executeCommand}
402                  disabled={isLoading || !command.trim()}
403                  className="px-6"
404                >
405                  {isLoading ? (
406                    <Loader2 className="h-4 w-4 animate-spin" />
407                  ) : (
408                    <Send className="h-4 w-4" />
409                  )}
410                  <span className="ml-2">
411                    {isLoading ? "Executing..." : "Execute"}
412                  </span>
413                </Button>
414              </div>
415            </Card>
416
417            {/* Response Area */}
418            <Card className="p-6">
419              <div className="flex items-center justify-between mb-4">
420                <div className="flex items-center space-x-2">
421                  <History className="h-5 w-5 text-primary" />
422                  <h2 className="text-lg font-semibold">
423                    Command History & Responses
424                  </h2>
425                </div>
426                <Button
427                  variant="outline"
428                  size="sm"
429                  onClick={clearHistory}
430                  disabled={commandHistory.length === 0}
431                >
432                  <Trash2 className="h-4 w-4 mr-2" />
433                  Clear
434                </Button>
435              </div>
436
437              <ScrollArea className="h-96" ref={responseAreaRef}>
438                {commandHistory.length === 0 ? (
439                  <div className="flex items-center justify-center h-full text-muted-foreground">
440                    <div className="text-center">
441                      <Terminal className="h-12 w-12 mx-auto mb-4 opacity-50" />
442                      <p>No commands executed yet</p>
443                      <p className="text-sm">
444                        Execute a command to see the response here
445                      </p>
446                    </div>
447                  </div>
448                ) : (
449                  <div className="space-y-4">
450                    {commandHistory.map((cmd) => (
451                      <div
452                        key={cmd.id}
453                        className="border border-border rounded-lg p-4"
454                      >
455                        <div className="flex items-center justify-between mb-2">
456                          <div className="flex items-center space-x-2">
457                            <Badge
458                              variant="outline"
459                              className="font-mono text-xs"
460                            >
461                              {cmd.timestamp.toLocaleTimeString()}
462                            </Badge>
463                            <Badge
464                              variant={
465                                cmd.status === "success"
466                                  ? "default"
467                                  : cmd.status === "error"
468                                  ? "destructive"
469                                  : "secondary"
470                              }
471                              className="flex items-center space-x-1"
472                            >
473                              {cmd.status === "success" && (
474                                <CheckCircle className="h-3 w-3" />
475                              )}
476                              {cmd.status === "error" && (
477                                <XCircle className="h-3 w-3" />
478                              )}
479                              {cmd.status === "pending" && (
480                                <Loader2 className="h-3 w-3 animate-spin" />
481                              )}
482                              <span className="capitalize">{cmd.status}</span>
483                            </Badge>
484                            {cmd.duration && (
485                              <Badge variant="outline" className="text-xs">
486                                {cmd.duration}ms
487                              </Badge>
488                            )}
489                          </div>
490                          {cmd.response && (
491                            <Button
492                              variant="ghost"
493                              size="sm"
494                              onClick={() => copyResponse(cmd.response!)}
495                            >
496                              <Copy className="h-3 w-3" />
497                            </Button>
498                          )}
499                        </div>
500
501                        <div className="space-y-2">
502                          <div className="bg-muted/50 rounded p-2 font-mono text-sm">
503                            <span className="text-muted-foreground">$ </span>
504                            <span>{cmd.command}</span>
505                          </div>
506
507                          {cmd.response && (
508                            <div
509                              className={`bg-muted/30 rounded p-2 font-mono text-sm ${
510                                cmd.status === "error"
511                                  ? "border-l-4 border-red-500"
512                                  : "border-l-4 border-green-500"
513                              }`}
514                            >
515                              {syntaxHighlight ? (
516                                <div
517                                  dangerouslySetInnerHTML={{
518                                    __html: formatResponse(
519                                      cmd.response,
520                                      syntaxHighlight
521                                    ),
522                                  }}
523                                />
524                              ) : (
525                                <span>{cmd.response}</span>
526                              )}
527                            </div>
528                          )}
529                        </div>
530                      </div>
531                    ))}
532                  </div>
533                )}
534              </ScrollArea>
535            </Card>
536          </div>
537        </div>
538
539        {/* Quick Commands */}
540        <Card className="p-6">
541          <h3 className="text-lg font-semibold mb-4">Quick Commands</h3>
542          <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-2">
543            {[
544              "list",
545              "time query daytime",
546              "weather clear",
547              "gamemode creative Steve",
548              "tp Steve Alex",
549              "give Steve diamond 64",
550            ].map((quickCmd) => (
551              <Button
552                key={quickCmd}
553                variant="outline"
554                size="sm"
555                onClick={() => setCommand(quickCmd)}
556                className="text-xs"
557                disabled={isLoading}
558              >
559                {quickCmd}
560              </Button>
561            ))}
562          </div>
563        </Card>
564
565        {/* Connection Status Alert */}
566        {!isConnected && commandHistory.length > 0 && (
567          <Alert>
568            <AlertTriangle className="h-4 w-4" />
569            <div>
570              <h4 className="font-semibold">Connection Status</h4>
571              <p className="text-sm">
572                Some commands may have failed. Check your server configuration
573                and ensure RCON is enabled.
574              </p>
575            </div>
576          </Alert>
577        )}
578      </div>
579    </div>
580  );
581};
582
583export default MinecraftRCONDashboard;

Dependencies

External Libraries

lucide-reactreact

Shadcn/UI Components

alertbadgebuttoncardinputlabelscroll-areaseparatorswitchtabs

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.

Contributors

Ramiro Godoy@milogodoy

Review Form Block

Aldhaneka@Aldhanekaa

Project Creator

For questions about licensing, please contact the project maintainers.