Compare commits

..

No commits in common. "main" and "v0.2" have entirely different histories.
main ... v0.2

16 changed files with 184 additions and 639 deletions

1
README.md Normal file
View file

@ -0,0 +1 @@
# Hosting

View file

@ -204,51 +204,6 @@ def delete_file_or_folder():
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@api.route('/files/content', methods=['GET'])
@oidc.require_login
def get_file_content():
username = oidc.user_getfield('preferred_username')
path = request.args.get('path')
base_path = os.path.abspath(f'./servers/mc-{username}')
file_path = os.path.abspath(os.path.join(base_path, path))
if not file_path.startswith(base_path) or not os.path.isfile(file_path):
return abort(403)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return jsonify({"success": True, "content": content})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@api.route('/files/content', methods=['POST'])
@oidc.require_login
def save_file_content():
data = request.get_json()
username = data.get('username')
if username != oidc.user_getfield('preferred_username'):
return jsonify({"error": "Unauthorized request."}), 403
path = data.get('path')
content = data.get('content')
base_path = os.path.abspath(f'./servers/mc-{username}')
file_path = os.path.abspath(os.path.join(base_path, path))
if not file_path.startswith(base_path) or not os.path.isfile(file_path):
return abort(403)
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return jsonify({"success": True})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
# Server config # Server config
@api.route('/config') @api.route('/config')
@oidc.require_login @oidc.require_login
@ -375,64 +330,31 @@ def download_mod():
server_version = data['version'] server_version = data['version']
base_path = f"./servers/mc-{username}/{'plugins' if server_type == 'paper' else 'mods'}" base_path = f"./servers/mc-{username}/{'plugins' if server_type == 'paper' else 'mods'}"
os.makedirs(base_path, exist_ok=True)
downloaded_files = [] downloaded = []
visited_versions = set()
def download_project(pid, version_id=None): def download_project(pid, version_id=None):
if version_id: if version_id:
version_data = requests.get(f"https://api.modrinth.com/v2/version/{version_id}").json() version_data = requests.get(f"https://api.modrinth.com/v2/version/{version_id}").json()
else: else:
all_versions = requests.get(f"https://api.modrinth.com/v2/project/{pid}/version").json() all_versions = requests.get(f"https://api.modrinth.com/v2/project/{pid}/version").json()
version_data = next( version_data = next((v for v in all_versions if server_version in v['game_versions'] and server_type in v['loaders']), None)
(v for v in all_versions
if server_version in v['game_versions']
and server_type in v['loaders']
and v.get('server_side') != 'unsupported'),
None)
if not version_data: if not version_data:
print(f"[SKIP] No compatible version found for {pid}")
return return
if version_data["id"] in visited_versions: file = version_data['files'][0]
return file_path = f"{base_path}/{file['filename']}"
visited_versions.add(version_data["id"]) if file['filename'] not in downloaded:
if version_data.get('server_side') == 'unsupported':
print(f"[SKIP] {version_data.get('name', 'Nieznana nazwa')} is client-only")
return
file = next((f for f in version_data['files'] if f.get('primary', True)), version_data['files'][0])
file_path = os.path.join(base_path, file['filename'])
if not os.path.exists(file_path):
print(f"[DOWNLOAD] {file['filename']}")
with requests.get(file['url'], stream=True) as r: with requests.get(file['url'], stream=True) as r:
with open(file_path, 'wb') as f: with open(file_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192): for chunk in r.iter_content(chunk_size=8192):
f.write(chunk) f.write(chunk)
downloaded.append(file['filename'])
downloaded_files.append({
"filename": file['filename'],
"name": version_data.get("name") or version_data.get("version_number") or "Nieznana wersja",
"project_id": version_data["project_id"],
"version_id": version_data["id"]
})
for dep in version_data.get('dependencies', []): for dep in version_data.get('dependencies', []):
if dep.get('version_id'): if dep.get('version_id'):
try: download_project(dep['project_id'], dep['version_id'])
download_project(dep['project_id'], dep['version_id'])
except Exception as e:
print(f"[ERROR] Failed to fetch dep version {dep['version_id']}: {e}")
elif dep.get('project_id'):
try:
download_project(dep['project_id'])
except Exception as e:
print(f"[ERROR] Failed to fetch dep project {dep['project_id']}: {e}")
download_project(project_id) download_project(project_id)
return jsonify({"success": True, "downloaded": downloaded_files}) return jsonify({"success": True, "downloaded": downloaded})

View file

@ -90,7 +90,6 @@ def start_server(username):
"EULA": "TRUE", "EULA": "TRUE",
"SERVER_PORT": ports[0], "SERVER_PORT": ports[0],
"ENABLE_RCON": "TRUE", "ENABLE_RCON": "TRUE",
"USE_AIKAR_FLAGS": "TRUE",
"MOTD": f"Serwer użytkownika §9{username}", "MOTD": f"Serwer użytkownika §9{username}",
"TYPE": server_type, "TYPE": server_type,
"VERSION": server_version, "VERSION": server_version,
@ -107,18 +106,6 @@ def start_server(username):
"MAX_MEMORY": "4G" "MAX_MEMORY": "4G"
} }
if server_config["andus-drasl"]:
drasl_args = (
"-Dminecraft.api.env=custom "
"-Dminecraft.api.auth.host=https://drasl.andus.ovh/auth "
"-Dminecraft.api.account.host=https://drasl.andus.ovh/account "
"-Dminecraft.api.session.host=https://drasl.andus.ovh/session "
"-Dminecraft.api.services.host=https://drasl.andus.ovh/services"
)
environment["JVM_OPTS"] = drasl_args
print(f"server {username} uses drasl {drasl_args}")
print(f"env: {environment}")
client.containers.run( client.containers.run(
"itzg/minecraft-server", "itzg/minecraft-server",
detach=True, detach=True,
@ -128,9 +115,9 @@ def start_server(username):
f"{ports[1]}/tcp": ports[1], f"{ports[1]}/tcp": ports[1],
f"{ports[2]}/tcp": ports[2], f"{ports[2]}/tcp": ports[2],
}, },
volumes=[ volumes={
f"{os.path.abspath(path)}:/data" os.path.abspath(path): {'bind': '/data', 'mode': 'rw'}
], },
environment=environment, environment=environment,
restart_policy={"Name": "unless-stopped"} restart_policy={"Name": "unless-stopped"}
) )

View file

@ -4,8 +4,6 @@ import random
from flask import Blueprint, render_template, jsonify from flask import Blueprint, render_template, jsonify
from .auth import oidc from .auth import oidc
from .port_utils import get_user_ports from .port_utils import get_user_ports
from .docker_utils import client
from docker.errors import NotFound
from dotenv import load_dotenv from dotenv import load_dotenv
main = Blueprint('main', __name__, static_folder='static') main = Blueprint('main', __name__, static_folder='static')
@ -14,14 +12,12 @@ load_dotenv()
@main.route("/") @main.route("/")
@oidc.require_login
def home(): def home():
is_logged_in = oidc.user_loggedin username = oidc.user_getfield("preferred_username")
has_server = False server_path = f"./servers/mc-{username}"
if is_logged_in: has_server = os.path.exists(server_path)
username = oidc.user_getfield("preferred_username") return render_template("home.html", has_server=has_server)
server_path = f"./servers/mc-{username}"
has_server = os.path.exists(server_path)
return render_template("home.html", has_server=has_server, is_logged_in=is_logged_in)
@main.route('/setup') @main.route('/setup')
@ -61,32 +57,6 @@ def list_ads():
return jsonify(files) return jsonify(files)
@main.route("/status")
def servers_status():
servers_dir = "./servers"
server_dirs = [
d for d in os.listdir(servers_dir)
if os.path.isdir(os.path.join(servers_dir, d)) and d.startswith('mc-')
]
server_data = []
for server_dir in server_dirs:
server_name = server_dir
server_status = "Offline"
try:
container = client.containers.get(server_name)
if container.status == "running":
server_status = "Online"
except NotFound:
pass
server_data.append({"name": server_name, "status": server_status})
return render_template('servers_list.html', servers=server_data)
@main.app_errorhandler(404) @main.app_errorhandler(404)
def page_not_found(e): def page_not_found(e):
return render_template("404.html"), 404 return render_template("404.html"), 404

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View file

@ -14,7 +14,6 @@ function loadConfig() {
document.getElementById("allow-nether").checked = cfg["allow-nether"]; document.getElementById("allow-nether").checked = cfg["allow-nether"];
document.getElementById("max-build-height").value = cfg["max-build-height"]; document.getElementById("max-build-height").value = cfg["max-build-height"];
document.getElementById("view-distance").value = cfg["view-distance"]; document.getElementById("view-distance").value = cfg["view-distance"];
document.getElementById("andus-drasl").value = cfg["andus-drasl"]
}) })
.catch(err => { .catch(err => {
console.error("Błąd wczytywania konfiguracji: ", err); console.error("Błąd wczytywania konfiguracji: ", err);
@ -36,8 +35,7 @@ function saveConfig() {
spawn_animals: document.getElementById("spawn-animals").checked, spawn_animals: document.getElementById("spawn-animals").checked,
allow_nether: document.getElementById("allow-nether").checked, allow_nether: document.getElementById("allow-nether").checked,
max_build_height: document.getElementById("max-build-height").value, max_build_height: document.getElementById("max-build-height").value,
view_distance: document.getElementById("view-distance").value, view_distance: document.getElementById("view-distance").value
andus_drasl: document.getElementById("andus-drasl").value
}; };
fetch('/api/config', { fetch('/api/config', {

View file

@ -1,134 +0,0 @@
let codeMirrorEditor = null;
let currentEditingFilePath = null;
function getFileExtension(filename) {
const lastDot = filename.lastIndexOf('.');
if (lastDot === -1) return '';
return filename.slice(lastDot + 1);
}
function getCodeMirrorMode(filename) {
const ext = getFileExtension(filename).toLowerCase();
switch (ext) {
case 'js':
return 'javascript';
case 'json':
return {
name: "javascript",
json: true
};
case 'html':
case 'htm':
return 'htmlmixed';
case 'css':
return 'css';
case 'xml':
return 'xml';
case 'yml':
case 'yaml':
return 'yaml';
case 'properties':
return 'properties';
case 'log':
case 'txt':
return 'text/plain';
default:
return null;
}
}
async function openFileEditor(filePath) {
const fileExtension = getFileExtension(filePath);
const textFileExtensions = ['txt', 'log', 'json', 'yml', 'yaml', 'properties', 'html', 'css', 'js', 'xml', 'py'];
if (!textFileExtensions.includes(fileExtension.toLowerCase())) {
alert('Ten typ pliku nie może być edytowany bezpośrednio. Możesz go pobrać.');
return;
}
try {
const response = await fetch(`/api/files/content?username=${username}&path=${encodeURIComponent(filePath)}`);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Server responded with status ${response.status}: ${errorText}`);
}
const data = await response.json();
if (data.success) {
document.getElementById('file-list').style.display = 'none';
document.getElementById('drop-zone').style.display = 'none';
document.getElementById('file-editor-container').style.display = 'block';
document.getElementById('editing-filename').textContent = filePath.split('/').pop();
currentEditingFilePath = filePath;
if (!codeMirrorEditor) {
codeMirrorEditor = CodeMirror.fromTextArea(document.getElementById('file-editor'), {
lineNumbers: true,
theme: 'material-darker',
mode: getCodeMirrorMode(filePath),
indentUnit: 4,
tabSize: 4,
indentWithTabs: false,
lineWrapping: true
});
} else {
codeMirrorEditor.setValue(data.content);
codeMirrorEditor.setOption('mode', getCodeMirrorMode(filePath));
}
codeMirrorEditor.refresh();
} else {
alert('Błąd podczas ładowania pliku: ' + (data.error || 'Nieznany błąd.'));
}
} catch (error) {
console.error('Błąd:', error);
alert('Wystąpił błąd podczas ładowania pliku: ' + error.message);
}
}
async function saveFileContent() {
if (!codeMirrorEditor || !currentEditingFilePath) {
alert('Brak pliku do zapisania.');
return;
}
const content = codeMirrorEditor.getValue();
try {
const response = await fetch('/api/files/content', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
path: currentEditingFilePath,
content: content
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Server responded with status ${response.status}: ${errorText}`);
}
const data = await response.json();
if (data.success) {
alert('Plik został pomyślnie zapisany!');
} else {
alert('Błąd podczas zapisywania pliku: ' + (data.error || 'Nieznany błąd.'));
}
} catch (error) {
console.error('Błąd:', error);
alert('Wystąpił błąd podczas zapisywania pliku: ' + error.message);
}
}
function closeFileEditor() {
document.getElementById('file-editor-container').style.display = 'none';
document.getElementById('file-list').style.display = 'block';
document.getElementById('drop-zone').style.display = 'block';
currentEditingFilePath = null;
if (codeMirrorEditor) {
codeMirrorEditor.setValue('');
}
}

View file

@ -9,42 +9,25 @@ function loadFileList(path = '') {
if (path) { if (path) {
list.innerHTML += `<li><a class="non-link" href="#" onclick="loadFileList('${path.split('/').slice(0, -1).join('/')}')">⬅️ ..</a></li>`; list.innerHTML += `<li><a class="non-link" href="#" onclick="loadFileList('${path.split('/').slice(0, -1).join('/')}')">⬅️ ..</a></li>`;
} }
data.sort((a, b) => { data.sort((a, b) => a.name.localeCompare(b.name));
if (a.is_dir === b.is_dir) {
return a.name.localeCompare(b.name);
}
return a.is_dir ? -1 : 1;
});
data.forEach(entry => { data.forEach(entry => {
const itemPath = path ? `${path}/${entry.name}` : entry.name; const itemPath = path ? `${path}/${entry.name}` : entry.name;
let html = ''; const html = entry.is_dir
if (entry.is_dir) { ? `<li>📁 <a class="non-link" href="#" onclick="loadFileList('${itemPath}')">${entry.name}</a>
html = `<li>📁 <a class="non-link" href="#" onclick="loadFileList('${itemPath}')">${entry.name}</a> <a class="non-link" onclick="deleteItem('${itemPath}')">
<a class="non-link action-icon" onclick="deleteItem('${itemPath}')" title="Usuń folder">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash"></i>
</a> </a>
</li>`; </li>`
} else { : `<li>📄 ${entry.name}
const fileExtension = getFileExtension(entry.name); <a class="non-link" href="/api/files/download?username=${username}&path=${encodeURIComponent(itemPath)}" target="_blank">
const editableExtensions = ['txt', 'log', 'json', 'yml', 'yaml', 'properties', 'html', 'css', 'js', 'xml', 'py'];
const isEditable = editableExtensions.includes(fileExtension.toLowerCase());
html = `<li>📄 ${entry.name}
${isEditable ? `
<a class="non-link action-icon" onclick="openFileEditor('${itemPath}')" title="Edytuj plik">
<i class="fa-solid fa-edit"></i>
</a>` : ''}
<a class="non-link action-icon" href="/api/files/download?username=${username}&path=${encodeURIComponent(itemPath)}" target="_blank" title="Pobierz plik">
<i class="fa-solid fa-download"></i> <i class="fa-solid fa-download"></i>
</a> </a>
<a class="non-link action-icon" onclick="deleteItem('${itemPath}')" title="Usuń plik"> <a class="non-link" onclick="deleteItem('${itemPath}')">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash"></i>
</a> </a>
</li>`; </li>`;
}
list.innerHTML += html; list.innerHTML += html;
}); });
closeFileEditor();
}); });
} }
@ -70,9 +53,7 @@ function deleteItem(path) {
}); });
} }
document.querySelector('[onclick="showTab(\'files\')"]').addEventListener('click', () => { document.querySelector('[onclick="showTab(\'files\')"]').addEventListener('click', () => loadFileList());
loadFileList(); // Load root directory when the tab is opened
});
const dropZone = document.getElementById('drop-zone'); const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input'); const fileInput = document.getElementById('file-input');

View file

@ -33,36 +33,36 @@ function searchMods() {
} }
function installMod(projectId) { function installMod(projectId) {
fetch('/api/config') fetch('/api/config')
.then(response => response.json()) .then(response => response.json())
.then(configData => { .then(configData => {
if (configData.success) { console.log(configData);
const serverType = configData.type; if (configData.success) {
const serverVersion = configData.version; const serverType = configData.type;
if (serverType && serverVersion) { const serverVersion = configData.version;
fetch('/api/modrinth/download', { if (serverType && serverVersion) {
method: 'POST', fetch('/api/modrinth/download', {
headers: { 'Content-Type': 'application/json' }, method: 'POST',
body: JSON.stringify({ project_id: projectId, username, type: serverType, version: serverVersion }), headers: { 'Content-Type': 'application/json' },
}) body: JSON.stringify({ project_id: projectId, username, type: serverType, version: serverVersion }),
.then(r => r.json()) })
.then(data => { .then(r => r.json())
if (data.success) { .then(data => {
const modFiles = data.downloaded.map(mod => "- " + mod.filename).join("\n"); if (data.success) {
alert(`Zainstalowano:\n${modFiles}`); alert(`Zainstalowano: ${data.file}`);
loadFileList(); loadFileList();
} else { } else {
alert("Nie udało się zainstalować moda."); alert("Nie udało się zainstalować moda.");
} }
}); });
} else {
console.error('Server type or version not available');
}
} else { } else {
console.error('Server type or version not available'); console.error('Server config not found');
} }
} else { })
console.error('Server config not found'); .catch(err => {
} console.error('Error fetching server config:', err);
}) });
.catch(err => {
console.error('Error fetching server config:', err);
});
} }

View file

@ -1,6 +1,3 @@
/* Imports */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
/* Reset & Base Styles */ /* Reset & Base Styles */
* { * {
margin: 0; margin: 0;
@ -105,9 +102,8 @@ h2 {
/* Tabs */ /* Tabs */
.tabs { .tabs {
/* Desktop default layout */
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); /* Original desktop layout */ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 14px; gap: 14px;
margin-bottom: 26px; margin-bottom: 26px;
} }
@ -122,11 +118,6 @@ h2 {
color: #ccc; color: #ccc;
transition: background-color 0.25s, transform 0.2s; transition: background-color 0.25s, transform 0.2s;
box-shadow: 0 0 8px transparent; box-shadow: 0 0 8px transparent;
/* Flexbox for text and icon alignment */
display: flex;
align-items: center;
justify-content: center; /* Center content horizontally */
gap: 8px; /* Space between text and icon */
} }
.tab-button:hover { .tab-button:hover {
@ -140,16 +131,6 @@ h2 {
box-shadow: 0 0 14px #4a5aef99; box-shadow: 0 0 14px #4a5aef99;
} }
.tab-button .tab-icon {
font-size: 1.2em; /* Adjust icon size as needed */
}
.tab-button .tab-text {
/* By default, text is visible on desktop */
display: block;
}
.square-button { .square-button {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
width: 200px; width: 200px;
@ -228,7 +209,6 @@ h2 {
.btn-primary { background: #4a5aef; color: white; box-shadow: 0 0 10px #4a5aef99; } .btn-primary { background: #4a5aef; color: white; box-shadow: 0 0 10px #4a5aef99; }
.btn-success { background: #2ecc71; color: white; box-shadow: 0 0 10px #2ecc7199; } .btn-success { background: #2ecc71; color: white; box-shadow: 0 0 10px #2ecc7199; }
.btn-danger { background: #e74c3c; color: white; box-shadow: 0 0 10px #e74c3c99; } .btn-danger { background: #e74c3c; color: white; box-shadow: 0 0 10px #e74c3c99; }
.btn-worsedanger { background: #8b0000; color: white; box-shadow: 0 0 10px #8b000099; }
.btn-warning { background: #f39c12; color: black; box-shadow: 0 0 10px #f39c1288; } .btn-warning { background: #f39c12; color: black; box-shadow: 0 0 10px #f39c1288; }
.btn-info { background: #3498db; color: white; box-shadow: 0 0 10px #3498db99; } .btn-info { background: #3498db; color: white; box-shadow: 0 0 10px #3498db99; }
@ -276,7 +256,18 @@ h2 {
background-color: #666; background-color: #666;
} }
body > *:not(.cursor-glow) { /* Chart Container */
.chart-container {
background: #12121c;
padding: 20px;
border-radius: 14px;
margin-top: 30px;
border: 1px solid #232334;
box-shadow: 0 0 12px #1a1a2a;
overflow-x: auto;
}
body > *:not(script):not(style):not(.cursor-glow) {
animation: growIn 0.6s ease-out; animation: growIn 0.6s ease-out;
} }
@ -586,197 +577,117 @@ a.non-link:hover {
color: #000; color: #000;
} }
/* Servers Status */
.server-table-container {
overflow-x: auto;
padding: 1rem;
border-radius: 16px;
background: rgba(14, 14, 26, 0.6);
backdrop-filter: blur(12px);
border: 1px solid #1a1a2a;
box-shadow: 0 0 24px rgba(74, 90, 239, 0.15);
}
.server-table {
width: 100%;
border-collapse: collapse;
color: #f0f0f0;
font-size: 0.95rem;
border-radius: 12px;
overflow: hidden;
}
.server-table thead {
background: linear-gradient(145deg, #1a1a2f, #23233a);
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.85rem;
color: #b9c5ff;
}
.server-table th,
.server-table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.server-table tbody tr:hover {
background: rgba(255, 255, 255, 0.03);
transition: background 0.3s ease;
}
.server-table th:first-child,
.server-table td:first-child {
border-left: none;
}
.server-table th:last-child,
.server-table td:last-child {
border-right: none;
}
.server-table tbody tr {
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0);
transition: box-shadow 0.2s ease;
}
.server-table tbody tr:hover {
box-shadow: inset 0 0 12px rgba(255, 255, 255, 0.03);
}
.status-label {
display: inline-block;
padding: 0.4em 0.75em;
border-radius: 999px;
font-weight: 600;
font-size: 0.875rem;
text-transform: capitalize;
color: white;
}
.status-label.online {
background-color: #2ecc71;
}
.status-label.offline {
background-color: #e74c3c;
}
/* Mobile Tweaks */ /* Mobile Tweaks */
@media (max-width: 768px) { @media (max-width: 480px) {
html { html {
font-size: clamp(15px, 4vw, 16px); font-size: clamp(14px, 2.5vw, 16px);
} }
body { /* Heading adjustment */
padding: 0 8px; h2 {
} font-size: 1.5rem;
}
h1, h2, h3 { /* Dashboard Card */
line-height: 1.2; .dashboard-card {
} padding: 16px;
max-width: 100%;
}
.dashboard-card { /* Tab buttons */
padding: 16px; .tab-button {
max-width: 100%; padding: 10px;
box-sizing: border-box; font-size: 0.85rem;
} }
/* Tabs on Mobile: Single row, icons only */ /* Square button adjustments */
.tabs { .square-button {
display: flex; /* Use flexbox for a single row */ width: 100%;
grid-template-columns: unset; /* Override grid for mobile */ aspect-ratio: unset;
justify-content: space-around; /* Distribute items evenly */ flex-direction: row;
flex-wrap: nowrap; /* Keep tabs in a single row */ justify-content: flex-start;
overflow-x: auto; /* Enable horizontal scrolling if buttons exceed screen width */ gap: 12px;
-webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */ padding: 10px;
padding-bottom: 5px; /* Add some padding for scrollbar if present */ font-size: 0.9rem;
gap: 8px; /* Slightly smaller gap on mobile */ }
}
.tab-button { .square-button img {
flex-shrink: 0; /* Prevent buttons from shrinking */ width: 32px;
padding: 10px 15px; /* Adjust padding for icon-only buttons */ margin: 0;
/* Center content for icon-only */ }
justify-content: center;
width: auto; /* Allow buttons to size to content */
}
.tab-button .tab-text { /* Navigation Bar */
display: none; /* Hide the text on mobile */ .top-nav {
} padding: 12px;
font-size: 0.9rem;
flex-direction: column;
align-items: flex-start;
}
.square-button { .nav-links a {
display: flex; margin-left: 10px;
align-items: center; font-size: 0.9rem;
width: 100%; }
flex-direction: row;
justify-content: flex-start;
gap: 12px;
padding: 12px;
font-size: 0.95rem;
}
.square-button img { /* Console Output */
width: 32px; .console-output {
height: auto; font-size: 0.8rem;
margin: 0; height: 160px;
} }
.top-nav { /* Chart container */
flex-direction: column; .chart-container {
align-items: center; width: 100%;
justify-content: center; height: auto;
text-align: center; padding: 12px;
} }
.nav-links { /* Buttons */
display: flex; .btn {
flex-direction: row; font-size: 0.9rem;
align-items: center; padding: 12px;
margin-top: 12px; width: 100%;
} }
.chart-container { /* Form layout */
width: 100%; form {
height: auto; padding: 0 8px;
padding: 12px; }
}
.btn { /* Tab content spacing */
font-size: 0.95rem; .tab-content {
padding: 14px; margin-top: 8px;
width: 100%; }
}
form { /* Particle background on small screens */
padding: 0 8px; #particle-background {
} display: none; /* Improves performance */
}
.tab-content { /* Charts Row - Allow horizontal scroll */
margin-top: 12px; .charts-row {
} overflow-x: auto;
padding-bottom: 10px;
}
.charts-row { /* Input Fields */
overflow-x: auto; input[type="text"], input[type="number"], select {
padding-bottom: 10px; width: 100%;
} }
input[type="text"], input[type="number"], select, textarea { /* Adjust drop zone */
width: 100%; #drop-zone {
font-size: 1rem; padding: 20px;
padding: 10px; }
box-sizing: border-box;
}
#drop-zone { /* Adjust text area height */
padding: 20px; .console-output {
} height: 160px;
}
} }
/* Universal Fixes */ /* Additional general fixes for small screens */
* { * {
max-width: 100%; max-width: 100%;
word-wrap: break-word; word-wrap: break-word;
box-sizing: border-box;
} }

View file

@ -3,24 +3,15 @@
<h2>Twój serwer Minecraft</h2> <h2>Twój serwer Minecraft</h2>
<div class="tabs"> <div class="tabs">
<button class="tab-button" onclick="showTab('controls')"> <button class="tab-button" onclick="showTab('controls')">Sterowanie 🎛️</button>
<span class="tab-text">Sterowanie</span> <span class="tab-icon">🎛️</span> <button class="tab-button" onclick="showTab('console')">Konsola 📟</button>
</button> <button class="tab-button" onclick="showTab('files')">Pliki 📁</button>
<button class="tab-button" onclick="showTab('console')">
<span class="tab-text">Konsola</span> <span class="tab-icon">📟</span> </div>
</button> <div class="tabs">
<button class="tab-button" onclick="showTab('files')"> <button class="tab-button" onclick="showTab('config')">Konfiguracja 🛠️</button>
<span class="tab-text">Pliki</span> <span class="tab-icon">📁</span> <button class="tab-button" onclick="showTab('mods')">Mody/Pluginy 🧩</button>
</button> <button class="tab-button" onclick="showTab('statistics')">Statystyki 📈</button>
<button class="tab-button" onclick="showTab('config')">
<span class="tab-text">Konfiguracja</span> <span class="tab-icon">🛠️</span>
</button>
<button class="tab-button" onclick="showTab('mods')">
<span class="tab-text">Mody/Pluginy</span> <span class="tab-icon">🧩</span>
</button>
<button class="tab-button" onclick="showTab('statistics')">
<span class="tab-text">Statystyki</span> <span class="tab-icon">📈</span>
</button>
</div> </div>
<div class="tab-content"> <div class="tab-content">
@ -34,7 +25,7 @@
<button class="btn btn-success" onclick="sendAction('start')">Start</button> <button class="btn btn-success" onclick="sendAction('start')">Start</button>
<button class="btn btn-warning" onclick="sendAction('restart')">Restart</button> <button class="btn btn-warning" onclick="sendAction('restart')">Restart</button>
<button class="btn btn-danger" onclick="sendAction('stop')">Stop</button><br> <button class="btn btn-danger" onclick="sendAction('stop')">Stop</button><br>
<button class="btn btn-worsedanger" onclick="deleteServer()">Usuń Serwer</button> <button class="btn btn-danger" onclick="deleteServer()">Usuń Serwer</button>
</div> </div>
<div class="tab-panel" id="console"> <div class="tab-panel" id="console">
@ -58,18 +49,11 @@
<br> <br>
<h4>Lista plików:</h4> <h4>Lista plików:</h4>
<ul id="file-list"></ul> <ul id="file-list"></ul>
<div id="file-editor-container" style="display: none;">
<h3>Edytuj plik: <span id="editing-filename"></span></h3>
<textarea id="file-editor" style="width: 100%; height: 400px;"></textarea>
<button class="btn btn-primary" onclick="saveFileContent()">Zapisz zmiany</button>
<button class="btn btn-secondary" onclick="closeFileEditor()">Zamknij</button>
</div>
</div> </div>
<div class="tab-panel" id="config"> <div class="tab-panel" id="config">
<h4>Konfiguracja Serwera:</h4>
<form id="config-form"> <form id="config-form">
<h4>Konfiguracja Serwera:</h4>
<div class="form-group"> <div class="form-group">
<label for="server-type">Typ serwera</label> <label for="server-type">Typ serwera</label>
<select id="server-type" class="form-control"> <select id="server-type" class="form-control">
@ -124,21 +108,12 @@
<label for="view-distance">Zasięg widzenia</label> <label for="view-distance">Zasięg widzenia</label>
<input type="number" id="view-distance" class="form-control" min="1" value="{{ config['view-distance'] }}"> <input type="number" id="view-distance" class="form-control" min="1" value="{{ config['view-distance'] }}">
</div> </div>
<h5>Inne:</h5>
<div class="form-group">
<label for="andus-drasl">Serwer Drasl Andusa</label>
<h5>Tylko dla serwerów 1.16 i wyższych</h5>
<h6>W tym trybie mogą dołączyć tylko gracze z kontem Drasl (https://drasl.andus.ovh).
<br>Tryb online musi być włączony, ponieważ ten serwer dziala tak samo jak serwery Mojang (Ale bez konieczności kupowania Minecrafta).</h6>
<h6 style="color: red">UWAGA: W tym trybie konta z kupionym Minecraftem nie mogą dołączyć na serwer, też muszą utworzyć konto Drasl.</h6>
<input type="checkbox" id="andus-drasl" class="form-check-input" {% if config['andus-drasl'] %} checked {% endif %}>
</div>
<button type="button" class="btn btn-primary" onclick="saveConfig()">Zapisz zmiany</button> <button type="button" class="btn btn-primary" onclick="saveConfig()">Zapisz zmiany</button>
</form> </form>
</div> </div>
<div class="tab-panel" id="mods"> <div class="tab-panel" id="mods">
<h4>Zainstaluj mody/pluginy z <span style="color: #2ecc71;">Modrinth</span></h4> <h2>Zainstaluj mody/pluginy z Modrinth</h2>
<input type="text" id="mod-search" placeholder="Wyszukaj mod/plugin..." oninput="searchMods()"> <input type="text" id="mod-search" placeholder="Wyszukaj mod/plugin..." oninput="searchMods()">
<div id="mod-results"></div> <div id="mod-results"></div>
</div> </div>
@ -151,8 +126,6 @@
</div> </div>
</div> </div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/material-darker.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="/static/js/controls.js"></script> <script src="/static/js/controls.js"></script>
<script src="/static/js/console.js"></script> <script src="/static/js/console.js"></script>
@ -171,11 +144,6 @@
function showTab(id) { function showTab(id) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
$(id).classList.add('active'); $(id).classList.add('active');
// Close the mobile menu if it's open (optional, depending on your menu implementation)
const tabsContainer = document.querySelector('.tabs');
if (tabsContainer.classList.contains('mobile-active')) {
tabsContainer.classList.remove('mobile-active');
}
} }
let currentPath = ''; let currentPath = '';
@ -295,26 +263,11 @@
pointRadius: 0 pointRadius: 0
}] }]
}, },
options: { options: chartOptions
...chartOptions,
scales: { ...chartOptions.scales, y: { min: 0, max: 4096, ticks: { color: '#aaa' } } }
}
}); });
checkServerStatus(); checkServerStatus();
setInterval(checkServerStatus, 5000); setInterval(checkServerStatus, 5000);
</script> </script>
<!-- CSS do Edytora Plików --> {% endblock %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/htmlmixed/htmlmixed.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/clike/clike.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/python/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/yaml/yaml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/properties/properties.min.js"></script>
<script src="/static/js/file_editor.js"></script>
{% endblock %}

View file

@ -2,30 +2,20 @@
{% block content %} {% block content %}
<div class="logo-wrapper"> <div class="logo-wrapper">
<h2 class="logo-text">MCPanel</h2> <h2 class="logo-text">MCPanel</h2>
<p id="quote" class="splash-text"></p> <p id="quote" class="splash-text">Jesteśmy 1.5x lepsi od Aternosa!</p>
</div> </div>
<div class="tabs"> <div class="tabs">
{% if is_logged_in %} {% if has_server %}
{% if has_server %} <a class="tab-button" href="{{ url_for('main.dashboard') }}">Idź do panelu</a>
<a class="tab-button" href="{{ url_for('main.dashboard') }}">Idź do panelu</a>
{% else %}
<a class="tab-button" href="{{ url_for('main.setup') }}">Ustaw własny serwer</a>
{% endif %}
{% else %} {% else %}
<a class="tab-button" href="{{ url_for('main.dashboard') }}">Zaloguj się</a> <a class="tab-button" href="{{ url_for('main.setup') }}">Ustaw własny serwer</a>
{% endif %} {% endif %}
<a class="tab-button" href="#news">Wiadomości</a>
</div>
<div id="news">
<!-- Z Tagami jak: WERSJA PANELU, PROBLEM -->
<h3>Wiadomości:</h3>
<p>Soon&trade;</p>
</div> </div>
<script> <script>
const quotes = [ const quotes = [
"Jesteśmy 2x lepsi od Aternosa!",
"Twój serwer, Twoje zasady!", "Twój serwer, Twoje zasady!",
"Stabilność? Nie znam takiego słowa!", "Stabilność? Nie znam takiego słowa!",
"Zero kolejki, tylko gra!", "Zero kolejki, tylko gra!",

View file

@ -3,7 +3,6 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>MCPanel</title> <title>MCPanel</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
</head> </head>
@ -23,9 +22,8 @@
<div class="nav-logo"><a href="/">MCPanel <h6>BETA</h6></a></div> <div class="nav-logo"><a href="/">MCPanel <h6>BETA</h6></a></div>
<div class="nav-links"> <div class="nav-links">
<a href="/dashboard">Panel</a> <a href="/dashboard">Panel</a>
<a href="/status">Statusy</a>
<a style="color: cornflowerblue;" href="/donate">Donate</a> <a style="color: cornflowerblue;" href="/donate">Donate</a>
<a style="color: red;" href="/logout">Wyloguj</a> <a href="/logout">Wyloguj</a>
</div> </div>
</nav> </nav>
@ -33,7 +31,7 @@
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<div id="ad-banner" class="ad-banner"></div> <div id="ad-banner" class="ad-banner"></div>
<p>Wersja: v0.3.1</p> <p>Wersja: v0.2</p>
<script> <script>
window.addEventListener('dragover', e => e.preventDefault()); window.addEventListener('dragover', e => e.preventDefault());
window.addEventListener('drop', e => e.preventDefault()); window.addEventListener('drop', e => e.preventDefault());

View file

@ -1,32 +0,0 @@
{% extends "layout.html" %}
{% block content %}
<h1 class="page-title">Wszystkie Serwery</h1>
<div class="server-table-container">
<table class="server-table">
<thead>
<tr>
<th>Nazwa</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for server in servers %}
<tr>
<td>{{ server.name }}</td>
<td>
<span class="status-label {{ 'online' if server.status == 'Online' else 'offline' }}">
{{ server.status }}
</span>
</td>
</tr>
{% else %}
<tr>
<td colspan="2" class="empty-row">Nie znaleziono serwerów</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View file

@ -4,7 +4,7 @@
<div style="display: flex; justify-content: center; gap: 2rem; margin-bottom: 2rem;"> <div style="display: flex; justify-content: center; gap: 2rem; margin-bottom: 2rem;">
<button class="square-button" onclick="selectType(this, 'paper')"> <button class="square-button" onclick="selectType(this, 'paper')">
<img src="https://assets.papermc.io/brand/papermc_logo.min.svg" alt="Paper"> <img src="https://docs.papermc.io/assets/images/papermc-logomark-512-f125384f3367cd4d9291ca983fcb7334.png" alt="Paper">
<b>Paper</b> <b>Paper</b>
<br> <br>
<p>Zoptymalizowany serwer Minecraft oferujący lepszą wydajność.</p> <p>Zoptymalizowany serwer Minecraft oferujący lepszą wydajność.</p>