mohammed978 commited on
Commit
2ac96b7
·
verified ·
1 Parent(s): e27d486

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1317 -119
app.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  import gradio as gr
2
  import subprocess
3
  import os
@@ -8,36 +11,38 @@ import psutil
8
  import time
9
  import threading
10
  import json
 
11
  import secrets
 
 
12
  from collections import deque
13
- from datetime import datetime
14
 
15
- # إعداد مسار العمل ليكون داخل مجلد التطبيق الحالي لتجنب مشاكل الصلاحيات
16
- BASE_DIR = os.getcwd()
17
- WORKSPACE = os.path.join(BASE_DIR, "workspace")
18
  LOGS_DIR = os.path.join(WORKSPACE, ".system_logs")
19
  SESSIONS_DIR = os.path.join(WORKSPACE, ".sessions")
20
  BACKUP_DIR = os.path.join(WORKSPACE, ".backups")
21
  CONFIG_FILE = os.path.join(WORKSPACE, ".vps_config.json")
22
 
23
- # إنشاء المجلدات
24
  for d in [WORKSPACE, LOGS_DIR, SESSIONS_DIR, BACKUP_DIR]:
25
  os.makedirs(d, exist_ok=True)
26
 
27
- # تغيير مسار العمل
28
  os.chdir(WORKSPACE)
29
 
30
  bg_processes = {}
31
  proc_lock = threading.Lock()
32
  cmd_history = deque(maxlen=500)
33
  resource_history = deque(maxlen=100)
 
34
 
35
  DEFAULT_CONFIG = {
36
  "max_bg_processes": 20,
37
  "command_timeout": 300,
 
 
38
  "monitor_interval": 30,
39
  }
40
 
 
41
  def load_config():
42
  try:
43
  if os.path.exists(CONFIG_FILE):
@@ -50,193 +55,1386 @@ def load_config():
50
  pass
51
  return DEFAULT_CONFIG.copy()
52
 
 
 
 
 
 
 
 
 
 
53
  config = load_config()
54
 
55
- # --- Resource Monitor ---
56
  def resource_monitor():
57
  while True:
58
  try:
59
  mem = psutil.virtual_memory()
60
  cpu = psutil.cpu_percent(interval=1)
 
61
  resource_history.append({
62
  "time": datetime.now().strftime("%H:%M:%S"),
63
  "cpu": cpu,
64
  "ram_percent": mem.percent,
65
  "ram_used": mem.used,
66
  "ram_total": mem.total,
 
67
  })
68
- # Cleanup logic
69
- with proc_lock:
70
- to_del = []
71
- for pid, info in bg_processes.items():
72
- if info["proc"].poll() is not None:
73
- try: info["log_f"].close()
74
- except: pass
75
- # Don't delete immediately to allow viewing logs of stopped/crashed processes
76
  except:
77
  pass
78
  time.sleep(config.get("monitor_interval", 30))
79
 
 
80
  monitor_thread = threading.Thread(target=resource_monitor, daemon=True)
81
  monitor_thread.start()
82
 
83
- # --- Helper Functions ---
 
 
 
 
 
 
 
 
 
 
 
 
84
  def fmt_size(size_bytes):
85
- if size_bytes < 0: return "0B"
 
86
  units = ["B", "KB", "MB", "GB", "TB"]
87
- idx = 0; size = float(size_bytes)
 
88
  while size >= 1024 and idx < len(units) - 1:
89
- size /= 1024; idx += 1
 
90
  return f"{size:.1f}{units[idx]}"
91
 
92
- def resolve_path(path):
93
- if not path: return WORKSPACE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  path = path.strip()
95
- if path.startswith("~"): path = path.replace("~", WORKSPACE, 1)
96
- if not os.path.isabs(path): path = os.path.join(WORKSPACE, path)
 
 
 
97
  return os.path.abspath(path)
98
 
99
- # --- Core Functions ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  def execute_terminal(command, work_dir):
101
- if not command or not command.strip(): return "", work_dir or WORKSPACE
102
-
 
103
  wd = resolve_path(work_dir) if work_dir else WORKSPACE
104
- if not os.path.isdir(wd): wd = WORKSPACE
105
-
106
- cmd = command.strip()
107
- if cmd.startswith("cd "):
108
- target = cmd[3:].strip()
109
- new_path = resolve_path(target)
110
- if os.path.isdir(new_path):
111
- return f"Changed directory to {new_path}", new_path
112
- return f"Directory not found: {target}", wd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  try:
 
 
 
 
 
 
 
 
115
  result = subprocess.run(
116
- cmd, shell=True, capture_output=True, text=True, timeout=120, cwd=wd, executable="/bin/bash"
 
 
 
 
 
 
 
117
  )
118
- output = result.stdout + result.stderr
119
- if not output and result.returncode == 0: output = "Done."
 
 
 
 
 
 
 
 
 
 
120
  return output, wd
 
 
 
 
 
 
 
 
 
121
  except Exception as e:
 
 
122
  return f"Error: {str(e)}", wd
123
 
 
124
  def execute_background(command, work_dir):
125
- if not command: return "No command"
126
- wd = resolve_path(work_dir)
127
-
 
 
 
 
 
 
 
 
128
  try:
129
  ts = datetime.now().strftime("%Y%m%d_%H%M%S")
130
  proc_id = secrets.token_hex(4)
131
  log_path = os.path.join(LOGS_DIR, f"bg_{ts}_{proc_id}.log")
132
  log_f = open(log_path, "w", buffering=1)
133
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  proc = subprocess.Popen(
135
- command, shell=True, stdout=log_f, stderr=subprocess.STDOUT,
136
- cwd=wd, executable="/bin/bash", preexec_fn=os.setsid
 
 
 
 
 
 
137
  )
138
-
139
  with proc_lock:
140
  bg_processes[proc.pid] = {
141
- "id": proc_id, "cmd": command, "pid": proc.pid,
142
- "start": datetime.now().strftime("%H:%M:%S"),
143
- "start_ts": time.time(), "log": log_path,
144
- "proc": proc, "log_f": log_f, "dir": wd
 
 
 
 
 
145
  }
146
- return f"Started PID: {proc.pid} (ID: {proc_id})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  except Exception as e:
148
- return f"Failed: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
  def get_process_list():
151
- lines = ["PID | ID | Uptime | Command"]
152
  with proc_lock:
153
- for pid, info in bg_processes.items():
154
- status = "RUN" if info["proc"].poll() is None else "STOP"
155
- lines.append(f"{pid} | {info['id']} | {status} | {info['cmd']}")
156
- return "\n".join(lines)
157
-
158
- def stop_process(pid_str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  try:
160
- target_pid = int(pid_str)
161
- if target_pid in bg_processes:
162
- os.killpg(os.getpgid(target_pid), signal.SIGTERM)
163
- return f"Stopped {target_pid}"
 
 
164
  except:
165
  pass
166
- return "Failed or not found"
167
 
168
- def read_file(path):
169
- p = resolve_path(path)
170
- if os.path.exists(p) and os.path.isfile(p):
171
- with open(p, 'r', encoding='utf-8', errors='ignore') as f: return f.read()
172
- return "File not found"
173
 
174
- def write_file(path, content):
175
- p = resolve_path(path)
176
- with open(p, 'w', encoding='utf-8') as f: f.write(content)
177
- return f"Saved to {p}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  def install_package(name):
180
- return execute_terminal(f"pip install {name}", WORKSPACE)[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
- # --- UI Layout ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  CSS = """
184
- .term textarea { font-family: monospace; background: #1e1e1e; color: #00ff00; }
 
 
 
 
 
 
 
 
 
185
  """
186
 
187
- with gr.Blocks(css=CSS, title="Cloud VPS", theme=gr.themes.Soft()) as app:
188
- gr.Markdown("## ☁️ Python Cloud VPS")
189
-
 
 
 
 
 
 
 
190
  with gr.Tabs():
191
- with gr.Tab("Terminal"):
 
 
 
 
 
 
 
 
 
 
 
192
  with gr.Row():
193
- t_cmd = gr.Textbox(label="Command", placeholder="ls -la", scale=3)
194
- t_dir = gr.Textbox(label="Dir", value=WORKSPACE, scale=1)
195
- t_run = gr.Button("Run", variant="primary")
196
- t_out = gr.Textbox(label="Output", lines=20, elem_classes="term")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  t_run.click(execute_terminal, [t_cmd, t_dir], [t_out, t_dir])
198
  t_cmd.submit(execute_terminal, [t_cmd, t_dir], [t_out, t_dir])
 
 
 
199
 
200
- with gr.Tab("Editor"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  with gr.Row():
202
- e_path = gr.Textbox(label="File Path", value="bot.py")
203
- e_load = gr.Button("Load")
204
- e_save = gr.Button("Save")
205
- e_code = gr.Code(label="Code", language="python", lines=20)
206
- e_status = gr.Textbox(label="Status")
207
- e_load.click(read_file, [e_path], [e_code])
208
- e_save.click(write_file, [e_path, e_code], [e_status])
209
-
210
- with gr.Tab("Processes & Background"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  with gr.Row():
212
- b_cmd = gr.Textbox(label="Background Command", placeholder="python3 bot.py")
213
- b_run = gr.Button("Start Background Process")
214
- b_out = gr.Textbox(label="Start Status")
215
-
216
- gr.Markdown("### Active Processes")
217
- p_refresh = gr.Button("Refresh List")
218
- p_list = gr.Textbox(label="List", lines=10)
219
-
220
  with gr.Row():
221
- p_pid = gr.Textbox(label="PID to Stop")
222
- p_stop = gr.Button("Stop Process", variant="stop")
223
- p_res = gr.Textbox(label="Stop Result")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- b_run.click(execute_background, [b_cmd, t_dir], [b_out])
226
  p_refresh.click(get_process_list, None, [p_list])
227
- p_stop.click(stop_process, [p_pid], [p_res])
 
 
 
228
 
229
- with gr.Tab("Package Installer"):
230
- pk_name = gr.Textbox(label="Package Name (e.g. py-telegram-bot-api)")
231
- pk_btn = gr.Button("Install")
232
- pk_log = gr.Textbox(label="Installation Log", lines=10)
233
- pk_btn.click(install_package, [pk_name], [pk_log])
 
 
 
234
 
235
- if __name__ == "__main__":
236
- # هذا هو الإعداد الصحيح للإطلاق في Hugging Face
237
- app.queue(default_concurrency_limit=5)
238
- app.launch(
239
- server_name="0.0.0.0",
240
- server_port=7860,
241
- show_error=True
242
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # الملف الكامل المصحح - app.py
2
+ # فقط التغييرات المهمة موضحة
3
+
4
  import gradio as gr
5
  import subprocess
6
  import os
 
11
  import time
12
  import threading
13
  import json
14
+ import hashlib
15
  import secrets
16
+ from pathlib import Path
17
+ from datetime import datetime, timedelta
18
  from collections import deque
 
19
 
20
+ WORKSPACE = "/home/user/workspace"
 
 
21
  LOGS_DIR = os.path.join(WORKSPACE, ".system_logs")
22
  SESSIONS_DIR = os.path.join(WORKSPACE, ".sessions")
23
  BACKUP_DIR = os.path.join(WORKSPACE, ".backups")
24
  CONFIG_FILE = os.path.join(WORKSPACE, ".vps_config.json")
25
 
 
26
  for d in [WORKSPACE, LOGS_DIR, SESSIONS_DIR, BACKUP_DIR]:
27
  os.makedirs(d, exist_ok=True)
28
 
 
29
  os.chdir(WORKSPACE)
30
 
31
  bg_processes = {}
32
  proc_lock = threading.Lock()
33
  cmd_history = deque(maxlen=500)
34
  resource_history = deque(maxlen=100)
35
+ active_monitors = {}
36
 
37
  DEFAULT_CONFIG = {
38
  "max_bg_processes": 20,
39
  "command_timeout": 300,
40
+ "max_log_size_mb": 50,
41
+ "auto_cleanup_hours": 24,
42
  "monitor_interval": 30,
43
  }
44
 
45
+
46
  def load_config():
47
  try:
48
  if os.path.exists(CONFIG_FILE):
 
55
  pass
56
  return DEFAULT_CONFIG.copy()
57
 
58
+
59
+ def save_config(cfg):
60
+ try:
61
+ with open(CONFIG_FILE, "w") as f:
62
+ json.dump(cfg, f, indent=2)
63
+ except:
64
+ pass
65
+
66
+
67
  config = load_config()
68
 
69
+
70
  def resource_monitor():
71
  while True:
72
  try:
73
  mem = psutil.virtual_memory()
74
  cpu = psutil.cpu_percent(interval=1)
75
+ disk = psutil.disk_usage("/")
76
  resource_history.append({
77
  "time": datetime.now().strftime("%H:%M:%S"),
78
  "cpu": cpu,
79
  "ram_percent": mem.percent,
80
  "ram_used": mem.used,
81
  "ram_total": mem.total,
82
+ "disk_percent": disk.percent,
83
  })
84
+ cleanup_dead_processes()
 
 
 
 
 
 
 
85
  except:
86
  pass
87
  time.sleep(config.get("monitor_interval", 30))
88
 
89
+
90
  monitor_thread = threading.Thread(target=resource_monitor, daemon=True)
91
  monitor_thread.start()
92
 
93
+
94
+ def cleanup_dead_processes():
95
+ with proc_lock:
96
+ dead = []
97
+ for pid, info in bg_processes.items():
98
+ if info["proc"].poll() is not None:
99
+ try:
100
+ info["log_f"].close()
101
+ except:
102
+ pass
103
+ dead.append(pid)
104
+
105
+
106
  def fmt_size(size_bytes):
107
+ if size_bytes < 0:
108
+ return "0B"
109
  units = ["B", "KB", "MB", "GB", "TB"]
110
+ idx = 0
111
+ size = float(size_bytes)
112
  while size >= 1024 and idx < len(units) - 1:
113
+ size /= 1024
114
+ idx += 1
115
  return f"{size:.1f}{units[idx]}"
116
 
117
+
118
+ def file_icon(name):
119
+ ext = name.rsplit(".", 1)[-1].lower() if "." in name else ""
120
+ m = {
121
+ "py": "🐍", "js": "📜", "ts": "📘", "jsx": "⚛️", "tsx": "⚛️",
122
+ "html": "🌐", "css": "🎨", "scss": "🎨", "less": "🎨",
123
+ "json": "📋", "yml": "⚙️", "yaml": "⚙️", "toml": "⚙️", "ini": "⚙️",
124
+ "md": "📝", "txt": "📄", "log": "📃", "csv": "📊", "xml": "📰",
125
+ "jpg": "🖼️", "jpeg": "🖼️", "png": "🖼️", "gif": "🖼️", "svg": "🖼️",
126
+ "webp": "🖼️",
127
+ "mp3": "🎵", "wav": "🎵", "mp4": "🎬", "avi": "🎬", "mkv": "🎬",
128
+ "zip": "📦", "tar": "📦", "gz": "📦", "rar": "📦", "7z": "📦",
129
+ "sh": "⚡", "bash": "⚡", "zsh": "⚡", "fish": "⚡",
130
+ "env": "🔐", "key": "🔑", "pem": "🔑", "crt": "🔑",
131
+ "db": "🗄️", "sqlite": "🗄️", "sqlite3": "🗄️", "sql": "🗄️",
132
+ "dockerfile": "🐳", "dockerignore": "🐳",
133
+ "gitignore": "📌", "gitmodules": "📌",
134
+ "lock": "🔒", "pid": "🔒",
135
+ "whl": "📀", "egg": "📀",
136
+ "cfg": "⚙️", "conf": "⚙️", "config": "⚙️",
137
+ "rst": "📝", "tex": "📝", "pdf": "📕",
138
+ "c": "🔧", "cpp": "🔧", "h": "🔧", "hpp": "🔧",
139
+ "java": "☕", "class": "☕", "jar": "☕",
140
+ "go": "🔵", "rs": "🦀", "rb": "💎", "php": "🐘",
141
+ "r": "📈", "rmd": "📈", "ipynb": "📓",
142
+ }
143
+ return m.get(ext, "📄")
144
+
145
+
146
+ def resolve_path(path, base=None):
147
+ if not path:
148
+ return os.getcwd()
149
  path = path.strip()
150
+ if path.startswith("~"):
151
+ path = path.replace("~", WORKSPACE, 1)
152
+ if not os.path.isabs(path):
153
+ base = base or os.getcwd()
154
+ path = os.path.join(base, path)
155
  return os.path.abspath(path)
156
 
157
+
158
+ def log_command(cmd, output, duration):
159
+ entry = {
160
+ "time": datetime.now().isoformat(),
161
+ "command": cmd,
162
+ "output_length": len(output),
163
+ "duration": round(duration, 3),
164
+ }
165
+ cmd_history.append(entry)
166
+ try:
167
+ log_file = os.path.join(
168
+ LOGS_DIR, f"commands_{datetime.now().strftime('%Y%m%d')}.log"
169
+ )
170
+ with open(log_file, "a") as f:
171
+ f.write(f"[{entry['time']}] ({duration:.3f}s) $ {cmd}\n")
172
+ except:
173
+ pass
174
+
175
+
176
  def execute_terminal(command, work_dir):
177
+ if not command or not command.strip():
178
+ return "$ _", work_dir or WORKSPACE
179
+ command = command.strip()
180
  wd = resolve_path(work_dir) if work_dir else WORKSPACE
181
+ if not os.path.isdir(wd):
182
+ wd = WORKSPACE
183
+ start = time.time()
184
+ parts = command.split()
185
+ base_cmd = parts[0] if parts else ""
186
+
187
+ if base_cmd == "cd":
188
+ target = parts[1] if len(parts) > 1 else WORKSPACE
189
+ target = resolve_path(target, wd)
190
+ if os.path.isdir(target):
191
+ os.chdir(target)
192
+ elapsed = time.time() - start
193
+ log_command(command, target, elapsed)
194
+ return f"$ cd {target}\n{target}", target
195
+ return f"bash: cd: {target}: No such file or directory", wd
196
+
197
+ if base_cmd in ("clear", "cls", "reset"):
198
+ return "", wd
199
+
200
+ if base_cmd == "history":
201
+ lines = []
202
+ for i, entry in enumerate(cmd_history):
203
+ lines.append(f" {i+1} {entry['command']}")
204
+ return "\n".join(lines[-50:]) if lines else "No history", wd
205
+
206
+ if base_cmd == "export":
207
+ if len(parts) >= 2 and "=" in parts[1]:
208
+ key, val = parts[1].split("=", 1)
209
+ os.environ[key] = val
210
+ return f"export {key}={val}", wd
211
 
212
  try:
213
+ env = os.environ.copy()
214
+ env["PYTHONUNBUFFERED"] = "1"
215
+ env["PIP_PROGRESS_BAR"] = "off"
216
+ env["TERM"] = "xterm-256color"
217
+ env["HOME"] = os.path.expanduser("~")
218
+ env["WORKSPACE"] = WORKSPACE
219
+ timeout = config.get("command_timeout", 300)
220
+
221
  result = subprocess.run(
222
+ command,
223
+ shell=True,
224
+ capture_output=True,
225
+ text=True,
226
+ timeout=timeout,
227
+ cwd=wd,
228
+ env=env,
229
+ executable="/bin/bash",
230
  )
231
+ elapsed = time.time() - start
232
+ output_parts = []
233
+ if result.stdout:
234
+ output_parts.append(result.stdout.rstrip())
235
+ if result.stderr:
236
+ output_parts.append(result.stderr.rstrip())
237
+ output = "\n".join(output_parts) if output_parts else ""
238
+ if not output and result.returncode != 0:
239
+ output = f"Process exited with code {result.returncode}"
240
+ elif not output:
241
+ output = ""
242
+ log_command(command, output, elapsed)
243
  return output, wd
244
+
245
+ except subprocess.TimeoutExpired:
246
+ elapsed = time.time() - start
247
+ log_command(command, "TIMEOUT", elapsed)
248
+ return (
249
+ f"Command timed out after {timeout}s. "
250
+ f"Use background execution for long tasks.",
251
+ wd,
252
+ )
253
  except Exception as e:
254
+ elapsed = time.time() - start
255
+ log_command(command, str(e), elapsed)
256
  return f"Error: {str(e)}", wd
257
 
258
+
259
  def execute_background(command, work_dir):
260
+ if not command or not command.strip():
261
+ return "No command specified"
262
+ with proc_lock:
263
+ if len(bg_processes) >= config.get("max_bg_processes", 20):
264
+ return (
265
+ f"Process limit reached ({config['max_bg_processes']}). "
266
+ f"Stop existing processes first."
267
+ )
268
+ wd = resolve_path(work_dir) if work_dir else WORKSPACE
269
+ if not os.path.isdir(wd):
270
+ wd = WORKSPACE
271
  try:
272
  ts = datetime.now().strftime("%Y%m%d_%H%M%S")
273
  proc_id = secrets.token_hex(4)
274
  log_path = os.path.join(LOGS_DIR, f"bg_{ts}_{proc_id}.log")
275
  log_f = open(log_path, "w", buffering=1)
276
+ header = (
277
+ f"=== Process Started ===\n"
278
+ f"Command: {command}\n"
279
+ f"Directory: {wd}\n"
280
+ f"Time: {datetime.now().isoformat()}\n"
281
+ f"{'=' * 40}\n\n"
282
+ )
283
+ log_f.write(header)
284
+ log_f.flush()
285
+
286
+ env = os.environ.copy()
287
+ env["PYTHONUNBUFFERED"] = "1"
288
+ env["TERM"] = "xterm-256color"
289
+ env["WORKSPACE"] = WORKSPACE
290
+
291
  proc = subprocess.Popen(
292
+ command,
293
+ shell=True,
294
+ stdout=log_f,
295
+ stderr=subprocess.STDOUT,
296
+ cwd=wd,
297
+ env=env,
298
+ preexec_fn=os.setsid,
299
+ executable="/bin/bash",
300
  )
301
+
302
  with proc_lock:
303
  bg_processes[proc.pid] = {
304
+ "cmd": command,
305
+ "pid": proc.pid,
306
+ "start": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
307
+ "start_ts": time.time(),
308
+ "log": log_path,
309
+ "proc": proc,
310
+ "log_f": log_f,
311
+ "dir": wd,
312
+ "id": proc_id,
313
  }
314
+
315
+ return (
316
+ f"Process started successfully\n"
317
+ f"{'─' * 45}\n"
318
+ f" PID : {proc.pid}\n"
319
+ f" ID : {proc_id}\n"
320
+ f" Command : {command}\n"
321
+ f" Dir : {wd}\n"
322
+ f" Log : {log_path}\n"
323
+ f"{'─' * 45}\n"
324
+ f"View log : cat {log_path}\n"
325
+ f"Stop : Use Process Manager tab"
326
+ )
327
+ except Exception as e:
328
+ return f"Failed to start process: {str(e)}"
329
+
330
+
331
+ def get_system_info():
332
+ try:
333
+ mem = psutil.virtual_memory()
334
+ swap = psutil.swap_memory()
335
+ disk = psutil.disk_usage("/")
336
+ cpu_freq = psutil.cpu_freq()
337
+ boot = datetime.fromtimestamp(psutil.boot_time())
338
+ uptime = datetime.now() - boot
339
+ net = psutil.net_io_counters()
340
+ freq_str = f"{cpu_freq.current:.0f}MHz" if cpu_freq else "N/A"
341
+ days = uptime.days
342
+ hours = uptime.seconds // 3600
343
+ mins = (uptime.seconds % 3600) // 60
344
+
345
+ with proc_lock:
346
+ bg_count = len(bg_processes)
347
+ bg_alive = sum(
348
+ 1 for p in bg_processes.values() if p["proc"].poll() is None
349
+ )
350
+
351
+ info = f"""╔══════════════════════════════════════════════╗
352
+ ║ SYSTEM INFORMATION ║
353
+ ╠══════════════════════════════════════════════╣
354
+ ║ OS : {os.uname().sysname} {os.uname().release[:22]}
355
+ ║ Arch : {os.uname().machine}
356
+ ║ Python : {sys.version.split()[0]}
357
+ ║ Uptime : {days}d {hours}h {mins}m
358
+ ║ CPU : {psutil.cpu_count()} cores @ {freq_str}
359
+ ║ CPU Load : {psutil.cpu_percent():.1f}%
360
+ ║ RAM Total: {fmt_size(mem.total)}
361
+ ║ RAM Used : {fmt_size(mem.used)} ({mem.percent}%)
362
+ ║ RAM Free : {fmt_size(mem.available)}
363
+ ║ Swap : {fmt_size(swap.used)}/{fmt_size(swap.total)}
364
+ ║ Disk Tot : {fmt_size(disk.total)}
365
+ ║ Disk Used: {fmt_size(disk.used)} ({disk.percent}%)
366
+ ║ Disk Free: {fmt_size(disk.free)}
367
+ ║ Net Sent : {fmt_size(net.bytes_sent)}
368
+ ║ Net Recv : {fmt_size(net.bytes_recv)}
369
+ ║ BG Procs : {bg_alive} running / {bg_count} total
370
+ ║ Work Dir : {os.getcwd()}
371
+ ╚══════════════════════════════════════════════╝"""
372
+ return info
373
  except Exception as e:
374
+ return f"Error: {str(e)}"
375
+
376
+
377
+ def browse_directory(path):
378
+ target = resolve_path(path)
379
+ if not os.path.isdir(target):
380
+ return f"Not a directory: {target}"
381
+ try:
382
+ all_items = sorted(
383
+ os.listdir(target),
384
+ key=lambda x: (not os.path.isdir(os.path.join(target, x)), x.lower()),
385
+ )
386
+ lines = [
387
+ f" {target}",
388
+ f"{'═' * 70}",
389
+ f" {'Type':<6} {'Size':>10} {'Modified':<18} {'Name'}",
390
+ f"{'─' * 70}",
391
+ ]
392
+ if target != "/":
393
+ lines.append(f" {'📁':<6} {'':>10} {'':.<18} ..")
394
+
395
+ dirs_c = 0
396
+ files_c = 0
397
+ total_sz = 0
398
+
399
+ for item in all_items:
400
+ fp = os.path.join(target, item)
401
+ try:
402
+ st = os.stat(fp, follow_symlinks=False)
403
+ mtime = datetime.fromtimestamp(st.st_mtime).strftime(
404
+ "%Y-%m-%d %H:%M"
405
+ )
406
+ if os.path.islink(fp):
407
+ real = os.path.realpath(fp)
408
+ lines.append(
409
+ f" {'🔗':<6} {'':>10} {mtime:<18} {item} -> {real}"
410
+ )
411
+ elif os.path.isdir(fp):
412
+ sub_count = 0
413
+ try:
414
+ sub_count = len(os.listdir(fp))
415
+ except:
416
+ pass
417
+ lines.append(
418
+ f" {'📁':<6} {f'{sub_count} items':>10} {mtime:<18} {item}/"
419
+ )
420
+ dirs_c += 1
421
+ else:
422
+ sz = st.st_size
423
+ total_sz += sz
424
+ ico = file_icon(item)
425
+ perm = ""
426
+ if os.access(fp, os.X_OK):
427
+ perm = " *"
428
+ lines.append(
429
+ f" {ico:<6} {fmt_size(sz):>10} {mtime:<18} {item}{perm}"
430
+ )
431
+ files_c += 1
432
+ except PermissionError:
433
+ lines.append(f" {'🔒':<6} {'denied':>10} {'':.<18} {item}")
434
+ except Exception:
435
+ lines.append(f" {'❓':<6} {'error':>10} {'':.<18} {item}")
436
+
437
+ lines.append(f"{'─' * 70}")
438
+ lines.append(
439
+ f" {dirs_c} directories {files_c} files {fmt_size(total_sz)} total"
440
+ )
441
+ return "\n".join(lines)
442
+ except PermissionError:
443
+ return f"Permission denied: {target}"
444
+ except Exception as e:
445
+ return f"Error: {str(e)}"
446
+
447
+
448
+ def read_file(filepath):
449
+ if not filepath or not filepath.strip():
450
+ return ""
451
+ fp = resolve_path(filepath)
452
+ if not os.path.exists(fp):
453
+ return f"File not found: {fp}"
454
+ if os.path.isdir(fp):
455
+ return f"Is a directory: {fp}"
456
+ size = os.path.getsize(fp)
457
+ if size > 10 * 1024 * 1024:
458
+ return f"File too large: {fmt_size(size)}. Use 'head' or 'tail' commands."
459
+ try:
460
+ with open(fp, "r", encoding="utf-8", errors="replace") as f:
461
+ return f.read()
462
+ except Exception as e:
463
+ return f"Error: {str(e)}"
464
+
465
+
466
+ def write_file(filepath, content):
467
+ if not filepath or not filepath.strip():
468
+ return "No filename specified"
469
+ fp = resolve_path(filepath)
470
+ try:
471
+ parent = os.path.dirname(fp)
472
+ if parent:
473
+ os.makedirs(parent, exist_ok=True)
474
+ with open(fp, "w", encoding="utf-8") as f:
475
+ f.write(content if content else "")
476
+ sz = os.path.getsize(fp)
477
+ return f"Saved: {fp} ({fmt_size(sz)})"
478
+ except Exception as e:
479
+ return f"Error: {str(e)}"
480
+
481
+
482
+ def delete_path(filepath):
483
+ if not filepath or not filepath.strip():
484
+ return "No path specified"
485
+ fp = resolve_path(filepath)
486
+ if not os.path.exists(fp):
487
+ return f"Not found: {fp}"
488
+ if fp in (WORKSPACE, "/", os.path.expanduser("~")):
489
+ return "Cannot delete system directories"
490
+ try:
491
+ if os.path.isdir(fp):
492
+ item_count = sum(
493
+ len(files) + len(dirs) for _, dirs, files in os.walk(fp)
494
+ )
495
+ shutil.rmtree(fp)
496
+ return f"Deleted directory: {fp} ({item_count} items)"
497
+ else:
498
+ sz = os.path.getsize(fp)
499
+ os.remove(fp)
500
+ return f"Deleted: {fp} ({fmt_size(sz)})"
501
+ except Exception as e:
502
+ return f"Error: {str(e)}"
503
+
504
+
505
+ def create_dir(dirpath):
506
+ if not dirpath or not dirpath.strip():
507
+ return "No directory name specified"
508
+ dp = resolve_path(dirpath)
509
+ try:
510
+ os.makedirs(dp, exist_ok=True)
511
+ return f"Created: {dp}"
512
+ except Exception as e:
513
+ return f"Error: {str(e)}"
514
+
515
+
516
+ def rename_item(old_path, new_path):
517
+ if not old_path or not new_path:
518
+ return "Specify both old and new paths"
519
+ old = resolve_path(old_path)
520
+ new = resolve_path(new_path)
521
+ if not os.path.exists(old):
522
+ return f"Not found: {old}"
523
+ try:
524
+ shutil.move(old, new)
525
+ return f"Renamed: {old} -> {new}"
526
+ except Exception as e:
527
+ return f"Error: {str(e)}"
528
+
529
+
530
+ def copy_item(src_path, dst_path):
531
+ if not src_path or not dst_path:
532
+ return "Specify both source and destination"
533
+ src = resolve_path(src_path)
534
+ dst = resolve_path(dst_path)
535
+ if not os.path.exists(src):
536
+ return f"Not found: {src}"
537
+ try:
538
+ if os.path.isdir(src):
539
+ shutil.copytree(src, dst)
540
+ else:
541
+ os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
542
+ shutil.copy2(src, dst)
543
+ return f"Copied: {src} -> {dst}"
544
+ except Exception as e:
545
+ return f"Error: {str(e)}"
546
+
547
+
548
+ def upload_handler(file_obj):
549
+ if file_obj is None:
550
+ return "No file selected"
551
+ try:
552
+ src = (
553
+ file_obj
554
+ if isinstance(file_obj, str)
555
+ else (file_obj.name if hasattr(file_obj, "name") else str(file_obj))
556
+ )
557
+ fname = os.path.basename(src)
558
+ dest = os.path.join(os.getcwd(), fname)
559
+ counter = 1
560
+ base, ext = os.path.splitext(fname)
561
+ while os.path.exists(dest):
562
+ dest = os.path.join(os.getcwd(), f"{base}_{counter}{ext}")
563
+ counter += 1
564
+ shutil.copy2(src, dest)
565
+ sz = os.path.getsize(dest)
566
+ return f"Uploaded: {os.path.basename(dest)} ({fmt_size(sz)})"
567
+ except Exception as e:
568
+ return f"Error: {str(e)}"
569
+
570
+
571
+ def chmod_item(filepath, mode):
572
+ if not filepath or not mode:
573
+ return "Specify file and mode (e.g., 755)"
574
+ fp = resolve_path(filepath)
575
+ if not os.path.exists(fp):
576
+ return f"Not found: {fp}"
577
+ try:
578
+ os.chmod(fp, int(mode, 8))
579
+ return f"Changed permissions: {fp} -> {mode}"
580
+ except Exception as e:
581
+ return f"Error: {str(e)}"
582
+
583
+
584
+ def load_file_to_editor(filepath):
585
+ content = read_file(filepath)
586
+ if content.startswith(("File not found", "Error", "Is a directory", "File too large")):
587
+ return gr.update(), content
588
+ return content, f"Loaded: {resolve_path(filepath)}"
589
+
590
+
591
+ def editor_save(filepath, code):
592
+ return write_file(filepath, code)
593
+
594
+
595
+ def editor_save_run(filepath, code):
596
+ if not filepath or not filepath.strip():
597
+ return "No filename specified"
598
+ fp = resolve_path(filepath)
599
+ save_result = write_file(filepath, code)
600
+ ext = fp.rsplit(".", 1)[-1].lower() if "." in fp else ""
601
+ runners = {
602
+ "py": f'python3 "{fp}"',
603
+ "js": f'node "{fp}"',
604
+ "ts": f'npx ts-node "{fp}"',
605
+ "sh": f'bash "{fp}"',
606
+ "bash": f'bash "{fp}"',
607
+ "rb": f'ruby "{fp}"',
608
+ "php": f'php "{fp}"',
609
+ "go": f'go run "{fp}"',
610
+ "rs": f'rustc "{fp}" -o /tmp/rs_out && /tmp/rs_out',
611
+ "c": f'gcc "{fp}" -o /tmp/c_out && /tmp/c_out',
612
+ "cpp": f'g++ "{fp}" -o /tmp/cpp_out && /tmp/cpp_out',
613
+ }
614
+ if ext in runners:
615
+ result, _ = execute_terminal(runners[ext], os.path.dirname(fp))
616
+ return f"{save_result}\n{'─' * 45}\n{result}"
617
+ return save_result
618
+
619
+
620
+ def editor_run_bg(filepath, code):
621
+ if not filepath or not filepath.strip():
622
+ return "No filename specified"
623
+ fp = resolve_path(filepath)
624
+ write_file(filepath, code)
625
+ ext = fp.rsplit(".", 1)[-1].lower() if "." in fp else ""
626
+ runners = {
627
+ "py": f'python3 "{fp}"',
628
+ "js": f'node "{fp}"',
629
+ "sh": f'bash "{fp}"',
630
+ }
631
+ if ext in runners:
632
+ return execute_background(runners[ext], os.path.dirname(fp))
633
+ return "File type not supported for direct execution"
634
+
635
 
636
  def get_process_list():
637
+ lines = ["BACKGROUND PROCESSES", "═" * 65]
638
  with proc_lock:
639
+ if not bg_processes:
640
+ lines.append("No background processes running")
641
+ else:
642
+ running = 0
643
+ stopped = 0
644
+ for pid, info in sorted(bg_processes.items()):
645
+ alive = info["proc"].poll() is None
646
+ if alive:
647
+ running += 1
648
+ elapsed = time.time() - info["start_ts"]
649
+ hours = int(elapsed // 3600)
650
+ mins = int((elapsed % 3600) // 60)
651
+ secs = int(elapsed % 60)
652
+ uptime_str = f"{hours:02d}:{mins:02d}:{secs:02d}"
653
+ mem_str = "N/A"
654
+ cpu_str = "N/A"
655
+ try:
656
+ p = psutil.Process(pid)
657
+ mem_mb = p.memory_info().rss / 1048576
658
+ mem_str = f"{mem_mb:.1f}MB"
659
+ cpu_str = f"{p.cpu_percent():.1f}%"
660
+ except:
661
+ pass
662
+ lines.append(
663
+ f"\n [{info['id']}] PID {pid} - RUNNING\n"
664
+ f" Command : {info['cmd']}\n"
665
+ f" Dir : {info['dir']}\n"
666
+ f" Uptime : {uptime_str}\n"
667
+ f" Memory : {mem_str}\n"
668
+ f" CPU : {cpu_str}\n"
669
+ f" Log : {info['log']}"
670
+ )
671
+ else:
672
+ stopped += 1
673
+ rc = info["proc"].returncode
674
+ lines.append(
675
+ f"\n [{info['id']}] PID {pid} - STOPPED (exit: {rc})\n"
676
+ f" Command : {info['cmd']}\n"
677
+ f" Started : {info['start']}\n"
678
+ f" Log : {info['log']}"
679
+ )
680
+ lines.append(f"\n{'─' * 65}")
681
+ lines.append(
682
+ f" Running: {running} Stopped: {stopped} Total: {running + stopped}"
683
+ )
684
  try:
685
+ mem = psutil.virtual_memory()
686
+ lines.append(f"\n{'═' * 65}")
687
+ lines.append(
688
+ f" System: CPU {psutil.cpu_percent()}% | "
689
+ f"RAM {mem.percent}% ({fmt_size(mem.used)}/{fmt_size(mem.total)})"
690
+ )
691
  except:
692
  pass
693
+ return "\n".join(lines)
694
 
 
 
 
 
 
695
 
696
+ def stop_process(pid_input):
697
+ if not pid_input or not pid_input.strip():
698
+ return "No PID specified"
699
+ pid_str = pid_input.strip()
700
+ target_pid = None
701
+ with proc_lock:
702
+ try:
703
+ target_pid = int(pid_str)
704
+ except ValueError:
705
+ for pid, info in bg_processes.items():
706
+ if info["id"] == pid_str:
707
+ target_pid = pid
708
+ break
709
+ if target_pid is None:
710
+ return f"Invalid PID or ID: {pid_str}"
711
+ if target_pid in bg_processes:
712
+ info = bg_processes[target_pid]
713
+ try:
714
+ if info["proc"].poll() is None:
715
+ os.killpg(os.getpgid(target_pid), signal.SIGTERM)
716
+ try:
717
+ info["proc"].wait(timeout=5)
718
+ except subprocess.TimeoutExpired:
719
+ os.killpg(os.getpgid(target_pid), signal.SIGKILL)
720
+ info["proc"].wait(timeout=3)
721
+ try:
722
+ info["log_f"].close()
723
+ except:
724
+ pass
725
+ rc = info["proc"].returncode
726
+ del bg_processes[target_pid]
727
+ return f"Process {target_pid} stopped (exit code: {rc})"
728
+ except Exception as e:
729
+ try:
730
+ del bg_processes[target_pid]
731
+ except:
732
+ pass
733
+ return f"Force killed {target_pid}: {str(e)}"
734
+ try:
735
+ os.kill(target_pid, signal.SIGTERM)
736
+ return f"Sent SIGTERM to {target_pid}"
737
+ except ProcessLookupError:
738
+ return f"Process {target_pid} not found"
739
+ except PermissionError:
740
+ return f"Permission denied for PID {target_pid}"
741
+ except Exception as e:
742
+ return f"Error: {str(e)}"
743
+
744
+
745
+ def stop_all_processes():
746
+ results = []
747
+ with proc_lock:
748
+ pids = list(bg_processes.keys())
749
+ for pid in pids:
750
+ result = stop_process(str(pid))
751
+ results.append(result)
752
+ if not results:
753
+ return "No processes to stop"
754
+ return "\n".join(results)
755
+
756
+
757
+ def restart_process(pid_input):
758
+ if not pid_input or not pid_input.strip():
759
+ return "No PID specified"
760
+ target_pid = None
761
+ cmd = None
762
+ wd = None
763
+ with proc_lock:
764
+ try:
765
+ target_pid = int(pid_input.strip())
766
+ except ValueError:
767
+ for pid, info in bg_processes.items():
768
+ if info["id"] == pid_input.strip():
769
+ target_pid = pid
770
+ break
771
+ if target_pid and target_pid in bg_processes:
772
+ cmd = bg_processes[target_pid]["cmd"]
773
+ wd = bg_processes[target_pid]["dir"]
774
+ if not cmd:
775
+ return f"Process not found: {pid_input}"
776
+ stop_process(str(target_pid))
777
+ time.sleep(1)
778
+ return execute_background(cmd, wd)
779
+
780
+
781
+ def view_log(pid_input):
782
+ if not pid_input or not pid_input.strip():
783
+ return "No PID specified"
784
+ target_pid = None
785
+ with proc_lock:
786
+ try:
787
+ target_pid = int(pid_input.strip())
788
+ except ValueError:
789
+ for pid, info in bg_processes.items():
790
+ if info["id"] == pid_input.strip():
791
+ target_pid = pid
792
+ break
793
+ if target_pid and target_pid in bg_processes:
794
+ log_path = bg_processes[target_pid]["log"]
795
+ try:
796
+ with open(log_path, "r") as f:
797
+ content = f.read()
798
+ if not content.strip():
799
+ return "Log is empty..."
800
+ lines = content.split("\n")
801
+ if len(lines) > 200:
802
+ return (
803
+ f"... (showing last 200 of {len(lines)} lines)\n\n"
804
+ + "\n".join(lines[-200:])
805
+ )
806
+ return content
807
+ except Exception as e:
808
+ return f"Error reading log: {str(e)}"
809
+ return f"Process not found: {pid_input}"
810
+
811
 
812
  def install_package(name):
813
+ if not name or not name.strip():
814
+ return "No package specified"
815
+ name = name.strip()
816
+ result, _ = execute_terminal(f"pip install {name}", WORKSPACE)
817
+ return result
818
+
819
+
820
+ def pip_list():
821
+ result, _ = execute_terminal("pip list --format=columns", WORKSPACE)
822
+ return result
823
+
824
+
825
+ def pip_upgrade():
826
+ result, _ = execute_terminal("pip install --upgrade pip", WORKSPACE)
827
+ return result
828
+
829
+
830
+ def search_files(pattern, search_path):
831
+ sp = resolve_path(search_path) if search_path else os.getcwd()
832
+ if not os.path.isdir(sp):
833
+ return f"Not a directory: {sp}"
834
+ result, _ = execute_terminal(
835
+ f'find "{sp}" -name "{pattern}" -type f 2>/dev/null | head -50', sp
836
+ )
837
+ return result if result else "No files found"
838
+
839
+
840
+ def search_in_files(text, search_path):
841
+ sp = resolve_path(search_path) if search_path else os.getcwd()
842
+ if not os.path.isdir(sp):
843
+ return f"Not a directory: {sp}"
844
+ result, _ = execute_terminal(
845
+ f"grep -rn \"{text}\" \"{sp}\" --include='*.py' --include='*.js' "
846
+ f"--include='*.txt' --include='*.md' --include='*.json' "
847
+ f"--include='*.yml' --include='*.yaml' --include='*.html' "
848
+ f"--include='*.css' --include='*.sh' 2>/dev/null | head -50",
849
+ sp,
850
+ )
851
+ return result if result else "No matches found"
852
+
853
+
854
+ def create_backup(name):
855
+ if not name or not name.strip():
856
+ name = datetime.now().strftime("backup_%Y%m%d_%H%M%S")
857
+ name = name.strip().replace(" ", "_")
858
+ backup_path = os.path.join(BACKUP_DIR, f"{name}.tar.gz")
859
+ result, _ = execute_terminal(
860
+ f'tar -czf "{backup_path}" -C "{WORKSPACE}" '
861
+ f"--exclude='.system_logs' --exclude='.backups' --exclude='.sessions' .",
862
+ WORKSPACE,
863
+ )
864
+ if os.path.exists(backup_path):
865
+ sz = os.path.getsize(backup_path)
866
+ return f"Backup created: {backup_path} ({fmt_size(sz)})"
867
+ return f"Backup failed: {result}"
868
+
869
+
870
+ def list_backups():
871
+ if not os.path.isdir(BACKUP_DIR):
872
+ return "No backups found"
873
+ backups = sorted(os.listdir(BACKUP_DIR), reverse=True)
874
+ if not backups:
875
+ return "No backups found"
876
+ lines = ["BACKUPS", "═" * 50]
877
+ for b in backups:
878
+ fp = os.path.join(BACKUP_DIR, b)
879
+ sz = os.path.getsize(fp)
880
+ mtime = datetime.fromtimestamp(os.path.getmtime(fp)).strftime(
881
+ "%Y-%m-%d %H:%M"
882
+ )
883
+ lines.append(f" {b:<35} {fmt_size(sz):>8} {mtime}")
884
+ return "\n".join(lines)
885
+
886
+
887
+ def get_resource_chart():
888
+ if not resource_history:
889
+ return "No resource data yet. Wait for monitoring..."
890
+ lines = ["RESOURCE MONITOR", "═" * 60, "", "CPU Usage:"]
891
+ for entry in list(resource_history)[-20:]:
892
+ bar_len = int(entry["cpu"] / 2)
893
+ bar = "█" * bar_len + "░" * (50 - bar_len)
894
+ lines.append(f" {entry['time']} [{bar}] {entry['cpu']:.1f}%")
895
+ lines.append("")
896
+ lines.append("RAM Usage:")
897
+ for entry in list(resource_history)[-20:]:
898
+ bar_len = int(entry["ram_percent"] / 2)
899
+ bar = "█" * bar_len + "░" * (50 - bar_len)
900
+ lines.append(f" {entry['time']} [{bar}] {entry['ram_percent']:.1f}%")
901
+ return "\n".join(lines)
902
+
903
+
904
+ TEMPLATES = {
905
+ "telegram_bot": {
906
+ "name": "telegram_bot.py",
907
+ "code": '''import asyncio
908
+ from telegram import Update
909
+ from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
910
+
911
+ TOKEN = "YOUR_TOKEN"
912
+
913
+ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
914
+ await update.message.reply_text("Bot is running!")
915
+
916
+ async def handle(update: Update, context: ContextTypes.DEFAULT_TYPE):
917
+ text = update.message.text
918
+ await update.message.reply_text(f"You said: {text}")
919
+
920
+ def main():
921
+ app = Application.builder().token(TOKEN).build()
922
+ app.add_handler(CommandHandler("start", start))
923
+ app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle))
924
+ print("Bot started...")
925
+ app.run_polling(drop_pending_updates=True)
926
+
927
+ if __name__ == "__main__":
928
+ main()
929
+ ''',
930
+ },
931
+ "flask_app": {
932
+ "name": "server.py",
933
+ "code": '''from flask import Flask, jsonify, request
934
+
935
+ app = Flask(__name__)
936
+
937
+ @app.route("/")
938
+ def index():
939
+ return jsonify({"status": "running", "message": "Hello World"})
940
+
941
+ @app.route("/api/data", methods=["GET", "POST"])
942
+ def data():
943
+ if request.method == "POST":
944
+ payload = request.get_json()
945
+ return jsonify({"received": payload})
946
+ return jsonify({"data": []})
947
+
948
+ if __name__ == "__main__":
949
+ app.run(host="0.0.0.0", port=7861, debug=False)
950
+ ''',
951
+ },
952
+ "discord_bot": {
953
+ "name": "discord_bot.py",
954
+ "code": '''import discord
955
+ from discord.ext import commands
956
+
957
+ TOKEN = "YOUR_TOKEN"
958
+ bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
959
+
960
+ @bot.event
961
+ async def on_ready():
962
+ print(f"Bot ready: {bot.user}")
963
+
964
+ @bot.command()
965
+ async def ping(ctx):
966
+ await ctx.send(f"Pong! {round(bot.latency * 1000)}ms")
967
+
968
+ bot.run(TOKEN)
969
+ ''',
970
+ },
971
+ "fastapi_app": {
972
+ "name": "api_server.py",
973
+ "code": '''from fastapi import FastAPI
974
+ import uvicorn
975
+
976
+ app = FastAPI()
977
+
978
+ @app.get("/")
979
+ async def root():
980
+ return {"status": "running"}
981
+
982
+ @app.get("/api/{item_id}")
983
+ async def get_item(item_id: int, q: str = None):
984
+ return {"item_id": item_id, "query": q}
985
+
986
+ if __name__ == "__main__":
987
+ uvicorn.run(app, host="0.0.0.0", port=7861)
988
+ ''',
989
+ },
990
+ "scraper": {
991
+ "name": "scraper.py",
992
+ "code": '''import requests
993
+ from bs4 import BeautifulSoup
994
+ import json
995
+
996
+ def scrape(url):
997
+ headers = {"User-Agent": "Mozilla/5.0"}
998
+ resp = requests.get(url, headers=headers, timeout=15)
999
+ resp.raise_for_status()
1000
+ soup = BeautifulSoup(resp.text, "html.parser")
1001
+ title = soup.title.string if soup.title else "N/A"
1002
+ links = [a.get("href") for a in soup.find_all("a", href=True)]
1003
+ return {"url": url, "title": title, "links_count": len(links), "links": links[:20]}
1004
+
1005
+ if __name__ == "__main__":
1006
+ result = scrape("https://example.com")
1007
+ print(json.dumps(result, indent=2, ensure_ascii=False))
1008
+ ''',
1009
+ },
1010
+ "scheduler": {
1011
+ "name": "scheduler.py",
1012
+ "code": '''import time
1013
+ import datetime
1014
+
1015
+ class Scheduler:
1016
+ def __init__(self):
1017
+ self.tasks = []
1018
+ self.running = True
1019
 
1020
+ def every(self, seconds, func, *args):
1021
+ self.tasks.append({"interval": seconds, "func": func, "args": args, "last": 0})
1022
+
1023
+ def start(self):
1024
+ print(f"Scheduler started with {len(self.tasks)} tasks")
1025
+ while self.running:
1026
+ now = time.time()
1027
+ for task in self.tasks:
1028
+ if now - task["last"] >= task["interval"]:
1029
+ try:
1030
+ task["func"](*task["args"])
1031
+ except Exception as e:
1032
+ print(f"Task error: {e}")
1033
+ task["last"] = now
1034
+ time.sleep(1)
1035
+
1036
+ def heartbeat():
1037
+ print(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] Heartbeat")
1038
+
1039
+ if __name__ == "__main__":
1040
+ s = Scheduler()
1041
+ s.every(10, heartbeat)
1042
+ s.start()
1043
+ ''',
1044
+ },
1045
+ "keep_alive": {
1046
+ "name": "keep_alive.py",
1047
+ "code": '''import time
1048
+ import urllib.request
1049
+ import datetime
1050
+
1051
+ SPACE_URL = "https://YOUR-SPACE-URL.hf.space"
1052
+ INTERVAL = 280
1053
+
1054
+ def ping():
1055
+ while True:
1056
+ try:
1057
+ urllib.request.urlopen(SPACE_URL, timeout=15)
1058
+ print(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] OK")
1059
+ except Exception as e:
1060
+ print(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] FAIL: {e}")
1061
+ time.sleep(INTERVAL)
1062
+
1063
+ if __name__ == "__main__":
1064
+ print(f"Keep-alive started for {SPACE_URL}")
1065
+ ping()
1066
+ ''',
1067
+ },
1068
+ }
1069
+
1070
+
1071
+ # ============================================================
1072
+ # CSS
1073
+ # ============================================================
1074
  CSS = """
1075
+ .term textarea {
1076
+ font-family: 'Courier New', monospace !important;
1077
+ font-size: 13px !important;
1078
+ background: #0a0e14 !important;
1079
+ color: #b3b1ad !important;
1080
+ border: 1px solid #1c2433 !important;
1081
+ border-radius: 6px !important;
1082
+ padding: 12px !important;
1083
+ }
1084
+ footer { display: none !important; }
1085
  """
1086
 
1087
+ # ============================================================
1088
+ # ✅ المتغير اسمه demo وليس app
1089
+ # ============================================================
1090
+ demo = gr.Blocks(css=CSS, title="Cloud VPS", theme=gr.themes.Default(
1091
+ primary_hue="blue", neutral_hue="gray"
1092
+ ))
1093
+
1094
+ with demo:
1095
+ gr.Markdown("# ☁️ Cloud VPS Terminal")
1096
+
1097
  with gr.Tabs():
1098
+
1099
+ # ===== Terminal Tab =====
1100
+ with gr.TabItem("🖥️ Terminal"):
1101
+ with gr.Row():
1102
+ t_cmd = gr.Textbox(
1103
+ label="Command", lines=1, scale=4,
1104
+ placeholder="Enter any Linux command..."
1105
+ )
1106
+ t_dir = gr.Textbox(
1107
+ label="Working Directory", value=WORKSPACE,
1108
+ lines=1, scale=2
1109
+ )
1110
  with gr.Row():
1111
+ t_run = gr.Button("▶ Run", variant="primary", size="lg")
1112
+ t_bg = gr.Button("⚡ Background", variant="secondary", size="lg")
1113
+ t_info = gr.Button("ℹ️ System Info", size="lg")
1114
+ t_clear = gr.Button("🗑️ Clear", size="lg")
1115
+
1116
+ t_out = gr.Textbox(
1117
+ label="Output", lines=24, interactive=False,
1118
+ elem_classes=["term"]
1119
+ )
1120
+
1121
+ with gr.Accordion("Quick Commands", open=False):
1122
+ with gr.Row():
1123
+ qc1 = gr.Button("ls -la", size="sm")
1124
+ qc2 = gr.Button("pwd", size="sm")
1125
+ qc3 = gr.Button("whoami", size="sm")
1126
+ qc4 = gr.Button("df -h", size="sm")
1127
+ qc5 = gr.Button("free -h", size="sm")
1128
+ qc6 = gr.Button("top snapshot", size="sm")
1129
+ with gr.Row():
1130
+ qc7 = gr.Button("pip list", size="sm")
1131
+ qc8 = gr.Button("python3 --version", size="sm")
1132
+ qc9 = gr.Button("node --version", size="sm")
1133
+ qc10 = gr.Button("git --version", size="sm")
1134
+ qc11 = gr.Button("env", size="sm")
1135
+ qc12 = gr.Button("netstat", size="sm")
1136
+
1137
  t_run.click(execute_terminal, [t_cmd, t_dir], [t_out, t_dir])
1138
  t_cmd.submit(execute_terminal, [t_cmd, t_dir], [t_out, t_dir])
1139
+ t_bg.click(execute_background, [t_cmd, t_dir], [t_out])
1140
+ t_info.click(get_system_info, None, [t_out])
1141
+ t_clear.click(lambda: ("", WORKSPACE), None, [t_out, t_dir])
1142
 
1143
+ qc1.click(lambda d: execute_terminal("ls -la", d), [t_dir], [t_out, t_dir])
1144
+ qc2.click(lambda d: execute_terminal("pwd", d), [t_dir], [t_out, t_dir])
1145
+ qc3.click(lambda d: execute_terminal("whoami", d), [t_dir], [t_out, t_dir])
1146
+ qc4.click(lambda d: execute_terminal("df -h", d), [t_dir], [t_out, t_dir])
1147
+ qc5.click(lambda d: execute_terminal("free -h", d), [t_dir], [t_out, t_dir])
1148
+ qc6.click(
1149
+ lambda d: execute_terminal("ps aux --sort=-%mem | head -20", d),
1150
+ [t_dir], [t_out, t_dir],
1151
+ )
1152
+ qc7.click(
1153
+ lambda d: execute_terminal("pip list --format=columns", d),
1154
+ [t_dir], [t_out, t_dir],
1155
+ )
1156
+ qc8.click(
1157
+ lambda d: execute_terminal("python3 --version", d),
1158
+ [t_dir], [t_out, t_dir],
1159
+ )
1160
+ qc9.click(
1161
+ lambda d: execute_terminal(
1162
+ "node --version 2>/dev/null || echo 'Not installed'", d
1163
+ ),
1164
+ [t_dir], [t_out, t_dir],
1165
+ )
1166
+ qc10.click(
1167
+ lambda d: execute_terminal("git --version", d),
1168
+ [t_dir], [t_out, t_dir],
1169
+ )
1170
+ qc11.click(
1171
+ lambda d: execute_terminal("env | sort", d),
1172
+ [t_dir], [t_out, t_dir],
1173
+ )
1174
+ qc12.click(
1175
+ lambda d: execute_terminal(
1176
+ "ss -tlnp 2>/dev/null || echo 'Not available'", d
1177
+ ),
1178
+ [t_dir], [t_out, t_dir],
1179
+ )
1180
+
1181
+ # ===== Files Tab =====
1182
+ with gr.TabItem("📁 Files"):
1183
  with gr.Row():
1184
+ with gr.Column(scale=1):
1185
+ f_path = gr.Textbox(label="Path", value=WORKSPACE, lines=1)
1186
+ with gr.Row():
1187
+ f_browse = gr.Button("Browse", variant="primary", size="sm")
1188
+ f_up = gr.Button("Up ..", size="sm")
1189
+ f_home = gr.Button("Home", size="sm")
1190
+ f_list = gr.Textbox(
1191
+ label="Directory Contents", lines=22,
1192
+ interactive=False, elem_classes=["term"]
1193
+ )
1194
+
1195
+ with gr.Column(scale=1):
1196
+ with gr.Tabs():
1197
+ with gr.TabItem("View"):
1198
+ fv_path = gr.Textbox(label="File Path", lines=1)
1199
+ fv_btn = gr.Button("Open", variant="primary", size="sm")
1200
+ fv_content = gr.Textbox(
1201
+ label="Content", lines=16,
1202
+ interactive=False, elem_classes=["term"]
1203
+ )
1204
+
1205
+ with gr.TabItem("Create/Edit"):
1206
+ fe_path = gr.Textbox(label="File Path", lines=1)
1207
+ fe_content = gr.Textbox(label="Content", lines=12)
1208
+ fe_btn = gr.Button("Save", variant="primary", size="sm")
1209
+ fe_status = gr.Textbox(label="Status", lines=1, interactive=False)
1210
+
1211
+ with gr.TabItem("Operations"):
1212
+ fo_mkdir = gr.Textbox(label="New Directory", lines=1)
1213
+ fo_mkdir_btn = gr.Button("Create Directory", size="sm")
1214
+ fo_del = gr.Textbox(label="Delete Path", lines=1)
1215
+ fo_del_btn = gr.Button("Delete", variant="stop", size="sm")
1216
+ fo_ren_old = gr.Textbox(label="Rename From", lines=1)
1217
+ fo_ren_new = gr.Textbox(label="Rename To", lines=1)
1218
+ fo_ren_btn = gr.Button("Rename/Move", size="sm")
1219
+ fo_cp_src = gr.Textbox(label="Copy From", lines=1)
1220
+ fo_cp_dst = gr.Textbox(label="Copy To", lines=1)
1221
+ fo_cp_btn = gr.Button("Copy", size="sm")
1222
+ fo_chmod_path = gr.Textbox(label="File Path", lines=1)
1223
+ fo_chmod_mode = gr.Textbox(label="Mode (e.g. 755)", lines=1)
1224
+ fo_chmod_btn = gr.Button("Change Permissions", size="sm")
1225
+ fo_status = gr.Textbox(label="Status", lines=2, interactive=False)
1226
+
1227
+ with gr.TabItem("Upload"):
1228
+ fu_file = gr.File(label="Select File")
1229
+ fu_btn = gr.Button("Upload", variant="primary", size="sm")
1230
+ fu_status = gr.Textbox(label="Status", lines=1, interactive=False)
1231
+
1232
+ with gr.TabItem("Search"):
1233
+ fs_name = gr.Textbox(label="Filename Pattern", lines=1, placeholder="*.py")
1234
+ fs_text = gr.Textbox(label="Search in Content", lines=1)
1235
+ fs_path = gr.Textbox(label="Search Directory", lines=1, value=WORKSPACE)
1236
+ with gr.Row():
1237
+ fs_name_btn = gr.Button("Search by Name", size="sm")
1238
+ fs_text_btn = gr.Button("Search in Files", size="sm")
1239
+ fs_result = gr.Textbox(
1240
+ label="Results", lines=10,
1241
+ interactive=False, elem_classes=["term"]
1242
+ )
1243
+
1244
+ f_browse.click(browse_directory, [f_path], [f_list])
1245
+ f_path.submit(browse_directory, [f_path], [f_list])
1246
+ f_up.click(
1247
+ lambda p: (
1248
+ os.path.dirname(resolve_path(p)),
1249
+ browse_directory(os.path.dirname(resolve_path(p))),
1250
+ ),
1251
+ [f_path], [f_path, f_list],
1252
+ )
1253
+ f_home.click(
1254
+ lambda: (WORKSPACE, browse_directory(WORKSPACE)),
1255
+ None, [f_path, f_list],
1256
+ )
1257
+ fv_btn.click(read_file, [fv_path], [fv_content])
1258
+ fe_btn.click(write_file, [fe_path, fe_content], [fe_status])
1259
+ fo_mkdir_btn.click(create_dir, [fo_mkdir], [fo_status])
1260
+ fo_del_btn.click(delete_path, [fo_del], [fo_status])
1261
+ fo_ren_btn.click(rename_item, [fo_ren_old, fo_ren_new], [fo_status])
1262
+ fo_cp_btn.click(copy_item, [fo_cp_src, fo_cp_dst], [fo_status])
1263
+ fo_chmod_btn.click(chmod_item, [fo_chmod_path, fo_chmod_mode], [fo_status])
1264
+ fu_btn.click(upload_handler, [fu_file], [fu_status])
1265
+ fs_name_btn.click(search_files, [fs_name, fs_path], [fs_result])
1266
+ fs_text_btn.click(search_in_files, [fs_text, fs_path], [fs_result])
1267
+
1268
+ # ===== Editor Tab =====
1269
+ with gr.TabItem("📝 Editor"):
1270
+ with gr.Row():
1271
+ e_path = gr.Textbox(label="Filename", value="script.py", lines=1, scale=3)
1272
+ e_lang = gr.Dropdown(
1273
+ label="Language",
1274
+ choices=["python", "javascript", "shell", "json", "yaml", "html", "css"],
1275
+ value="python",
1276
+ scale=1,
1277
+ )
1278
+ with gr.Row():
1279
+ e_load = gr.Button("📂 Load", size="sm")
1280
+ e_save = gr.Button("💾 Save", variant="primary", size="sm")
1281
+ e_run = gr.Button("▶ Save & Run", variant="primary", size="sm")
1282
+ e_run_bg = gr.Button("⚡ Run Background", variant="secondary", size="sm")
1283
+
1284
+ e_code = gr.Code(label="Code", language="python", lines=24, value="")
1285
+ e_out = gr.Textbox(
1286
+ label="Output", lines=10, interactive=False,
1287
+ elem_classes=["term"]
1288
+ )
1289
+
1290
+ with gr.Accordion("Templates", open=False):
1291
+ with gr.Row():
1292
+ et1 = gr.Button("Telegram Bot", size="sm")
1293
+ et2 = gr.Button("Flask App", size="sm")
1294
+ et3 = gr.Button("Discord Bot", size="sm")
1295
+ et4 = gr.Button("FastAPI", size="sm")
1296
+ with gr.Row():
1297
+ et5 = gr.Button("Web Scraper", size="sm")
1298
+ et6 = gr.Button("Scheduler", size="sm")
1299
+ et7 = gr.Button("Keep Alive", size="sm")
1300
+
1301
+ e_load.click(load_file_to_editor, [e_path], [e_code, e_out])
1302
+ e_save.click(editor_save, [e_path, e_code], [e_out])
1303
+ e_run.click(editor_save_run, [e_path, e_code], [e_out])
1304
+ e_run_bg.click(editor_run_bg, [e_path, e_code], [e_out])
1305
+
1306
+ def apply_template(key):
1307
+ t = TEMPLATES[key]
1308
+ return t["code"], t["name"]
1309
+
1310
+ et1.click(lambda: apply_template("telegram_bot"), None, [e_code, e_path])
1311
+ et2.click(lambda: apply_template("flask_app"), None, [e_code, e_path])
1312
+ et3.click(lambda: apply_template("discord_bot"), None, [e_code, e_path])
1313
+ et4.click(lambda: apply_template("fastapi_app"), None, [e_code, e_path])
1314
+ et5.click(lambda: apply_template("scraper"), None, [e_code, e_path])
1315
+ et6.click(lambda: apply_template("scheduler"), None, [e_code, e_path])
1316
+ et7.click(lambda: apply_template("keep_alive"), None, [e_code, e_path])
1317
+
1318
+ # ===== Processes Tab =====
1319
+ with gr.TabItem("⚙️ Processes"):
1320
  with gr.Row():
1321
+ p_refresh = gr.Button("🔄 Refresh", variant="primary", size="lg")
1322
+ p_stop_all = gr.Button("⛔ Stop All", variant="stop", size="lg")
1323
+
1324
+ p_list = gr.Textbox(
1325
+ label="Processes", lines=18, interactive=False,
1326
+ elem_classes=["term"]
1327
+ )
1328
+
1329
  with gr.Row():
1330
+ p_pid = gr.Textbox(label="PID or Process ID", lines=1, scale=2)
1331
+ p_stop = gr.Button("Stop", variant="stop", scale=1)
1332
+ p_restart = gr.Button("Restart", scale=1)
1333
+ p_log = gr.Button("View Log", scale=1)
1334
+
1335
+ p_result = gr.Textbox(
1336
+ label="Result", lines=12, interactive=False,
1337
+ elem_classes=["term"]
1338
+ )
1339
+
1340
+ with gr.Accordion("Resource Monitor", open=False):
1341
+ p_monitor_btn = gr.Button("Show Resources", size="sm")
1342
+ p_monitor = gr.Textbox(
1343
+ label="Resources", lines=15, interactive=False,
1344
+ elem_classes=["term"]
1345
+ )
1346
+ p_monitor_btn.click(get_resource_chart, None, [p_monitor])
1347
 
 
1348
  p_refresh.click(get_process_list, None, [p_list])
1349
+ p_stop_all.click(stop_all_processes, None, [p_result])
1350
+ p_stop.click(stop_process, [p_pid], [p_result])
1351
+ p_restart.click(restart_process, [p_pid], [p_result])
1352
+ p_log.click(view_log, [p_pid], [p_result])
1353
 
1354
+ # ===== Packages Tab =====
1355
+ with gr.TabItem("📦 Packages"):
1356
+ with gr.Row():
1357
+ pk_name = gr.Textbox(
1358
+ label="Package Name", lines=1, scale=3,
1359
+ placeholder="package_name"
1360
+ )
1361
+ pk_btn = gr.Button("Install", variant="primary", scale=1, size="lg")
1362
 
1363
+ pk_out = gr.Textbox(
1364
+ label="Output", lines=15, interactive=False,
1365
+ elem_classes=["term"]
1366
+ )
1367
+
1368
+ with gr.Accordion("Quick Install", open=True):
1369
+ with gr.Row():
1370
+ pi1 = gr.Button("python-telegram-bot", size="sm")
1371
+ pi2 = gr.Button("flask", size="sm")
1372
+ pi3 = gr.Button("requests", size="sm")
1373
+ pi4 = gr.Button("discord.py", size="sm")
1374
+ pi5 = gr.Button("aiohttp", size="sm")
1375
+ with gr.Row():
1376
+ pi6 = gr.Button("beautifulsoup4", size="sm")
1377
+ pi7 = gr.Button("sqlalchemy", size="sm")
1378
+ pi8 = gr.Button("pandas", size="sm")
1379
+ pi9 = gr.Button("fastapi uvicorn", size="sm")
1380
+ pi10 = gr.Button("celery redis", size="sm")
1381
+ with gr.Row():
1382
+ pi11 = gr.Button("pillow", size="sm")
1383
+ pi12 = gr.Button("numpy", size="sm")
1384
+ pi13 = gr.Button("httpx", size="sm")
1385
+ pi14 = gr.Button("pyyaml", size="sm")
1386
+ pi15 = gr.Button("python-dotenv", size="sm")
1387
+
1388
+ with gr.Row():
1389
+ pk_list_btn = gr.Button("List Installed")
1390
+ pk_upgrade_btn = gr.Button("Upgrade pip")
1391
+
1392
+ pk_btn.click(install_package, [pk_name], [pk_out])
1393
+ pk_name.submit(install_package, [pk_name], [pk_out])
1394
+ pk_list_btn.click(pip_list, None, [pk_out])
1395
+ pk_upgrade_btn.click(pip_upgrade, None, [pk_out])
1396
+
1397
+ pi1.click(lambda: install_package("python-telegram-bot"), None, [pk_out])
1398
+ pi2.click(lambda: install_package("flask"), None, [pk_out])
1399
+ pi3.click(lambda: install_package("requests"), None, [pk_out])
1400
+ pi4.click(lambda: install_package("discord.py"), None, [pk_out])
1401
+ pi5.click(lambda: install_package("aiohttp"), None, [pk_out])
1402
+ pi6.click(lambda: install_package("beautifulsoup4 lxml"), None, [pk_out])
1403
+ pi7.click(lambda: install_package("sqlalchemy"), None, [pk_out])
1404
+ pi8.click(lambda: install_package("pandas"), None, [pk_out])
1405
+ pi9.click(lambda: install_package("fastapi uvicorn"), None, [pk_out])
1406
+ pi10.click(lambda: install_package("celery redis"), None, [pk_out])
1407
+ pi11.click(lambda: install_package("Pillow"), None, [pk_out])
1408
+ pi12.click(lambda: install_package("numpy"), None, [pk_out])
1409
+ pi13.click(lambda: install_package("httpx"), None, [pk_out])
1410
+ pi14.click(lambda: install_package("pyyaml"), None, [pk_out])
1411
+ pi15.click(lambda: install_package("python-dotenv"), None, [pk_out])
1412
+
1413
+ # ===== Backup Tab =====
1414
+ with gr.TabItem("💾 Backup"):
1415
+ with gr.Row():
1416
+ bk_name = gr.Textbox(label="Backup Name (optional)", lines=1, scale=3)
1417
+ bk_create = gr.Button("Create Backup", variant="primary", scale=1)
1418
+
1419
+ bk_out = gr.Textbox(label="Output", lines=5, interactive=False)
1420
+
1421
+ bk_list_btn = gr.Button("List Backups")
1422
+ bk_list = gr.Textbox(
1423
+ label="Backups", lines=10, interactive=False,
1424
+ elem_classes=["term"]
1425
+ )
1426
+
1427
+ bk_create.click(create_backup, [bk_name], [bk_out])
1428
+ bk_list_btn.click(list_backups, None, [bk_list])
1429
+
1430
+
1431
+ # ============================================================
1432
+ # ✅ التشغيل الصحيح
1433
+ # ============================================================
1434
+ demo.queue()
1435
+ demo.launch(
1436
+ server_name="0.0.0.0",
1437
+ server_port=7860,
1438
+ show_error=True,
1439
+ share=False
1440
+ )