From c337e35697ad0a21c85bd655dc1ac419d2fe49a2 Mon Sep 17 00:00:00 2001 From: Angelillo15 Date: Fri, 16 Dec 2022 18:47:24 +0100 Subject: [PATCH] Added installer --- install.sh | 111 ++++++++ repair.sh | 55 ++++ resources/scripts/IceMinecraftTheme.css | 268 ++++++++++++++++++ .../components/server/console/Console.tsx | 224 +++++++++++++++ resources/scripts/index.tsx | 17 ++ 5 files changed, 675 insertions(+) create mode 100644 install.sh create mode 100644 repair.sh create mode 100644 resources/scripts/IceMinecraftTheme.css create mode 100644 resources/scripts/components/server/console/Console.tsx create mode 100644 resources/scripts/index.tsx diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..25b96df --- /dev/null +++ b/install.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +BLUE='\033[0;34m'. +NO_COLOR='\033[0m' + +if (( $EUID != 0 )); then + echo "Please run as root" + exit +fi + +clear + +installTheme(){ + cd /var/www/ + tar -cvf IceMinecraftTheme.tar.gz pterodactyl + echo "Installing theme..." + cd /var/www/pterodactyl + rm -r IceMinecraftTheme + git clone https://github.com/Angelillo15/IceMinecraftTheme.git + cd MinecraftPurpleTheme + rm /var/www/pterodactyl/resources/scripts/IceMinecraftTheme.css + rm /var/www/pterodactyl/resources/scripts/index.tsx + rm /var/www/pterodactyl/resources/scripts/components/server/console/Console.tsx + mv index.tsx /var/www/pterodactyl/resources/scripts/index.tsx + mv IceMinecraftTheme.css /var/www/pterodactyl/resources/scripts/IceMinecraftTheme.css + mv components/server/console/Console.tsx /var/www/pterodactyl/resources/scripts/components/server/console/Console.tsx + cd /var/www/pterodactyl + + curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - + apt update + apt install -y nodejs + + npm i -g yarn + yarn + + cd /var/www/pterodactyl + yarn build:production + sudo php artisan optimize:clear + + +} + +installThemeQuestion(){ + while true; do + read -p "Are you sure that you want to install the theme [y/N]? " yn + case $yn in + [Yy]* ) installTheme; break;; + [Nn]* ) exit;; + * ) exit;; + esac + done +} + +repair(){ + bash <(curl https://raw.githubusercontent.com/Angelillo15/IceMinecraftTheme/main/repair.sh) +} + +restoreBackUp(){ + echo "Restoring backup..." + cd /var/www/ + tar -xvf IceMinecraftTheme.tar.gz + rm IceMinecraftTheme.tar.gz + + cd /var/www/pterodactyl + yarn build:production + sudo php artisan optimize:clear +} + +printf "${blue} ____ _____ ______ _________________ ____ ____ ______ ______ _______ ______ \n" +printf "${blue}| | ___|\ \ ___|\ \ / \| | | | ___|\ \ | \/ \ ___|\ \ \n" +printf "${blue}| | / /\ \ | \ \ \______ ______/| | | || \ \ / /\ \ | \ \ \n" +printf "${blue}| || | | || ,_____/| \( / / )/ | |_| || ,_____/| / /\ / /\ || ,_____/| \n" +printf "${blue}| || | |____|| \--'\_|/ ' | | ' | .-. || \--'\_|/ / /\ \_/ / / /|| \--'\_|/ \n" +printf "${blue}| || | ____ | /___/| | | | | | || /___/| | | \|_|/ / / || /___/| \n" +printf "${blue}| || | | || \____|\ / // | | | || \____|\ | | | | || \____|\ \n" +printf "${blue}|____||\ ___\/ /||____ ' /| /___// |____| |____||____ ' /||\____\ |____| /|____ ' /| \n" +printf "${blue}| || | /____/ || /_____/ | | | | | | || /_____/ || | | | | / | /_____/ | \n" +printf "${blue}|____| \|___| | /|____| | / |____| |____| |____||____| | / \|____| |____|/ |____| | / \n" +printf "${blue} \( \( |____|/ \( |_____|/ \( \( )/ \( |_____|/ \( )/ \( |_____|/ \n" +printf "${blue} ' ' )/ ' )/ ' ' ' ' )/ ' ' ' )/ \n" +printf "${blue} ' ' ' ' \n" +echo "" +echo "Copyright (c) 2022 Angelillo15 | angelillo15.es" +echo "This program is free software: you can redistribute it and/or modify" +echo "" +echo "Discord: https://discord.angelillo15.es/" +echo "Website: https://angelillo15.es/" +echo "" +echo "[1] Install theme" +echo "[2] Restore backup" +echo "[3] Repair panel (use if you have an error in the theme installation)" +echo "[4] Exit" +printf "${NO_COLOR}" + +read -p "Please enter a number: " choice +if [ $choice == "1" ] + then + installThemeQuestion +fi +if [ $choice == "2" ] + then + restoreBackUp +fi +if [ $choice == "3" ] + then + repair +fi +if [ $choice == "4" ] + then + exit +fi \ No newline at end of file diff --git a/repair.sh b/repair.sh new file mode 100644 index 0000000..26b4ed3 --- /dev/null +++ b/repair.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +if (( $EUID != 0 )); then + echo "Please run as root" + exit +fi + +repairPanel(){ + cd /var/www/pterodactyl + + php artisan down + + rm -r /var/www/pterodactyl/resources + + curl -L https://github.com/pterodactyl/panel/releases/latest/download/panel.tar.gz | tar -xzv + + chmod -R 755 storage/* bootstrap/cache + + composer install --no-dev --optimize-autoloader + + php artisan view:clear + + php artisan config:clear + + php artisan migrate --seed --force + + chown -R www-data:www-data /var/www/pterodactyl/* + + php artisan queue:restart + + curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - + + apt update + + apt install -y nodejs + + npm i -g yarn + + yarn + + yarn build:production + + sudo php artisan optimize:clear + + php artisan up +} + +while true; do + read -p "Are you sure that you want to repair the panel [y/N]? " yn + case $yn in + [Yy]* ) repairPanel; break;; + [Nn]* ) exit;; + * ) exit;; + esac +done \ No newline at end of file diff --git a/resources/scripts/IceMinecraftTheme.css b/resources/scripts/IceMinecraftTheme.css new file mode 100644 index 0000000..198041a --- /dev/null +++ b/resources/scripts/IceMinecraftTheme.css @@ -0,0 +1,268 @@ +:root { + --main-color: #0D181E; + --secundary-color: #132537; + --tertiary-color: #0a354d; + --bacground-url: url("https://i.imgur.com/u6HYFAY.png"); + } + + html { + background-image: var(--bacground-url); + background-attachment: fixed; + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + background-color: transparent; + } + + a.GreyRowBox-sc-1xo9c6v-0.ServerRow__StatusIndicatorBox-sc-1ibsw91-2.dyLna-D.fRwFrz.DashboardContainer___StyledServerRow-sc-1topkxf-2 { + background-color: var(--main-color) !important; + } + + a.GreyRowBox-sc-1xo9c6v-0.ServerRow__StatusIndicatorBox-sc-1ibsw91-2.dyLna-D.fwbDSe.DashboardContainer___StyledServerRow-sc-1topkxf-2.jbVWLN { + background-color: var(--main-color) !important; + background-size: cover; + + } + + div.SubNavigation-sc-lfuaoi-0 { + background-color: rgba(255, 255, 255, 0) !important; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1) !important; + + } + + div.grid.grid-cols-6.gap-2.md:gap-4.col-span-4.lg:col-span-1.order-last.lg:order-none { + background-color: var(--main-color); + } + + div.style-module_2Vp6MaXq.bg-gray-600.cursor-pointer { + background: rgba(255, 255, 255, 0); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(9.2px); + -webkit-backdrop-filter: blur(9.2px); + } + + div.style-module_2Vp6MaXq.bg-gray-600 { + background: rgba(255, 255, 255, 0); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(9.2px); + -webkit-backdrop-filter: blur(9.2px); + } + + div.style-module_2XbmHEcn.group { + background-color: var(--secundary-color); + } + + div.w-full.bg-neutral-900.shadow-md.overflow-x-auto { + background-color: var(--main-color) + } + + div.icon.mr-4 { + background-color: var(--secundary-color) + } + + a.style-module_35MPv1CD.style-module_35MPv1CD.active { + background-color: var(--main-color); + } + + + .style-module_1WqkLT9X { + background-color: var(--main-color) !important; + } + + .style-module_1WqkLT9X:hover { + background-color: var(--secundary-color) !important; + } + + .style-module_35MPv1CD.active { + background-color: var(--main-color) !important; + } + + .style-module_35MPv1CD.active:hover { + background-color: var(--secundary-color) !important; + } + + div.GreyRowBox-sc-1xo9c6v-0.iTERJN.flex-wrap.md:flex-nowrap.mt-2 { + background-color: var(--secundary-color) + } + + div.TitledGreyBox___StyledDiv3-sc-gvsoy-4.fKIIIQ { + background-color: var(--main-color); + } + + input.Input-sc-19rce1w-0.jqTCDz.cursor-pointer { + background-color: var(--secundary-color); + } + + input.Input-sc-19rce1w-0.jqTCDz { + background-color: var(--secundary-color); + } + + div.grid.grid-cols-10.py-4.border-b-2.border-gray-800.last:rounded-b.last:border-0.group { + background-color: var(--secundary-color); + } + + div.TitledGreyBox___StyledDiv2-sc-gvsoy-1.jRrWLs { + background-color: var(--secundary-color); + } + + + div.ContentBox___StyledDiv-sc-mjlt6f-2.iGOcRf { + background-color: var(--secundary-color); + } + + div.Modal___StyledDiv2-sc-9vzni8-3.ekHIsr { + background-color: var(--secundary-color) + } + + + div.GreyRowBox-sc-1xo9c6v-0.DatabaseRow___StyledGreyRowBox-sc-1t67zwr-13.iTERJN.gkSIus { + background-color: var(--secundary-color); + } + + div.EditScheduleModal___StyledDiv2-sc-wh9db9-4.fhEpAC { + background-color: var(--secundary-color); + } + + div.EditScheduleModal___StyledDiv4-sc-wh9db9-6.jyDDEO { + background-color: var(--secundary-color); + } + + div.EditScheduleModal___StyledDiv5-sc-wh9db9-7.ueIjC { + background-color: var(--secundary-color); + } + + a.GreyRowBox-sc-1xo9c6v-0.ScheduleContainer___StyledGreyRowBox-sc-dlqnx9-2.dyLna-D.bppajE { + background-color: var(--secundary-color); + } + + div.GreyRowBox-sc-1xo9c6v-0.UserRow___StyledGreyRowBox-sc-hg2wjz-0.dyLna-D.dmlaEn { + background-color: var(--secundary-color); + } + + div.CreateBackupButton___StyledDiv2-sc-da8bqw-3.eDncUf { + background-color: var(--secundary-color); + } + + textarea.Input__Textarea-sc-19rce1w-1.kKFWRA { + background-color: var(--secundary-color); + } + + div.flex.justify-end.space-x-4.mt-4.w-full.md:mt-0.md:w-48 { + background-color: var(--secundary-color); + } + + select.Select-sc-17exaqp-0.dupyoa { + background-color: var(--main-color); + } + + div.TitledGreyBox___StyledDiv-sc-gvsoy-0.oLbNP.StartupContainer___StyledTitledGreyBox-sc-163imy2-1.kRunTE { + background-color: var(--secundary-color); + } + + a.GreyRowBox-sc-1xo9c6v-0.ServerRow__StatusIndicatorBox-sc-1ibsw91-2.dyLna-D { + background-color: var(--main-color) + } + + label.PermissionRow__Container-sc-1h899cn-0.icxFlO:hover { + background-color: var(--tertiary-color); + } + + div.style-module_1DtraXMW.bg-gray-700 { + background-color: var(--main-color); + } + + input.Input-sc-19rce1w-0.jqTCDz { + background-color: var(--secundary-color); + } + + div.TitledGreyBox___StyledDiv-sc-gvsoy-0.oLbNP { + background-color: var(--main-color); + } + + div.grid.grid-cols-10.py-4.border-b-2.border-gray-800.last:rounded-b.last:border-0.group { + background-color: var(--main-color); + } + + .group { + background-color: var(--main-color); + } + + .GreyRowBox-sc-1xo9c6v-0 { + background-color: var(--main-color); + } + + #app { + background-image: var(--bacground-url); + background-attachment: fixed; + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + background-color: transparent; + } + + /* + Server Console style module + */ + + div.server-console-style-module__stat_block { + background: rgba(255, 255, 255, 0); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(9.2px); + -webkit-backdrop-filter: blur(9.2px); + } + + div.server-console-style-module__icon.bg-gray-700 { + background-color: var(--secundary-color); + } + + button.style-module_4LBM1DKx { + /* From https://css.glass */ + background: rgba(255, 255, 255, 0); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(8.6px); + -webkit-backdrop-filter: blur(8.6px); + } + + div.server-console-style-module__chart_container { + background: rgba(255, 255, 255, 0); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(9.2px); + -webkit-backdrop-filter: blur(9.2px); + } + + button.style-module_4LBM1DKx.style-module_3kBDV_wo.style-module_Yp7-2Fw-.flex-1:hover { + background-color: var(--main-color); + } + + input.Input-sc-19rce1w-0.ZkNLd { + background-color: var(--main-color); + } + + input.Input-sc-19rce1w-0.jqTCDz { + background-color: var(--secundary-color); + ; + } + + body { + overflow: overlay; + } + + Input { + background-color: var(--secundary-color) !important; + } + + select { + background-color: var(--secundary-color) !important; + } + + select { + background-color: var(--secundary-color) !important; + } + + p.StartupContainer___StyledP-sc-163imy2-3.ekbHAG { + background-color: var(--secundary-color) !important; + } \ No newline at end of file diff --git a/resources/scripts/components/server/console/Console.tsx b/resources/scripts/components/server/console/Console.tsx new file mode 100644 index 0000000..8e4ccf7 --- /dev/null +++ b/resources/scripts/components/server/console/Console.tsx @@ -0,0 +1,224 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { ITerminalOptions, Terminal } from 'xterm'; +import { FitAddon } from 'xterm-addon-fit'; +import { SearchAddon } from 'xterm-addon-search'; +import { SearchBarAddon } from 'xterm-addon-search-bar'; +import { WebLinksAddon } from 'xterm-addon-web-links'; +import { ScrollDownHelperAddon } from '@/plugins/XtermScrollDownHelperAddon'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import { ServerContext } from '@/state/server'; +import { usePermissions } from '@/plugins/usePermissions'; +import { theme as th } from 'twin.macro'; +import useEventListener from '@/plugins/useEventListener'; +import { debounce } from 'debounce'; +import { usePersistedState } from '@/plugins/usePersistedState'; +import { SocketEvent, SocketRequest } from '@/components/server/events'; +import classNames from 'classnames'; +import { ChevronDoubleRightIcon } from '@heroicons/react/solid'; + +import 'xterm/css/xterm.css'; +import styles from './style.module.css'; + +const theme = { + background: '#111F26', + cursor: 'transparent', + black: th`colors.black`.toString(), + red: '#E54B4B', + green: '#9ECE58', + yellow: '#FAED70', + blue: '#396FE2', + magenta: '#BB80B3', + cyan: '#2DDAFD', + white: '#d0d0d0', + brightBlack: 'rgba(255, 255, 255, 0.2)', + brightRed: '#FF5370', + brightGreen: '#C3E88D', + brightYellow: '#FFCB6B', + brightBlue: '#82AAFF', + brightMagenta: '#C792EA', + brightCyan: '#89DDFF', + brightWhite: '#ffffff', + selection: '#FAF089', +}; + +const terminalProps: ITerminalOptions = { + disableStdin: true, + cursorStyle: 'underline', + allowTransparency: true, + fontSize: 12, + fontFamily: th('fontFamily.mono'), + rows: 30, + theme: theme, +}; + +export default () => { + const TERMINAL_PRELUDE = '\u001b[1m\u001b[33mcontainer@pterodactyl~ \u001b[0m'; + const ref = useRef(null); + const terminal = useMemo(() => new Terminal({ ...terminalProps }), []); + const fitAddon = new FitAddon(); + const searchAddon = new SearchAddon(); + const searchBar = new SearchBarAddon({ searchAddon }); + const webLinksAddon = new WebLinksAddon(); + const scrollDownHelperAddon = new ScrollDownHelperAddon(); + const { connected, instance } = ServerContext.useStoreState((state) => state.socket); + const [canSendCommands] = usePermissions(['control.console']); + const serverId = ServerContext.useStoreState((state) => state.server.data!.id); + const isTransferring = ServerContext.useStoreState((state) => state.server.data!.isTransferring); + const [history, setHistory] = usePersistedState(`${serverId}:command_history`, []); + const [historyIndex, setHistoryIndex] = useState(-1); + + const handleConsoleOutput = (line: string, prelude = false) => + terminal.writeln((prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m'); + + const handleTransferStatus = (status: string) => { + switch (status) { + // Sent by either the source or target node if a failure occurs. + case 'failure': + terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m'); + return; + + // Sent by the source node whenever the server was archived successfully. + case 'archive': + terminal.writeln( + TERMINAL_PRELUDE + 'Server has been archived successfully, attempting connection to target node..\u001b[0m' + ); + } + }; + + const handleDaemonErrorOutput = (line: string) => + terminal.writeln(TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m'); + + const handlePowerChangeEvent = (state: string) => + terminal.writeln(TERMINAL_PRELUDE + 'Server marked as ' + state + '...\u001b[0m'); + + const handleCommandKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'ArrowUp') { + const newIndex = Math.min(historyIndex + 1, history!.length - 1); + + setHistoryIndex(newIndex); + e.currentTarget.value = history![newIndex] || ''; + + // By default up arrow will also bring the cursor to the start of the line, + // so we'll preventDefault to keep it at the end. + e.preventDefault(); + } + + if (e.key === 'ArrowDown') { + const newIndex = Math.max(historyIndex - 1, -1); + + setHistoryIndex(newIndex); + e.currentTarget.value = history![newIndex] || ''; + } + + const command = e.currentTarget.value; + if (e.key === 'Enter' && command.length > 0) { + setHistory((prevHistory) => [command, ...prevHistory!].slice(0, 32)); + setHistoryIndex(-1); + + instance && instance.send('send command', command); + e.currentTarget.value = ''; + } + }; + + useEffect(() => { + if (connected && ref.current && !terminal.element) { + terminal.loadAddon(fitAddon); + terminal.loadAddon(searchAddon); + terminal.loadAddon(searchBar); + terminal.loadAddon(webLinksAddon); + terminal.loadAddon(scrollDownHelperAddon); + + terminal.open(ref.current); + fitAddon.fit(); + + // Add support for capturing keys + terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key === 'c') { + document.execCommand('copy'); + return false; + } else if ((e.ctrlKey || e.metaKey) && e.key === 'f') { + e.preventDefault(); + searchBar.show(); + return false; + } else if (e.key === 'Escape') { + searchBar.hidden(); + } + return true; + }); + } + }, [terminal, connected]); + + useEventListener( + 'resize', + debounce(() => { + if (terminal.element) { + fitAddon.fit(); + } + }, 100) + ); + + useEffect(() => { + const listeners: Record void> = { + [SocketEvent.STATUS]: handlePowerChangeEvent, + [SocketEvent.CONSOLE_OUTPUT]: handleConsoleOutput, + [SocketEvent.INSTALL_OUTPUT]: handleConsoleOutput, + [SocketEvent.TRANSFER_LOGS]: handleConsoleOutput, + [SocketEvent.TRANSFER_STATUS]: handleTransferStatus, + [SocketEvent.DAEMON_MESSAGE]: (line) => handleConsoleOutput(line, true), + [SocketEvent.DAEMON_ERROR]: handleDaemonErrorOutput, + }; + + if (connected && instance) { + // Do not clear the console if the server is being transferred. + if (!isTransferring) { + terminal.clear(); + } + + Object.keys(listeners).forEach((key: string) => { + instance.addListener(key, listeners[key]); + }); + instance.send(SocketRequest.SEND_LOGS); + } + + return () => { + if (instance) { + Object.keys(listeners).forEach((key: string) => { + instance.removeListener(key, listeners[key]); + }); + } + }; + }, [connected, instance]); + + return ( +
+ +
+
+
+
+
+ {canSendCommands && ( +
+ +
+ +
+
+ )} +
+ ); +}; diff --git a/resources/scripts/index.tsx b/resources/scripts/index.tsx new file mode 100644 index 0000000..66eabb0 --- /dev/null +++ b/resources/scripts/index.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from '@/components/App'; +import { setConfig } from 'react-hot-loader'; +import './IceMinecraftTheme.css'; + +// Enable language support. +import './i18n'; + +// Prevents page reloads while making component changes which +// also avoids triggering constant loading indicators all over +// the place in development. +// +// @see https://github.com/gaearon/react-hot-loader#hook-support +setConfig({ reloadHooks: false }); + +ReactDOM.render(, document.getElementById('app'));