From 0a63ae00d74de823ac8654017ba2f49455a0e339 Mon Sep 17 00:00:00 2001 From: westnife3 Date: Wed, 27 Aug 2025 19:02:11 +0900 Subject: [PATCH] =?UTF-8?q?spinner=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qtDash/loading_overlay.py | 29 +++++++++++++ qtDash/main.py | 91 +++++++++++++++++++++------------------ qtDash/monitor_card.py | 48 +++++++++++++++++++++ qtDash/monitor_data.py | 74 +++++++++++++++++++++++++++++++ qtDash/t.py | 32 ++++++++++++++ 5 files changed, 231 insertions(+), 43 deletions(-) create mode 100644 qtDash/loading_overlay.py create mode 100644 qtDash/monitor_card.py create mode 100644 qtDash/monitor_data.py create mode 100644 qtDash/t.py diff --git a/qtDash/loading_overlay.py b/qtDash/loading_overlay.py new file mode 100644 index 0000000..fd3ea7a --- /dev/null +++ b/qtDash/loading_overlay.py @@ -0,0 +1,29 @@ +# loading_overlay.py +from PyQt6.QtWidgets import QWidget, QLabel +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QMovie + +class LoadingOverlay(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setStyleSheet("background-color: rgba(0, 0, 0, 150);") + self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) + + self.spinner = QLabel(self) + self.spinner.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.movie = QMovie("spinner.gif") + self.spinner.setMovie(self.movie) + + layout = self.spinner.geometry() + self.resize(parent.size()) + self.spinner.resize(100, 100) + self.spinner.move((self.width() - 100) // 2, (self.height() - 100) // 2) + self.hide() + + def start(self): + self.movie.start() + self.show() + + def stop(self): + self.movie.stop() + self.hide() diff --git a/qtDash/main.py b/qtDash/main.py index 722cc2a..a2eb313 100644 --- a/qtDash/main.py +++ b/qtDash/main.py @@ -1,60 +1,65 @@ -from PyQt6.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QLabel, QGridLayout, QProgressBar -) -from PyQt6.QtCore import Qt, QTimer +# main.py +from PyQt6.QtWidgets import QApplication, QWidget, QGridLayout, QVBoxLayout +from PyQt6.QtCore import QTimer import sys +from loading_overlay import LoadingOverlay +from monitor_data import get_monitor_data +from monitor_card import MonitorCard # 카드 UI는 따로 분리해도 좋음 -class LoadingScreen(QWidget): +class Dashboard(QWidget): def __init__(self): super().__init__() + self.setWindowTitle("Server Monitor Dashboard") self.setFixedSize(960, 640) - self.setWindowTitle("서버 모니터링 - 로딩 중") - layout = QVBoxLayout() - layout.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.label = QLabel("서버 상태를 불러오는 중...") - self.label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.spinner = QProgressBar() - self.spinner.setRange(0, 0) # 무한 로딩 표시 + self.cards = {} + self.titles = [ + "CPU", "RAM", "Disk", "Uptime", + "CPU Temp", "GPU Temp", "GPU Usage", "Swap", + "Download", "Upload", "Alive", "Processes" + ] - layout.addWidget(self.label) - layout.addWidget(self.spinner) - self.setLayout(layout) + # 메인 레이아웃 + self.layout = QVBoxLayout(self) + self.setLayout(self.layout) -class MonitoringGrid(QWidget): - def __init__(self): - super().__init__() - self.setFixedSize(960, 640) - self.setWindowTitle("서버 모니터링") + # 카드 그리드 + self.grid = QGridLayout() + self.grid.setSpacing(10) + self.layout.addLayout(self.grid) - grid = QGridLayout() - grid.setSpacing(10) + # 카드 생성 + for i, title in enumerate(self.titles): + row = i // 4 + col = i % 4 + card = MonitorCard(title) + self.grid.addWidget(card, row, col) + self.cards[title] = card - for row in range(3): - for col in range(4): - label = QLabel(f"서버 {row * 3 + col + 1}") - label.setStyleSheet("background-color: #e0e0e0; padding: 20px; border: 1px solid #aaa;") - label.setAlignment(Qt.AlignmentFlag.AlignCenter) - grid.addWidget(label, row, col) + # 오버레이 생성 + self.overlay = LoadingOverlay(self) + self.overlay.start() - self.setLayout(grid) + # 데이터 수신 후 + QTimer.singleShot(2000, self.finish_loading) -class MainApp: - def __init__(self): - self.app = QApplication(sys.argv) - self.loading = LoadingScreen() - self.grid = MonitoringGrid() + # 주기적 업데이트 + self.timer = QTimer() + self.timer.timeout.connect(self.update_data) + self.timer.start(3000) - def run(self): - self.loading.show() + def finish_loading(self): + self.overlay.stop() + self.update_data() - # 3초 후 로딩 화면 종료하고 그리드 표시 - QTimer.singleShot(3000, self.show_grid) - sys.exit(self.app.exec()) + def update_data(self): + data = get_monitor_data() + for key in self.titles: + self.cards[key].update_value(data.get(key, "--")) - def show_grid(self): - self.loading.close() - self.grid.show() if __name__ == "__main__": - MainApp().run() + app = QApplication(sys.argv) + dashboard = Dashboard() + dashboard.show() + sys.exit(app.exec()) diff --git a/qtDash/monitor_card.py b/qtDash/monitor_card.py new file mode 100644 index 0000000..2fa8ffa --- /dev/null +++ b/qtDash/monitor_card.py @@ -0,0 +1,48 @@ +# monitor_card.py +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QMovie + +class MonitorCard(QWidget): + def __init__(self, title, bg_color="#2e2e2e"): + super().__init__() + self.setStyleSheet(f""" + background-color: {bg_color}; + border-radius: 10px; + border: 1px solid #444; + """) + layout = QVBoxLayout() + + self.title = QLabel(title) + self.title.setStyleSheet("font-weight: bold; font-size: 16px; color: white;") + self.title.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.value = QLabel("--") + self.value.setStyleSheet("font-size: 24px; font-weight: bold; color: white;") + self.value.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.spinner = QLabel() + self.spinner.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.movie = QMovie("spinner.gif") + self.spinner.setMovie(self.movie) + self.spinner.hide() + + layout.addWidget(self.title) + layout.addWidget(self.value) + layout.addWidget(self.spinner) + self.setLayout(layout) + + def start_loading(self): + self.value.hide() + self.spinner.show() + self.movie.start() + + def stop_loading(self): + self.movie.stop() + self.spinner.hide() + self.value.show() + + def update_value(self, text): + self.stop_loading() + self.value.setText(text) + diff --git a/qtDash/monitor_data.py b/qtDash/monitor_data.py new file mode 100644 index 0000000..3cdddc6 --- /dev/null +++ b/qtDash/monitor_data.py @@ -0,0 +1,74 @@ +# monitor_data.py +import psutil +import subprocess +import GPUtil +import time + +def format_uptime(): + uptime_seconds = time.time() - psutil.boot_time() + days = int(uptime_seconds // 86400) + hours = int((uptime_seconds % 86400) // 3600) + minutes = int((uptime_seconds % 3600) // 60) + return f"{days}d {hours:02d}h {minutes:02d}m" if days > 0 else f"{hours:02d}h {minutes:02d}m" + +def get_cpu_temp(): + try: + output = subprocess.check_output(['sensors'], universal_newlines=True) + for line in output.splitlines(): + if "Package id 0:" in line or "Core 0:" in line: + temp_str = line.split(":")[1].strip().split("°")[0] + return f"{float(temp_str)}°C" + except Exception: + return "---" + +def get_gpu_temp(): + try: + gpus = GPUtil.getGPUs() + return f"{gpus[0].temperature}°C" if gpus else "---" + except Exception: + return "---" + +def get_gpu_usage(): + try: + gpus = GPUtil.getGPUs() + return f"{gpus[0].load * 100:.1f}%" if gpus else "---" + except Exception: + return "---" + +def get_net_speed(interface='enp0s31f6', interval=1): + try: + start = psutil.net_io_counters(pernic=True)[interface] + time.sleep(interval) + end = psutil.net_io_counters(pernic=True)[interface] + download = (end.bytes_recv - start.bytes_recv) / 1024 / interval + upload = (end.bytes_sent - start.bytes_sent) / 1024 / interval + return f"{download:.2f} KB/s", f"{upload:.2f} KB/s" + except Exception: + return "---", "---" + +def check_alive(ip="192.168.0.101"): + try: + result = subprocess.run(["ping", "-c", "1", "-W", "1", ip], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + return "True" if result.returncode == 0 else "False" + except Exception: + return "---" + +def get_monitor_data(): + download, upload = get_net_speed() + return { + "CPU": f"{psutil.cpu_percent()}%", + "RAM": f"{psutil.virtual_memory().percent}%", + "Disk": f"{psutil.disk_usage('/').percent}%", + "Uptime": format_uptime(), + "CPU Temp": get_cpu_temp(), + "GPU Temp": get_gpu_temp(), + "GPU Usage": get_gpu_usage(), + "Swap": f"{psutil.swap_memory().percent}%", + "Download": download, + "Upload": upload, + "Alive": check_alive(), + "Processes": str(len(psutil.pids())) + } + diff --git a/qtDash/t.py b/qtDash/t.py new file mode 100644 index 0000000..9535078 --- /dev/null +++ b/qtDash/t.py @@ -0,0 +1,32 @@ +# main.py +# main.py +from PyQt6.QtWidgets import QApplication, QWidget, QGridLayout +from PyQt6.QtCore import QTimer +import sys +from monitor_data import get_monitor_data +from monitor_card import MonitorCard # 카드 UI는 따로 분리해도 좋음 + +from loading_overlay import LoadingOverlay + +class Dashboard(QWidget): + def __init__(self): + super().__init__() + self.init_ui() + + def init_ui(self): + # 카드들 배치 + self.layout = QVBoxLayout(self) + self.setLayout(self.layout) + + # 오버레이 생성 + self.overlay = LoadingOverlay(self) + + # 로딩 시작 + self.overlay.start() + + # 데이터 수신 후 + QTimer.singleShot(2000, self.finish_loading) + + def finish_loading(self): + self.overlay.stop() + # 카드들에 값 업데이트