Add holesail key section along with holesail tutorial and easy share tutorial so folks can share easy on discord
This commit is contained in:
@ -4,7 +4,7 @@ import { checkConnectionStatus, checkGeyserStatus, checkSftpStatus } from './sta
|
||||
import { apiRequest } from './api.js';
|
||||
|
||||
const clients = new Map();
|
||||
const staticEndpoints = ['log', 'website', 'map', 'my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'my-link', 'my-geyser-link', 'my-sftp'];
|
||||
const staticEndpoints = ['log', 'website', 'map', 'my-link-cache', 'my-geyser-cache', 'my-sftp-cache', 'my-link', 'my-geyser-link', 'my-sftp', 'holesail-hashes'];
|
||||
const dynamicEndpoints = ['hello', 'time', 'mod-list'];
|
||||
|
||||
// Helper function to start Docker stats interval
|
||||
@ -94,6 +94,44 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (endpoint === 'holesail-hashes' && client.cache['holesail-hashes']) {
|
||||
ws.send(JSON.stringify({ type: endpoint, data: client.cache['holesail-hashes'] }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (endpoint === 'holesail-hashes') {
|
||||
try {
|
||||
const [hashResponse, geyserHashResponse, sftpHashResponse] = await Promise.all([
|
||||
apiRequest('/my-hash', client.apiKey),
|
||||
apiRequest('/my-geyser-hash', client.apiKey),
|
||||
apiRequest('/my-sftp-hash', client.apiKey)
|
||||
]);
|
||||
|
||||
const response = {
|
||||
myHash: hashResponse.success ? hashResponse.message : null,
|
||||
geyserHash: geyserHashResponse.success ? geyserHashResponse.message : null,
|
||||
sftpHash: sftpHashResponse.success ? sftpHashResponse.message : null,
|
||||
errors: []
|
||||
};
|
||||
|
||||
if (!hashResponse.success) response.errors.push(`my-hash: ${hashResponse.message || 'Failed to fetch'}`);
|
||||
if (!geyserHashResponse.success) response.errors.push(`my-geyser-hash: ${geyserHashResponse.message || 'Failed to fetch'}`);
|
||||
if (!sftpHashResponse.success) response.errors.push(`my-sftp-hash: ${sftpHashResponse.message || 'Failed to fetch'}`);
|
||||
|
||||
client.cache['holesail-hashes'] = response;
|
||||
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify({ type: endpoint, data: response }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching holesail-hashes for ${client.user}:`, error.message);
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify({ type: endpoint, error: `Failed to fetch holesail hashes: ${error.message}` }));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await apiRequest(`/${endpoint}`, client.apiKey);
|
||||
if (!response.error) {
|
||||
if (endpoint === 'time') client.cache['time'] = response;
|
||||
@ -174,7 +212,6 @@ async function fetchAndSendUpdate(ws, endpoint, client, docker) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function manageStatusChecks(ws, client, user, docker) {
|
||||
try {
|
||||
const container = docker.getContainer(user);
|
||||
@ -402,7 +439,7 @@ export function handleWebSocket(ws, req, docker) {
|
||||
client.intervals.push(setInterval(async () => {
|
||||
try {
|
||||
for (const endpoint of staticEndpoints) {
|
||||
if (client.subscriptions.has(endpoint)) {
|
||||
if (client.subscriptions.has(endpoint) && endpoint !== 'holesail-hashes') {
|
||||
await fetchAndSendUpdate(ws, endpoint, client, docker);
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +185,6 @@
|
||||
<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
|
||||
@ -219,7 +218,6 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6" id="sftpBrowserSection" style="display: none;">
|
||||
<!-- Container for header and button -->
|
||||
<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"
|
||||
@ -228,7 +226,6 @@
|
||||
<span>Refresh SFTP</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Iframe remains unchanged -->
|
||||
<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>
|
||||
@ -287,6 +284,188 @@
|
||||
</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>
|
||||
</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">
|
||||
|
@ -48,6 +48,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
myLink: document.getElementById('myLink'),
|
||||
geyserLink: document.getElementById('geyserLink'),
|
||||
sftpLink: document.getElementById('sftpLink'),
|
||||
holesailHash: document.getElementById('holesailHash'),
|
||||
geyserHash: document.getElementById('geyserHash'),
|
||||
sftpHash: document.getElementById('sftpHash'),
|
||||
modResults: document.getElementById('modResults'),
|
||||
consoleOutput: document.getElementById('consoleOutput'),
|
||||
consoleInput: document.getElementById('consoleInput'),
|
||||
@ -177,6 +180,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
myLink: '',
|
||||
geyserLink: '',
|
||||
sftpLink: '',
|
||||
holesailHash: '',
|
||||
geyserHash: '',
|
||||
sftpHash: '',
|
||||
hasShownStartNotification: false,
|
||||
connectionStatus: '',
|
||||
geyserStatus: '',
|
||||
@ -365,7 +371,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
'my-geyser-cache',
|
||||
'my-sftp-cache',
|
||||
'backup',
|
||||
'sftp-status'
|
||||
'sftp-status',
|
||||
'holesail-hashes'
|
||||
]
|
||||
}));
|
||||
responseTimeout = setTimeout(() => {
|
||||
@ -451,6 +458,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
updateModsUI(message);
|
||||
} else if (message.type === 'backup') {
|
||||
console.log('Received backup message:', message);
|
||||
} else if (message.type === 'holesail-hashes') {
|
||||
updateHolesailHashesUI(message);
|
||||
} else {
|
||||
updateNonDockerUI(message);
|
||||
}
|
||||
@ -494,7 +503,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const editPropertiesBtn = elements.editPropertiesBtn;
|
||||
const updateModsBtn = elements.updateModsBtn;
|
||||
const backupBtn = elements.backupBtn;
|
||||
const sftpBtn = elements.sftpBtn; // Commented out as it appears to be disabled in the provided code
|
||||
const sftpBtn = elements.sftpBtn;
|
||||
const startBtn = document.getElementById('startBtn');
|
||||
const stopBtn = elements.stopBtn;
|
||||
const restartBtn = elements.restartBtn;
|
||||
@ -629,14 +638,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateSftpCacheUI(message) {
|
||||
// For testing, this is currently configured to be internally networked to port 22 for the given container.
|
||||
// The IP Address is sent from server side on page load, in theory, this should allow us to always allow
|
||||
// SFTP Client to WORK! Even if SFTP Holesail ports are down!
|
||||
// To Revert, move hostname to message.data.hostname and port to message.data.port
|
||||
// Doing so will configure the connection to the Jump node.
|
||||
// state.user = /etc/hosts assignment on the host OS.
|
||||
if (message.data?.hostname && message.data?.port && message.data?.user && message.data?.password) {
|
||||
sftpCredentials = {
|
||||
hostname: state.user,
|
||||
@ -649,7 +651,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
elements.sftpLink.textContent = sftpLinkText;
|
||||
state.sftpLink = sftpLinkText;
|
||||
}
|
||||
// Call connectSftp only if it hasn't been attempted yet
|
||||
if (!hasAttemptedSftpConnect) {
|
||||
hasAttemptedSftpConnect = true;
|
||||
connectSftp();
|
||||
@ -657,6 +658,43 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function updateHolesailHashesUI(message) {
|
||||
if (message.error) {
|
||||
showNotification(`Failed to load Holesail hashes: ${message.error}`, 'error', 'holesail-hashes-error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.data?.myHash && elements.holesailHash) {
|
||||
const hashText = message.data.myHash;
|
||||
if (state.holesailHash !== hashText) {
|
||||
elements.holesailHash.textContent = hashText;
|
||||
state.holesailHash = hashText;
|
||||
}
|
||||
}
|
||||
|
||||
if (message.data?.geyserHash && elements.geyserHash) {
|
||||
const hashText = message.data.geyserHash;
|
||||
if (state.geyserHash !== hashText) {
|
||||
elements.geyserHash.textContent = hashText;
|
||||
state.geyserHash = hashText;
|
||||
}
|
||||
}
|
||||
|
||||
if (message.data?.sftpHash && elements.sftpHash) {
|
||||
const hashText = message.data.sftpHash;
|
||||
if (state.sftpHash !== hashText) {
|
||||
elements.sftpHash.textContent = hashText;
|
||||
state.sftpHash = hashText;
|
||||
}
|
||||
}
|
||||
|
||||
if (message.data?.errors?.length > 0) {
|
||||
message.data.errors.forEach(error => {
|
||||
showNotification(error, 'error', `holesail-hashes-error-${error}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateModsUI(message) {
|
||||
if (message.error) {
|
||||
elements.updateModsOutput.textContent = `Error: ${message.error}`;
|
||||
@ -1410,7 +1448,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
|
||||
let displayProperties = {}; // Store original properties for filtering
|
||||
let displayProperties = {};
|
||||
|
||||
function parseServerProperties(content) {
|
||||
const properties = {};
|
||||
@ -1429,7 +1467,6 @@ function parseServerProperties(content) {
|
||||
function renderPropertiesFields(properties, filter = '') {
|
||||
const fieldsContainer = elements.propertiesFields;
|
||||
|
||||
// Create search box only if not already present
|
||||
let searchContainer = fieldsContainer.querySelector('#searchContainer');
|
||||
if (!searchContainer) {
|
||||
searchContainer = document.createElement('div');
|
||||
@ -1452,7 +1489,6 @@ function renderPropertiesFields(properties, filter = '') {
|
||||
searchContainer.appendChild(searchLabel);
|
||||
searchContainer.appendChild(searchInput);
|
||||
|
||||
// Add custom property button (mini style)
|
||||
const addButton = document.createElement('button');
|
||||
addButton.textContent = 'Add Property';
|
||||
addButton.type = 'button';
|
||||
@ -1462,16 +1498,13 @@ function renderPropertiesFields(properties, filter = '') {
|
||||
|
||||
fieldsContainer.appendChild(searchContainer);
|
||||
|
||||
// Add input event listener
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
renderPropertiesList(displayProperties, e.target.value);
|
||||
});
|
||||
|
||||
// Store reference to search input
|
||||
elements.searchInput = searchInput;
|
||||
}
|
||||
|
||||
// Create or update properties list container
|
||||
let propertiesList = fieldsContainer.querySelector('#propertiesList');
|
||||
if (!propertiesList) {
|
||||
propertiesList = document.createElement('div');
|
||||
@ -1482,7 +1515,6 @@ function renderPropertiesFields(properties, filter = '') {
|
||||
|
||||
renderPropertiesList(properties, filter);
|
||||
|
||||
// Add modal close handler
|
||||
const closeButton = elements.editPropertiesModal.querySelector('.close-button');
|
||||
if (closeButton && !closeButton.dataset.closeHandlerAdded) {
|
||||
closeButton.addEventListener('click', () => {
|
||||
@ -1731,6 +1763,7 @@ function propertiesToString(properties) {
|
||||
async function fetchServerProperties() {
|
||||
try {
|
||||
const key = `action-fetch-properties`;
|
||||
const notification = showNotification('Fetching server properties...', 'loading', key);
|
||||
const response = await wsRequest('/server-properties', 'GET');
|
||||
if (response.error) {
|
||||
updateNotification(notification, `Failed to load server properties: ${response.error}`, 'error', key);
|
||||
@ -1746,6 +1779,7 @@ async function fetchServerProperties() {
|
||||
);
|
||||
renderPropertiesFields(displayProperties);
|
||||
elements.editPropertiesModal.classList.remove('hidden');
|
||||
updateNotification(notification, 'Server properties loaded successfully', 'success', key);
|
||||
} catch (error) {
|
||||
console.error('Fetch server properties error:', error);
|
||||
showNotification(`Failed to load server properties: ${error.message}`, 'error', 'fetch-properties-error');
|
||||
@ -1755,10 +1789,10 @@ async function fetchServerProperties() {
|
||||
async function saveServerProperties() {
|
||||
try {
|
||||
const key = `action-save-properties`;
|
||||
const notification = showNotification('Saving server properties...', 'loading', key);
|
||||
const properties = {};
|
||||
const inputs = elements.propertiesFields.querySelectorAll('input:not(#propertiesSearch):not([id="customPropertyKey"]):not([id="customPropertyValue"])');
|
||||
|
||||
// Collect modified properties
|
||||
inputs.forEach(input => {
|
||||
const key = input.name;
|
||||
let value = input.value.trim();
|
||||
@ -1767,7 +1801,6 @@ async function saveServerProperties() {
|
||||
}
|
||||
});
|
||||
|
||||
// Merge with allProperties to include all properties, even if not displayed
|
||||
const fullProperties = { ...allProperties, ...properties };
|
||||
const content = propertiesToString(fullProperties);
|
||||
const response = await wsRequest('/server-properties', 'POST', { content });
|
||||
@ -1775,12 +1808,12 @@ async function saveServerProperties() {
|
||||
updateNotification(notification, `Failed to save server properties: ${response.error}`, 'error', key);
|
||||
return;
|
||||
}
|
||||
// Clear search input and reset properties list
|
||||
if (elements.searchInput) {
|
||||
elements.searchInput.value = '';
|
||||
renderPropertiesList(displayProperties, '');
|
||||
}
|
||||
elements.editPropertiesModal.classList.add('hidden');
|
||||
updateNotification(notification, 'Server properties saved successfully', 'success', key);
|
||||
} catch (error) {
|
||||
console.error('Save server properties error:', error);
|
||||
showNotification(`Failed to save server properties: ${error.message}`, 'error', 'save-properties-error');
|
||||
@ -1790,6 +1823,7 @@ async function saveServerProperties() {
|
||||
async function updateMods() {
|
||||
try {
|
||||
const key = `action-update-mods`;
|
||||
const notification = showNotification('Updating mods...', 'loading', key);
|
||||
const response = await wsRequest('/update-mods', 'POST');
|
||||
if (response.error) {
|
||||
elements.updateModsOutput.textContent = `Error: ${response.error}`;
|
||||
@ -1836,11 +1870,10 @@ async function saveServerProperties() {
|
||||
}
|
||||
|
||||
async function connectSftp() {
|
||||
// Only enable this if we are using Jump Box based SFTP Connections
|
||||
// if (!isSftpOnline) {
|
||||
// showNotification('SFTP is offline. Please try again later.', 'error', 'sftp-offline');
|
||||
// return;
|
||||
// }
|
||||
if (!isSftpOnline) {
|
||||
showNotification('SFTP is offline. Please try again later.', 'error', 'sftp-offline');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sftpCredentials) {
|
||||
showNotification('SFTP credentials not available.', 'error', 'sftp-credentials');
|
||||
|
Reference in New Issue
Block a user