Move panel to website theme

This commit is contained in:
MCHost
2025-07-06 10:27:26 -04:00
parent 1b58acb38b
commit 82c5970060
6 changed files with 3418 additions and 935 deletions

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

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
@import "tailwindcss";
@font-face {
font-family: 'Minecraft';
src: url('../font/MinecraftRegular-Bmg3.otf') format('opentype');
@ -5,148 +7,69 @@
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;
margin: 0;
padding: 0;
box-sizing: border-box;
overflow-x: hidden;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
@apply min-h-full flex flex-col;
@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 {
font-family: 'Minecraft', sans-serif;
@apply font-[Minecraft] text-white;
letter-spacing: 0.5px;
}
#app {
@apply flex-1 flex flex-col;
}
main {
@apply flex-grow;
}
main {
@apply flex-grow container mx-auto px-4 sm:px-6 lg:px-8 py-6;
}
footer {
@apply flex-shrink-0;
@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 {
.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;
.section-bg {
@apply bg-gray-800 p-6 rounded-lg shadow-lg mb-6 w-full max-w-full;
overflow-x: hidden;
}
.modal {
@ -159,6 +82,13 @@
scrollbar-color: #4B5563 #1F2937;
}
@media (max-width: 640px) {
.holesail-output-mobile-hidden {
display: none;
}
}
.modal-content::-webkit-scrollbar {
@apply w-2;
}
@ -179,97 +109,315 @@
@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;
.control-btn {
@apply bg-blue-600 hover:bg-blue-700 text-white font-medium px-4 py-2 rounded transition duration-200;
}
.disabled-btn:hover {
@apply cursor-not-allowed;
.spinner {
@apply border-4 border-gray-700 border-t-blue-600 rounded-full w-6 h-6 animate-spin inline-block;
}
/* Search box and properties fields */
#searchContainer {
@apply mb-4 block;
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#propertiesSearch {
@apply bg-gray-700 px-4 py-2 rounded text-white w-full block;
#notificationContainer {
@apply fixed bottom-4 right-4 z-[1000] flex flex-col-reverse gap-2;
}
#propertiesList {
@apply space-y-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;
}
/* 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 {
.notification.success {
@apply bg-green-600;
}
[role="switch"][aria-checked="false"] > div:first-child {
@apply bg-gray-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; /* Increase height to make the section taller */
overflow-y: hidden; /* Remove vertical scrollbar */
overflow-x: hidden; /* Maintain no horizontal scrollbar */
}
.xterm .xterm-viewport {
@apply overflow-y-auto;
height: 250px; /* Increase height to make the section taller */
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-6;
@apply left-5;
}
[role="switch"][aria-checked="false"] > div:last-child {
@apply left-1;
}
#sftpIframe {
@apply w-full rounded;
height: 50vh;
min-height: 300px;
}
@layer utilities {
/* Custom utilities */
#propertiesSearch {
@apply bg-gray-700 p-2 rounded text-white w-full;
}
/* Media queries */
@media (max-width: 640px) {
.bg-gray-800.p-6.rounded-lg.shadow-lg .grid {
#propertiesList {
@apply space-y-2;
}
}
.nav-btn {
@apply bg-blue-600 hover:bg-blue-700 text-white font-medium px-4 py-2 rounded transition duration-200;
}
.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;
}
@media (max-width: 768px) {
.section-bg {
@apply p-4;
}
.modal-content {
@apply p-4 max-w-[95%];
}
.section-bg .grid {
@apply grid-cols-1;
}
.bg-gray-800.p-6.rounded-lg.shadow-lg p {
@apply flex flex-col gap-2 break-all;
.section-bg .flex {
@apply flex-col gap-2;
}
.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;
canvas {
@apply max-w-[120px];
}
.bg-gray-800.p-6.rounded-lg.shadow-lg button {
@apply w-full mt-2;
.section-bg input,
.section-bg textarea,
.section-bg select {
@apply p-2 text-sm;
}
.tell-player,
.give-player,
.teleport-player,
.effect-player,
.op-player,
.deop-player,
.kick-player,
.ban-player {
@apply w-full text-center mt-1;
.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;
}
}
/* Additional styles */
.bg-gray-800.p-6.rounded-lg.shadow-lg .grid {
@apply overflow-x-hidden;
@media (max-width: 640px) {
.section-bg {
@apply p-3;
}
.bg-gray-800.p-6.rounded-lg.shadow-lg p {
@apply max-w-full;
.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

@ -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/styles.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="btn-minecraft 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="btn-minecraft 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="btn-minecraft 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="btn-minecraft 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="btn-minecraft 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="btn-minecraft w-full">Apply Effect</button>
</form>
</div>
</div>
@ -120,220 +194,204 @@
<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="btn-minecraft 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">
<!-- 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-6 sm:p-8 mb-8" data-section="server-status">
<div class="flex flex-col space-y-2 mb-4">
<div class="flex justify-between items-center">
<h2 class="text-3xl minecraft-font">Server Status</h2>
<div class="flex space-x-2">
<button id="startBtn" class="control-btn">Start</button>
<button id="stopBtn" class="control-btn">Stop</button>
<button id="restartBtn" class="control-btn">Restart</button>
</div>
</div>
<div class="flex justify-end space-x-2">
<button id="editPropertiesBtn" class="control-btn">Edit Server Properties</button>
<button id="updateModsBtn" class="control-btn">Update Mods</button>
</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>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex flex-col space-y-1">
<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-4 justify-center items-start">
<div class="text-center">
<canvas id="memoryMeter" width="150" height="150"></canvas>
<p class="text-sm mt-2"><b>Memory Usage</b></p>
<canvas id="memoryMeter" width="120" height="120"></canvas>
<p class="text-sm mt-1"><b>Memory Usage</b></p>
<p id="memoryPercent" class="text-lg 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>
<canvas id="cpuMeter" width="120" height="120"></canvas>
<p class="text-sm mt-1"><b>CPU Usage</b></p>
<p id="cpuPercent" class="text-lg 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>
<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>
<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">
<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>
<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-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>
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="btn-minecraft" style="display: none;">Send</button>
</form>
<pre id="consoleOutput" class="bg-gray-900 p-4 rounded mt-4 h-48 overflow-y-auto"></pre>
<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="bg-gray-800 p-6 rounded-lg shadow-lg mb-6" id="sftpBrowserSection" style="display: none;">
<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="control-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-12">
<h2 class="text-3xl minecraft-font mb-8">Mod Management</h2>
<form id="modSearchForm" onsubmit="event.preventDefault(); searchMods(1);">
<div class="mb-4 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"
class="bg-[rgba(10,17,40,0.3)] backdrop-blur-[12px] px-4 py-2 rounded 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="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded hidden">Close</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="modResults" class="grid grid-cols-1 md:grid-cols-2 gap-6"></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>
<h3 class="text-xl minecraft-font mt-4">Installed Mods</h3>
<div class="mb-4 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-4 py-2 rounded 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="modList" class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-6"></div>
<div id="modListPagination" class="mt-4 flex justify-center space-x-2"></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-12 p-8 rounded-lg">
<h2 class="text-3xl minecraft-font mb-10 font-bold text-white">Server Links</h2>
<div class="flex flex-wrap gap-6 mb-6">
<!-- Connection Link Card -->
<div class="bg-gray-800/40 p-6 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-xl minecraft-font font-semibold text-white mb-3">Connection Link</h3>
<p class="text-gray-300 mb-4 break-all">
<span id="myLink" class="text-blue-400">Link Not Created</span>
<span id="connectionStatus" class="text-sm"></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="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors">
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-6 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-xl minecraft-font font-semibold text-white mb-3">Geyser Link</h3>
<p class="text-gray-300 mb-4 break-all">
<span id="geyserLink" class="text-blue-400">Link Not Created</span>
<span id="geyserStatus" class="text-sm"></span>
</p>
<button id="generateGeyserLinkBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors">
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-6 rounded-xl shadow-md hover:shadow-lg transition-shadow flex-1 min-w-[250px]">
<h3 class="text-xl minecraft-font font-semibold text-white mb-3">SFTP Link</h3>
<p class="text-gray-300 mb-4 break-all">
<span id="sftpLink" class="text-blue-400">Link Not Created</span>
<span id="sftpStatus" class="text-sm"></span>
</p>
<button id="generateSftpLinkBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors">
Generate SFTP Link
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Advanced Log URL Card -->
<div class="bg-gray-800/40 p-6 rounded-xl shadow-md hover:shadow-lg transition-shadow">
<h3 class="text-xl minecraft-font font-semibold text-white mb-3">Advanced Log</h3>
<p class="text-gray-300 mb-4 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-6 rounded-xl shadow-md hover:shadow-lg transition-shadow">
<h3 class="text-xl minecraft-font font-semibold text-white mb-3">Website</h3>
<p class="text-gray-300 mb-4 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-6 rounded-xl shadow-md hover:shadow-lg transition-shadow">
<h3 class="text-xl minecraft-font font-semibold text-white mb-3">BlueMap</h3>
<p class="text-gray-300 mb-4 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>
<!-- Holesail Keys Section -->
<div class="section-bg mb-12">
<div class="flex items-center justify-between mb-4 flex-wrap">
<h2 class="text-3xl minecraft-font">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>
class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 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="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="flex items-center p-4">
<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><strong>Minecraft Key:</strong></p>
<p class="break-all" id="holesailHash">Not Loaded</p>
<p class="text-sm">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>
</svg>
</button>
</div>
<div class="flex items-center bg-gray-700 p-3 rounded-md">
<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>
</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>
</svg>
</button>
</div>
<div class="flex items-center bg-gray-700 p-3 rounded-md">
<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>
</div>
<button class="copy-key-btn ml-2 text-gray-300 hover:text-white" data-key-id="sftpHash" data-key-type="SFTP"
<button class="copy-key-btn ml-2" 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">
@ -343,73 +401,108 @@
</svg>
</button>
</div>
<div class=" flex items-center p-4">
<div class="flex-grow">
<p><strong>Geyser Key:</strong></p>
<p class="break-all" id="geyserHash">Not Loaded</p>
<p class="text-sm">Port: 127.0.0.1:19132</p>
</div>
<button class="copy-key-btn ml-2" 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>
</svg>
</button>
</div>
<div class="flex items-center p-4">
<div class="flex-grow">
<p><strong>SFTP Key:</strong></p>
<p class="break-all" id="sftpHash">Not Loaded</p>
<p class="text-sm">Port: 127.0.0.1:22</p>
</div>
<button class="copy-key-btn ml-2" 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>
</svg>
</button>
</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,49 +518,48 @@
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
</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="btn-minecraft">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/app_test.js"></script>
<script src="js/tutorial.js"></script>
</body>
</html>

View File

@ -28,6 +28,137 @@ 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 });
// sections.forEach(section => {
// section.style.transform = 'translateY(30px)';
// section.style.opacity = '0';
// section.style.transition = 'transform 0.7s ease-out, opacity 0.7s ease-out';
// observer.observe(section);
// });
// const PARTICLE_POOL_SIZE = 50;
// const particlePool = [];
// const activeParticles = new Set();
// function createParticleElement() {
// const particle = document.createElement('div');
// particle.classList.add('particle');
// if (Math.random() > 0.6) particle.classList.add('large');
// return particle;
// }
// function initializeParticlePool() {
// for (let i = 0; i < PARTICLE_POOL_SIZE; i++) {
// particlePool.push(createParticleElement());
// }
// }
// function resetParticle(particle) {
// particle.style.left = `${Math.random() * 100}%`;
// particle.style.top = `${Math.random() * 100}%`;
// particle.style.animationDelay = `${Math.random() * 8}s`;
// particle.style.animationDuration = `${8 + Math.random() * 6}s`;
// particle.classList.remove('fade-out');
// return particle;
// }
// function spawnParticle() {
// if (particlePool.length === 0 || activeParticles.size >= PARTICLE_POOL_SIZE) return;
// const particle = resetParticle(particlePool.pop());
// if (!particle.parentNode) document.body.appendChild(particle);
// activeParticles.add(particle);
// setTimeout(() => {
// particle.classList.add('fade-out');
// setTimeout(() => {
// activeParticles.delete(particle);
// particlePool.push(particle);
// }, 500);
// }, 14000);
// }
// function animate() {
// if (Math.random() < 0.1) spawnParticle(); // Reduced spawn frequency
// requestAnimationFrame(animate);
// }
// // Initialize and start
// setTimeout(() => {
// initializeParticlePool();
// animate();
// }, 500);
function throttle(fn, wait) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= wait) {
fn.apply(this, args);
lastTime = now;
}
};
}
// const buttons = document.querySelectorAll('.btn-minecraft');
// buttons.forEach(button => {
// button.addEventListener('mousemove', (e) => {
// const rect = button.getBoundingClientRect();
// const x = e.clientX - rect.left;
// const y = e.clientY - rect.top;
// button.style.setProperty('--x', `${x}px`);
// button.style.setProperty('--y', `${y}px`);
// });
// });
// 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(() => {
console.log('Hamburger clicked, toggling menu');
mobileNav.classList.toggle('active');
hamburger.classList.toggle('active');
}, 100));
navLinks.forEach(link => {
link.addEventListener('click', () => {
console.log('Nav link clicked, closing menu');
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')) {
console.log('Clicked outside, closing menu');
mobileNav.classList.remove('active');
hamburger.classList.remove('active');
}
});
const elements = {
loginPage: document.getElementById('loginPage'),
loginApiKey: document.getElementById('loginApiKey'),
@ -321,7 +452,7 @@ document.addEventListener('DOMContentLoaded', () => {
terminal.clear();
} else {
terminal = new Terminal({
rows: 8,
rows: 22,
fontSize: 14,
fontFamily: 'monospace',
theme: {
@ -1038,9 +1169,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 +1198,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 +1246,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 = '';
@ -1687,7 +1850,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 +2080,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
elements.apiKeyInput.addEventListener('change', handleApiKeyChange);
// elements.apiKeyInput.addEventListener('change', handleApiKeyChange);
elements.generateMyLinkBtn.addEventListener('click', async () => {
try {
@ -2065,6 +2227,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
elements.updateModsBtn.addEventListener('click', updateMods);
elements.closeUpdateModsBtn.addEventListener('click', () => {
@ -2177,4 +2341,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);
});

View File

@ -4,6 +4,7 @@ module.exports = {
'./public/**/*.html',
'./public/**/*.js',
'./public/*.html',
'./public/css/*.css',
'./public/*.js'
],
theme: {