您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Pterodactyl server sorter
当前为
// ==UserScript== // @name PteroSort // @namespace http://tampermonkey.net/ // @version 1.1 // @description Pterodactyl server sorter // @homepage https://github.com/Ricman-MC/PteroSort // @author Ricman // @license Apache 2.0 // @match https://panel.your-server.eu/ // @match https://panel.your-second-server.com/ // @grant none // ==/UserScript== // IMPORTANT // Set your own domain for the pterodactyl panel in the @match example is provided you can add as many as you need // Script supports only vanilla pterodactyl panel v.1.11.10 (its possible it will work on diferent versions not tested) // IMPORTANT // this script has some hardcoded parts i expect it will break when update comes iam fixing this in later updates // In development second script that has also category sorting, basically create category and assaign it to server (function () { 'use strict'; const STORAGE_KEY_YOURS = 'ptero_server_order_yours'; const STORAGE_KEY_OTHERS = 'ptero_server_order_others'; const containerSelector = 'section > div'; const serverSelector = '.DashboardContainer___StyledServerRow-sc-1topkxf-2'; const toggleSelector = 'input[name="show_all_servers"]'; const buttonContainerSelector = '.DashboardContainer___StyledDiv-sc-1topkxf-0'; let autoSaveEnabled = localStorage.getItem('autoSaveEnabled') !== 'false'; let dragLockEnabled = localStorage.getItem('dragLockEnabled') === 'true'; function getStorageKey() { return document.querySelector(toggleSelector)?.checked ? STORAGE_KEY_OTHERS : STORAGE_KEY_YOURS; } function saveOrder() { if (!autoSaveEnabled) return; const serverElements = document.querySelectorAll(serverSelector); const order = Array.from(serverElements).map(el => el.href.split('/').pop()); localStorage.setItem(getStorageKey(), JSON.stringify(order)); } function loadOrder() { const savedOrder = JSON.parse(localStorage.getItem(getStorageKey()) || '[]'); if (!savedOrder.length) return; const container = document.querySelector(containerSelector); const servers = Array.from(document.querySelectorAll(serverSelector)); const serverMap = new Map(servers.map(el => [el.href.split('/').pop(), el])); savedOrder.forEach(id => { if (serverMap.has(id)) { container.appendChild(serverMap.get(id)); } }); } function enableDragAndDrop() { const container = document.querySelector(containerSelector); if (!container) return; let dragged = null; // reset all drag listeners document.querySelectorAll(serverSelector).forEach(el => { el.draggable = !dragLockEnabled; el.removeEventListener('dragstart', handleDragStart); el.removeEventListener('dragover', handleDragOver); el.removeEventListener('drop', handleDrop); if (!dragLockEnabled) { el.addEventListener('dragstart', handleDragStart); el.addEventListener('dragover', handleDragOver); el.addEventListener('drop', handleDrop); } }); function handleDragStart(e) { if (dragLockEnabled) return; dragged = e.target; e.dataTransfer.effectAllowed = 'move'; } function handleDragOver(e) { if (dragLockEnabled) return; e.preventDefault(); e.dataTransfer.dropEffect = 'move'; const target = e.target.closest(serverSelector); if (!target || target === dragged) return; const bounding = target.getBoundingClientRect(); const offset = e.clientY - bounding.top; if (offset > bounding.height * 0.1) { target.parentNode.insertBefore(dragged, target); } else if (offset < bounding.height * 0.9) { target.parentNode.insertBefore(dragged, target.nextSibling); } } function handleDrop(e) { if (dragLockEnabled) return; e.preventDefault(); saveOrder(); fixSpacing(); } } function fixSpacing() { document.querySelectorAll(serverSelector).forEach(el => { el.style.marginTop = '8px'; }); } function createButtons() { const container = document.querySelector(buttonContainerSelector); const toggleSwitch = document.querySelector(toggleSelector); if (!container || !toggleSwitch) return; // make sure elements exist // prevent duplicate buttons if (document.getElementById('lockDragButton')) return; const buttonWrapper = document.createElement('div'); buttonWrapper.style.display = 'flex'; buttonWrapper.style.gap = '10px'; buttonWrapper.style.paddingLeft = '15px'; buttonWrapper.style.paddingRight = '15px'; buttonWrapper.style.float = 'right'; const lockButton = document.createElement('button'); lockButton.id = 'lockDragButton'; lockButton.title = 'Toggle drag lock'; lockButton.style.padding = '5px 10px'; lockButton.style.cursor = 'pointer'; lockButton.innerText = dragLockEnabled ? '🔒' : '🔓'; lockButton.addEventListener('click', () => { dragLockEnabled = !dragLockEnabled; localStorage.setItem('dragLockEnabled', dragLockEnabled); lockButton.innerText = dragLockEnabled ? '🔒' : '🔓'; enableDragAndDrop(); }); const autoSaveButton = document.createElement('button'); autoSaveButton.id = 'autoSaveButton'; autoSaveButton.title = 'Toggle auto-saving'; autoSaveButton.style.padding = '5px 10px'; autoSaveButton.style.cursor = 'pointer'; autoSaveButton.innerText = `Auto-Save: ${autoSaveEnabled ? 'ON' : 'OFF'}`; autoSaveButton.addEventListener('click', () => { autoSaveEnabled = !autoSaveEnabled; localStorage.setItem('autoSaveEnabled', autoSaveEnabled); autoSaveButton.innerText = `Auto-Save: ${autoSaveEnabled ? 'ON' : 'OFF'}`; }); const clearButton = document.createElement('button'); clearButton.id = 'clearStorageButton'; clearButton.title = 'Delete all saved server orders'; clearButton.style.marginRight = '10px'; clearButton.style.padding = '5px 10px'; clearButton.style.cursor = 'pointer'; clearButton.style.color = 'red'; clearButton.innerText = '🗑️'; // click event - show confirmation overlay clearButton.addEventListener('click', () => { const overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100vw'; overlay.style.height = '100vh'; overlay.style.background = 'rgba(0, 0, 0, 0.5)'; overlay.style.display = 'flex'; overlay.style.alignItems = 'center'; overlay.style.justifyContent = 'center'; overlay.style.zIndex = '10000'; const confirmation = document.createElement('div'); confirmation.style.padding = '20px'; confirmation.style.background = 'white'; confirmation.style.boxShadow = '0px 4px 6px rgba(0,0,0,0.1)'; confirmation.style.borderRadius = '10px'; confirmation.style.textAlign = 'center'; confirmation.innerHTML = ` <p style="margin-bottom: 15px; color: black;">Are you sure you want to delete all saved server orders?</p> <button id="confirmDelete" style="margin-right: 10px; padding: 5px 10px; border-radius: 5px; background: red; color: white; border: none; cursor: pointer;">Yes</button> <button id="cancelDelete" style="padding: 5px 10px; border-radius: 5px; background: gray; color: white; border: none; cursor: pointer;">No</button> `; overlay.appendChild(confirmation); document.body.appendChild(overlay); document.getElementById('confirmDelete').addEventListener('click', () => { localStorage.removeItem(STORAGE_KEY_YOURS); localStorage.removeItem(STORAGE_KEY_OTHERS); overlay.remove(); location.reload(); // refresh the page }); document.getElementById('cancelDelete').addEventListener('click', () => { overlay.remove(); }); // close overlay when clicking outside the confirmation box overlay.addEventListener('click', () => { overlay.remove(); }); // prevent clicking inside the box from closing the overlay confirmation.addEventListener('click', (e) => { e.stopPropagation(); }); }); container.appendChild(clearButton); buttonWrapper.appendChild(lockButton); buttonWrapper.appendChild(autoSaveButton); const toggleContainer = toggleSwitch.closest('.Switch___StyledDiv-sc-1nxt82m-2'); if (toggleContainer && toggleContainer.contains(toggleSwitch)) { toggleContainer.appendChild(buttonWrapper); } else { console.warn('Could not find valid toggle container, appending buttons at the end.'); container.appendChild(buttonWrapper); } } function init() { loadOrder(); enableDragAndDrop(); fixSpacing(); createButtons(); } function observePageChanges() { let lastUrl = location.href; let lastPath = location.pathname; const observer = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; } if (location.pathname !== lastPath) { lastPath = location.pathname; waitForElement(serverSelector, () => { init() }); } }); observer.observe(document.body, { childList: true, subtree: true }); // interval-based fallback setInterval(() => { if (location.pathname !== lastPath) { lastPath = location.pathname; waitForElement(serverSelector, () => { // init() sometimes does not work loadOrder(); enableDragAndDrop(); fixSpacing(); createButtons(); }); } }, 500); } function observeViewSwitch() { const toggleSwitch = document.querySelector(toggleSelector); if (toggleSwitch) { toggleSwitch.addEventListener('change', () => { setTimeout(() => { // delay to avoid using old elements waitForElement(serverSelector, () => { loadOrder(); enableDragAndDrop(); fixSpacing(); }); }, 200); }); } } function waitForElement(selector, callback) { const observer = new MutationObserver(() => { if (document.querySelector(selector)) { observer.disconnect(); callback(); } }); observer.observe(document.body, { childList: true, subtree: true }); } waitForElement(serverSelector, () => { init(); observePageChanges(); observeViewSwitch(); }); })();