-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_gui.py
More file actions
186 lines (140 loc) · 5.5 KB
/
Copy pathapi_gui.py
File metadata and controls
186 lines (140 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import subprocess
import threading
import tkinter as tk
from tkinter import scrolledtext, messagebox
import os
import sys
import psutil
from datetime import datetime
api_process = None
stop_reading = False
# --- Helper: Update Status Indicator ---
def update_status(is_running):
if is_running:
status_label.config(text="🟢 API Running", fg="green")
else:
status_label.config(text="🔴 API Stopped", fg="red")
# --- Log helper with timestamp ---
def add_log(message, prefix="ℹ️", separator=False):
"""Insert timestamped log entry at the top (newest first)."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
formatted = f"[{timestamp}] {prefix} {message}\n"
if separator:
formatted = (
"\n" + "-" * 60 + f"\n[{timestamp}] {prefix} {message}\n" + "-" * 60 + "\n"
)
log_box.insert("1.0", formatted) # Insert newest log at top
log_box.see("1.0") # Keep view at top
# --- Run API ---
def run_api():
global api_process, stop_reading
if api_process and api_process.poll() is None:
messagebox.showinfo("Info", "API is already running.")
return
stop_reading = False
add_log("Starting FastAPI server...", "🚀", separator=True)
update_status(False)
def start():
global api_process
try:
# Detect if running from PyInstaller EXE
if getattr(sys, 'frozen', False):
base_dir = os.path.dirname(sys.executable)
python_executable = "python" # system Python
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
python_executable = sys.executable
main_path = os.path.join(base_dir, "main.py")
# If main.py not found → show readable error
if not os.path.exists(main_path):
add_log(f"Cannot find main.py at {main_path}", "❌", separator=True)
return
api_process = subprocess.Popen(
[python_executable, "-m", "uvicorn", f"{main_path}:app", "--host", "0.0.0.0", "--port", "8001"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
cwd=base_dir # important — ensures working directory consistency
)
update_status(True)
add_log("Opening Swagger UI at http://127.0.0.1:8001/docs", "🌐")
threading.Timer(2, lambda: os.system("start http://127.0.0.1:8001/docs")).start()
read_logs()
except Exception as e:
add_log(f"Failed to start API: {e}", "❌", separator=True)
update_status(False)
threading.Thread(target=start, daemon=True).start()
# --- Read Logs (non-blocking) ---
def read_logs():
global api_process, stop_reading
def _read():
if not api_process:
return
for line in iter(api_process.stdout.readline, ''):
if stop_reading:
break
if line.strip():
add_log(line.strip(), "📜")
api_process.stdout.close()
threading.Thread(target=_read, daemon=True).start()
# --- Stop API (cross-platform, using psutil) ---
def stop_api():
global api_process, stop_reading
if not api_process or api_process.poll() is not None:
messagebox.showinfo("Info", "API is not running.")
update_status(False)
return
add_log("Stopping FastAPI server...", "🛑", separator=True)
try:
stop_reading = True
process = psutil.Process(api_process.pid)
# Terminate child processes (like uvicorn workers)
for child in process.children(recursive=True):
child.terminate()
process.terminate()
gone, still_alive = psutil.wait_procs([process], timeout=5)
for p in still_alive:
p.kill()
api_process = None
add_log("API stopped successfully.", "✅")
update_status(False)
except Exception as e:
add_log(f"Error stopping API: {e}", "❌")
update_status(False)
# --- Restart API ---
def restart_api():
add_log("Restarting API...", "🔁", separator=True)
stop_api()
run_api()
# --- Settings Placeholder ---
def open_settings():
messagebox.showinfo("Settings", "Settings menu placeholder — coming soon!")
# --- GUI Layout ---
root = tk.Tk()
root.title("FastAPI Control Panel")
root.geometry("700x520")
root.resizable(False, False)
title = tk.Label(root, text="🚀 FastAPI Local Server Controller", font=("Segoe UI", 16, "bold"))
title.pack(pady=10)
# --- Button Row ---
frame = tk.Frame(root)
frame.pack(pady=5)
btn_run = tk.Button(frame, text="▶️ Run API", width=15, command=run_api)
btn_run.grid(row=0, column=0, padx=5)
btn_restart = tk.Button(frame, text="🔁 Restart", width=15, command=restart_api)
btn_restart.grid(row=0, column=1, padx=5)
btn_stop = tk.Button(frame, text="⏹️ Stop", width=15, command=stop_api)
btn_stop.grid(row=0, column=2, padx=5)
btn_settings = tk.Button(frame, text="⚙️ Settings", width=15, command=open_settings)
btn_settings.grid(row=0, column=3, padx=5)
# --- Status Indicator ---
status_label = tk.Label(root, text="🔴 API Stopped", font=("Segoe UI", 12, "bold"), fg="red")
status_label.pack(pady=10)
# --- Log output area ---
log_box = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=80, height=20, font=("Consolas", 10))
log_box.pack(padx=10, pady=10)
add_log("Ready.", "✅", separator=True)
# --- Initialize ---
update_status(False)
root.mainloop()