SAyyeedd commited on
Commit
c76f804
·
verified ·
1 Parent(s): ba1b5c4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +299 -142
app.py CHANGED
@@ -5,144 +5,250 @@ import threading
5
  import shutil
6
  import spaces
7
 
8
- # تحديد مجلد السيرفر والانتقال داخله
9
  WORKDIR = "/home/user/app/server"
10
  os.makedirs(WORKDIR, exist_ok=True)
11
- os.chdir(WORKDIR)
12
 
13
  log_output = []
 
14
 
 
15
  @spaces.GPU
16
  def activate_gpu():
17
  return "[SYSTEM] GPU Activated."
18
 
 
 
 
 
 
 
19
  def download_dependencies():
20
- log_output.append("[SYSTEM] Starting initialization...")
21
-
22
- # 1. تحميل Java 21
23
- if not os.path.exists("jdk"):
24
- log_output.append("[SYSTEM] Downloading Java 21...")
25
- os.system("wget -qO jdk.tar.gz https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.4%2B7/OpenJDK21U-jre_x64_linux_hotspot_21.0.4_7.tar.gz")
26
- os.system("tar -xzf jdk.tar.gz")
27
- os.system("mv jdk-21* jdk")
28
- os.system("rm jdk.tar.gz")
29
-
30
- # 2. تحميل Purpur 1.21.1
31
- if not os.path.exists("paper.jar"):
32
- log_output.append("[SYSTEM] Downloading Purpur 1.21.1...")
33
- os.system("wget -qO paper.jar https://api.purpurmc.org/v2/purpur/1.21.1/latest/download")
34
-
35
- # 3. إعداد المجلدات والإضافات الأساسية
36
- os.makedirs("plugins", exist_ok=True)
37
-
38
- if not os.path.exists("plugins/playit.jar"):
39
- log_output.append("[SYSTEM] Downloading Playit Plugin...")
40
- os.system("wget -qO plugins/playit.jar https://github.com/playit-cloud/playit-minecraft-plugin/releases/latest/download/playit-minecraft-plugin.jar")
41
-
42
- if not os.path.exists("plugins/ViaVersion.jar"):
43
- log_output.append("[SYSTEM] Downloading ViaVersion Plugin...")
44
- via_version_url = "https://cdn.modrinth.com/data/P1OZGk5p/versions/ruzmiBqe/ViaVersion-5.10.0.jar?mr_download_reason=standalone&mr_game_version=1.21.1&mr_loader=paper"
45
- os.system(f'wget -qO plugins/ViaVersion.jar "{via_version_url}"')
46
-
47
- if not os.path.exists("plugins/ViaBackwards.jar"):
48
- log_output.append("[SYSTEM] Downloading ViaBackwards Plugin...")
49
- via_backwards_url = "https://cdn.modrinth.com/data/NpvuJQoq/versions/YjpKsm6j/ViaBackwards-5.10.0.jar?mr_download_reason=standalone&mr_game_version=1.21.1&mr_loader=paper"
50
- os.system(f'wget -qO plugins/ViaBackwards.jar "{via_backwards_url}"')
51
-
52
- # 4. الموافقة على EULA
53
- if not os.path.exists("eula.txt"):
54
- with open("eula.txt", "w") as f:
55
  f.write("eula=true\n")
56
-
57
- # 5. فرض نمط السيرفر المكرك
58
- if not os.path.exists("server.properties"):
59
- log_output.append("[SYSTEM] Configuring server.properties for cracked mode...")
60
- with open("server.properties", "w") as f:
61
- f.write("online-mode=false\n")
62
- f.write("spawn-protection=0\n")
63
-
64
- log_output.append("[SYSTEM] Initialization complete.")
65
-
66
- def run_server():
 
 
67
  download_dependencies()
68
-
69
- log_output.append("[SYSTEM] Starting Minecraft Server (Purpur 1.21.1)...")
70
- java_path = os.path.abspath("jdk/bin/java")
71
- mc_proc = subprocess.Popen([java_path, "-Xms2G", "-Xmx4G", "-jar", "paper.jar", "nogui"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
72
-
73
- def read_logs(proc, prefix):
74
- for line in iter(proc.stdout.readline, ''):
75
- if line:
76
- log_output.append(f"[{prefix}] {line.strip()}")
77
- if len(log_output) > 100:
78
- log_output.pop(0)
79
-
80
- threading.Thread(target=read_logs, args=(mc_proc, "MINECRAFT"), daemon=True).start()
81
-
82
- # إطلاق السيرفر في الخلفية
83
- threading.Thread(target=run_server, daemon=True).start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  def get_logs():
86
  return "\n".join(log_output)
87
 
88
- # --- وظائف مدير وتعديل الملفات ---
89
- def list_files():
90
- editable_files = []
91
- for f in ["server.properties", "eula.txt"]:
92
- if os.path.exists(f):
93
- editable_files.append(f)
94
- if os.path.exists("plugins"):
95
- for root, dirs, files in os.walk("plugins"):
96
- for file in files:
97
- if file.endswith(('.yml', '.yaml', '.txt', '.properties', '.json', '.toml')):
98
- rel_path = os.path.relpath(os.path.join(root, file), ".")
99
- editable_files.append(rel_path)
100
- return gr.Dropdown(choices=editable_files, value=editable_files[0] if editable_files else None)
101
-
102
- def read_file(filename):
103
- if not filename:
104
- return "الرجاء اختيار ملف أولاً."
105
  try:
106
- with open(filename, "r", encoding="utf-8", errors="ignore") as f:
107
- return f.read()
 
 
 
 
108
  except Exception as e:
109
- return f"خطأ أثناء قراءة الملف: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- def save_file(filename, content):
112
- if not filename:
113
- return "لم يتم تحديد أي ملف للحفظ."
114
  try:
115
- with open(filename, "w", encoding="utf-8") as f:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  f.write(content)
117
- return f"✅ تم حفظ الملف [{filename}] بنجاح!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  except Exception as e:
119
- return f"❌ خطأ أثناء الحفظ: {e}"
120
 
121
- # --- وظيفة رفع الملفات المخصصة ---
122
- def handle_file_upload(file_objs, folder_destination):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  if not file_objs:
124
- return "❌ لم يتم اختيار أي ملفات للرفع."
125
-
126
- # تحديد مسار الحفظ بناءً على اختيار المستخدم
127
- target_dir = WORKDIR if folder_destination == "المجلد الرئيسي لسيرفر ماين كرافت (Root)" else os.path.join(WORKDIR, "plugins")
128
- os.makedirs(target_dir, exist_ok=True)
129
-
130
  uploaded_list = []
131
- # التعامل مع الملفات سواء كانت ملفاً واحداً أو متعددة
132
  if not isinstance(file_objs, list):
133
  file_objs = [file_objs]
134
-
135
  for f_obj in file_objs:
136
  original_name = getattr(f_obj, 'orig_name', os.path.basename(f_obj.name))
137
- destination_path = os.path.join(target_dir, original_name)
138
-
139
- # نسخ الملف من المسار المؤقت لـ Gradio للمجلد الفعلي
140
- shutil.copy(f_obj.name, destination_path)
141
  uploaded_list.append(original_name)
142
-
143
- return f"✅ تم بنجاح رفع واستيراد الملفات التالية إلى مجلد ({folder_destination}):\n" + "\n".join([f"- {name}" for name in uploaded_list])
 
 
144
 
145
- # واجهة التحكم الخضراء التقليدية (MC Server Host)
146
  custom_theme = gr.themes.Default(
147
  primary_hue="green",
148
  secondary_hue="emerald",
@@ -151,42 +257,93 @@ custom_theme = gr.themes.Default(
151
 
152
  with gr.Blocks(title="MC Server Host") as demo:
153
  demo.load(fn=activate_gpu, inputs=None, outputs=None)
154
-
155
- gr.Markdown("# 🟢 MC Server Host - Control Panel")
156
-
157
- with gr.Tab("🖥️ السجلات (Console)"):
158
- console_logs = gr.Textbox(label="Server Console", lines=20, max_lines=20, value="Loading...", interactive=False)
159
- refresh_btn = gr.Button("تحديث السجلات (Refresh Logs)", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  refresh_btn.click(fn=get_logs, outputs=console_logs)
161
-
162
- with gr.Tab("📂 تعديل إعدادات الملفات"):
163
- gr.Markdown("### تعديل النصوص والملفات البرمجية للسيرفر")
164
- file_dropdown = gr.Dropdown(label="اختر الملف للتعديل عليه", choices=["اضغط على تحديث القائمة أولاً"])
165
- refresh_files_btn = gr.Button("🔄 تحديث قائمة الملفات النصية", variant="secondary")
166
-
167
- file_content = gr.Code(label="محتوى الملف", language="yaml", lines=15)
168
- save_status = gr.Markdown("")
169
- save_btn = gr.Button("💾 حفظ التعديلات", variant="primary")
170
-
171
- refresh_files_btn.click(fn=list_files, inputs=None, outputs=file_dropdown)
172
- file_dropdown.change(fn=read_file, inputs=file_dropdown, outputs=file_content)
173
- save_btn.click(fn=save_file, inputs=[file_dropdown, file_content], outputs=save_status)
174
-
175
- with gr.Tab("📤 رفع الملفات والبلودجنات"):
176
- gr.Markdown("### ارفع ملفات الـ Plugins أو أي ملفات أخرى مباشرة إلى السيرفر الخاص بك")
177
-
178
- upload_dest = gr.Radio(
179
- choices=["المجلد الرئيسي لسيرفر ماين كرافت (Root)", "مجلد الإضافات (plugins)"],
180
- value="مجلد الإضافات (plugins)",
181
- label="أين تريد حفظ الملفات المرفوعة؟"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  )
183
-
184
- file_input = gr.File(label="اسحب وأفلت الملفات هنا (يدعم رفع ملفات متعددة في نفس الوقت)", file_count="multiple")
185
- upload_status = gr.Markdown("")
186
- upload_btn = gr.Button("🚀 ابدأ رفع وحفظ الملفات الآن", variant="primary")
187
-
188
- upload_btn.click(fn=handle_file_upload, inputs=[file_input, upload_dest], outputs=upload_status)
189
-
190
- demo.load(fn=get_logs, outputs=console_logs)
 
 
 
 
 
191
 
192
  demo.launch(theme=custom_theme)
 
5
  import shutil
6
  import spaces
7
 
 
8
  WORKDIR = "/home/user/app/server"
9
  os.makedirs(WORKDIR, exist_ok=True)
 
10
 
11
  log_output = []
12
+ mc_process = None
13
 
14
+ # الحفاظ على استمرار تشغيل الـ Space وتنشيط الـ GPU
15
  @spaces.GPU
16
  def activate_gpu():
17
  return "[SYSTEM] GPU Activated."
18
 
19
+ def log(message):
20
+ log_output.append(message)
21
+ if len(log_output) > 120:
22
+ log_output.pop(0)
23
+
24
+ # --- نظام إدارة تبعيات السيرفر تشغيل/إيقاف ---
25
  def download_dependencies():
26
+ if not os.path.exists(os.path.join(WORKDIR, "jdk")):
27
+ log("[SYSTEM] Downloading Java 21...")
28
+ os.system(f"wget -qO {WORKDIR}/jdk.tar.gz https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.4%2B7/OpenJDK21U-jre_x64_linux_hotspot_21.0.4_7.tar.gz")
29
+ os.system(f"tar -xzf {WORKDIR}/jdk.tar.gz -C {WORKDIR}")
30
+ for extracted in os.listdir(WORKDIR):
31
+ if extracted.startswith("jdk-21"):
32
+ os.rename(os.path.join(WORKDIR, extracted), os.path.join(WORKDIR, "jdk"))
33
+ os.system(f"rm {WORKDIR}/jdk.tar.gz")
34
+
35
+ if not os.path.exists(os.path.join(WORKDIR, "paper.jar")):
36
+ log("[SYSTEM] Downloading Purpur 1.21.1...")
37
+ os.system(f"wget -qO {WORKDIR}/paper.jar https://api.purpurmc.org/v2/purpur/1.21.1/latest/download")
38
+
39
+ plugins_dir = os.path.join(WORKDIR, "plugins")
40
+ os.makedirs(plugins_dir, exist_ok=True)
41
+
42
+ if not os.path.exists(os.path.join(plugins_dir, "playit.jar")):
43
+ log("[SYSTEM] Downloading Playit Plugin...")
44
+ os.system(f"wget -qO {plugins_dir}/playit.jar https://github.com/playit-cloud/playit-minecraft-plugin/releases/latest/download/playit-minecraft-plugin.jar")
45
+
46
+ if not os.path.exists(os.path.join(plugins_dir, "ViaVersion.jar")):
47
+ log("[SYSTEM] Downloading ViaVersion Plugin...")
48
+ via_url = "https://cdn.modrinth.com/data/P1OZGk5p/versions/ruzmiBqe/ViaVersion-5.10.0.jar?mr_download_reason=standalone&mr_game_version=1.21.1&mr_loader=paper"
49
+ os.system(f'wget -qO {plugins_dir}/ViaVersion.jar "{via_url}"')
50
+
51
+ if not os.path.exists(os.path.join(plugins_dir, "ViaBackwards.jar")):
52
+ log("[SYSTEM] Downloading ViaBackwards Plugin...")
53
+ via_b_url = "https://cdn.modrinth.com/data/NpvuJQoq/versions/YjpKsm6j/ViaBackwards-5.10.0.jar?mr_download_reason=standalone&mr_game_version=1.21.1&mr_loader=paper"
54
+ os.system(f'wget -qO {plugins_dir}/ViaBackwards.jar "{via_b_url}"')
55
+
56
+ eula_p = os.path.join(WORKDIR, "eula.txt")
57
+ if not os.path.exists(eula_p):
58
+ with open(eula_p, "w") as f:
 
 
59
  f.write("eula=true\n")
60
+
61
+ props_p = os.path.join(WORKDIR, "server.properties")
62
+ if not os.path.exists(props_p):
63
+ log("[SYSTEM] Configuring server.properties for cracked mode...")
64
+ with open(props_p, "w") as f:
65
+ f.write("online-mode=false\nspawn-protection=0\n")
66
+
67
+ def start_server_backend():
68
+ global mc_process
69
+ if mc_process and mc_process.poll() is None:
70
+ log("[SYSTEM] Server is already running!")
71
+ return
72
+
73
  download_dependencies()
74
+ log("[SYSTEM] Starting Minecraft Server (Purpur 1.21.1)...")
75
+
76
+ java_path = os.path.join(WORKDIR, "jdk/bin/java")
77
+ mc_process = subprocess.Popen(
78
+ [java_path, "-Xms2G", "-Xmx4G", "-jar", "paper.jar", "nogui"],
79
+ cwd=WORKDIR, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
80
+ )
81
+
82
+ for line in iter(mc_process.stdout.readline, ''):
83
+ if line:
84
+ log(f"[MINECRAFT] {line.strip()}")
85
+
86
+ mc_process.stdout.close()
87
+
88
+ def control_server(action):
89
+ global mc_process
90
+ if action == "START":
91
+ if mc_process and mc_process.poll() is None:
92
+ return "❌ السيرفر يعمل بالفعل الآن!"
93
+ threading.Thread(target=start_server_backend, daemon=True).start()
94
+ return "⚡ جاري تشغيل خادم ماين كرافت الآن..."
95
+ elif action == "STOP":
96
+ if mc_process and mc_process.poll() is None:
97
+ mc_process.terminate()
98
+ mc_process = None
99
+ log("[SYSTEM] Server stopped by user.")
100
+ return "🛑 تم إرسال أمر إيقاف السيرفر بنجاح."
101
+ return "⚠️ السيرفر متوقف بالفعل."
102
+ elif action == "RESTART":
103
+ if mc_process and mc_process.poll() is None:
104
+ mc_process.terminate()
105
+ mc_process = None
106
+ log("[SYSTEM] Restarting server...")
107
+ threading.Thread(target=start_server_backend, daemon=True).start()
108
+ return "🔄 جاري إعادة تشغيل السيرفر..."
109
 
110
  def get_logs():
111
  return "\n".join(log_output)
112
 
113
+ # --- محرك إدارة الملفات المتقدم الحصري ---
114
+ def get_safe_path(rel_path):
115
+ abs_path = os.path.abspath(os.path.join(WORKDIR, rel_path))
116
+ if abs_path.startswith(os.path.abspath(WORKDIR)):
117
+ return abs_path
118
+ return WORKDIR
119
+
120
+ def scan_directory(current_rel):
121
+ target_dir = get_safe_path(current_rel)
122
+ folders = []
123
+ files = []
 
 
 
 
 
 
124
  try:
125
+ for item in os.listdir(target_dir):
126
+ full_path = os.path.join(target_dir, item)
127
+ if os.path.isdir(full_path):
128
+ folders.append(item)
129
+ else:
130
+ files.append(item)
131
  except Exception as e:
132
+ log(f"[FILE MGR ERROR] {e}")
133
+
134
+ folders.sort()
135
+ files.sort()
136
+
137
+ folder_choices = [".. (المجلد الأعلى)"] + folders if current_rel else folders
138
+ file_choices = files if files else ["لا توجد ملفات في هذا المسار"]
139
+
140
+ path_display = f"📁 المسار الحالي المفتوح: /root/{current_rel}" if current_rel else "📁 المسار الحالي المفتوح: /root (المجلد الرئيسي)"
141
+
142
+ return path_display, gr.Dropdown(choices=folder_choices, value=None), gr.Dropdown(choices=file_choices, value=None)
143
+
144
+ def enter_folder(selected_folder, current_rel):
145
+ if not selected_folder:
146
+ return current_rel, *scan_directory(current_rel)
147
+
148
+ if selected_folder == ".. (المجلد الأعلى)":
149
+ if not current_rel:
150
+ return "", *scan_directory("")
151
+ parts = current_rel.strip("/").split("/")
152
+ parent_rel = "/".join(parts[:-1])
153
+ return parent_rel, *scan_directory(parent_rel)
154
+ else:
155
+ new_rel = os.path.join(current_rel, selected_folder).strip("/")
156
+ return new_rel, *scan_directory(new_rel)
157
+
158
+ def load_file_content(selected_file, current_rel):
159
+ """
160
+ يقرأ محتوى الملف المحدد ويعرضه في المحرر.
161
+ تم إصلاح المشكلة: الدالة كانت تُستدعى فقط عبر زر واحد، والآن تُستدعى
162
+ أيضاً تلقائياً عند تغيير الاختيار في القائمة (file_dropdown.change).
163
+ """
164
+ if not selected_file or selected_file == "لا توجد ملفات في هذا المسار":
165
+ return gr.update(value="الرجاء تحديد ملف متاح للقراءة.")
166
+
167
+ file_path = os.path.join(get_safe_path(current_rel), selected_file)
168
+
169
+ if not os.path.isfile(file_path):
170
+ return gr.update(value=f"⚠️ لا يمكن فتح [{selected_file}] لأنه ليس ملفاً نصياً (قد يكون مجلداً أو ملف ثنائي).")
171
 
 
 
 
172
  try:
173
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
174
+ content = f.read()
175
+ # نحدد نوع اللغة للتلوين بناءً على الامتداد (اختياري لكن يحسن التجربة)
176
+ ext = os.path.splitext(selected_file)[1].lower()
177
+ lang_map = {".yml": "yaml", ".yaml": "yaml", ".json": "json",
178
+ ".properties": "yaml", ".txt": None, ".log": None}
179
+ return gr.update(value=content, language=lang_map.get(ext, None))
180
+ except Exception as e:
181
+ return gr.update(value=f"خطأ أثناء قراءة الملف: {e}")
182
+
183
+ def save_file_content(selected_file, content, current_rel):
184
+ if not selected_file or selected_file == "لا توجد ملفات في هذا المسار":
185
+ return "❌ لم يتم تحديد ملف برمجى صالح لحفظه."
186
+ file_path = os.path.join(get_safe_path(current_rel), selected_file)
187
+ try:
188
+ with open(file_path, "w", encoding="utf-8") as f:
189
  f.write(content)
190
+ return f"✅ تم حفظ الملف [{selected_file}] بنجاح في المسار الحالي!"
191
+ except Exception as e:
192
+ return f"❌ فشل الحفظ: {e}"
193
+
194
+ def create_item(name, item_type, current_rel):
195
+ if not name:
196
+ return "⚠️ الرجاء إدخال اسم المجلد أو الملف أولاً.", *scan_directory(current_rel)
197
+ target_path = os.path.join(get_safe_path(current_rel), name)
198
+ try:
199
+ if item_type == "مجلد Folder":
200
+ os.makedirs(target_path, exist_ok=True)
201
+ msg = f"📁 تم إنشاء المجلد [{name}] بنجاح."
202
+ else:
203
+ with open(target_path, "w", encoding="utf-8") as f:
204
+ f.write("")
205
+ msg = f"📄 تم إنشاء الملف الفارغ [{name}] بنجاح."
206
+ return msg, *scan_directory(current_rel)
207
+ except Exception as e:
208
+ return f"❌ فشل الإنشاء: {e}", *scan_directory(current_rel)
209
+
210
+ def rename_item(old_name, new_name, current_rel):
211
+ if not old_name or not new_name:
212
+ return "⚠️ حدد العنصر واكتب الاسم الجديد.", *scan_directory(current_rel)
213
+ old_p = os.path.join(get_safe_path(current_rel), old_name)
214
+ new_p = os.path.join(get_safe_path(current_rel), new_name)
215
+ try:
216
+ os.rename(old_p, new_p)
217
+ return f"✏️ تم إعادة التسمية بنجاح إلى [{new_name}].", *scan_directory(current_rel)
218
  except Exception as e:
219
+ return f"❌ خطأ أثناء التسمية: {e}", *scan_directory(current_rel)
220
 
221
+ def delete_item(item_name, is_folder, current_rel):
222
+ if not item_name or item_name == "لا توجد ملفات في هذا المسار":
223
+ return "⚠️ لم يتم تحديد عنصر لحذفه.", *scan_directory(current_rel)
224
+ target_p = os.path.join(get_safe_path(current_rel), item_name)
225
+ try:
226
+ if is_folder:
227
+ shutil.rmtree(target_p)
228
+ msg = f"🗑️ تم حذف المجلد ومحتوياته [{item_name}] بالكامل."
229
+ else:
230
+ os.remove(target_p)
231
+ msg = f"🗑️ تم حذف الملف [{item_name}]."
232
+ return msg, *scan_directory(current_rel)
233
+ except Exception as e:
234
+ return f"❌ فشل الحذف: {e}", *scan_directory(current_rel)
235
+
236
+ def upload_to_current_dir(file_objs, current_rel):
237
  if not file_objs:
238
+ return "❌ لم يتم اختيار ملفات للرفع.", *scan_directory(current_rel)
239
+ target_dir = get_safe_path(current_rel)
 
 
 
 
240
  uploaded_list = []
 
241
  if not isinstance(file_objs, list):
242
  file_objs = [file_objs]
 
243
  for f_obj in file_objs:
244
  original_name = getattr(f_obj, 'orig_name', os.path.basename(f_obj.name))
245
+ shutil.copy(f_obj.name, os.path.join(target_dir, original_name))
 
 
 
246
  uploaded_list.append(original_name)
247
+ return f"✅ تم رفع {len(uploaded_list)} ملفات مباشرة إلى الدليل الحالي ومستعدة للاستخدام!", *scan_directory(current_rel)
248
+
249
+ # تشغيل تلقائي للسيرفر في البداية عند بناء الـ Space لأول مرة
250
+ threading.Thread(target=start_server_backend, daemon=True).start()
251
 
 
252
  custom_theme = gr.themes.Default(
253
  primary_hue="green",
254
  secondary_hue="emerald",
 
257
 
258
  with gr.Blocks(title="MC Server Host") as demo:
259
  demo.load(fn=activate_gpu, inputs=None, outputs=None)
260
+
261
+ current_path_state = gr.State("")
262
+
263
+ gr.Markdown("# 🟢 MC Server Host - Elite Control Panel")
264
+ gr.Markdown("مرحباً بك في لوحة التحكم المتكاملة. يمكنك الآن التحكم في تشغيل السيرفر وإدارة كافة ملفات خادمك وتحديثها مباشرة.")
265
+
266
+ with gr.Tab("🖥️ الكونسول والتحكم الحى"):
267
+ with gr.Row():
268
+ btn_start = gr.Button("▶️ تشغيل السيرفر (Start)", variant="primary")
269
+ btn_stop = gr.Button("🛑 إيقاف السيرفر (Stop)", variant="stop")
270
+ btn_restart = gr.Button("🔄 إعادة تشغيل (Restart)", variant="secondary")
271
+
272
+ sys_status = gr.Markdown("⚡ حالة السيرفر الحالية تظهر في السجلات بالأسفل...")
273
+ console_logs = gr.Textbox(label="Server Live Console", lines=22, max_lines=22, value="Loading Server System...", interactive=False)
274
+ refresh_btn = gr.Button("🔄 تحديث شاشة السجلات (Refresh Logs)", variant="primary")
275
+
276
+ btn_start.click(fn=control_server, inputs=gr.State("START"), outputs=sys_status)
277
+ btn_stop.click(fn=control_server, inputs=gr.State("STOP"), outputs=sys_status)
278
+ btn_restart.click(fn=control_server, inputs=gr.State("RESTART"), outputs=sys_status)
279
  refresh_btn.click(fn=get_logs, outputs=console_logs)
280
+
281
+ with gr.Tab("📂 مدير الملفات الاحترافي (File Manager)"):
282
+ path_text = gr.Markdown("📁 المسار الحالي المفتوح: /root")
283
+
284
+ with gr.Row():
285
+ with gr.Column(scale=1):
286
+ gr.Markdown("### 🗂️ التصفح والمجلدات")
287
+ folder_dropdown = gr.Dropdown(label="المجلدات الفرعية (اضغط للاختيار والدخول)", choices=[])
288
+ btn_refresh_dir = gr.Button("🔄 تحديث الدليل الحالي", variant="secondary")
289
+
290
+ gr.Markdown("### 📄 الملفات المتاحة")
291
+ file_dropdown = gr.Dropdown(label="الملفات في هذا المجلد (سيتم فتح الملف تلقائياً عند اختياره)", choices=[])
292
+
293
+ with gr.Column(scale=2):
294
+ gr.Markdown("### 📝 محرر نصوص الملف المفتوح")
295
+ editor_content = gr.Code(label=حتويات الملف الحالي", language=None, lines=18, interactive=True)
296
+ with gr.Row():
297
+ btn_load_f = gr.Button("📂 إعادة تحميل الملف المحدد", variant="secondary")
298
+ btn_save_f = gr.Button("💾 حفظ التعديلات النصية", variant="primary")
299
+ file_op_status = gr.Markdown("")
300
+
301
+ gr.Markdown("---")
302
+ with gr.Row():
303
+ with gr.Column():
304
+ gr.Markdown("### ➕ إنشاء عناصر جديدة")
305
+ new_item_name = gr.Textbox(label="اسم الملف أو المجلد الجديد (بالامتداد مثل world أو spigot.yml)")
306
+ new_item_type = gr.Radio(choices=["مجلد Folder", "ملف نصي File"], value="ملف نصي File", label="نوع العنصر")
307
+ btn_create = gr.Button("➕ إنشاء الآن", variant="primary")
308
+
309
+ gr.Markdown("### ✏️ تعديل الاسم وإعادة التسمية")
310
+ rename_new_name = gr.Textbox(label="الاسم الجديد للعنصر المختار")
311
+ btn_rename = gr.Button("✏️ تطبيق تغيير الاسم", variant="secondary")
312
+
313
+ with gr.Column():
314
+ gr.Markdown("### 📤 رفع ملفات مباشرة هنا")
315
+ file_uploader = gr.File(label="اسحب وأفلت الملفات لرفعها للمسار المفتوح حالياً", file_count="multiple")
316
+ btn_upload = gr.Button("🚀 رفع وحفظ الملفات المحددة", variant="primary")
317
+
318
+ gr.Markdown("### 🗑️ منطقة الحذف السريع")
319
+ delete_is_folder = gr.Checkbox(label="العنصر المراد حذفه عبارة عن مجلد كامل", value=False)
320
+ btn_delete = gr.Button("❌ حذف العنصر المحدد نهائياً", variant="stop")
321
+
322
+ def run_initial_scan():
323
+ return scan_directory("")
324
+
325
+ demo.load(fn=run_initial_scan, outputs=[path_text, folder_dropdown, file_dropdown])
326
+ demo.load(fn=get_logs, outputs=console_logs)
327
+
328
+ btn_refresh_dir.click(fn=scan_directory, inputs=current_path_state, outputs=[path_text, folder_dropdown, file_dropdown])
329
+
330
+ folder_dropdown.change(
331
+ fn=enter_folder,
332
+ inputs=[folder_dropdown, current_path_state],
333
+ outputs=[current_path_state, path_text, folder_dropdown, file_dropdown]
334
  )
335
+
336
+ # الإصلاح الأساسي: فتح الملف تلقائياً فوراً عند اختياره من القائمة
337
+ file_dropdown.change(fn=load_file_content, inputs=[file_dropdown, current_path_state], outputs=editor_content)
338
+
339
+ # الزر يبقى متاحاً أيضاً لإعادة التحميل اليدوي (مثلاً بعد تعديل الملف من مكان آخر)
340
+ btn_load_f.click(fn=load_file_content, inputs=[file_dropdown, current_path_state], outputs=editor_content)
341
+ btn_save_f.click(fn=save_file_content, inputs=[file_dropdown, editor_content, current_path_state], outputs=file_op_status)
342
+
343
+ btn_create.click(fn=create_item, inputs=[new_item_name, new_item_type, current_path_state], outputs=[file_op_status, path_text, folder_dropdown, file_dropdown])
344
+ btn_rename.click(fn=rename_item, inputs=[file_dropdown, rename_new_name, current_path_state], outputs=[file_op_status, path_text, folder_dropdown, file_dropdown])
345
+ btn_delete.click(fn=delete_item, inputs=[file_dropdown, delete_is_folder, current_path_state], outputs=[file_op_status, path_text, folder_dropdown, file_dropdown])
346
+
347
+ btn_upload.click(fn=upload_to_current_dir, inputs=[file_uploader, current_path_state], outputs=[file_op_status, path_text, folder_dropdown, file_dropdown])
348
 
349
  demo.launch(theme=custom_theme)