Files
panel/public/index.html
2025-06-26 04:24:45 -04:00

489 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8">
<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="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="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">
</head>
<body class="bg-gray-900 text-white overflow-x-hidden min-h-full flex flex-col">
<div id="app" class="flex-grow">
<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>
<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">
</div>
<button id="loginBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Login</button>
<p id="loginError" class="text-red-500 text-sm mt-2 hidden"></p>
</div>
</div>
<div id="notificationContainer"></div>
<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>
<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>
</form>
</div>
</div>
<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>
<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">
<option value="custom">Custom</option>
<option value="starter">Starter Kit (Torches, Food)</option>
<option value="builder">Builder Kit (Stone, Wood)</option>
<option value="combat">Combat Kit (Sword, Armor)</option>
<option value="miner">Miner Kit (Pickaxe, Torches, Shovel)</option>
<option value="adventurer">Adventurer Kit (Bow, Arrows, Compass)</option>
<option value="alchemist">Alchemist Kit (Potions, Brewing Stand)</option>
<option value="enchanter">Enchanter Kit (Books, Lapis, Enchanting Table)</option>
<option value="farmer">Farmer Kit (Seeds, Hoe, Bone Meal)</option>
<option value="nether">Nether Survival Kit (Fire Resistance, Obsidian)</option>
<option value="end">End Prep Kit (Ender Pearls, Blaze Rods)</option>
</select>
</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>
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Give</button>
</form>
</div>
</div>
<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>
<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">
<!-- 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>
</form>
</div>
</div>
<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>
<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">
<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>
<option value="jump_boost:30:1">Jump Boost (30s, Level 1)</option>
<option value="invisibility:30:1">Invisibility (30s, Level 1)</option>
<option value="night_vision:60:1">Night Vision (60s, Level 1)</option>
<option value="fire_resistance:60:1">Fire Resistance (60s, Level 1)</option>
<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>
</form>
</div>
</div>
<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>
<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>
</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>
<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>
</div>
</div>
</div>
<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 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>
<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="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>
</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>
</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>
<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 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>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6" 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>
<span>Refresh SFTP</span>
</button>
</div>
<iframe id="sftpIframe" class="w-full rounded" style="height: 650px; min-height: 650px;"
sandbox="allow-same-origin allow-scripts allow-downloads allow-popups"
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>
<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"
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>
</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">
<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>
</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>
<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>
</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>
</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>
</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>
</div>
</div>
</div>
<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>
<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-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>
</div>
<button onclick="navigator.clipboard.writeText(document.getElementById('holesailHash').textContent)" class="ml-2 text-gray-300 hover:text-white" 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 onclick="navigator.clipboard.writeText(document.getElementById('geyserHash').textContent)" class="ml-2 text-gray-300 hover:text-white" 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 onclick="navigator.clipboard.writeText(document.getElementById('sftpHash').textContent)" class="ml-2 text-gray-300 hover:text-white" 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">
<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.
</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">
<li>* Access machines over the internet securely.</li>
<li>* Share locally running servers, websites, or AI models with others.</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>
</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>
<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>
</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>
<li>Install Holesail by running:
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">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>
</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>
</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>
</li>
</ul>
</li>
<li>Holesail will confirm the connection. Example output for your Minecraft server:
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">
~ holesail <span id="tutorialHolesailHashOutput">Not Loaded</span>
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| |
| |
| Holesail TCP Client Started ⛵️ |
| Connection Mode: Private Connection String |
| Access application on http://127.0.0.1:25565/ |
| Connected to key: <span id="tutorialHolesailHashOutput2">Not Loaded</span> |
| NOTE: TREAT PRIVATE CONNECTION STRINGS HOW YOU WOULD TREAT SSH KEY, DO NOT SHARE IT WITH ANYONE YOU DO NOT TRUST |
| |
| |
| |
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
</pre>
</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:
<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>
</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!
</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>
<p id="copyNotification" class="hidden text-green-400 mt-2 text-sm">Tutorial copied to clipboard!</p>
</div>
</div>
<script>
// Function to update tutorial spans when key display spans are populated
function updateTutorialSpans() {
const holesailHash = document.getElementById('holesailHash').textContent;
const geyserHash = document.getElementById('geyserHash').textContent;
const sftpHash = document.getElementById('sftpHash').textContent;
document.getElementById('tutorialHolesailHash').textContent = holesailHash;
document.getElementById('tutorialGeyserHash').textContent = geyserHash;
document.getElementById('tutorialSftpHash').textContent = sftpHash;
document.getElementById('tutorialHolesailHashOutput').textContent = holesailHash;
document.getElementById('tutorialHolesailHashOutput2').textContent = holesailHash;
document.getElementById('tutorialHolesailHashHost').textContent = holesailHash;
}
// Run initially
updateTutorialSpans();
// Observe changes to key display spans
const observer = new MutationObserver(updateTutorialSpans);
document.querySelectorAll('#holesailHash, #geyserHash, #sftpHash').forEach(span => {
observer.observe(span, { childList: true, characterData: true, subtree: true });
});
// Toggle tutorial section visibility
document.getElementById('toggleTutorial').addEventListener('click', () => {
const tutorialSection = document.getElementById('tutorialSection');
tutorialSection.classList.toggle('hidden');
});
// Copy summarized tutorial in Markdown and show success message
document.getElementById('copyTutorial').addEventListener('click', () => {
const holesailHash = document.getElementById('holesailHash').textContent;
const markdownTutorial = `# Join My Minecraft Server with Holesail!
Holesail is a secure, peer-to-peer tool that lets you connect to my server without public IPs.
1. **Install Node.js**: Download and install from [nodejs.org](https://nodejs.org/).
2. **Install Holesail**: Open a terminal and run:
\`\`\`bash
npm i holesail@2.1.0
\`\`\`
3. **Connect to the server**: Use this command with the key:
\`\`\`bash
holesail ${holesailHash}
\`\`\`
4. **Join in Minecraft**: Once Holesail confirms the connection, connect to \`127.0.0.1:25565\` in Minecraft.
**Note**: Keep the key private, like an SSH key. No public IPs needed—it's all peer-to-peer!`;
navigator.clipboard.writeText(markdownTutorial);
// Show success message
const notification = document.getElementById('copyNotification');
notification.classList.remove('hidden');
// Hide success message after 3 seconds
setTimeout(() => {
notification.classList.add('hidden');
}, 3000);
});
</script>
</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>
</p>
</div>
</footer>
</div>
<script src="js/app.js"></script>
</body>
</html>