release website style for panel

This commit is contained in:
MCHost
2025-07-06 21:11:43 -04:00
parent efea81e9bb
commit 452a27ece3
7 changed files with 3943 additions and 1256 deletions

1527
public/css/main-site.css Normal file

File diff suppressed because it is too large Load Diff

552
public/css/style.css Normal file
View File

@@ -0,0 +1,552 @@
@import "tailwindcss";
@font-face {
font-family: 'Minecraft';
src: url('../font/MinecraftRegular-Bmg3.otf') format('opentype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@layer base {
html {
@apply h-full;
margin: 0;
padding: 0;
box-sizing: border-box;
overflow-x: hidden;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
@apply min-h-full flex flex-col bg-gray-900 text-white;
font-family: 'Roboto', 'Verdana', sans-serif;
overflow-x: hidden;
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-[Minecraft] text-white;
letter-spacing: 0.5px;
}
.xterm {
background-color: #111426 !important;
}
#app {
@apply flex-grow;
}
main {
@apply flex-grow container mx-auto px-4 sm:px-6 lg:px-8 py-6;
}
footer {
@apply bg-gray-800 py-4 text-center;
}
canvas {
@apply w-full max-w-[150px] h-auto;
aspect-ratio: 1/1;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
@apply bg-gray-800;
}
::-webkit-scrollbar-thumb {
@apply bg-blue-600 rounded;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-blue-700;
}
}
@layer components {
.section-bg {
@apply bg-gray-800 p-6 rounded-lg shadow-lg mb-6 w-full max-w-full;
overflow-x: hidden;
}
.modal {
@apply fixed inset-0 bg-black/75 flex items-center justify-center z-[1000];
}
.modal-content {
@apply bg-gray-800 p-6 rounded-lg w-full max-w-xl relative max-h-[80vh] overflow-y-auto;
scrollbar-width: thin;
scrollbar-color: #4B5563 #1F2937;
}
@media (max-width: 640px) {
.holesail-output-mobile-hidden {
display: none;
}
}
.modal-content::-webkit-scrollbar {
@apply w-2;
}
.modal-content::-webkit-scrollbar-track {
@apply bg-gray-800;
}
.modal-content::-webkit-scrollbar-thumb {
@apply bg-gray-600 rounded;
}
.modal-content::-webkit-scrollbar-thumb:hover {
@apply bg-gray-500;
}
.modal-close {
@apply absolute top-2 right-2 bg-transparent border-none text-white text-xl cursor-pointer;
}
.control-btn {
@apply text-white font-medium px-4 py-2 rounded transition duration-200;
}
.spinner {
@apply border-4 border-gray-700 border-t-blue-600 rounded-full w-6 h-6 animate-spin inline-block;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#notificationContainer {
@apply fixed bottom-4 right-4 z-[1000] flex flex-col-reverse gap-2;
}
.notification {
@apply bg-gray-800 p-3 rounded-lg text-white flex items-center gap-2 shadow-lg;
max-width: 90%;
font-size: 0.875rem;
}
.notification.success {
@apply bg-green-600;
}
.notification.error {
@apply bg-red-600;
}
#dockerLogsTerminal {
@apply bg-gray-900 p-4 rounded-lg w-full max-w-full box-border;
height: 250px;
overflow-y: hidden;
overflow-x: hidden;
}
.xterm .xterm-viewport {
@apply overflow-y-auto;
height: 250px;
scrollbar-width: thin;
scrollbar-color: #3b82f6 #1f2937;
}
.xterm .xterm-viewport::-webkit-scrollbar {
width: 8px;
}
.xterm .xterm-viewport::-webkit-scrollbar-track {
@apply bg-gray-800;
}
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
@apply bg-blue-600 rounded;
}
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
@apply bg-blue-700;
}
.xterm .xterm-screen {
@apply w-full;
}
.item-entry {
@apply flex items-center gap-2 mb-2 w-full flex-wrap;
}
.item-entry input {
@apply bg-gray-700 p-2 rounded text-white w-full sm:w-auto;
}
.item-entry .item-amount {
@apply w-16 sm:w-12;
}
[role="switch"] {
@apply inline-block relative w-10 h-6 cursor-pointer;
}
[role="switch"]>div:first-child {
@apply block w-full h-full rounded-full bg-gray-700 transition-colors duration-200;
}
[role="switch"]>div:last-child {
@apply absolute w-4 h-4 rounded-full bg-white top-1 transition-all duration-200;
}
[role="switch"][aria-checked="true"]>div:first-child {
@apply bg-blue-600;
}
[role="switch"][aria-checked="true"]>div:last-child {
@apply left-5;
}
[role="switch"][aria-checked="false"]>div:last-child {
@apply left-1;
}
#sftpIframe {
@apply w-full rounded;
height: 50vh;
min-height: 300px;
}
#propertiesSearch {
@apply bg-gray-700 p-2 rounded text-white w-full;
}
#propertiesList {
@apply space-y-2;
}
.nav-btn {
@apply bg-blue-600 text-white font-medium px-3 py-1 rounded transition duration-200 text-sm;
}
.nav-btn2 {
@apply bg-blue-600 text-white font-medium px-3 py-1 rounded transition duration-200 text-sm;
}
.nav-btn:hover:not(.disabled) {
@apply bg-blue-700;
}
.nav-btn.current {
@apply bg-blue-800 border-2 border-blue-400 font-bold;
}
.nav-btn.disabled {
@apply bg-gray-600 cursor-not-allowed opacity-50;
}
.pagination-container {
@apply flex justify-center items-center gap-1 mt-4;
}
.pagination-container .nav-btn {
@apply px-2 py-0.5 text-xs rounded-sm;
}
.pagination-container .nav-btn.current {
@apply bg-blue-800 border-1 border-blue-400 font-bold;
}
.pagination-container .nav-btn.disabled {
@apply bg-gray-600 cursor-not-allowed opacity-50;
}
}
.hamburger {
@apply md:hidden flex flex-col justify-center items-center w-10 h-10 fixed top-4 right-4 z-[99999];
}
.hamburger .bar {
@apply w-6 h-0.5 bg-blue-400 mb-1.5 transition-all duration-300;
}
.hamburger.active .bar:nth-child(1) {
@apply translate-y-2 rotate-45;
}
.hamburger.active .bar:nth-child(2) {
@apply opacity-0;
}
.hamburger.active .bar:nth-child(3) {
@apply -translate-y-2 -rotate-45;
}
.mobile-nav-container {
@apply hidden fixed inset-0 flex-col items-center justify-center bg-gray-900 z-[99998] p-4;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.mobile-nav-container.active {
@apply flex opacity-100 visible;
}
.mobile-nav-container ul li a,
.mobile-nav-container ul li button {
@apply block px-4 py-2 rounded text-lg text-white bg-blue-600 hover:bg-blue-700 mb-4;
}
.control-btn {
@apply text-white font-medium px-4 py-2 rounded transition duration-200;
}
#startBtn {
@apply bg-green-600 hover:bg-green-700;
}
#stopBtn {
@apply bg-red-600 hover:bg-red-700;
}
#restartBtn {
@apply bg-yellow-500 hover:bg-yellow-600 text-black;
}
.disabled-btn {
@apply pointer-events-none cursor-not-allowed opacity-50;
}
.disabled-btn:hover {
@apply cursor-not-allowed;
}
.copy-key-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.25rem;
background-color: #374151;
border-radius: 0.25rem;
color: #ffffff;
transition: background-color 0.2s;
}
.copy-key-btn:hover {
background-color: #4b5563;
}
.copy-key-btn svg {
width: 1rem;
height: 1rem;
}
@media (max-width: 768px) {
.section-bg {
@apply p-4;
}
.modal-content {
@apply p-4 max-w-[95%];
}
.section-bg .grid {
@apply grid-cols-1;
}
.section-bg .flex {
@apply flex-col gap-2;
}
canvas {
@apply max-w-[120px];
}
.section-bg input,
.section-bg textarea,
.section-bg select {
@apply p-2 text-sm;
}
.section-bg pre {
@apply text-sm;
}
#sftpIframe {
@apply h-[40vh] min-h-[300px];
}
#consoleOutput {
@apply h-32 text-sm;
}
.notification {
@apply text-sm p-2 max-w-[80%];
}
h1 {
@apply text-3xl;
}
h2 {
@apply text-xl;
}
h3 {
@apply text-base;
}
p,
span,
a {
@apply text-sm;
}
.nav-btn {
@apply hidden;
}
.hamburger {
@apply flex;
}
.control-btn {
@apply px-3 py-1 text-sm;
}
.item-entry {
@apply flex-col gap-1;
}
.item-entry .item-amount {
@apply w-full;
}
}
@media (max-width: 640px) {
.section-bg {
@apply p-3;
}
.modal-content {
@apply p-3 max-w-[95%];
}
canvas {
@apply max-w-[100px];
}
.section-bg input,
.section-bg textarea,
.section-bg select {
@apply p-1 text-xs;
}
.section-bg pre {
@apply text-xs;
}
#sftpIframe {
@apply h-[35vh] min-h-[250px];
}
#consoleOutput {
@apply h-28 text-xs;
}
.notification {
@apply text-xs p-1 max-w-[90%];
}
h1 {
@apply text-2xl;
}
h2 {
@apply text-lg;
}
h3 {
@apply text-sm;
}
p,
span,
a {
@apply text-xs;
}
.control-btn {
@apply px-2 py-1 text-xs;
}
}
@media (max-width: 480px) {
.section-bg {
@apply p-2;
}
.modal-content {
@apply p-2 max-w-[98%];
}
canvas {
@apply max-w-[80px];
}
.section-bg input,
.section-bg textarea,
.section-bg select {
@apply p-1 text-xs;
}
.section-bg pre {
@apply text-xs;
}
#sftpIframe {
@apply h-[30vh] min-h-[200px];
}
#consoleOutput {
@apply h-24 text-xs;
}
.notification {
@apply text-xs p-1 max-w-[95%];
}
h1 {
@apply text-xl;
}
h2 {
@apply text-base;
}
h3 {
@apply text-xs;
}
p,
span,
a {
@apply text-xs;
}
.control-btn {
@apply px-2 py-1 text-xs;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,275 +0,0 @@
@font-face {
font-family: 'Minecraft';
src: url('../font/MinecraftRegular-Bmg3.otf') format('opentype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@media (max-width: 640px) {
.holesail-output-mobile-hidden {
display: none;
}
}
@import "tailwindcss";
@layer base {
/* Sticky footer base styles */
html {
@apply h-full;
}
body {
@apply min-h-full flex flex-col;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Minecraft', sans-serif;
}
#app {
@apply flex-1 flex flex-col;
}
main {
@apply flex-grow;
}
footer {
@apply flex-shrink-0;
}
}
@layer components {
.spinner {
@apply border-4 border-gray-700 border-t-white rounded-full w-6 h-6 animate-spin inline-block;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#notificationContainer {
@apply fixed bottom-5 right-5 z-[1000] flex flex-col-reverse gap-2;
}
.notification {
@apply bg-gray-800 text-white p-4 rounded-lg flex items-center gap-3 shadow-lg transition-opacity duration-300;
}
.notification.success {
@apply bg-green-700;
}
.notification.error {
@apply bg-red-700;
}
#dockerLogsTerminal {
@apply bg-gray-800 p-4 rounded-lg max-h-48 w-full;
}
.xterm .xterm-viewport {
@apply overflow-y-auto;
scrollbar-width: thin;
scrollbar-color: #4B5563 #1F2937;
}
.xterm .xterm-viewport::-webkit-scrollbar {
@apply w-2;
}
.xterm .xterm-viewport::-webkit-scrollbar-track {
@apply bg-gray-800;
}
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
@apply bg-gray-600 rounded;
}
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
@apply bg-gray-500;
}
.xterm .xterm-screen {
@apply w-full;
}
.control-btn {
@apply px-4 py-2 text-sm font-medium transition-all duration-200 min-w-[80px] text-center shadow-sm;
}
.control-btn:hover:not(.disabled-btn) {
@apply -translate-y-px;
}
.control-btn:active:not(.disabled-btn) {
@apply translate-y-0;
}
/* Player button styles */
.tell-player {
@apply bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200;
}
.give-player {
@apply bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200;
}
.op-player, .deop-player {
@apply bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200;
}
.kick-player, .ban-player {
@apply bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200;
}
.teleport-player {
@apply bg-cyan-600 hover:bg-cyan-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200;
}
.teleport-player:hover:not(.disabled-btn) {
@apply -translate-y-px;
}
.teleport-player:active:not(.disabled-btn) {
@apply translate-y-0;
}
.effect-player {
@apply bg-teal-600 hover:bg-teal-700 px-2 py-1 rounded text-sm font-[Minecraft] transition-all duration-200;
}
.effect-player:hover:not(.disabled-btn) {
@apply -translate-y-px;
}
.effect-player:active:not(.disabled-btn) {
@apply translate-y-0;
}
.modal {
@apply fixed inset-0 bg-black/75 flex items-center justify-center z-[1000];
}
.modal-content {
@apply bg-gray-800 p-6 rounded-lg w-full max-w-xl relative max-h-[80vh] overflow-y-auto;
scrollbar-width: thin;
scrollbar-color: #4B5563 #1F2937;
}
.modal-content::-webkit-scrollbar {
@apply w-2;
}
.modal-content::-webkit-scrollbar-track {
@apply bg-gray-800;
}
.modal-content::-webkit-scrollbar-thumb {
@apply bg-gray-600 rounded;
}
.modal-content::-webkit-scrollbar-thumb:hover {
@apply bg-gray-500;
}
.modal-close {
@apply absolute top-2 right-2 bg-transparent border-none text-white text-xl cursor-pointer;
}
.disabled-btn {
@apply opacity-50 cursor-not-allowed pointer-events-none;
}
.disabled-btn:hover {
@apply cursor-not-allowed;
}
/* Search box and properties fields */
#searchContainer {
@apply mb-4 block;
}
#propertiesSearch {
@apply bg-gray-700 px-4 py-2 rounded text-white w-full block;
}
#propertiesList {
@apply space-y-2;
}
/* Toggle switch styles */
[role="switch"] {
@apply inline-block relative w-10 h-6 cursor-pointer z-10;
}
[role="switch"] > div:first-child {
@apply w-full h-full rounded-full transition-colors duration-200 ease-in-out;
}
[role="switch"] > div:last-child {
@apply absolute w-4 h-4 rounded-full bg-white top-1 transition-all duration-200 ease-in-out z-[11];
}
[role="switch"][aria-checked="true"] > div:first-child {
@apply bg-green-600;
}
[role="switch"][aria-checked="false"] > div:first-child {
@apply bg-gray-600;
}
[role="switch"][aria-checked="true"] > div:last-child {
@apply left-6;
}
[role="switch"][aria-checked="false"] > div:last-child {
@apply left-1;
}
}
@layer utilities {
/* Custom utilities */
}
/* Media queries */
@media (max-width: 640px) {
.bg-gray-800.p-6.rounded-lg.shadow-lg .grid {
@apply grid-cols-1;
}
.bg-gray-800.p-6.rounded-lg.shadow-lg p {
@apply flex flex-col gap-2 break-all;
}
.bg-gray-800.p-6.rounded-lg.shadow-lg a,
.bg-gray-800.p-6.rounded-lg.shadow-lg span {
@apply break-words whitespace-normal;
}
.bg-gray-800.p-6.rounded-lg.shadow-lg button {
@apply w-full mt-2;
}
.tell-player,
.give-player,
.teleport-player,
.effect-player,
.op-player,
.deop-player,
.kick-player,
.ban-player {
@apply w-full text-center mt-1;
}
}
/* Additional styles */
.bg-gray-800.p-6.rounded-lg.shadow-lg .grid {
@apply overflow-x-hidden;
}
.bg-gray-800.p-6.rounded-lg.shadow-lg p {
@apply max-w-full;
}

View File

@@ -6,29 +6,100 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Permissions-Policy" content="clipboard-write=(self https://sftp.my-mc.link)">
<title>My-MC Panel</title>
<link rel="stylesheet" href="/css/styles.min.css?p=1">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
<link rel="stylesheet" href="css/style.min.css">
<link rel="stylesheet" href="css/main-site.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" rel="stylesheet" />
<link rel="icon" href="https://minecraft.wiki/images/Favicon.png" type="image/png">
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/favicon/site.webmanifest">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
</head>
<body class="bg-gray-900 text-white overflow-x-hidden min-h-full flex flex-col">
<body class="min-h-full flex flex-col">
<!-- Hamburger Menu Icon (visible on mobile) -->
<button class="hamburger md:hidden flex flex-col justify-center items-center w-10 h-10 focus:outline-none"
style="z-index: 99999; position: fixed; top: 1rem; right: 1rem; pointer-events: auto;">
<span class="bar w-6 h-0.5 bg-teal-400 mb-1.5 transition-all duration-300"></span>
<span class="bar w-6 h-0.5 bg-teal-400 mb-1.5 transition-all duration-300"></span>
<span class="bar w-6 h-0.5 bg-teal-400 transition-all duration-300"></span>
</button>
<!-- Mobile Navigation Menu -->
<nav class="mobile-nav mobile-nav-container hidden fixed inset-0 flex-col items-center justify-center md:hidden"
data-mobile-nav style="z-index: 99998; position: fixed; inset: 0; pointer-events: auto;">
<ul class="text-center">
<li class="mb-6">
<a href="https://my-mc.link"
class="text-lg minecraft-font text-white bg-gradient-to-r from-teal-400 to-blue-500 px-4 py-2 rounded-md hover:bg-gradient-to-r hover:from-blue-500 hover:to-teal-400">Home</a>
</li>
<li class="mb-6">
<a href="https://stats.my-mc.link"
class="text-lg minecraft-font text-white bg-gradient-to-r from-teal-400 to-blue-500 px-4 py-2 rounded-md hover:bg-gradient-to-r hover:from-blue-500 hover:to-teal-400">System
Stats</a>
</li>
<li class="mb-6">
<button id="mobileRefresh"
class="text-lg minecraft-font text-white bg-gradient-to-r from-teal-400 to-blue-500 px-4 py-2 rounded-md hover:bg-gradient-to-r hover:from-blue-500 hover:to-teal-400">Refresh</button>
</li>
<li class="mb-6">
<button id="mobileBackupBtn"
class="text-lg minecraft-font text-white bg-gradient-to-r from-teal-400 to-blue-500 px-4 py-2 rounded-md hover:bg-gradient-to-r hover:from-blue-500 hover:to-teal-400">Backup</button>
</li>
<li class="mb-6">
<a href="https://info.my-mc.link"
class="text-lg minecraft-font text-white bg-gradient-to-r from-teal-400 to-blue-500 px-4 py-2 rounded-md hover:bg-gradient-to-r hover:from-blue-500 hover:to-teal-400"
target="_blank">Wiki</a>
</li>
<li class="mb-6">
<button id="mobileLogoutBtn"
class="text-lg minecraft-font text-white bg-gradient-to-r from-teal-400 to-blue-500 px-4 py-2 rounded-md hover:bg-gradient-to-r hover:from-blue-500 hover:to-teal-400">Logout</button>
</li>
</ul>
</nav>
<!-- Particle Effects -->
<div class="particle"></div>
<div class="particle large"></div>
<div class="particle"></div>
<div class="particle large"></div>
<div class="particle"></div>
<header class="header-bg py-16 text-center relative z-2">
<div class="header-content flex items-center justify-between max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div>
<h1
class="text-5xl minecraft-font bg-clip-text text-transparent bg-gradient-to-r from-teal-400 to-blue-500">
My-MC Panel</h1>
<p class="text-lg mt-4 opacity-90 tracking-wide font-medium">Manage Your Minecraft Server</p>
</div>
<!-- Navigation Buttons (hidden on mobile) -->
<nav class="flex items-center gap-2 hidden md:flex">
<a href="https://my-mc.link" class="nav-btn">Home</a>
<a href="https://stats.my-mc.link" class="nav-btn" target="_blank">System Stats</a>
<button id="refresh" class="nav-btn">Refresh</button>
<button id="backupBtn" class="nav-btn">Backup</button>
<a href="https://info.my-mc.link" class="nav-btn" target="_blank">Wiki</a>
<button id="logoutBtn" class="nav-btn">Logout</button>
</nav>
</div>
</header>
<div id="app" class="flex-grow">
<!-- Login Page and Modals (unchanged) -->
<div id="loginPage" class="hidden fixed inset-0 bg-gray-900 flex items-center justify-center">
<div class="bg-gray-800 p-8 rounded-lg shadow-lg w-full max-w-md">
<h2 class="text-2xl font-bold mb-6 text-center">My-MC Panel</h2>
<!-- Login Page and Modals -->
<div id="loginPage" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-2xl minecraft-font mb-6 text-center">My-MC Panel Login</h2>
<div class="mb-4">
<input id="loginApiKey" type="text" placeholder="Enter API Key"
class="bg-gray-700 px-4 py-2 rounded text-white w-full">
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-4 py-2 rounded text-white w-full border border-[rgba(255,255,255,0.15)]">
</div>
<button id="loginBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Login</button>
<button id="loginBtn" class="nav-btn w-full">Login</button>
<p id="loginError" class="text-red-500 text-sm mt-2 hidden"></p>
</div>
</div>
@@ -38,11 +109,11 @@
<div id="tellModal" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-xl font-semibold mb-4">Send Message to <span id="tellPlayerName"></span></h2>
<h2 class="text-xl minecraft-font mb-4">Send Message to <span id="tellPlayerName"></span></h2>
<form id="tellForm">
<textarea id="tellMessage" placeholder="Enter your message"
class="bg-gray-700 px-4 py-2 rounded text-white w-full h-24 mb-4"></textarea>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Send</button>
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-4 py-2 rounded text-white w-full h-24 mb-4 border border-[rgba(255,255,255,0.15)]"></textarea>
<button type="submit" class="nav-btn w-full">Send</button>
</form>
</div>
</div>
@@ -50,11 +121,12 @@
<div id="giveModal" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-xl font-semibold mb-4">Give Items to <span id="givePlayerName"></span></h2>
<h2 class="text-xl minecraft-font mb-4">Give Items to <span id="givePlayerName"></span></h2>
<form id="giveForm">
<div class="mb-4">
<label for="loadoutSelect" class="block text-sm font-medium mb-1">Select Loadout</label>
<select id="loadoutSelect" class="bg-gray-700 px-4 py-2 rounded text-white w-full">
<select id="loadoutSelect"
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-4 py-2 rounded text-white w-full border border-[rgba(255,255,255,0.15)]">
<option value="custom">Custom</option>
<option value="starter">Starter Kit (Torches, Food)</option>
<option value="builder">Builder Kit (Stone, Wood)</option>
@@ -70,10 +142,9 @@
</div>
<div id="customGiveFields" class="hidden mb-4">
<div id="itemList" class="space-y-2"></div>
<button type="button" id="addItemBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded mt-2">Add
Item</button>
<button type="button" id="addItemBtn" class="nav-btn mt-2">Add Item</button>
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Give</button>
<button type="submit" class="nav-btn w-full">Give</button>
</form>
</div>
</div>
@@ -81,15 +152,17 @@
<div id="teleportModal" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-xl font-semibold mb-4">Teleport <span id="teleportPlayerName"></span></h2>
<h2 class="text-xl minecraft-font mb-4">Teleport <span id="teleportPlayerName"></span></h2>
<form id="teleportForm">
<div class="mb-4">
<label for="teleportDestination" class="block text-sm font-medium mb-1">Select Destination Player</label>
<select id="teleportDestination" class="bg-gray-700 px-4 py-2 rounded text-white w-full">
<label for="teleportDestination" class="block text-sm font-medium mb-1">Select Destination
Player</label>
<select id="teleportDestination"
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-4 py-2 rounded text-white w-full border border-[rgba(255,255,255,0.15)]">
<!-- Options populated dynamically -->
</select>
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Teleport</button>
<button type="submit" class="nav-btn w-full">Teleport</button>
</form>
</div>
</div>
@@ -97,11 +170,12 @@
<div id="effectModal" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-xl font-semibold mb-4">Apply Effect to <span id="effectPlayerName"></span></h2>
<h2 class="text-xl minecraft-font mb-4">Apply Effect to <span id="effectPlayerName"></span></h2>
<form id="effectForm">
<div class="mb-4">
<label for="effectSelect" class="block text-sm font-medium mb-1">Select Effect</label>
<select id="effectSelect" class="bg-gray-700 px-4 py-2 rounded text-white w-full">
<select id="effectSelect"
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-4 py-2 rounded text-white w-full border border-[rgba(255,255,255,0.15)]">
<option value="speed:30:1">Speed (30s, Level 1)</option>
<option value="strength:30:1">Strength (30s, Level 1)</option>
<option value="regeneration:30:1">Regeneration (30s, Level 1)</option>
@@ -112,7 +186,7 @@
<option value="water_breathing:60:1">Water Breathing (60s, Level 1)</option>
</select>
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Apply Effect</button>
<button type="submit" class="nav-btn w-full">Apply Effect</button>
</form>
</div>
</div>
@@ -120,296 +194,296 @@
<div id="editPropertiesModal" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-xl font-semibold mb-4">Edit server.properties</h2>
<h2 class="text-xl minecraft-font mb-4">Edit server.properties</h2>
<form id="editPropertiesForm" class="space-y-4">
<div id="propertiesFields" class="space-y-2"></div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Save</button>
<button type="submit" class="nav-btn w-full">Save</button>
</form>
</div>
</div>
<div id="updateModsModal"
class="modal hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-2 sm:p-4">
<div
class="modal-content bg-gray-900 rounded-lg max-w-[95%] w-full sm:w-[90%] md:w-[85%] lg:w-[80%] mx-auto flex flex-col max-h-[95vh]">
<div class="flex justify-between items-center p-4 border-b">
<h2 class="text-xl font-semibold text-white">Mod Update Output</h2>
<button class="modal-close text-2xl font-bold text-white">×</button>
<div id="updateModsModal" class="modal hidden">
<div class="modal-content flex flex-col">
<div class="flex justify-between items-center p-4 border-b border-[rgba(255,255,255,0.12)]">
<h2 class="text-xl minecraft-font">Mod Update Output</h2>
<button class="modal-close text-2xl">×</button>
</div>
<pre id="updateModsOutput"
class="flex-1 p-4 text-white text-sm max-h-[calc(95vh-10rem)] overflow-y-auto overflow-x-auto whitespace-pre-wrap break-words leading-relaxed"></pre>
<div class="p-4 border-t">
<button id="closeUpdateModsBtn"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded w-full">Close</button>
class="flex-1 p-4 text-[#e0e7ff] text-sm max-h-[calc(95vh-10rem)] overflow-y-auto overflow-x-auto whitespace-pre-wrap break-words leading-relaxed"></pre>
<div class="p-4 border-t border-[rgba(255,255,255,0.12)]">
<button id="closeUpdateModsBtn" class="btn-minecraft w-full">Close</button>
</div>
</div>
</div>
<!-- Navigation (unchanged) -->
<nav class="bg-gray-800 p-4 shadow-lg">
<div class="container mx-auto flex justify-between items-center">
<h1 class="text-2xl font-bold">My-MC Panel</h1>
<div class="flex space-x-4">
<button id="refresh" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded">Refresh</button>
<button id="backupBtn" class="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded">Backup</button>
<div id="authControls">
<input id="apiKey" type="text" placeholder="Enter API Key" class="bg-gray-700 px-4 py-2 rounded text-white">
</div>
</div>
</div>
</nav>
<!-- Main Content (unchanged except for Holesail Keys section) -->
<main id="mainContent" class="container mx-auto p-6">
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6" data-section="server-status">
<h2 class="text-xl font-semibold mb-4">Server Status</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<!-- Main Content -->
<main id="mainContent" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-16 relative z-2">
<div class="section-bg p-4 mb-6" data-section="server-status">
<div class="flex flex-col space-y-2 mb-3">
<h2 class="text-2xl minecraft-font">Server Status</h2>
<div class="flex justify-between items-center">
<div class="flex flex-col space-y-1 text-sm">
<p><strong>User:</strong> <span id="user">Loading...</span></p>
<p><strong>Key Expiry:</strong> <span id="keyExpiry">Loading...</span></p>
<p><strong>Status:</strong> <span id="serverStatus">Loading...</span></p>
</div>
<div class="flex space-x-4 justify-center">
<div class="flex space-x-3 justify-center">
<div class="text-center">
<canvas id="memoryMeter" width="150" height="150"></canvas>
<p class="text-sm mt-2"><b>Memory Usage</b></p>
<p id="memoryPercent" class="text-lg font-bold">0%</p>
<canvas id="memoryMeter" width="100" height="100"></canvas>
<p class="text-xs mt-1"><b>Memory</b></p>
<p id="memoryPercent" class="text-base font-bold">0%</p>
</div>
<div class="text-center">
<canvas id="cpuMeter" width="150" height="150"></canvas>
<p class="text-sm mt-2"><b>CPU Usage</b></p>
<p id="cpuPercent" class="text-lg font-bold">0%</p>
<canvas id="cpuMeter" width="100" height="100"></canvas>
<p class="text-xs mt-1"><b>CPU</b></p>
<p id="cpuPercent" class="text-base font-bold">0%</p>
</div>
</div>
<div class="flex flex-wrap space-x-2 justify-end items-center">
<div class="flex space-x-2">
<button id="startBtn"
class="bg-green-600 hover:bg-green-700 rounded font-medium control-btn">Start</button>
<button id="stopBtn" class="bg-red-600 hover:bg-red-700 rounded font-medium control-btn">Stop</button>
<button id="restartBtn"
class="bg-yellow-600 hover:bg-yellow-700 rounded font-medium control-btn">Restart</button>
<div class="flex flex-col space-y-1">
<div class="flex justify-end space-x-1">
<button id="startBtn" class="control-btn text-sm">Start</button>
<button id="stopBtn" class="control-btn text-sm">Stop</button>
<button id="restartBtn" class="control-btn text-sm">Restart</button>
</div>
<div class="flex justify-end space-x-1">
<button id="editPropertiesBtn" class="nav-btn control-btn text-sm">Edit Properties</button>
<button id="updateModsBtn" class="nav-btn text-sm">Update Mods</button>
</div>
</div>
<button id="editPropertiesBtn"
class="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded control-btn w-full mt-2">Edit Server
Properties</button>
<button id="updateModsBtn"
class="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded control-btn w-full mt-2">Update Mods</button>
</div>
</div>
</div>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
<h2 class="text-xl font-semibold mb-4">Player Management</h2>
<div class="mb-4"></div>
<p><strong>Connected Players:</strong><br> <span id="playerList">Loading...</span></p>
</div>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
<h2 class="text-xl font-semibold mb-4">Server Logs</h2>
<div class="section-bg mb-12">
<h2 class="text-3xl minecraft-font mb-8">Server Logs</h2>
<div id="dockerLogsTerminal" class="mt-4"></div>
</div>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
<h2 class="text-xl font-semibold mb-4">Server Console</h2>
<form id="consoleForm" onsubmit="event.preventDefault(); sendConsoleCommand();">
<input id="consoleInput" type="text" placeholder="Enter RCON Command (Hit Enter To Submit)"
class="bg-gray-700 px-4 py-2 rounded text-white w-full mb-2">
<button id="sendConsole" type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded"
style="display: none;">Send</button>
</form>
<pre id="consoleOutput" class="bg-gray-900 p-4 rounded mt-4 h-48 overflow-y-auto"></pre>
<div class="section-bg mb-12">
<h2 class="text-3xl minecraft-font mb-8">Player Management</h2>
<p><strong>Connected Players:</strong><br> <span id="playerList">Loading...</span></p>
</div>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6" id="sftpBrowserSection" style="display: none;">
<div class="section-bg mb-12">
<h2 class="text-3xl minecraft-font mb-8">Server Console</h2>
<form id="consoleForm" onsubmit="event.preventDefault(); sendConsoleCommand();">
<input id="consoleInput" type="text" placeholder="Enter RCON Command (Hit Enter To Submit)"
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-4 py-2 rounded text-white w-full mb-2 border border-[rgba(255,255,255,0.15)]">
<button id="sendConsole" type="submit" class="nav-btn" style="display: none;">Send</button>
</form>
<pre id="consoleOutput"
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] p-4 rounded mt-4 h-48 overflow-y-auto border border-[rgba(255,255,255,0.15)]"></pre>
</div>
<div class="section-bg mb-12" id="sftpBrowserSection" style="display: none;">
<div class="flex justify-between items-baseline mb-4">
<h2 class="text-xl font-semibold">SFTP Browser</h2>
<button id="sftpBtn" class="px-3 py-1 rounded text-sm flex items-center space-x-1"
style="background-color: #121724; color: #ffffff;">
<i class="fas fa-sync-alt" style="color: #121724;"></i>
<h2 class="text-3xl minecraft-font">SFTP Browser</h2>
<button id="sftpBtn" class="nav-btn text-sm flex items-center space-x-1">
<i class="fas fa-sync-alt"></i>
<span>Refresh SFTP</span>
</button>
</div>
<iframe id="sftpIframe" class="w-full rounded" style="height: 650px; min-height: 650px;"
allow="clipboard-read; clipboard-write"></iframe>
<iframe id="sftpIframe" class="w-full rounded" allow="clipboard-read; clipboard-write"></iframe>
</div>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
<h2 class="text-xl font-semibold mb-4">Mod Management</h2>
<div class="section-bg mb-8">
<h2 class="text-2xl minecraft-font mb-6">Mod Management</h2>
<form id="modSearchForm" onsubmit="event.preventDefault(); searchMods(1);">
<div class="mb-4 flex space-x-2">
<div class="mb-3 flex space-x-2">
<input id="modSearch" type="text" placeholder="Search Mods (Hit Enter To Submit)"
class="bg-gray-700 px-4 py-2 rounded text-white flex-grow">
<button id="searchBtn" type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded"
style="display: none;">Search</button>
<button id="closeSearchBtn" type="button"
class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded hidden">Close</button>
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-3 py-1 rounded text-sm text-white flex-grow border border-[rgba(255,255,255,0.15)]">
<button id="searchBtn" type="submit" class="btn-minecraft" style="display: none;">Search</button>
<button id="closeSearchBtn" type="button" class="btn-minecraft hidden">Close</button>
</div>
</form>
<div id="modResults" class="grid grid-cols-1 md:grid-cols-2 gap-4"></div>
<div id="pagination" class="mt-4 flex justify-center space-x-2"></div>
<h3 class="text-lg font-semibold mt-4">Installed Mods</h3>
<div class="mb-4 flex space-x-2">
<div id="pagination" class="pagination-container mt-3"></div>
<h3 class="text-lg minecraft-font mt-3">Installed Mods</h3>
<div class="mb-3 flex space-x-2">
<input id="modListSearch" type="text" placeholder="Search Installed Mods"
class="bg-gray-700 px-4 py-2 rounded text-white flex-grow">
<button id="clearModListSearch" type="button"
class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded hidden">Clear</button>
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-3 py-1 rounded text-sm text-white flex-grow border border-[rgba(255,255,255,0.15)]">
<button id="clearModListSearch" type="button" class="btn-minecraft hidden">Clear</button>
</div>
<div id="modList" class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-4"></div>
<div id="modListPagination" class="mt-4 flex justify-center space-x-2"></div>
<div id="modListPagination" class="pagination-container mt-3"></div>
</div>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
<h2 class="text-xl font-semibold mb-4">Server Links</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<p><strong>Advanced Log URL:</strong> <a id="logUrl" href="#" class="text-blue-400"
target="_blank">Loading...</a></p>
<p><strong>Website URL:</strong> <a id="websiteUrl" href="#" class="text-blue-400"
target="_blank">Loading...</a></p>
<p><strong>BlueMap URL:</strong> <a id="mapUrl" href="#" class="text-blue-400" target="_blank">Loading...</a>
<div class="section-bg mb-8 p-6 rounded-lg">
<h2 class="text-2xl minecraft-font mb-6 font-bold text-white">Server Links</h2>
<div class="flex flex-row gap-4 mb-4">
<!-- Connection Link Card -->
<div class="bg-gray-800/40 p-4 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-lg minecraft-font font-semibold text-white mb-2">Connection Link</h3>
<p class="text-gray-300 mb-3 break-all">
<span id="myLink" class="text-blue-400">Link Not Created</span>
<span id="connectionStatus" class="text-xs"></span>
</p>
<div>
<p><strong>Connection Link:</strong> <span id="myLink">Link Not Created</span> <span
id="connectionStatus"></span></p>
<button id="generateMyLinkBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-1 rounded mt-2">Generate
Connection Link</button>
<button id="generateMyLinkBtn" class="nav-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded-md transition-colors text-sm">
Generate Connection Link
</button>
</div>
<div>
<p><strong>Geyser Link:</strong> <span id="geyserLink">Link Not Created</span> <span
id="geyserStatus"></span></p>
<button id="generateGeyserLinkBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-1 rounded mt-2">Generate
Geyser Link</button>
<!-- Geyser Link Card -->
<div class="bg-gray-800/40 p-4 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-lg minecraft-font font-semibold text-white mb-2">Geyser Link</h3>
<p class="text-gray-300 mb-3 break-all">
<span id="geyserLink" class="text-blue-400">Link Not Created</span>
<span id="geyserStatus" class="text-xs"></span>
</p>
<button id="generateGeyserLinkBtn" class="nav-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded-md transition-colors text-sm">
Generate Geyser Link
</button>
</div>
<div>
<p><strong>SFTP Link:</strong> <span id="sftpLink">Link Not Created</span> <span id="sftpStatus"></span></p>
<button id="generateSftpLinkBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-1 rounded mt-2">Generate SFTP
Link</button>
<!-- SFTP Link Card -->
<div class="bg-gray-800/40 p-4 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-lg minecraft-font font-semibold text-white mb-2">SFTP Link</h3>
<p class="text-gray-300 mb-3 break-all">
<span id="sftpLink" class="text-blue-400">Link Not Created</span>
<span id="sftpStatus" class="text-xs"></span>
</p>
<button id="generateSftpLinkBtn" class="nav-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded-md transition-colors text-sm">
Generate SFTP Link
</button>
</div>
</div>
<div class="flex flex-row gap-4">
<!-- Advanced Log URL Card -->
<div class="bg-gray-800/40 p-4 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-lg minecraft-font font-semibold text-white mb-2">Advanced Log</h3>
<p class="text-gray-300 mb-3 break-all">
<a id="logUrl" href="#" class="text-blue-400 hover:underline" target="_blank">Loading...</a>
</p>
</div>
<!-- Website URL Card -->
<div class="bg-gray-800/40 p-4 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-lg minecraft-font font-semibold text-white mb-2">Website</h3>
<p class="text-gray-300 mb-3 break-all">
<a id="websiteUrl" href="#" class="text-blue-400 hover:underline" target="_blank">Loading...</a>
</p>
</div>
<!-- BlueMap URL Card -->
<div class="bg-gray-800/40 p-4 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-lg minecraft-font font-semibold text-white mb-2">BlueMap</h3>
<p class="text-gray-300 mb-3 break-all">
<a id="mapUrl" href="#" class="text-blue-400 hover:underline" target="_blank">Loading...</a>
</p>
</div>
</div>
</div>
<!-- Updated Holesail Keys Section -->
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-white">Holesail Keys</h2>
<button id="toggleTutorial"
class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">Tutorial</button>
<div class="section-bg mb-8">
<div class="flex items-center justify-between mb-3 flex-wrap">
<h2 class="text-2xl minecraft-font">Holesail Keys</h2>
<button id="toggleTutorial" class="nav-btn2 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-1 px-3 rounded-md transition duration-200 md:mt-0 mt-2">Tutorial</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="flex items-center bg-gray-700 p-3 rounded-md">
<div class="flex flex-row gap-4 mb-4">
<div class="flex items-center p-3 bg-gray-800 rounded-md min-w-0 flex-1">
<div class="flex-grow">
<p class="text-gray-300"><strong>Minecraft Key:</strong></p>
<p class="text-gray-100 break-all" id="holesailHash">Not Loaded</p>
<p class="text-sm text-gray-400">Port: 127.0.0.1:25565</p>
<p class="text-sm"><strong>Minecraft Key:</strong></p>
<p class="break-all text-sm" id="holesailHash">Not Loaded</p>
<p class="text-xs">Port: 127.0.0.1:25565</p>
</div>
<button class="copy-key-btn ml-2 text-gray-300 hover:text-white" data-key-id="holesailHash"
data-key-type="Minecraft" title="Copy">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
</path>
<button class="copy-key-btn ml-2" data-key-id="holesailHash" data-key-type="Minecraft" title="Copy">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
<div class="flex items-center bg-gray-700 p-3 rounded-md">
<div class="flex items-center p-3 bg-gray-800 rounded-md min-w-0 flex-1">
<div class="flex-grow">
<p class="text-gray-300"><strong>Geyser Key:</strong></p>
<p class="text-gray-100 break-all" id="geyserHash">Not Loaded</p>
<p class="text-sm text-gray-400">Port: 127.0.0.1:19132</p>
<p class="text-sm"><strong>Geyser Key:</strong></p>
<p class="break-all text-sm" id="geyserHash">Not Loaded</p>
<p class="text-xs">Port: 127.0.0.1:19132</p>
</div>
<button class="copy-key-btn ml-2 text-gray-300 hover:text-white" data-key-id="geyserHash"
data-key-type="Geyser" title="Copy">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
</path>
<button class="copy-key-btn ml-2" data-key-id="geyserHash" data-key-type="Geyser" title="Copy">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
<div class="flex items-center bg-gray-700 p-3 rounded-md">
<div class="flex items-center p-3 bg-gray-800 rounded-md min-w-0 flex-1">
<div class="flex-grow">
<p class="text-gray-300"><strong>SFTP Key:</strong></p>
<p class="text-gray-100 break-all" id="sftpHash">Not Loaded</p>
<p class="text-sm text-gray-400">Port: 127.0.0.1:22</p>
<p class="text-sm"><strong>SFTP Key:</strong></p>
<p class="break-all text-sm" id="sftpHash">Not Loaded</p>
<p class="text-xs">Port: 127.0.0.1:22</p>
</div>
<button class="copy-key-btn ml-2 text-gray-300 hover:text-white" data-key-id="sftpHash" data-key-type="SFTP"
title="Copy">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
</path>
<button class="copy-key-btn ml-2" data-key-id="sftpHash" data-key-type="SFTP" title="Copy">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
</button>
</div>
</div>
</div>
<div id="tutorialSection" class="hidden">
<div class="bg-gray-700 p-6 rounded-xl mb-6 border border-gray-600">
<h3 class="text-xl font-bold mb-4 text-white border-b border-gray-500 pb-2">What is Holesail.io?</h3>
<p class="text-gray-200 text-base leading-relaxed mb-4">
<div class="feature-card tilt-card p-6 mb-6">
<h3 class="text-xl minecraft-font mb-4">What is Holesail.io?</h3>
<p class="text-base leading-relaxed mb-4">
<a href="https://holesail.io" target="_blank"
class="text-blue-400 hover:underline font-medium">Holesail.io</a> is an open-source, peer-to-peer
networking tool that creates secure, encrypted tunnels that bypass network restrictions, firewalls, and
NAT.<BR>It exposes your local network to the internet without needing port forwarding, static IPs, or
Dynamic DNS, acting as a versatile tunneling and reverse proxy solution.
class="text-teal-400 hover:text-blue-400">Holesail.io</a> is an open-source,
peer-to-peer networking tool that creates secure, encrypted tunnels that bypass network
restrictions, firewalls, and NAT. It exposes your local network to the internet without
needing port forwarding, static IPs, or Dynamic DNS, acting as a versatile tunneling and
reverse proxy solution.
</p>
<div class="mb-4">
<p class="text-gray-200 font-semibold mb-2">With Holesail, you can:</p>
<ul class="list-disc pl-6 space-y-1 text-gray-200 text-base">
<p class="font-semibold mb-2">With Holesail, you can:</p>
<ul class="list-disc pl-6 space-y-1 text-base">
<li>Access machines over the internet securely.</li>
<li>Share locally running servers, websites, or AI models with other ports/services.</li>
<li>Share locally running servers, websites, or AI models with other ports/services.
</li>
<li>Transfer files and folders remotely without bandwidth or size limits.</li>
<li>Play LAN games like Minecraft with friends remotely.</li>
<li>Secure SSH servers by blocking IP access and using Holesail for connections.</li>
<li>Built for ANY application, Holesail supports both the TCP and UDP Protocols nativly.</li>
<li>Expose single ports to the peer-to-peer network, unlike VPNs you never expose your entire local
network.</li>
<BR>And so much more! With Holesail, the possiblities are endless!
<li>Built for ANY application, Holesail supports both the TCP and UDP Protocols
natively.</li>
<li>Expose single ports to the peer-to-peer network, unlike VPNs you never expose your
entire local network.</li>
</ul>
</div>
<p class="text-gray-200 text-base leading-relaxed">
Built with security in mind, Holesail ensures all data is encrypted and never touches third-party
servers.<BR>Connections are truly peer-to-peer, accessible only to those with whom you share your private
key, providing both ease of use and robust security.<BR>Other peers cannot detect your activity or
services.<BR>As an open-source tool, Holesail enables third-party services to integrate it, enhancing
their security and connectivity.
<p class="text-base leading-relaxed">
Built with security in mind, Holesail ensures all data is encrypted and never touches
third-party servers. Connections are truly peer-to-peer, accessible only to those with whom
you share your private key, providing both ease of use and robust security. Other peers
cannot detect your activity or services. As an open-source tool, Holesail enables
third-party services to integrate it, enhancing their security and connectivity.
</p>
<p class="text-gray-200 text-base leading-relaxed">
<BR>Your Public My-MC Ports are Holesail connections hosted on a separate server, not our Minecraft
host.<BR>They use the same keys from the tutorial below. We hook 'em up at our jump host to go public.
Pretty dope, right?
<p class="text-base leading-relaxed mt-4">
Your Public My-MC Ports are Holesail connections hosted on a separate server, not our
Minecraft host. They use the same keys from the tutorial below. We hook 'em up at our jump
host to go public.
</p>
</div>
<div class="bg-gray-700 p-4 rounded-md mb-6">
<h3 class="text-lg font-semibold mb-3 text-white">How to Use Holesail Keys</h3>
<ol class="list-decimal list-inside space-y-2 text-gray-300">
<li>Ensure <a href="https://nodejs.org/" target="_blank" class="text-blue-400 hover:underline">Node.js</a>
is installed on your system.</li>
<div class="feature-card tilt-card p-6 mb-6">
<h3 class="text-xl minecraft-font mb-4">How to Use Holesail Keys</h3>
<ol class="list-decimal list-inside space-y-2">
<li>Ensure <a href="https://nodejs.org/" target="_blank"
class="text-teal-400 hover:text-blue-400">Node.js</a> is installed on your system.
</li>
<li>Install Holesail by running:
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">npm i holesail@2.1.0</pre>
<pre
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] p-2 rounded mt-1 text-sm border border-[rgba(255,255,255,0.15)]">npm i holesail@2.1.0</pre>
</li>
<li>Connect to a key using the appropriate command:
<ul class="list-disc list-inside ml-4 mt-1">
<li>For Minecraft Key (e.g., Minecraft server):
<pre
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialHolesailHash">Not Loaded</span></pre>
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] p-2 rounded mt-1 text-sm border border-[rgba(255,255,255,0.15)]">holesail <span id="tutorialHolesailHash">Not Loaded</span></pre>
</li>
<li>For Geyser key (e.g., cross-platform Minecraft):
<pre
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialGeyserHash">Not Loaded</span></pre>
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] p-2 rounded mt-1 text-sm border border-[rgba(255,255,255,0.15)]">holesail <span id="tutorialGeyserHash">Not Loaded</span></pre>
</li>
<li>For SFTP key (e.g., file transfer):
<pre
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialSftpHash">Not Loaded</span></pre>
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] p-2 rounded mt-1 text-sm border border-[rgba(255,255,255,0.15)]">holesail <span id="tutorialSftpHash">Not Loaded</span></pre>
</li>
</ul>
</li>
<li class="holesail-output-mobile-hidden">Holesail will confirm the connection. Example output for your
Minecraft server:
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">
<li class="holesail-output-mobile-hidden">Holesail will confirm the connection. Example
output for your Minecraft server:
<pre
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] p-2 rounded mt-1 text-sm border border-[rgba(255,255,255,0.15)]">
~ holesail <span id="tutorialHolesailHashOutput">Not Loaded</span>
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| |
@@ -425,48 +499,49 @@
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
</pre>
</li>
<BR>Now you can connect to your own localhost within Minecraft!<BR><BR>Connect to:
<code class="bg-gray-800 px-1 rounded">127.0.0.1:25565</code><BR><BR>
<li>Now you can connect to your own localhost within Minecraft!<br>Connect to:
<code
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-1 rounded border border-[rgba(255,255,255,0.15)]">127.0.0.1:25565</code><br><br>
</li>
<li>To share the port over your internet IP as an open port, add the <code
class="bg-gray-800 px-1 rounded">--host 0.0.0.0</code> switch:
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-1 rounded border border-[rgba(255,255,255,0.15)]">--host 0.0.0.0</code>
switch:
<pre
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialHolesailHashHost">Not Loaded</span> --host 0.0.0.0</pre><BR>
Note: Using the --host 0.0.0.0 method will require you open your port within your router in order to serve the port from your IP Address remotely.
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] p-2 rounded mt-1 text-sm border border-[rgba(255,255,255,0.15)]">holesail <span id="tutorialHolesailHashHost">Not Loaded</span> --host 0.0.0.0</pre>
<br>Note: Using the --host 0.0.0.0 method will require you open your port within your
router in order to serve the port from your IP Address remotely.
</li>
</ol>
</div>
<div class="bg-gray-700 p-4 rounded-md">
<h3 class="text-lg font-semibold mb-3 text-white">Share with Friends</h3>
<p class="text-gray-300 mb-3">
Share this tutorial with your friends so they can connect to your Minecraft server or other services on
their own localhosts!<BR>With Holesail, no public IPs are needed! Everything stays secure and
peer-to-peer!
<div class="feature-card tilt-card p-6">
<h3 class="text-xl minecraft-font mb-4">Share with Friends</h3>
<p class="mb-3">
Share this tutorial with your friends so they can connect to your Minecraft server or other
services on their own localhosts! With Holesail, no public IPs are needed! Everything stays
secure and peer-to-peer!
</p>
<button id="copyTutorial"
class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">Copy
Tutorial</button>
<button id="copyTutorial" class="nav-btn">Copy Tutorial</button>
</div>
</div>
</div>
</main>
<footer class="bg-gray-800 py-4">
<div class="container mx-auto px-6 text-center">
<p class="text-gray-400 text-sm">
© 2025 My-MC.Link. All rights reserved.<br>
<a href="https://raven-scott.fyi" target="_blank" class="text-blue-400 hover:text-blue-500">Made with ❤️ by
SNXRaven</a> | <a href="https://git.ssh.surf/hypermc/panel" target="_blank"
class="text-blue-400 hover:text-blue-500">Source Code</a>
<footer class="bg-[rgba(10,17,40,0.75)] backdrop-filter backdrop-blur-xl py-8 text-center relative z-2">
<p class="text-sm opacity-90">© 2025 My-MC.Link. All rights reserved.</p>
<p class="text-sm opacity-90 mt-3">
Powered by <a href="https://holesail.io" class="underline text-teal-400 hover:text-blue-400"
target="_blank">Holesail</a> with services
donated by <a href="https://raven-scott.fyi"
class="underline text-teal-400 hover:text-blue-400">SNXRaven</a> |
<a href="https://git.ssh.surf/hypermc/panel" class="underline text-teal-400 hover:text-blue-400"
target="_blank">Source Code</a>
</p>
</div>
</footer>
</div>
<script src="js/app.js"></script>
<script src="js/tutorial.js"></script>
<script src="https://my-mc.link/js/main.js"></script>
</body>

View File

@@ -28,6 +28,61 @@ document.addEventListener('DOMContentLoaded', () => {
'enable-status'
];
const sections = document.querySelectorAll('.section-bg');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
entry.target.style.transform = 'translateY(0)';
entry.target.style.opacity = '1';
entry.target.style.transitionDelay = `${index * 0.1}s`;
}
});
}, { threshold: 0.1 });
function throttle(fn, wait) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= wait) {
fn.apply(this, args);
lastTime = now;
}
};
}
// Hamburger Menu Toggle
const hamburger = document.querySelector('.hamburger');
const mobileNav = document.querySelector('[data-mobile-nav]');
const navLinks = mobileNav.querySelectorAll('a');
// Debounce function to prevent rapid clicks
function debounce(fn, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), wait);
};
}
hamburger.addEventListener('click', debounce(() => {
mobileNav.classList.toggle('active');
hamburger.classList.toggle('active');
}, 100));
navLinks.forEach(link => {
link.addEventListener('click', () => {
mobileNav.classList.remove('active');
hamburger.classList.remove('active');
});
});
document.addEventListener('click', (e) => {
if (!mobileNav.contains(e.target) && !hamburger.contains(e.target) && mobileNav.classList.contains('active')) {
mobileNav.classList.remove('active');
hamburger.classList.remove('active');
}
});
const elements = {
loginPage: document.getElementById('loginPage'),
loginApiKey: document.getElementById('loginApiKey'),
@@ -321,7 +376,7 @@ document.addEventListener('DOMContentLoaded', () => {
terminal.clear();
} else {
terminal = new Terminal({
rows: 8,
rows: 22,
fontSize: 14,
fontFamily: 'monospace',
theme: {
@@ -497,17 +552,56 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function updateDockerUI(message) {
if (message.error) {
console.log('Docker error detected, setting status to Not Running');
if (elements.serverStatus) elements.serverStatus.textContent = 'Not Running';
toggleSections('Not Running');
return;
}
const memoryPercent = parseFloat(message.data?.memory?.percent) || 0;
if (state.memoryPercent !== memoryPercent && elements.memoryPercent && memoryMeter) {
memoryMeter.data.datasets[0].data = [memoryPercent, 100 - memoryPercent];
memoryMeter.update();
elements.memoryPercent.textContent = `${memoryPercent.toFixed(1)}%`;
state.memoryPercent = memoryPercent;
}
const cpuPercent = parseFloat(message.data?.cpu) || 0;
if (state.cpuPercent !== cpuPercent && elements.cpuPercent && cpuMeter) {
const scaledCpuPercent = Math.min((cpuPercent / 600) * 100, 100);
cpuMeter.data.datasets[0].data = [scaledCpuPercent, 100 - scaledCpuPercent];
cpuMeter.update();
elements.cpuPercent.textContent = `${cpuPercent.toFixed(1)}%`;
state.cpuPercent = cpuPercent;
}
const status = message.data?.status || 'Unknown';
if (elements.serverStatus) {
elements.serverStatus.textContent = status;
state.serverStatus = status;
toggleSections(status);
}
}
function toggleSections(status) {
const sections = document.querySelectorAll('.bg-gray-800.p-6.rounded-lg.shadow-lg.mb-6');
const serverStatusSection = document.querySelector('.bg-gray-800.p-6.rounded-lg.shadow-lg.mb-6[data-section="server-status"]');
const sections = document.querySelectorAll('.section-bg');
const serverStatusSection = document.querySelector('.section-bg[data-section="server-status"]');
const editPropertiesBtn = elements.editPropertiesBtn;
const updateModsBtn = elements.updateModsBtn;
const backupBtn = elements.backupBtn;
const sftpBtn = elements.sftpBtn;
const startBtn = document.getElementById('startBtn');
const stopBtn = elements.stopBtn;
const restartBtn = elements.restartBtn;
const sftpBrowserSection = elements.sftpBrowserSection;
// Menu items in desktop and mobile nav
const refreshBtn = elements.refresh || document.getElementById('refresh');
const mobileRefreshBtn = elements.mobileRefresh || document.getElementById('mobileRefresh');
const backupBtn = elements.backupBtn;
const mobileBackupBtn = elements.mobileBackupBtn;
const logoutBtn = elements.logoutBtn || document.getElementById('logoutBtn');
const mobileLogoutBtn = elements.mobileLogoutBtn || document.getElementById('mobileLogoutBtn');
if (startBtn) {
if (status.toLowerCase() === 'running') {
@@ -520,19 +614,20 @@ document.addEventListener('DOMContentLoaded', () => {
}
if (status.toLowerCase() !== 'running') {
// Hide all sections except Server Status
sections.forEach(section => {
if (section !== serverStatusSection) {
section.classList.add('hidden');
}
});
// Hide control buttons
if (editPropertiesBtn) {
editPropertiesBtn.classList.add('hidden');
editPropertiesBtn.style.display = 'none';
}
if (updateModsBtn) {
updateModsBtn.classList.add('hidden');
}
if (backupBtn) {
backupBtn.classList.add('hidden');
updateModsBtn.style.display = 'none';
}
if (sftpBtn) {
sftpBtn.classList.add('hidden');
@@ -548,22 +643,46 @@ document.addEventListener('DOMContentLoaded', () => {
if (sftpBrowserSection) {
sftpBrowserSection.style.display = 'none';
}
// Hide Refresh, Backup, and Logout buttons in desktop nav
if (refreshBtn) {
refreshBtn.classList.add('hidden');
refreshBtn.style.display = 'none';
}
if (backupBtn) {
backupBtn.classList.add('hidden');
backupBtn.style.display = 'none';
}
if (logoutBtn) {
logoutBtn.classList.add('hidden');
logoutBtn.style.display = 'none';
}
// Hide Refresh, Backup, and Logout menu items in mobile nav
if (mobileRefreshBtn && mobileRefreshBtn.parentElement) {
mobileRefreshBtn.parentElement.classList.add('hidden');
}
if (mobileBackupBtn && mobileBackupBtn.parentElement) {
mobileBackupBtn.parentElement.classList.add('hidden');
}
if (mobileLogoutBtn && mobileLogoutBtn.parentElement) {
mobileLogoutBtn.parentElement.classList.add('hidden');
}
if (!state.hasShownStartNotification) {
showNotification('Server is stopped. Click "Start" to enable all features.', 'error', 'server-stopped');
state.hasShownStartNotification = true;
}
} else {
// Show all sections
sections.forEach(section => {
section.classList.remove('hidden');
});
// Show control buttons
if (editPropertiesBtn) {
editPropertiesBtn.classList.remove('hidden');
editPropertiesBtn.style.display = '';
}
if (updateModsBtn) {
updateModsBtn.classList.remove('hidden');
}
if (backupBtn) {
backupBtn.classList.remove('hidden');
updateModsBtn.style.display = '';
}
if (sftpBtn) {
sftpBtn.classList.remove('hidden');
@@ -574,11 +693,34 @@ document.addEventListener('DOMContentLoaded', () => {
}
if (restartBtn) {
restartBtn.disabled = false;
restartBtn.classList.remove('disabled-btn');
stopBtn.classList.remove('disabled-btn');
}
if (sftpBrowserSection) {
sftpBrowserSection.style.display = 'block';
}
// Show Refresh, Backup, and Logout buttons in desktop nav
if (refreshBtn) {
refreshBtn.classList.remove('hidden');
refreshBtn.style.display = '';
}
if (backupBtn) {
backupBtn.classList.remove('hidden');
refreshBtn.style.display = '';
}
if (logoutBtn) {
logoutBtn.classList.remove('hidden');
logoutBtn.style.display = '';
}
// Show Refresh, Backup, and Logout menu items in mobile nav
if (mobileRefreshBtn && mobileRefreshBtn.parentElement) {
mobileRefreshBtn.parentElement.classList.remove('hidden');
}
if (mobileBackupBtn && mobileBackupBtn.parentElement) {
mobileBackupBtn.parentElement.classList.remove('hidden');
}
if (mobileLogoutBtn && mobileLogoutBtn.parentElement) {
mobileLogoutBtn.parentElement.classList.remove('hidden');
}
state.hasShownStartNotification = false;
}
}
@@ -751,7 +893,9 @@ document.addEventListener('DOMContentLoaded', () => {
? players.map(player => `
<div class="flex items-center space-x-4 mb-2">
<span class="w-32">${player}</span>
<div class="flex flex-wrap gap-2">
<div class="flex flex-col gap-2">
<button class="toggle-actions bg-gray-600 hover:bg-gray-700 px-2 py-1 rounded text-sm" data-player="${player}">Actions</button>
<div class="action-buttons hidden flex-wrap gap-2" data-player="${player}">
<button class="tell-player bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-sm" data-player="${player}">Tell</button>
<button class="give-player bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-sm" data-player="${player}">Give</button>
<button class="teleport-player bg-cyan-600 hover:bg-cyan-700 px-2 py-1 rounded text-sm ${isSinglePlayer ? 'opacity-50 cursor-not-allowed' : ''}" data-player="${player}" ${isSinglePlayer ? 'disabled' : ''}>Teleport</button>
@@ -760,6 +904,8 @@ document.addEventListener('DOMContentLoaded', () => {
<button class="deop-player bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm" data-player="${player}">Deop</button>
<button class="kick-player bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm" data-player="${player}">Kick</button>
<button class="ban-player bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm" data-player="${player}">Ban</button>
<button class="close-actions bg-gray-600 hover:bg-gray-700 px-2 py-1 rounded text-sm" data-player="${player}">Close</button>
</div>
</div>
</div>
`).join('')
@@ -768,6 +914,31 @@ document.addEventListener('DOMContentLoaded', () => {
elements.playerList.innerHTML = playerListHtml;
state.playerList = playerListHtml;
// Toggle action buttons
document.querySelectorAll('.toggle-actions').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
const actionDiv = document.querySelector(`.action-buttons[data-player="${player}"]`);
if (actionDiv) {
actionDiv.classList.toggle('hidden');
button.textContent = actionDiv.classList.contains('hidden') ? 'Actions' : 'Hide';
}
});
});
// Close action buttons
document.querySelectorAll('.close-actions').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
const actionDiv = document.querySelector(`.action-buttons[data-player="${player}"]`);
const toggleButton = document.querySelector(`.toggle-actions[data-player="${player}"]`);
if (actionDiv && toggleButton) {
actionDiv.classList.add('hidden');
toggleButton.textContent = 'Actions';
}
});
});
document.querySelectorAll('.tell-player').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
@@ -1038,9 +1209,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
elements.loginPage.classList.remove('hidden');
elements.mainContent.classList.add('hidden');
elements.authControls.innerHTML = '<input id="apiKey" type="text" placeholder="Enter API Key" class="bg-gray-700 px-4 py-2 rounded text-white">';
elements.apiKeyInput = document.getElementById('apiKey');
elements.apiKeyInput.addEventListener('change', handleApiKeyChange);
if (ws) {
ws.close();
ws = null;
@@ -1069,13 +1238,35 @@ document.addEventListener('DOMContentLoaded', () => {
}
elements.loginPage.classList.add('hidden');
elements.mainContent.classList.remove('hidden');
elements.authControls.innerHTML = '<button id="logoutBtn" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded">Logout</button>';
// Verify buttons exist
const logoutBtn = document.getElementById('logoutBtn');
const mobileLogoutBtn = document.getElementById('mobileLogoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', handleLogout);
console.log('Desktop logout button found');
} else {
console.error('Logout button not found after insertion');
console.error('Desktop logout button (#logoutBtn) not found');
}
if (mobileLogoutBtn) {
console.log('Mobile logout button found');
} else {
console.error('Mobile logout button (#mobileLogoutBtn) not found');
}
// Remove any existing logout listeners to prevent duplicates
document.removeEventListener('click', handleLogoutClick);
// Event delegation for logout buttons
function handleLogoutClick(event) {
if (event.target.id === 'logoutBtn' || event.target.id === 'mobileLogoutBtn') {
console.log(`Logout button clicked: ${event.target.id}`);
handleLogout();
}
}
document.addEventListener('click', handleLogoutClick);
initializeCharts();
initializeTerminal();
}
@@ -1095,6 +1286,18 @@ document.addEventListener('DOMContentLoaded', () => {
showLoginPage();
}
async function handleRefresh() {
if (ws && ws.readyState === WebSocket.OPEN) {
const key = `action-refresh`;
const notification = showNotification('Refreshing server data...', 'loading', key);
ws.send(JSON.stringify({ type: 'refresh' }));
initializeTerminal();
setTimeout(() => updateNotification(notification, 'Server data refreshed successfully', 'success', key), 1000);
} else {
showNotification('Not connected to server. Please log in.', 'error', 'ws-disconnected');
}
}
function updatePagination() {
const totalPages = Math.max(1, Math.ceil(totalResults / resultsPerPage));
elements.pagination.innerHTML = '';
@@ -1102,9 +1305,9 @@ document.addEventListener('DOMContentLoaded', () => {
const createPageButton = (page, text, disabled = false) => {
const button = document.createElement('button');
button.textContent = text;
button.className = `px-3 py-1 rounded ${disabled || page === currentPage
? 'bg-gray-600 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
button.className = `nav-btn ${disabled || page === currentPage
? 'disabled'
: 'active'
}`;
if (!disabled && page !== currentPage) {
button.addEventListener('click', () => {
@@ -1137,9 +1340,9 @@ document.addEventListener('DOMContentLoaded', () => {
const createPageButton = (page, text, disabled = false) => {
const button = document.createElement('button');
button.textContent = text;
button.className = `px-3 py-1 rounded ${disabled || page === modListCurrentPage
? 'bg-gray-600 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
button.className = `nav-btn ${disabled || page === modListCurrentPage
? 'disabled'
: 'active'
}`;
if (!disabled && page !== modListCurrentPage) {
button.addEventListener('click', () => {
@@ -1648,9 +1851,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (filteredSettings.includes(key)) {
return;
}
console.log(`Rendering field for ${key}: ${value}`);
const fieldDiv = document.createElement('div');
fieldDiv.className = 'flex items-center space-x-2';
fieldDiv.style.display = 'flex';
@@ -1687,7 +1887,6 @@ document.addEventListener('DOMContentLoaded', () => {
switchContainer.className = 'relative inline-block';
switchContainer.setAttribute('role', 'switch');
switchContainer.setAttribute('aria-checked', value.toLowerCase());
switchContainer.setAttribute('tabindex', '0');
switchContainer.dataset.name = key;
switchContainer.style.width = '40px';
switchContainer.style.height = '24px';
@@ -1918,7 +2117,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
elements.apiKeyInput.addEventListener('change', handleApiKeyChange);
// elements.apiKeyInput.addEventListener('change', handleApiKeyChange);
elements.generateMyLinkBtn.addEventListener('click', async () => {
try {
@@ -2010,6 +2209,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
document.getElementById('stopBtn').addEventListener('click', async () => {
try {
const key = `action-stop`;
@@ -2065,6 +2265,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
elements.updateModsBtn.addEventListener('click', updateMods);
elements.closeUpdateModsBtn.addEventListener('click', () => {
@@ -2177,4 +2379,42 @@ document.addEventListener('DOMContentLoaded', () => {
showLoginPage();
}
window.showNotification = showNotification;
// Add this at the end of the document.addEventListener('DOMContentLoaded', ...) block
function applyResponsiveStyles() {
const sections = document.querySelectorAll('.section-bg');
sections.forEach(section => {
section.style.padding = window.innerWidth <= 640 ? '1rem' : window.innerWidth <= 768 ? '1.5rem' : '2rem';
section.style.maxWidth = '100%';
section.style.overflowX = 'hidden';
});
const iframes = document.querySelectorAll('#sftpIframe');
iframes.forEach(iframe => {
iframe.style.height = window.innerWidth <= 640 ? '300px' : window.innerWidth <= 768 ? '400px' : '650px';
iframe.style.minHeight = iframe.style.height;
iframe.style.width = '100%';
iframe.style.maxWidth = '100%';
});
const canvases = document.querySelectorAll('#memoryMeter, #cpuMeter');
canvases.forEach(canvas => {
canvas.style.width = window.innerWidth <= 640 ? '80px' : window.innerWidth <= 768 ? '100px' : '150px';
canvas.style.height = canvas.style.width;
});
const terminals = document.querySelectorAll('#dockerLogsTerminal');
terminals.forEach(terminal => {
terminal.style.maxHeight = window.innerWidth <= 640 ? '7rem' : window.innerWidth <= 768 ? '8rem' : '12rem';
});
const consoles = document.querySelectorAll('#consoleOutput');
consoles.forEach(console => {
console.style.height = window.innerWidth <= 640 ? '7rem' : window.innerWidth <= 768 ? '8rem' : '12rem';
});
}
window.addEventListener('resize', applyResponsiveStyles);
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 KiB

After

Width:  |  Height:  |  Size: 942 KiB