Update Dockerfile
Browse files- Dockerfile +273 -25
Dockerfile
CHANGED
|
@@ -80,7 +80,7 @@ echo "✅ Auto-backup enabled (every 5 minutes)"
|
|
| 80 |
echo ""
|
| 81 |
|
| 82 |
# ==========================================
|
| 83 |
-
# WEB TERMINAL SERVER (
|
| 84 |
# ==========================================
|
| 85 |
cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
|
| 86 |
import os
|
|
@@ -120,7 +120,6 @@ def load_logs_from_file():
|
|
| 120 |
if os.path.exists(LOGS_FILE):
|
| 121 |
with open(LOGS_FILE, 'r', encoding='utf-8') as f:
|
| 122 |
logs_history = f.read().splitlines()
|
| 123 |
-
# Keep only last 1000 lines
|
| 124 |
logs_history = logs_history[-1000:]
|
| 125 |
print(f"[LOGS] Loaded {len(logs_history)} lines from file", flush=True)
|
| 126 |
except Exception as e:
|
|
@@ -141,22 +140,18 @@ def push_logs_to_github():
|
|
| 141 |
try:
|
| 142 |
if not GITHUB_TOKEN:
|
| 143 |
return
|
| 144 |
-
|
| 145 |
-
# Read current logs
|
| 146 |
if not os.path.exists(LOGS_FILE):
|
| 147 |
return
|
| 148 |
|
| 149 |
with open(LOGS_FILE, 'r', encoding='utf-8') as f:
|
| 150 |
content = f.read()
|
| 151 |
|
| 152 |
-
# GitHub API - get current file SHA
|
| 153 |
api_url = f"https://api.github.com/repos/{GITHUB_REPO}/contents/logs/console_logs.txt"
|
| 154 |
headers = {
|
| 155 |
"Authorization": f"token {GITHUB_TOKEN}",
|
| 156 |
"Accept": "application/vnd.github.v3+json"
|
| 157 |
}
|
| 158 |
|
| 159 |
-
# Check if file exists
|
| 160 |
sha = None
|
| 161 |
try:
|
| 162 |
resp = requests.get(api_url, headers=headers, timeout=10)
|
|
@@ -165,7 +160,6 @@ def push_logs_to_github():
|
|
| 165 |
except:
|
| 166 |
pass
|
| 167 |
|
| 168 |
-
# Create or update file
|
| 169 |
import base64
|
| 170 |
encoded_content = base64.b64encode(content.encode()).decode()
|
| 171 |
|
|
@@ -212,11 +206,9 @@ def add_log_line(line):
|
|
| 212 |
timestamp = datetime.now().strftime('%H:%M:%S')
|
| 213 |
log_entry = f"[{timestamp}] {line}"
|
| 214 |
logs_history.append(log_entry)
|
| 215 |
-
# Keep only last 1000 lines
|
| 216 |
if len(logs_history) > 1000:
|
| 217 |
logs_history = logs_history[-1000:]
|
| 218 |
output_queue.put(log_entry)
|
| 219 |
-
# Save to file periodically (every 10 lines)
|
| 220 |
if len(logs_history) % 10 == 0:
|
| 221 |
save_logs_to_file()
|
| 222 |
|
|
@@ -227,7 +219,6 @@ def start_minecraft():
|
|
| 227 |
print("[MC] Starting Minecraft server...", flush=True)
|
| 228 |
add_log_line("[SYSTEM] Starting Minecraft server...")
|
| 229 |
|
| 230 |
-
# Record start time
|
| 231 |
server_start_time = time.time()
|
| 232 |
save_uptime_to_file()
|
| 233 |
|
|
@@ -356,10 +347,27 @@ HTML_TEMPLATE = """
|
|
| 356 |
display: flex;
|
| 357 |
align-items: center;
|
| 358 |
justify-content: center;
|
|
|
|
| 359 |
}
|
| 360 |
|
| 361 |
.nav-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
| 362 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
/* Main Layout */
|
| 364 |
.main-container {
|
| 365 |
display: flex;
|
|
@@ -457,6 +465,23 @@ HTML_TEMPLATE = """
|
|
| 457 |
.terminal-line.success { color: #3fb950; }
|
| 458 |
.terminal-line.system { color: #a855f7; font-weight: 500; }
|
| 459 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
/* Command Input Area */
|
| 461 |
.command-area {
|
| 462 |
background: #161b22;
|
|
@@ -642,6 +667,90 @@ HTML_TEMPLATE = """
|
|
| 642 |
|
| 643 |
.sidebar-overlay.active { display: block; }
|
| 644 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
/* ============================================
|
| 646 |
MOBILE RESPONSIVE BREAKPOINTS
|
| 647 |
============================================ */
|
|
@@ -750,7 +859,7 @@ HTML_TEMPLATE = """
|
|
| 750 |
}
|
| 751 |
|
| 752 |
#commandInput {
|
| 753 |
-
font-size: 16px;
|
| 754 |
}
|
| 755 |
|
| 756 |
.send-btn {
|
|
@@ -776,6 +885,11 @@ HTML_TEMPLATE = """
|
|
| 776 |
.stat-value {
|
| 777 |
font-size: 14px;
|
| 778 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 779 |
}
|
| 780 |
|
| 781 |
/* Landscape mobile */
|
|
@@ -826,11 +940,59 @@ HTML_TEMPLATE = """
|
|
| 826 |
</a>
|
| 827 |
</div>
|
| 828 |
<div class="nav-right">
|
| 829 |
-
<button class="nav-btn"
|
| 830 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 831 |
</div>
|
| 832 |
</nav>
|
| 833 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
<!-- Sidebar Overlay -->
|
| 835 |
<div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleSidebar()"></div>
|
| 836 |
|
|
@@ -952,6 +1114,9 @@ HTML_TEMPLATE = """
|
|
| 952 |
const connectionBar = document.getElementById('connectionBar');
|
| 953 |
const sidebar = document.getElementById('sidebar');
|
| 954 |
const sidebarOverlay = document.getElementById('sidebarOverlay');
|
|
|
|
|
|
|
|
|
|
| 955 |
|
| 956 |
let ws = null;
|
| 957 |
let reconnectAttempts = 0;
|
|
@@ -959,6 +1124,8 @@ HTML_TEMPLATE = """
|
|
| 959 |
let isServerRunning = false;
|
| 960 |
let sidebarOpen = false;
|
| 961 |
let serverStartTime = null;
|
|
|
|
|
|
|
| 962 |
|
| 963 |
function updateConnectionStatus(status, message) {
|
| 964 |
connectionBar.className = 'connection-bar ' + status;
|
|
@@ -968,6 +1135,16 @@ HTML_TEMPLATE = """
|
|
| 968 |
'connecting': 'fa-circle-notch fa-spin'
|
| 969 |
};
|
| 970 |
connectionBar.innerHTML = '<i class="fas ' + icons[status] + '"></i><span>' + message + '</span>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 971 |
}
|
| 972 |
|
| 973 |
function updateServerStatus(running, startTime = null) {
|
|
@@ -982,9 +1159,11 @@ HTML_TEMPLATE = """
|
|
| 982 |
stopBtn.disabled = false;
|
| 983 |
commandInput.disabled = false;
|
| 984 |
sendBtn.disabled = false;
|
|
|
|
| 985 |
if (startTime) {
|
| 986 |
-
serverStartTime = startTime * 1000;
|
| 987 |
}
|
|
|
|
| 988 |
} else {
|
| 989 |
statusEl.className = 'status-indicator status-offline';
|
| 990 |
statusEl.innerHTML = '<span class="pulse"></span>Offline';
|
|
@@ -993,15 +1172,22 @@ HTML_TEMPLATE = """
|
|
| 993 |
stopBtn.disabled = true;
|
| 994 |
commandInput.disabled = true;
|
| 995 |
sendBtn.disabled = true;
|
|
|
|
| 996 |
serverStartTime = null;
|
|
|
|
| 997 |
}
|
| 998 |
}
|
| 999 |
|
| 1000 |
function addTerminalLine(text, type = '') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1001 |
const line = document.createElement('div');
|
| 1002 |
line.className = 'terminal-line ' + type;
|
| 1003 |
|
| 1004 |
-
// Check if it's a system message
|
| 1005 |
if (text.includes('[SYSTEM]')) {
|
| 1006 |
line.className += ' system';
|
| 1007 |
} else if (text.includes('> ')) {
|
|
@@ -1018,12 +1204,36 @@ HTML_TEMPLATE = """
|
|
| 1018 |
terminal.appendChild(line);
|
| 1019 |
terminal.scrollTop = terminal.scrollHeight;
|
| 1020 |
|
| 1021 |
-
// Limit terminal lines for performance on mobile
|
| 1022 |
while (terminal.children.length > 500) {
|
| 1023 |
terminal.removeChild(terminal.firstChild);
|
| 1024 |
}
|
| 1025 |
}
|
| 1026 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1027 |
function formatUptime(seconds) {
|
| 1028 |
if (!seconds || seconds <= 0) return 'Offline';
|
| 1029 |
|
|
@@ -1042,11 +1252,14 @@ HTML_TEMPLATE = """
|
|
| 1042 |
if (serverStartTime && isServerRunning) {
|
| 1043 |
const now = Date.now();
|
| 1044 |
const uptime = Math.floor((now - serverStartTime) / 1000);
|
| 1045 |
-
|
|
|
|
| 1046 |
document.getElementById('uptimeSub').textContent = 'Running';
|
|
|
|
| 1047 |
} else {
|
| 1048 |
document.getElementById('uptime').textContent = 'Offline';
|
| 1049 |
document.getElementById('uptimeSub').textContent = 'Not running';
|
|
|
|
| 1050 |
}
|
| 1051 |
}
|
| 1052 |
|
|
@@ -1069,19 +1282,24 @@ HTML_TEMPLATE = """
|
|
| 1069 |
if (data.type === 'output') {
|
| 1070 |
addTerminalLine(data.data);
|
| 1071 |
} else if (data.type === 'stats') {
|
|
|
|
| 1072 |
document.getElementById('cpuLoad').textContent = data.cpu.toFixed(1) + '%';
|
| 1073 |
document.getElementById('memoryUsage').textContent = data.memory.toFixed(1) + '%';
|
| 1074 |
const memTotal = (data.memoryTotal / 1024 / 1024 / 1024).toFixed(2);
|
| 1075 |
const memUsed = ((data.memoryTotal * data.memory / 100) / 1024 / 1024 / 1024).toFixed(2);
|
| 1076 |
document.getElementById('memorySub').textContent = memUsed + ' / ' + memTotal + ' GB';
|
| 1077 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1078 |
updateServerStatus(data.running, data.serverStartTime);
|
| 1079 |
|
| 1080 |
if (data.players !== undefined) {
|
| 1081 |
document.getElementById('playerCount').textContent = data.players;
|
|
|
|
| 1082 |
}
|
| 1083 |
} else if (data.type === 'history') {
|
| 1084 |
-
// Load history logs
|
| 1085 |
data.logs.forEach(log => {
|
| 1086 |
addTerminalLine(log);
|
| 1087 |
});
|
|
@@ -1108,6 +1326,11 @@ HTML_TEMPLATE = """
|
|
| 1108 |
function sendInput() {
|
| 1109 |
const cmd = commandInput.value.trim();
|
| 1110 |
if (cmd && ws && ws.readyState === WebSocket.OPEN) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1111 |
ws.send(JSON.stringify({type: 'command', data: cmd}));
|
| 1112 |
commandInput.value = '';
|
| 1113 |
}
|
|
@@ -1119,8 +1342,27 @@ HTML_TEMPLATE = """
|
|
| 1119 |
}
|
| 1120 |
}
|
| 1121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1122 |
commandInput.addEventListener('keypress', (e) => {
|
| 1123 |
-
if (e.key === 'Enter')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1124 |
});
|
| 1125 |
|
| 1126 |
function switchTab(tab, element) {
|
|
@@ -1147,6 +1389,14 @@ HTML_TEMPLATE = """
|
|
| 1147 |
}
|
| 1148 |
}
|
| 1149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1150 |
// Close sidebar when clicking outside on mobile
|
| 1151 |
document.addEventListener('click', (e) => {
|
| 1152 |
if (window.innerWidth <= 768 && sidebarOpen) {
|
|
@@ -1214,7 +1464,7 @@ def websocket(ws):
|
|
| 1214 |
if logs_history:
|
| 1215 |
ws.send(json.dumps({
|
| 1216 |
'type': 'history',
|
| 1217 |
-
'logs': logs_history[-100:]
|
| 1218 |
}))
|
| 1219 |
except Exception as e:
|
| 1220 |
print(f"[WS] History send error: {e}", flush=True)
|
|
@@ -1226,7 +1476,6 @@ def websocket(ws):
|
|
| 1226 |
memory = psutil.virtual_memory()
|
| 1227 |
running = mc_process is not None and mc_process.poll() is None
|
| 1228 |
|
| 1229 |
-
# Calculate actual uptime from server_start_time
|
| 1230 |
uptime_to_send = None
|
| 1231 |
if running and server_start_time:
|
| 1232 |
uptime_to_send = server_start_time
|
|
@@ -1302,17 +1551,14 @@ signal.signal(signal.SIGTERM, cleanup)
|
|
| 1302 |
signal.signal(signal.SIGINT, cleanup)
|
| 1303 |
|
| 1304 |
if __name__ == '__main__':
|
| 1305 |
-
# Load previous logs and uptime
|
| 1306 |
load_logs_from_file()
|
| 1307 |
load_uptime_from_file()
|
| 1308 |
|
| 1309 |
print("[MAIN] Starting web server...", flush=True)
|
| 1310 |
|
| 1311 |
-
# Start Minecraft immediately
|
| 1312 |
mc_thread = threading.Thread(target=start_minecraft, daemon=True)
|
| 1313 |
mc_thread.start()
|
| 1314 |
|
| 1315 |
-
# Start GitHub push thread (every 5 minutes)
|
| 1316 |
def github_push_loop():
|
| 1317 |
while True:
|
| 1318 |
time.sleep(300)
|
|
@@ -1339,11 +1585,13 @@ fi
|
|
| 1339 |
|
| 1340 |
echo ""
|
| 1341 |
echo "=========================================="
|
| 1342 |
-
echo "✅ Minecraft Server with
|
| 1343 |
echo "🌐 Access at: https://your-space.hf.space"
|
| 1344 |
echo "📱 Mobile optimized interface enabled"
|
| 1345 |
echo "💾 Logs are now persistent across refreshes"
|
| 1346 |
echo "⏰ Uptime tracking fixed"
|
|
|
|
|
|
|
| 1347 |
echo "=========================================="
|
| 1348 |
|
| 1349 |
# Keep container alive
|
|
|
|
| 80 |
echo ""
|
| 81 |
|
| 82 |
# ==========================================
|
| 83 |
+
# WEB TERMINAL SERVER (Real-time Typing + Status Icon)
|
| 84 |
# ==========================================
|
| 85 |
cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
|
| 86 |
import os
|
|
|
|
| 120 |
if os.path.exists(LOGS_FILE):
|
| 121 |
with open(LOGS_FILE, 'r', encoding='utf-8') as f:
|
| 122 |
logs_history = f.read().splitlines()
|
|
|
|
| 123 |
logs_history = logs_history[-1000:]
|
| 124 |
print(f"[LOGS] Loaded {len(logs_history)} lines from file", flush=True)
|
| 125 |
except Exception as e:
|
|
|
|
| 140 |
try:
|
| 141 |
if not GITHUB_TOKEN:
|
| 142 |
return
|
|
|
|
|
|
|
| 143 |
if not os.path.exists(LOGS_FILE):
|
| 144 |
return
|
| 145 |
|
| 146 |
with open(LOGS_FILE, 'r', encoding='utf-8') as f:
|
| 147 |
content = f.read()
|
| 148 |
|
|
|
|
| 149 |
api_url = f"https://api.github.com/repos/{GITHUB_REPO}/contents/logs/console_logs.txt"
|
| 150 |
headers = {
|
| 151 |
"Authorization": f"token {GITHUB_TOKEN}",
|
| 152 |
"Accept": "application/vnd.github.v3+json"
|
| 153 |
}
|
| 154 |
|
|
|
|
| 155 |
sha = None
|
| 156 |
try:
|
| 157 |
resp = requests.get(api_url, headers=headers, timeout=10)
|
|
|
|
| 160 |
except:
|
| 161 |
pass
|
| 162 |
|
|
|
|
| 163 |
import base64
|
| 164 |
encoded_content = base64.b64encode(content.encode()).decode()
|
| 165 |
|
|
|
|
| 206 |
timestamp = datetime.now().strftime('%H:%M:%S')
|
| 207 |
log_entry = f"[{timestamp}] {line}"
|
| 208 |
logs_history.append(log_entry)
|
|
|
|
| 209 |
if len(logs_history) > 1000:
|
| 210 |
logs_history = logs_history[-1000:]
|
| 211 |
output_queue.put(log_entry)
|
|
|
|
| 212 |
if len(logs_history) % 10 == 0:
|
| 213 |
save_logs_to_file()
|
| 214 |
|
|
|
|
| 219 |
print("[MC] Starting Minecraft server...", flush=True)
|
| 220 |
add_log_line("[SYSTEM] Starting Minecraft server...")
|
| 221 |
|
|
|
|
| 222 |
server_start_time = time.time()
|
| 223 |
save_uptime_to_file()
|
| 224 |
|
|
|
|
| 347 |
display: flex;
|
| 348 |
align-items: center;
|
| 349 |
justify-content: center;
|
| 350 |
+
position: relative;
|
| 351 |
}
|
| 352 |
|
| 353 |
.nav-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
| 354 |
|
| 355 |
+
/* Status indicator dot on status icon */
|
| 356 |
+
.nav-btn .status-dot {
|
| 357 |
+
position: absolute;
|
| 358 |
+
bottom: 6px;
|
| 359 |
+
right: 6px;
|
| 360 |
+
width: 8px;
|
| 361 |
+
height: 8px;
|
| 362 |
+
border-radius: 50%;
|
| 363 |
+
background: #22c55e;
|
| 364 |
+
border: 2px solid #252a30;
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
.nav-btn .status-dot.offline {
|
| 368 |
+
background: #ef4444;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
/* Main Layout */
|
| 372 |
.main-container {
|
| 373 |
display: flex;
|
|
|
|
| 465 |
.terminal-line.success { color: #3fb950; }
|
| 466 |
.terminal-line.system { color: #a855f7; font-weight: 500; }
|
| 467 |
|
| 468 |
+
/* Typing Preview Line */
|
| 469 |
+
.typing-preview {
|
| 470 |
+
color: #6e7681;
|
| 471 |
+
font-style: italic;
|
| 472 |
+
opacity: 0.8;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
.typing-preview .cursor {
|
| 476 |
+
animation: blink 1s infinite;
|
| 477 |
+
color: #58a6ff;
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
@keyframes blink {
|
| 481 |
+
0%, 50% { opacity: 1; }
|
| 482 |
+
51%, 100% { opacity: 0; }
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
/* Command Input Area */
|
| 486 |
.command-area {
|
| 487 |
background: #161b22;
|
|
|
|
| 667 |
|
| 668 |
.sidebar-overlay.active { display: block; }
|
| 669 |
|
| 670 |
+
/* Status Modal */
|
| 671 |
+
.status-modal {
|
| 672 |
+
display: none;
|
| 673 |
+
position: fixed;
|
| 674 |
+
top: 50%;
|
| 675 |
+
left: 50%;
|
| 676 |
+
transform: translate(-50%, -50%);
|
| 677 |
+
background: #1f2428;
|
| 678 |
+
border: 1px solid #363b42;
|
| 679 |
+
border-radius: 12px;
|
| 680 |
+
padding: 20px;
|
| 681 |
+
z-index: 300;
|
| 682 |
+
min-width: 300px;
|
| 683 |
+
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
.status-modal.active {
|
| 687 |
+
display: block;
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
.status-modal-header {
|
| 691 |
+
display: flex;
|
| 692 |
+
justify-content: space-between;
|
| 693 |
+
align-items: center;
|
| 694 |
+
margin-bottom: 15px;
|
| 695 |
+
padding-bottom: 10px;
|
| 696 |
+
border-bottom: 1px solid #363b42;
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
.status-modal-title {
|
| 700 |
+
font-size: 16px;
|
| 701 |
+
font-weight: 600;
|
| 702 |
+
color: #f0f6fc;
|
| 703 |
+
}
|
| 704 |
+
|
| 705 |
+
.status-modal-close {
|
| 706 |
+
background: transparent;
|
| 707 |
+
border: none;
|
| 708 |
+
color: #9ca3af;
|
| 709 |
+
cursor: pointer;
|
| 710 |
+
font-size: 18px;
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
.status-modal-close:hover {
|
| 714 |
+
color: #fff;
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
.status-item {
|
| 718 |
+
display: flex;
|
| 719 |
+
justify-content: space-between;
|
| 720 |
+
padding: 8px 0;
|
| 721 |
+
border-bottom: 1px solid #2a2f35;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
.status-item:last-child {
|
| 725 |
+
border-bottom: none;
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
.status-label-modal {
|
| 729 |
+
color: #9ca3af;
|
| 730 |
+
font-size: 13px;
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
.status-value-modal {
|
| 734 |
+
color: #f0f6fc;
|
| 735 |
+
font-weight: 500;
|
| 736 |
+
font-size: 13px;
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
.modal-overlay {
|
| 740 |
+
display: none;
|
| 741 |
+
position: fixed;
|
| 742 |
+
top: 0;
|
| 743 |
+
left: 0;
|
| 744 |
+
right: 0;
|
| 745 |
+
bottom: 0;
|
| 746 |
+
background: rgba(0,0,0,0.7);
|
| 747 |
+
z-index: 250;
|
| 748 |
+
}
|
| 749 |
+
|
| 750 |
+
.modal-overlay.active {
|
| 751 |
+
display: block;
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
/* ============================================
|
| 755 |
MOBILE RESPONSIVE BREAKPOINTS
|
| 756 |
============================================ */
|
|
|
|
| 859 |
}
|
| 860 |
|
| 861 |
#commandInput {
|
| 862 |
+
font-size: 16px;
|
| 863 |
}
|
| 864 |
|
| 865 |
.send-btn {
|
|
|
|
| 885 |
.stat-value {
|
| 886 |
font-size: 14px;
|
| 887 |
}
|
| 888 |
+
|
| 889 |
+
.status-modal {
|
| 890 |
+
min-width: 90vw;
|
| 891 |
+
max-width: 350px;
|
| 892 |
+
}
|
| 893 |
}
|
| 894 |
|
| 895 |
/* Landscape mobile */
|
|
|
|
| 940 |
</a>
|
| 941 |
</div>
|
| 942 |
<div class="nav-right">
|
| 943 |
+
<button class="nav-btn" id="statusBtn" title="Server Status" onclick="toggleStatusModal()">
|
| 944 |
+
<i class="fas fa-heartbeat"></i>
|
| 945 |
+
<span class="status-dot offline" id="navStatusDot"></span>
|
| 946 |
+
</button>
|
| 947 |
+
<button class="nav-btn" title="Full Screen" onclick="toggleFullscreen()">
|
| 948 |
+
<i class="fas fa-expand"></i>
|
| 949 |
+
</button>
|
| 950 |
</div>
|
| 951 |
</nav>
|
| 952 |
|
| 953 |
+
<!-- Status Modal -->
|
| 954 |
+
<div class="modal-overlay" id="modalOverlay" onclick="toggleStatusModal()"></div>
|
| 955 |
+
<div class="status-modal" id="statusModal">
|
| 956 |
+
<div class="status-modal-header">
|
| 957 |
+
<div class="status-modal-title">
|
| 958 |
+
<i class="fas fa-server"></i> Server Status
|
| 959 |
+
</div>
|
| 960 |
+
<button class="status-modal-close" onclick="toggleStatusModal()">
|
| 961 |
+
<i class="fas fa-times"></i>
|
| 962 |
+
</button>
|
| 963 |
+
</div>
|
| 964 |
+
<div class="status-content">
|
| 965 |
+
<div class="status-item">
|
| 966 |
+
<span class="status-label-modal">WebSocket</span>
|
| 967 |
+
<span class="status-value-modal" id="modalWsStatus">Connecting...</span>
|
| 968 |
+
</div>
|
| 969 |
+
<div class="status-item">
|
| 970 |
+
<span class="status-label-modal">Minecraft Server</span>
|
| 971 |
+
<span class="status-value-modal" id="modalMcStatus">Offline</span>
|
| 972 |
+
</div>
|
| 973 |
+
<div class="status-item">
|
| 974 |
+
<span class="status-label-modal">Server Uptime</span>
|
| 975 |
+
<span class="status-value-modal" id="modalUptime">--</span>
|
| 976 |
+
</div>
|
| 977 |
+
<div class="status-item">
|
| 978 |
+
<span class="status-label-modal">CPU Usage</span>
|
| 979 |
+
<span class="status-value-modal" id="modalCpu">--%</span>
|
| 980 |
+
</div>
|
| 981 |
+
<div class="status-item">
|
| 982 |
+
<span class="status-label-modal">Memory Usage</span>
|
| 983 |
+
<span class="status-value-modal" id="modalMemory">--%</span>
|
| 984 |
+
</div>
|
| 985 |
+
<div class="status-item">
|
| 986 |
+
<span class="status-label-modal">Active Players</span>
|
| 987 |
+
<span class="status-value-modal" id="modalPlayers">--</span>
|
| 988 |
+
</div>
|
| 989 |
+
<div class="status-item">
|
| 990 |
+
<span class="status-label-modal">Server Address</span>
|
| 991 |
+
<span class="status-value-modal" id="modalAddress">--</span>
|
| 992 |
+
</div>
|
| 993 |
+
</div>
|
| 994 |
+
</div>
|
| 995 |
+
|
| 996 |
<!-- Sidebar Overlay -->
|
| 997 |
<div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleSidebar()"></div>
|
| 998 |
|
|
|
|
| 1114 |
const connectionBar = document.getElementById('connectionBar');
|
| 1115 |
const sidebar = document.getElementById('sidebar');
|
| 1116 |
const sidebarOverlay = document.getElementById('sidebarOverlay');
|
| 1117 |
+
const statusModal = document.getElementById('statusModal');
|
| 1118 |
+
const modalOverlay = document.getElementById('modalOverlay');
|
| 1119 |
+
const navStatusDot = document.getElementById('navStatusDot');
|
| 1120 |
|
| 1121 |
let ws = null;
|
| 1122 |
let reconnectAttempts = 0;
|
|
|
|
| 1124 |
let isServerRunning = false;
|
| 1125 |
let sidebarOpen = false;
|
| 1126 |
let serverStartTime = null;
|
| 1127 |
+
let typingPreviewLine = null;
|
| 1128 |
+
let currentStats = {};
|
| 1129 |
|
| 1130 |
function updateConnectionStatus(status, message) {
|
| 1131 |
connectionBar.className = 'connection-bar ' + status;
|
|
|
|
| 1135 |
'connecting': 'fa-circle-notch fa-spin'
|
| 1136 |
};
|
| 1137 |
connectionBar.innerHTML = '<i class="fas ' + icons[status] + '"></i><span>' + message + '</span>';
|
| 1138 |
+
|
| 1139 |
+
// Update modal status
|
| 1140 |
+
const wsStatus = document.getElementById('modalWsStatus');
|
| 1141 |
+
if (status === 'connected') {
|
| 1142 |
+
wsStatus.innerHTML = '<span style="color: #22c55e;">● Connected</span>';
|
| 1143 |
+
} else if (status === 'disconnected') {
|
| 1144 |
+
wsStatus.innerHTML = '<span style="color: #ef4444;">● Disconnected</span>';
|
| 1145 |
+
} else {
|
| 1146 |
+
wsStatus.innerHTML = '<span style="color: #eab308;">● Connecting...</span>';
|
| 1147 |
+
}
|
| 1148 |
}
|
| 1149 |
|
| 1150 |
function updateServerStatus(running, startTime = null) {
|
|
|
|
| 1159 |
stopBtn.disabled = false;
|
| 1160 |
commandInput.disabled = false;
|
| 1161 |
sendBtn.disabled = false;
|
| 1162 |
+
navStatusDot.className = 'status-dot';
|
| 1163 |
if (startTime) {
|
| 1164 |
+
serverStartTime = startTime * 1000;
|
| 1165 |
}
|
| 1166 |
+
document.getElementById('modalMcStatus').innerHTML = '<span style="color: #22c55e;">● Online</span>';
|
| 1167 |
} else {
|
| 1168 |
statusEl.className = 'status-indicator status-offline';
|
| 1169 |
statusEl.innerHTML = '<span class="pulse"></span>Offline';
|
|
|
|
| 1172 |
stopBtn.disabled = true;
|
| 1173 |
commandInput.disabled = true;
|
| 1174 |
sendBtn.disabled = true;
|
| 1175 |
+
navStatusDot.className = 'status-dot offline';
|
| 1176 |
serverStartTime = null;
|
| 1177 |
+
document.getElementById('modalMcStatus').innerHTML = '<span style="color: #ef4444;">● Offline</span>';
|
| 1178 |
}
|
| 1179 |
}
|
| 1180 |
|
| 1181 |
function addTerminalLine(text, type = '') {
|
| 1182 |
+
// Remove typing preview if exists
|
| 1183 |
+
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1184 |
+
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1185 |
+
typingPreviewLine = null;
|
| 1186 |
+
}
|
| 1187 |
+
|
| 1188 |
const line = document.createElement('div');
|
| 1189 |
line.className = 'terminal-line ' + type;
|
| 1190 |
|
|
|
|
| 1191 |
if (text.includes('[SYSTEM]')) {
|
| 1192 |
line.className += ' system';
|
| 1193 |
} else if (text.includes('> ')) {
|
|
|
|
| 1204 |
terminal.appendChild(line);
|
| 1205 |
terminal.scrollTop = terminal.scrollHeight;
|
| 1206 |
|
|
|
|
| 1207 |
while (terminal.children.length > 500) {
|
| 1208 |
terminal.removeChild(terminal.firstChild);
|
| 1209 |
}
|
| 1210 |
}
|
| 1211 |
|
| 1212 |
+
function updateTypingPreview() {
|
| 1213 |
+
const text = commandInput.value.trim();
|
| 1214 |
+
|
| 1215 |
+
// Remove old preview
|
| 1216 |
+
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1217 |
+
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1218 |
+
typingPreviewLine = null;
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
// Show preview if text exists
|
| 1222 |
+
if (text && isServerRunning) {
|
| 1223 |
+
typingPreviewLine = document.createElement('div');
|
| 1224 |
+
typingPreviewLine.className = 'terminal-line typing-preview';
|
| 1225 |
+
typingPreviewLine.innerHTML = '> ' + escapeHtml(text) + '<span class="cursor">_</span>';
|
| 1226 |
+
terminal.appendChild(typingPreviewLine);
|
| 1227 |
+
terminal.scrollTop = terminal.scrollHeight;
|
| 1228 |
+
}
|
| 1229 |
+
}
|
| 1230 |
+
|
| 1231 |
+
function escapeHtml(text) {
|
| 1232 |
+
const div = document.createElement('div');
|
| 1233 |
+
div.textContent = text;
|
| 1234 |
+
return div.innerHTML;
|
| 1235 |
+
}
|
| 1236 |
+
|
| 1237 |
function formatUptime(seconds) {
|
| 1238 |
if (!seconds || seconds <= 0) return 'Offline';
|
| 1239 |
|
|
|
|
| 1252 |
if (serverStartTime && isServerRunning) {
|
| 1253 |
const now = Date.now();
|
| 1254 |
const uptime = Math.floor((now - serverStartTime) / 1000);
|
| 1255 |
+
const formatted = formatUptime(uptime);
|
| 1256 |
+
document.getElementById('uptime').textContent = formatted;
|
| 1257 |
document.getElementById('uptimeSub').textContent = 'Running';
|
| 1258 |
+
document.getElementById('modalUptime').textContent = formatted;
|
| 1259 |
} else {
|
| 1260 |
document.getElementById('uptime').textContent = 'Offline';
|
| 1261 |
document.getElementById('uptimeSub').textContent = 'Not running';
|
| 1262 |
+
document.getElementById('modalUptime').textContent = 'Offline';
|
| 1263 |
}
|
| 1264 |
}
|
| 1265 |
|
|
|
|
| 1282 |
if (data.type === 'output') {
|
| 1283 |
addTerminalLine(data.data);
|
| 1284 |
} else if (data.type === 'stats') {
|
| 1285 |
+
currentStats = data;
|
| 1286 |
document.getElementById('cpuLoad').textContent = data.cpu.toFixed(1) + '%';
|
| 1287 |
document.getElementById('memoryUsage').textContent = data.memory.toFixed(1) + '%';
|
| 1288 |
const memTotal = (data.memoryTotal / 1024 / 1024 / 1024).toFixed(2);
|
| 1289 |
const memUsed = ((data.memoryTotal * data.memory / 100) / 1024 / 1024 / 1024).toFixed(2);
|
| 1290 |
document.getElementById('memorySub').textContent = memUsed + ' / ' + memTotal + ' GB';
|
| 1291 |
|
| 1292 |
+
// Update modal stats
|
| 1293 |
+
document.getElementById('modalCpu').textContent = data.cpu.toFixed(1) + '%';
|
| 1294 |
+
document.getElementById('modalMemory').textContent = data.memory.toFixed(1) + '%';
|
| 1295 |
+
|
| 1296 |
updateServerStatus(data.running, data.serverStartTime);
|
| 1297 |
|
| 1298 |
if (data.players !== undefined) {
|
| 1299 |
document.getElementById('playerCount').textContent = data.players;
|
| 1300 |
+
document.getElementById('modalPlayers').textContent = data.players;
|
| 1301 |
}
|
| 1302 |
} else if (data.type === 'history') {
|
|
|
|
| 1303 |
data.logs.forEach(log => {
|
| 1304 |
addTerminalLine(log);
|
| 1305 |
});
|
|
|
|
| 1326 |
function sendInput() {
|
| 1327 |
const cmd = commandInput.value.trim();
|
| 1328 |
if (cmd && ws && ws.readyState === WebSocket.OPEN) {
|
| 1329 |
+
// Remove preview immediately on send
|
| 1330 |
+
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1331 |
+
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1332 |
+
typingPreviewLine = null;
|
| 1333 |
+
}
|
| 1334 |
ws.send(JSON.stringify({type: 'command', data: cmd}));
|
| 1335 |
commandInput.value = '';
|
| 1336 |
}
|
|
|
|
| 1342 |
}
|
| 1343 |
}
|
| 1344 |
|
| 1345 |
+
// Input event listeners for real-time typing preview
|
| 1346 |
+
commandInput.addEventListener('input', updateTypingPreview);
|
| 1347 |
+
commandInput.addEventListener('keyup', updateTypingPreview);
|
| 1348 |
+
|
| 1349 |
commandInput.addEventListener('keypress', (e) => {
|
| 1350 |
+
if (e.key === 'Enter') {
|
| 1351 |
+
// Remove preview immediately on enter
|
| 1352 |
+
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1353 |
+
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1354 |
+
typingPreviewLine = null;
|
| 1355 |
+
}
|
| 1356 |
+
sendInput();
|
| 1357 |
+
}
|
| 1358 |
+
});
|
| 1359 |
+
|
| 1360 |
+
// Clear preview when input loses focus
|
| 1361 |
+
commandInput.addEventListener('blur', () => {
|
| 1362 |
+
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1363 |
+
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1364 |
+
typingPreviewLine = null;
|
| 1365 |
+
}
|
| 1366 |
});
|
| 1367 |
|
| 1368 |
function switchTab(tab, element) {
|
|
|
|
| 1389 |
}
|
| 1390 |
}
|
| 1391 |
|
| 1392 |
+
function toggleStatusModal() {
|
| 1393 |
+
statusModal.classList.toggle('active');
|
| 1394 |
+
modalOverlay.classList.toggle('active');
|
| 1395 |
+
|
| 1396 |
+
// Update address in modal
|
| 1397 |
+
document.getElementById('modalAddress').textContent = window.location.hostname;
|
| 1398 |
+
}
|
| 1399 |
+
|
| 1400 |
// Close sidebar when clicking outside on mobile
|
| 1401 |
document.addEventListener('click', (e) => {
|
| 1402 |
if (window.innerWidth <= 768 && sidebarOpen) {
|
|
|
|
| 1464 |
if logs_history:
|
| 1465 |
ws.send(json.dumps({
|
| 1466 |
'type': 'history',
|
| 1467 |
+
'logs': logs_history[-100:]
|
| 1468 |
}))
|
| 1469 |
except Exception as e:
|
| 1470 |
print(f"[WS] History send error: {e}", flush=True)
|
|
|
|
| 1476 |
memory = psutil.virtual_memory()
|
| 1477 |
running = mc_process is not None and mc_process.poll() is None
|
| 1478 |
|
|
|
|
| 1479 |
uptime_to_send = None
|
| 1480 |
if running and server_start_time:
|
| 1481 |
uptime_to_send = server_start_time
|
|
|
|
| 1551 |
signal.signal(signal.SIGINT, cleanup)
|
| 1552 |
|
| 1553 |
if __name__ == '__main__':
|
|
|
|
| 1554 |
load_logs_from_file()
|
| 1555 |
load_uptime_from_file()
|
| 1556 |
|
| 1557 |
print("[MAIN] Starting web server...", flush=True)
|
| 1558 |
|
|
|
|
| 1559 |
mc_thread = threading.Thread(target=start_minecraft, daemon=True)
|
| 1560 |
mc_thread.start()
|
| 1561 |
|
|
|
|
| 1562 |
def github_push_loop():
|
| 1563 |
while True:
|
| 1564 |
time.sleep(300)
|
|
|
|
| 1585 |
|
| 1586 |
echo ""
|
| 1587 |
echo "=========================================="
|
| 1588 |
+
echo "✅ Minecraft Server with Real-time Typing + Status Icon is LIVE!"
|
| 1589 |
echo "🌐 Access at: https://your-space.hf.space"
|
| 1590 |
echo "📱 Mobile optimized interface enabled"
|
| 1591 |
echo "💾 Logs are now persistent across refreshes"
|
| 1592 |
echo "⏰ Uptime tracking fixed"
|
| 1593 |
+
echo "⌨️ Real-time command typing preview enabled"
|
| 1594 |
+
echo "📊 Status modal with heartbeat icon added"
|
| 1595 |
echo "=========================================="
|
| 1596 |
|
| 1597 |
# Keep container alive
|