Compare commits

...

8 Commits

Author SHA1 Message Date
unknown
9d5d2b8d99 Update 2025-12-19 20:23:59 2025-12-19 20:23:59 +09:00
unknown
b37c43ab86 Update 2025-12-19 19:18:16 2025-12-19 19:18:16 +09:00
unknown
b18412ecb2 Update 2025-12-19 16:23:03 2025-12-19 16:23:03 +09:00
804204ab97 update 2025-11-29 16:50:48 +09:00
25cbb6b8f8 update 2025-11-29 16:30:27 +09:00
2e4fc20523 Merge branch 'main' of https://gitea.mouse84.com/Kim.KANGHEE/iDRAC_Info 2025-11-29 11:14:52 +09:00
19798cca66 update 2025-11-29 11:13:55 +09:00
c0d3312bca update 2025-11-28 18:27:15 +09:00
120 changed files with 30901 additions and 2340 deletions

915
README.md

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

87
app.py
View File

@@ -8,6 +8,7 @@ from flask_login import LoginManager
from flask_migrate import Migrate
from flask_socketio import SocketIO
from flask_wtf import CSRFProtect
from dotenv import load_dotenv
from config import Config
from backend.models.user import db, load_user
@@ -15,10 +16,19 @@ from backend.routes import register_routes
from backend.services.logger import setup_logging
from backend.services import watchdog_handler
# 텔레그램 서비스 (별도 모듈)에서 가져옴
from telegram_bot_service import run_polling as telegram_run_polling
# ─────────────────────────────────────────────────────────────
# .env 파일 로드 (환경변수 우선순위 보장)
# ─────────────────────────────────────────────────────────────
load_dotenv()
# ─────────────────────────────────────────────────────────────
# 템플릿/정적 경로를 파일 위치 기준으로 안전하게 설정
# structure: <project_root>/backend/templates, <project_root>/backend/static
# ─────────────────────────────────────────────────────────────
BASE_DIR = Path(__file__).resolve().parent
TEMPLATE_DIR = (BASE_DIR / "backend" / "templates").resolve()
STATIC_DIR = (BASE_DIR / "backend" / "static").resolve()
@@ -32,9 +42,18 @@ setup_logging(app)
# ─────────────────────────────────────────────────────────────
# CSRF 보호 + 템플릿에서 {{ csrf_token() }} 사용 가능하게 주입
# ─────────────────────────────────────────────────────────────
csrf = CSRFProtect()
csrf.init_app(app)
# ─────────────────────────────────────────────────────────────
# ProxyFix: Nginx/NPM 등 리버스 프록시 뒤에서 실행 시 헤더 신뢰
# (HTTPS 인식, 올바른 IP/Scheme 파악으로 CSRF/세션 문제 해결)
# ─────────────────────────────────────────────────────────────
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
@app.context_processor
def inject_csrf():
try:
@@ -44,9 +63,11 @@ def inject_csrf():
# Flask-WTF 미설치/에러 시에도 앱이 뜨도록 방어
return dict(csrf_token=lambda: "")
# ─────────────────────────────────────────────────────────────
# SocketIO: Windows 기본 threading, Linux는 eventlet 설치 시 eventlet 사용
# 환경변수 SOCKETIO_ASYNC_MODE 로 강제 지정 가능 ("threading"/"eventlet"/"gevent"/"auto")
# ─────────────────────────────────────────────────────────────
async_mode = (app.config.get("SOCKETIO_ASYNC_MODE") or "threading").lower()
if async_mode == "auto":
async_mode = "threading"
@@ -67,39 +88,101 @@ socketio = SocketIO(app, cors_allowed_origins="*", async_mode=async_mode)
# watchdog에서 socketio 사용
watchdog_handler.socketio = socketio
# ─────────────────────────────────────────────────────────────
# DB / 마이그레이션
# ─────────────────────────────────────────────────────────────
app.logger.info("DB URI = %s", app.config.get("SQLALCHEMY_DATABASE_URI"))
db.init_app(app)
Migrate(app, db)
# (선택) 개발 편의용: 테이블 자동 부트스트랩
# 환경변수 AUTO_BOOTSTRAP_DB=true 일 때만 동작 (운영에서는 flask db upgrade 사용 권장)
if (os.getenv("AUTO_BOOTSTRAP_DB", "false").lower() == "true"):
if os.getenv("AUTO_BOOTSTRAP_DB", "false").lower() == "true":
from sqlalchemy import inspect
with app.app_context():
insp = inspect(db.engine)
if "user" not in insp.get_table_names():
db.create_all()
app.logger.info("DB bootstrap: created tables via create_all()")
# ─────────────────────────────────────────────────────────────
# Login
# ─────────────────────────────────────────────────────────────
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "auth.login"
@login_manager.user_loader
def _load_user(user_id: str):
return load_user(user_id)
# 라우트 등록 (Blueprints 등)
register_routes(app, socketio)
# ─────────────────────────────────────────────────────────────
# 텔레그램 봇 폴링 서비스 (중복 실행 방지 포함)
# ─────────────────────────────────────────────────────────────
_bot_socket_lock = None
def start_telegram_bot_polling() -> None:
"""텔레그램 봇 폴링을 백그라운드 스레드로 시작 (TCP 소켓 락으로 중복 방지)"""
import threading
import socket
global _bot_socket_lock
if _bot_socket_lock:
return
app.logger.info("🔒 봇 중복 실행 방지 락(TCP:50000) 획득 시도...")
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 50000))
s.listen(1)
_bot_socket_lock = s
app.logger.info("🔒 락 획득 성공! 봇 폴링 스레드를 시작합니다.")
except OSError:
app.logger.warning("⛔ 락 획득 실패: 이미 다른 프로세스(또는 좀비 프로세스)가 포트 50000을 점유 중입니다. 봇 폴링을 건너뜁니다.")
return
def _runner():
try:
telegram_run_polling(app)
except Exception as e:
app.logger.error("텔레그램 봇 폴링 서비스 오류: %s", e)
polling_thread = threading.Thread(target=_runner, daemon=True)
polling_thread.start()
# ─────────────────────────────────────────────────────────────
# 텔레그램 봇 폴링 자동 시작
# Flask 앱이 초기화되면 자동으로 봇 폴링 시작
# 주의: Flask 리로더(Debug 모드) 사용 시 메인/워커 프로세스 중복 실행 방지
# ─────────────────────────────────────────────────────────────
# 1. 리로더의 워커 프로세스인 경우 (WERKZEUG_RUN_MAIN = "true")
# 2. 또는 디버그 모드가 꺼진 경우 (Production)
if os.environ.get("WERKZEUG_RUN_MAIN") == "true" or not app.config.get("DEBUG"):
start_telegram_bot_polling()
# ─────────────────────────────────────────────────────────────
# 엔트리포인트
# ─────────────────────────────────────────────────────────────
if __name__ == "__main__":
host = os.getenv("FLASK_HOST", "0.0.0.0")
port = int(os.getenv("FLASK_PORT", 5000))
debug = os.getenv("FLASK_DEBUG", "true").lower() == "true"
socketio.run(app, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True)
# python app.py로 직접 실행 시(use_reloader=False)에는 위 조건문에서 실행되지 않을 수 있으므로
# 여기서 명시적으로 실행 (중복 실행 방지 플래그가 있어 안전함)
start_telegram_bot_polling()
socketio.run(app, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True, use_reloader=False)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,24 @@
from backend.models.user import db
class TelegramBot(db.Model):
__tablename__ = 'telegram_bots'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) # 봇 식별 이름 (예: 알림용, 경고용)
token = db.Column(db.String(200), nullable=False)
chat_id = db.Column(db.String(100), nullable=False)
is_active = db.Column(db.Boolean, default=True)
description = db.Column(db.String(255), nullable=True)
# 알림 유형: 콤마로 구분 (예: "auth,activity,system")
notification_types = db.Column(db.String(255), default="auth,activity,system")
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'token': self.token,
'chat_id': self.chat_id,
'is_active': self.is_active,
'description': self.description,
'notification_types': self.notification_types
}

View File

@@ -38,6 +38,10 @@ class User(db.Model, UserMixin):
password: Mapped[str] = mapped_column(String(255), nullable=False)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# 가입 승인 관련 필드
is_approved: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
approval_token: Mapped[Optional[str]] = mapped_column(String(100), unique=True, nullable=True)
# ── 유틸 메서드
def __repr__(self) -> str: # pragma: no cover
@@ -109,14 +113,24 @@ class User(db.Model, UserMixin):
q = (email or "").strip().lower()
if not q:
return None
return User.query.filter_by(email=q).first()
try:
return User.query.filter_by(email=q).first()
except Exception as e:
logging.error(f"User find_by_email error: {e}")
db.session.rollback()
return None
@staticmethod
def find_by_username(username: Optional[str]) -> Optional["User"]:
q = (username or "").strip()
if not q:
return None
return User.query.filter_by(username=q).first()
try:
return User.query.filter_by(username=q).first()
except Exception as e:
logging.error(f"User find_by_username error: {e}")
db.session.rollback()
return None
# Flask-Login user_loader (SQLAlchemy 2.0 방식)

View File

@@ -5,11 +5,14 @@ from .auth import register_auth_routes
from .admin import register_admin_routes
from .main import register_main_routes
from .xml import register_xml_routes
from .utilities import register_util_routes
from .utilities import register_util_routes, utils_bp
from .file_view import register_file_view
from .jobs import register_jobs_routes
from .idrac_routes import register_idrac_routes
from .catalog_sync import catalog_bp
from .scp_routes import scp_bp
from .drm_sync import drm_sync_bp
def register_routes(app: Flask, socketio=None) -> None:
"""블루프린트 일괄 등록. socketio는 main 라우트에서만 사용."""
@@ -18,8 +21,10 @@ def register_routes(app: Flask, socketio=None) -> None:
register_admin_routes(app)
register_main_routes(app, socketio)
register_xml_routes(app)
register_util_routes(app)
app.register_blueprint(utils_bp, url_prefix="/utils")
register_file_view(app)
register_jobs_routes(app)
register_idrac_routes(app)
app.register_blueprint(catalog_bp)
app.register_blueprint(scp_bp)
app.register_blueprint(drm_sync_bp)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -18,6 +18,11 @@ from flask import (
from flask_login import login_required, current_user
from backend.models.user import User, db
from backend.models.telegram_bot import TelegramBot
try:
from telegram import Bot
except ImportError:
Bot = None
admin_bp = Blueprint("admin", __name__)
@@ -124,3 +129,163 @@ def reset_password(user_id: int):
flash("비밀번호 변경 중 오류가 발생했습니다.", "danger")
return redirect(url_for("admin.admin_panel"))
# ▼▼▼ 시스템 설정 (텔레그램 봇 관리) ▼▼▼
@admin_bp.route("/admin/settings", methods=["GET"])
@login_required
@admin_required
def settings():
# 테이블 생성 확인 (임시)
try:
bots = TelegramBot.query.all()
except Exception:
db.create_all()
bots = []
return render_template("admin_settings.html", bots=bots)
@admin_bp.route("/admin/settings/bot/add", methods=["POST"])
@login_required
@admin_required
def add_bot():
name = request.form.get("name")
token = request.form.get("token")
chat_id = request.form.get("chat_id")
desc = request.form.get("description")
# 알림 유형 (체크박스 다중 선택)
notify_types = request.form.getlist("notify_types")
notify_str = ",".join(notify_types) if notify_types else ""
if not (name and token and chat_id):
flash("필수 항목(이름, 토큰, Chat ID)을 입력하세요.", "warning")
return redirect(url_for("admin.settings"))
bot = TelegramBot(
name=name,
token=token,
chat_id=chat_id,
description=desc,
is_active=True,
notification_types=notify_str
)
db.session.add(bot)
db.session.commit()
flash("텔레그램 봇이 추가되었습니다.", "success")
return redirect(url_for("admin.settings"))
@admin_bp.route("/admin/settings/bot/edit/<int:bot_id>", methods=["POST"])
@login_required
@admin_required
def edit_bot(bot_id):
bot = db.session.get(TelegramBot, bot_id)
if not bot:
abort(404)
bot.name = request.form.get("name")
bot.token = request.form.get("token")
bot.chat_id = request.form.get("chat_id")
bot.description = request.form.get("description")
bot.is_active = request.form.get("is_active") == "on"
# 알림 유형 업데이트
notify_types = request.form.getlist("notify_types")
bot.notification_types = ",".join(notify_types) if notify_types else ""
db.session.commit()
flash("봇 설정이 수정되었습니다.", "success")
return redirect(url_for("admin.settings"))
@admin_bp.route("/admin/settings/bot/delete/<int:bot_id>", methods=["POST"])
@login_required
@admin_required
def delete_bot(bot_id):
bot = db.session.get(TelegramBot, bot_id)
if bot:
db.session.delete(bot)
db.session.commit()
flash("봇이 삭제되었습니다.", "success")
return redirect(url_for("admin.settings"))
@admin_bp.route("/admin/settings/bot/test/<int:bot_id>", methods=["POST"])
@login_required
@admin_required
def test_bot(bot_id):
if not Bot:
flash("python-telegram-bot 라이브러리가 설치되지 않았습니다.", "danger")
return redirect(url_for("admin.settings"))
bot_obj = db.session.get(TelegramBot, bot_id)
if not bot_obj:
abort(404)
import asyncio
async def _send():
bot = Bot(token=bot_obj.token)
await bot.send_message(chat_id=bot_obj.chat_id, text="🔔 <b>테스트 메시지</b>\n이 메시지가 보이면 설정이 정상입니다.", parse_mode="HTML")
try:
asyncio.run(_send())
flash(f"'{bot_obj.name}' 봇으로 테스트 메시지를 보냈습니다.", "success")
except Exception as e:
flash(f"테스트 실패: {e}", "danger")
return redirect(url_for("admin.settings"))
# ▼▼▼ 시스템 로그 뷰어 ▼▼▼
@admin_bp.route("/admin/logs", methods=["GET"])
@login_required
@admin_required
def view_logs():
import os
import re
from collections import deque
log_folder = current_app.config.get('LOG_FOLDER')
log_file = os.path.join(log_folder, 'app.log') if log_folder else None
# 1. 실제 ANSI 이스케이프 코드 (\x1B로 시작)
ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
# 2. 텍스트로 찍힌 ANSI 코드 패턴 (예: [36m, [0m 등) - Werkzeug가 이스케이프 된 상태로 로그에 남길 경우 대비
literal_ansi = re.compile(r'\[[0-9;]+m')
# 3. 제어 문자 제거
control_char_re = re.compile(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]')
logs = []
if log_file and os.path.exists(log_file):
try:
with open(log_file, 'r', encoding='utf-8', errors='replace') as f:
raw_lines = deque(f, 1000)
for line in raw_lines:
# A. 실제 ANSI 코드 제거
clean_line = ansi_escape.sub('', line)
# B. 리터럴 ANSI 패턴 제거 (사용자가 [36m 등을 텍스트로 보고 있다면 이것이 원인)
clean_line = literal_ansi.sub('', clean_line)
# C. 제어 문자 제거
clean_line = control_char_re.sub('', clean_line)
# D. 앞뒤 공백 제거
clean_line = clean_line.strip()
# E. 빈 줄 제외
if clean_line:
logs.append(clean_line)
except Exception as e:
logs = [f"Error reading log file: {str(e)}"]
else:
logs = [f"Log file not found at: {log_file}"]
return render_template("admin_logs.html", logs=logs)

View File

@@ -3,8 +3,11 @@ from __future__ import annotations
import logging
import threading
import asyncio
import secrets
from typing import Optional
from urllib.parse import urlparse, urljoin
from datetime import datetime
from flask import (
Blueprint,
@@ -23,11 +26,13 @@ from backend.models.user import User, db
# ── (선택) Telegram: 미설정이면 조용히 패스
try:
from telegram import Bot
from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode
except Exception: # 라이브러리 미설치/미사용 환경
Bot = None
ParseMode = None
InlineKeyboardButton = None
InlineKeyboardMarkup = None
auth_bp = Blueprint("auth", __name__)
@@ -42,21 +47,162 @@ def _is_safe_url(target: str) -> bool:
return (test.scheme in ("http", "https")) and (ref.netloc == test.netloc)
def _notify(text: str) -> None:
"""텔레그램 알림 (설정 없으면 바로 return)."""
token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip()
chat_id = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip()
if not (token and chat_id and Bot and ParseMode):
return
def _send():
def _notify(text: str, category: str = "system") -> None:
"""
텔레그램 알림 전송
- DB(TelegramBot)에 등록된 활성 봇들에게 전송
- category: 'auth', 'activity', 'system'
"""
try:
from backend.models.telegram_bot import TelegramBot
# 앱 컨텍스트 안에서 실행되므로 바로 DB 접근 가능
try:
bot = Bot(token=token)
bot.send_message(chat_id=chat_id, text=text, parse_mode=ParseMode.HTML)
except Exception as e:
current_app.logger.warning("Telegram send failed: %s", e)
bots = TelegramBot.query.filter_by(is_active=True).all()
except Exception:
db.create_all()
bots = []
threading.Thread(target=_send, daemon=True).start()
# 1. DB에 봇이 없고, Config에 설정이 있는 경우 -> 자동 마이그레이션
if not bots:
cfg_token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip()
cfg_chat = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip()
if cfg_token and cfg_chat:
new_bot = TelegramBot(
name="기본 봇 (Config)",
token=cfg_token,
chat_id=cfg_chat,
is_active=True,
description="config.py 설정에서 자동 가져옴",
notification_types="auth,activity,system"
)
db.session.add(new_bot)
db.session.commit()
bots = [new_bot]
current_app.logger.info("Telegram: Config settings migrated to DB.")
if not bots:
return
# 카테고리 필터링
target_bots = []
for b in bots:
allowed = (b.notification_types or "auth,activity,system").split(",")
if category in allowed:
target_bots.append(b)
if not target_bots:
return
if not (Bot and ParseMode):
current_app.logger.warning("Telegram: Library not installed.")
return
app = current_app._get_current_object()
async def _send_to_bot(bot_obj, msg):
try:
t_bot = Bot(token=bot_obj.token)
await t_bot.send_message(chat_id=bot_obj.chat_id, text=msg, parse_mode=ParseMode.HTML)
except Exception as e:
app.logger.error(f"Telegram fail ({bot_obj.name}): {e}")
async def _send_all():
tasks = [_send_to_bot(b, text) for b in target_bots]
await asyncio.gather(*tasks)
def _run_thread():
try:
asyncio.run(_send_all())
except Exception as e:
app.logger.error(f"Telegram async loop error: {e}")
threading.Thread(target=_run_thread, daemon=True).start()
except Exception as e:
current_app.logger.error(f"Telegram notification error: {e}")
def _notify_with_buttons(text: str, buttons: list, category: str = "auth") -> None:
"""
텔레그램 알림 전송 (인라인 버튼 포함)
- buttons: [(text, callback_data), ...] 형식의 리스트
"""
try:
from backend.models.telegram_bot import TelegramBot
try:
bots = TelegramBot.query.filter_by(is_active=True).all()
except Exception:
db.create_all()
bots = []
if not bots:
cfg_token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip()
cfg_chat = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip()
if cfg_token and cfg_chat:
new_bot = TelegramBot(
name="기본 봇 (Config)",
token=cfg_token,
chat_id=cfg_chat,
is_active=True,
description="config.py 설정에서 자동 가져옴",
notification_types="auth,activity,system"
)
db.session.add(new_bot)
db.session.commit()
bots = [new_bot]
if not bots:
return
target_bots = []
for b in bots:
allowed = (b.notification_types or "auth,activity,system").split(",")
if category in allowed:
target_bots.append(b)
if not target_bots:
return
if not (Bot and ParseMode and InlineKeyboardButton and InlineKeyboardMarkup):
current_app.logger.warning("Telegram: Library not installed.")
return
# 인라인 키보드 생성
keyboard = [[InlineKeyboardButton(btn[0], callback_data=btn[1]) for btn in buttons]]
reply_markup = InlineKeyboardMarkup(keyboard)
app = current_app._get_current_object()
async def _send_to_bot(bot_obj, msg, markup):
try:
t_bot = Bot(token=bot_obj.token)
await t_bot.send_message(
chat_id=bot_obj.chat_id,
text=msg,
parse_mode=ParseMode.HTML,
reply_markup=markup
)
except Exception as e:
app.logger.error(f"Telegram fail ({bot_obj.name}): {e}")
async def _send_all():
tasks = [_send_to_bot(b, text, reply_markup) for b in target_bots]
await asyncio.gather(*tasks)
def _run_thread():
try:
asyncio.run(_send_all())
except Exception as e:
app.logger.error(f"Telegram async loop error: {e}")
threading.Thread(target=_run_thread, daemon=True).start()
except Exception as e:
current_app.logger.error(f"Telegram notification with buttons error: {e}")
# ─────────────────────────────────────────────────────────────
@@ -67,12 +213,28 @@ def register_auth_routes(app):
app.register_blueprint(auth_bp)
@app.before_request
def _touch_session():
# 요청마다 세션 갱신(만료 슬라이딩) + 로그아웃 플래그 정리
def _global_hooks():
# 1. 세션 갱신 (요청마다 세션 타임아웃 연장)
session.modified = True
if current_user.is_authenticated and session.get("just_logged_out"):
session.pop("just_logged_out", None)
flash("세션이 만료되어 자동 로그아웃 되었습니다.", "info")
# 2. 활동 알림 (로그인된 사용자)
if current_user.is_authenticated:
# 정적 리소스 및 불필요한 경로 제외
if request.endpoint == 'static':
return
# 제외할 확장자/경로 (필요 시 추가)
ignored_exts = ('.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2')
if request.path.lower().endswith(ignored_exts):
return
# 활동 내용 구성
msg = (
f"👣 <b>활동 감지</b>\n"
f"👤 <code>{current_user.username}</code>\n"
f"📍 <code>{request.method} {request.path}</code>"
)
_notify(msg, category="activity")
# ─────────────────────────────────────────────────────────────
@@ -97,21 +259,54 @@ def register():
current_app.logger.info("REGISTER: dup username %s", form.username.data)
return render_template("register.html", form=form)
user = User(username=form.username.data, email=form.email.data, is_active=False)
user.set_password(form.password.data) # passlib: 기본 Argon2id
db.session.add(user)
db.session.commit()
_notify(
f"🆕 <b>신규 가입 요청</b>\n"
f"📛 사용자: <code>{user.username}</code>\n"
f"📧 이메일: <code>{user.email}</code>"
# 승인 토큰 생성
approval_token = secrets.token_urlsafe(32)
user = User(
username=form.username.data,
email=form.email.data,
is_active=False,
is_approved=False,
approval_token=approval_token
)
current_app.logger.info("REGISTER: created id=%s email=%s", user.id, user.email)
user.set_password(form.password.data)
try:
db.session.add(user)
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.error("REGISTER: DB commit failed: %s", e)
flash("회원가입 처리 중 오류가 발생했습니다. (DB Error)", "danger")
return render_template("register.html", form=form)
# 텔레그램 알림 (인라인 버튼 포함)
message = (
f"🆕 <b>신규 가입 요청</b>\n\n"
f"<EFBFBD> 사용자: <code>{user.username}</code>\n"
f"📧 이메일: <code>{user.email}</code>\n"
f"🕐 신청시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
buttons = [
("✅ 승인", f"approve_{approval_token}"),
("❌ 거부", f"reject_{approval_token}")
]
_notify_with_buttons(message, buttons, category="auth")
current_app.logger.info("REGISTER: created id=%s email=%s token=%s", user.id, user.email, approval_token[:10])
flash("회원가입이 완료되었습니다. 관리자의 승인을 기다려주세요.", "success")
return redirect(url_for("auth.login"))
else:
if request.method == "POST":
# 폼 검증 실패 에러를 Flash 메시지로 출력
for field_name, errors in form.errors.items():
for error in errors:
# 필드 객체 가져오기 (라벨 텍스트 확인용)
field = getattr(form, field_name, None)
label = field.label.text if field else field_name
flash(f"{label}: {error}", "warning")
current_app.logger.info("REGISTER: form errors=%s", form.errors)
return render_template("register.html", form=form)
@@ -136,24 +331,28 @@ def login():
current_app.logger.info("LOGIN: user not found")
return render_template("login.html", form=form)
pass_ok = user.check_password(form.password.data) # passlib verify(+자동 재해시)
pass_ok = user.check_password(form.password.data)
current_app.logger.info(
"LOGIN: found id=%s active=%s pass_ok=%s",
user.id, user.is_active, pass_ok
"LOGIN: found id=%s active=%s approved=%s pass_ok=%s",
user.id, user.is_active, user.is_approved, pass_ok
)
if not pass_ok:
flash("이메일 또는 비밀번호가 올바르지 않습니다.", "danger")
return render_template("login.html", form=form)
if not user.is_approved:
flash("계정이 아직 승인되지 않았습니다. 관리자의 승인을 기다려주세요.", "warning")
return render_template("login.html", form=form)
if not user.is_active:
flash("계정이 아직 승인되지 않았습니다.", "warning")
flash("계정이 비활성화되었습니다. 관리자에게 문의하세요.", "warning")
return render_template("login.html", form=form)
# 성공
login_user(user, remember=form.remember.data)
session.permanent = True
_notify(f"🔐 <b>로그인 성공</b>\n👤 <code>{user.username}</code>")
_notify(f"🔐 <b>로그인 성공</b>\n👤 <code>{user.username}</code>", category="auth")
current_app.logger.info("LOGIN: SUCCESS → redirect")
nxt = request.args.get("next")
@@ -175,6 +374,7 @@ def login():
def logout():
if current_user.is_authenticated:
current_app.logger.info("LOGOUT: user=%s", current_user.username)
_notify(f"🚪 <b>로그아웃</b>\n👤 <code>{current_user.username}</code>", category="auth")
logout_user()
session["just_logged_out"] = True
flash("정상적으로 로그아웃 되었습니다.", "success")
return redirect(url_for("auth.login"))

View File

@@ -0,0 +1,61 @@
"""
DRM 카탈로그 동기화 라우트
backend/routes/drm_sync.py
"""
from flask import Blueprint, request, jsonify
from backend.services.drm_catalog_sync import sync_from_drm, check_drm_repository
drm_sync_bp = Blueprint('drm_sync', __name__, url_prefix='/drm')
@drm_sync_bp.route('/check', methods=['POST'])
def check_repository():
"""DRM 리포지토리 상태 확인"""
try:
data = request.get_json() or {}
repository_path = data.get('repository_path')
if not repository_path:
return jsonify({
'success': False,
'message': 'repository_path가 필요합니다'
}), 400
info = check_drm_repository(repository_path)
return jsonify({
'success': info.get('exists', False),
'info': info
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
}), 500
@drm_sync_bp.route('/sync', methods=['POST'])
def sync_repository():
"""DRM 리포지토리에서 펌웨어 동기화"""
try:
data = request.get_json() or {}
repository_path = data.get('repository_path')
model = data.get('model', 'PowerEdge R750')
if not repository_path:
return jsonify({
'success': False,
'message': 'repository_path가 필요합니다'
}), 400
result = sync_from_drm(repository_path, model)
return jsonify(result)
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
}), 500

View File

@@ -835,26 +835,37 @@ def delete_firmware_version(version_id):
"""펌웨어 버전 정보 삭제"""
try:
version = FirmwareVersion.query.get(version_id)
if not version:
from flask import current_app
current_app.logger.warning(f"Firmware version not found: {version_id}")
return jsonify({
'success': False,
'message': '버전 정보를 찾을 수 없습니다'
})
'message': '펌웨어 버전을 찾을 수 없습니다'
}), 404
version.is_active = False
component_name = version.component_name
version_number = version.latest_version
db.session.delete(version)
db.session.commit()
from flask import current_app
current_app.logger.info(f"Firmware version deleted: {component_name} v{version_number} (ID: {version_id})")
return jsonify({
'success': True,
'message': f'{version.component_name} 버전 정보 삭제 완료'
'message': f'{component_name} 버전 정보 삭제되었습니다'
})
except Exception as e:
from flask import current_app
current_app.logger.error(f"Error deleting firmware version {version_id}: {str(e)}")
db.session.rollback()
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
'message': f'삭제 실패: {str(e)}'
}), 500
@idrac_bp.route('/api/servers/<int:server_id>/firmware/compare', methods=['GET'])
def compare_server_firmware(server_id):
@@ -962,6 +973,13 @@ def compare_multi_servers_firmware():
for server_id in server_ids:
server = IdracServer.query.get(server_id)
if not server:
results.append({
'server_id': server_id,
'server_name': 'Unknown',
'server_ip': '',
'success': False,
'message': '서버를 찾을 수 없습니다'
})
continue
try:
@@ -971,44 +989,47 @@ def compare_multi_servers_firmware():
latest_versions = FirmwareVersion.query.filter_by(is_active=True).all()
outdated_count = 0
outdated_items = []
comparisons = []
for current_fw in current_inventory:
component_name = current_fw['Name']
current_version = current_fw['Version']
# 최신 버전 찾기
latest = None
for lv in latest_versions:
if lv.component_name.lower() in component_name.lower():
comparison = FirmwareComparisonResult(
component_name=component_name,
current_version=current_version,
latest_version=lv.latest_version
)
if comparison.status == 'outdated':
outdated_count += 1
outdated_items.append({
'component': component_name,
'current': current_version,
'latest': lv.latest_version
})
break
# 서버 모델 확인
if not lv.server_model or lv.server_model == server.model:
latest = lv
break
# 비교 결과 생성
comparison = FirmwareComparisonResult(
component_name=component_name,
current_version=current_version,
latest_version=latest.latest_version if latest else None
)
comparisons.append(comparison.to_dict())
results.append({
'server_id': server.id,
'server_name': server.name,
'outdated_count': outdated_count,
'outdated_items': outdated_items,
'status': 'needs_update' if outdated_count > 0 else 'up_to_date'
'server_ip': server.ip_address,
'success': True,
'comparisons': comparisons,
'message': f'{len(comparisons)}개 컴포넌트 비교 완료'
})
except Exception as e:
current_app.logger.error(f"Error comparing firmware for server {server.id}: {str(e)}")
results.append({
'server_id': server.id,
'server_name': server.name,
'error': str(e)
'server_ip': server.ip_address,
'success': False,
'message': f'비교 실패: {str(e)}'
})
return jsonify({
@@ -1017,6 +1038,7 @@ def compare_multi_servers_firmware():
})
except Exception as e:
current_app.logger.error(f"Error in compare_multi_servers_firmware: {str(e)}")
return jsonify({
'success': False,
'message': f'오류: {str(e)}'

View File

@@ -47,11 +47,43 @@ def index():
info_dir = Path(Config.IDRAC_INFO_FOLDER)
backup_dir = Path(Config.BACKUP_FOLDER)
scripts = [f.name for f in script_dir.glob("*") if f.is_file() and f.name != ".env"]
scripts = natsorted(scripts)
# 1. 스크립트 목록 조회 및 카테고리 분류
all_scripts = [f.name for f in script_dir.glob("*") if f.is_file() and f.name != ".env"]
all_scripts = natsorted(all_scripts)
grouped_scripts = {}
for script in all_scripts:
upper = script.upper()
category = "General"
if upper.startswith("GPU"):
category = "GPU"
elif upper.startswith("LOM"):
category = "LOM"
elif upper.startswith("TYPE") or upper.startswith("XE"):
category = "Server Models"
elif "MAC" in upper:
category = "MAC Info"
elif "GUID" in upper:
category = "GUID Info"
elif "SET_" in upper or "CONFIG" in upper:
category = "Configuration"
if category not in grouped_scripts:
grouped_scripts[category] = []
grouped_scripts[category].append(script)
# 카테고리 정렬 (General은 마지막에)
sorted_categories = sorted(grouped_scripts.keys())
if "General" in sorted_categories:
sorted_categories.remove("General")
sorted_categories.append("General")
grouped_scripts_sorted = {k: grouped_scripts[k] for k in sorted_categories}
# 2. XML 파일 목록
xml_files = [f.name for f in xml_dir.glob("*.xml")]
# 페이지네이션
# 3. 페이지네이션 및 파일 목록
page = int(request.args.get("page", 1))
info_files = [f.name for f in info_dir.glob("*") if f.is_file()]
info_files = natsorted(info_files)
@@ -62,11 +94,10 @@ def index():
total_pages = (len(info_files) + Config.FILES_PER_PAGE - 1) // Config.FILES_PER_PAGE
# ✅ 추가: 10개 단위로 표시될 페이지 범위 계산
start_page = ((page - 1) // 10) * 10 + 1
end_page = min(start_page + 9, total_pages)
# 백업 폴더 목록 (디렉터리만)
# 4. 백업 폴더 목록
backup_dirs = [d for d in backup_dir.iterdir() if d.is_dir()]
backup_dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
@@ -91,7 +122,8 @@ def index():
backup_files=backup_files,
total_backup_pages=total_backup_pages,
backup_page=backup_page,
scripts=scripts,
scripts=all_scripts, # 기존 리스트 호환
grouped_scripts=grouped_scripts_sorted, # 카테고리별 분류
xml_files=xml_files,
)
@@ -175,13 +207,13 @@ def delete_file(filename: str):
if file_path.exists():
try:
file_path.unlink()
flash(f"{filename} 삭제됨.")
flash(f"'{filename}' 파일이 삭제되었습니다.", "success")
logging.info(f"파일 삭제됨: {filename}")
except Exception as e:
logging.error(f"파일 삭제 오류: {e}")
flash("파일 삭제 중 오류가 발생했습니다.", "danger")
else:
flash("파일이 존재하지 않습니다.")
flash("파일이 존재하지 않습니다.", "warning")
return redirect(url_for("main.index"))

View File

@@ -0,0 +1,166 @@
from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for
from flask_login import login_required, current_user
import logging
import difflib
from pathlib import Path
from config import Config
from backend.services.redfish_client import RedfishClient
from backend.routes.xml import sanitize_preserve_unicode
scp_bp = Blueprint("scp", __name__)
logger = logging.getLogger(__name__)
@scp_bp.route("/scp/diff", methods=["GET"])
@login_required
def diff_scp():
"""
두 XML 파일의 차이점을 비교하여 보여줍니다.
"""
file1_name = request.args.get("file1")
file2_name = request.args.get("file2")
if not file1_name or not file2_name:
flash("비교할 두 파일을 선택해주세요.", "warning")
return redirect(url_for("xml.xml_management"))
try:
file1_path = Path(Config.XML_FOLDER) / sanitize_preserve_unicode(file1_name)
file2_path = Path(Config.XML_FOLDER) / sanitize_preserve_unicode(file2_name)
if not file1_path.exists() or not file2_path.exists():
flash("파일을 찾을 수 없습니다.", "danger")
return redirect(url_for("xml.xml_management"))
# 파일 내용 읽기 (LF로 통일)
# 파일 내용 읽기 (LF로 통일)
# Monaco Editor에 원본 텍스트를 그대로 전달하기 위해 splitlines() 제거
# 파일 내용 읽기 (LF로 통일)
logger.info(f"Reading file1: {file1_path}")
content1 = file1_path.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
logger.info(f"Reading file2: {file2_path}")
content2 = file2_path.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
logger.info(f"Content1 length: {len(content1)}, Content2 length: {len(content2)}")
return render_template("scp_diff.html",
file1=file1_name,
file2=file2_name,
content1=content1,
content2=content2)
except Exception as e:
logger.error(f"Diff error: {e}")
flash(f"비교 중 오류가 발생했습니다: {str(e)}", "danger")
return redirect(url_for("xml.xml_management"))
@scp_bp.route("/scp/content/<path:filename>")
@login_required
def get_scp_content(filename):
"""
XML 파일 내용을 반환하는 API (Monaco Editor용)
"""
try:
safe_name = sanitize_preserve_unicode(filename)
path = Path(Config.XML_FOLDER) / safe_name
if not path.exists():
return "File not found", 404
# 텍스트로 읽어서 반환
content = path.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}
except Exception as e:
logger.error(f"Content read error: {e}")
return str(e), 500
@scp_bp.route("/scp/export", methods=["POST"])
@login_required
def export_scp():
"""
iDRAC에서 설정을 내보내기 (Export)
네트워크 공유 설정이 필요합니다.
"""
data = request.form
target_ip = data.get("target_ip")
username = data.get("username")
password = data.get("password")
# Share Parameters
share_ip = data.get("share_ip")
share_name = data.get("share_name")
share_user = data.get("share_user")
share_pwd = data.get("share_pwd")
filename = data.get("filename")
if not all([target_ip, username, password, share_ip, share_name, filename]):
flash("필수 정보가 누락되었습니다.", "warning")
return redirect(url_for("xml.xml_management"))
share_params = {
"IPAddress": share_ip,
"ShareName": share_name,
"FileName": filename,
"ShareType": "CIFS", # 기본값 CIFS
"UserName": share_user,
"Password": share_pwd
}
try:
with RedfishClient(target_ip, username, password) as client:
job_id = client.export_system_configuration(share_params)
if job_id:
flash(f"내보내기 작업이 시작되었습니다. Job ID: {job_id}", "success")
else:
flash("작업을 시작했으나 Job ID를 받지 못했습니다.", "warning")
except Exception as e:
logger.error(f"Export failed: {e}")
flash(f"내보내기 실패: {str(e)}", "danger")
return redirect(url_for("xml.xml_management"))
@scp_bp.route("/scp/import", methods=["POST"])
@login_required
def import_scp():
"""
iDRAC로 설정 가져오기 (Import/Deploy)
"""
data = request.form
target_ip = data.get("target_ip")
username = data.get("username")
password = data.get("password")
# Share Parameters
share_ip = data.get("share_ip")
share_name = data.get("share_name")
share_user = data.get("share_user")
share_pwd = data.get("share_pwd")
filename = data.get("filename")
import_mode = data.get("import_mode", "Replace")
if not all([target_ip, username, password, share_ip, share_name, filename]):
flash("필수 정보가 누락되었습니다.", "warning")
return redirect(url_for("xml.xml_management"))
share_params = {
"IPAddress": share_ip,
"ShareName": share_name,
"FileName": filename,
"ShareType": "CIFS",
"UserName": share_user,
"Password": share_pwd
}
try:
with RedfishClient(target_ip, username, password) as client:
job_id = client.import_system_configuration(share_params, import_mode=import_mode)
if job_id:
flash(f"설정 적용(Import) 작업이 시작되었습니다. Job ID: {job_id}", "success")
else:
flash("작업을 시작했으나 Job ID를 받지 못했습니다.", "warning")
except Exception as e:
logger.error(f"Import failed: {e}")
flash(f"설정 적용 실패: {str(e)}", "danger")
return redirect(url_for("xml.xml_management"))

View File

@@ -290,13 +290,83 @@ def update_gpu_list():
return redirect(url_for("main.index"))
@utils_bp.route("/download_excel")
@login_required
def download_excel():
path = Path(Config.SERVER_LIST_FOLDER) / "mac_info.xlsx"
if not path.is_file():
flash("엑셀 파일을 찾을 수 없습니다.", "danger")
return redirect(url_for("main.index"))
logging.info(f"엑셀 파일 다운로드: {path}")
return send_file(str(path), as_attachment=True, download_name="mac_info.xlsx")
return send_file(str(path), as_attachment=True, download_name="mac_info.xlsx")
@utils_bp.route("/scan_network", methods=["POST"])
@login_required
def scan_network():
"""
지정된 IP 범위(Start ~ End)에 대해 Ping 테스트를 수행하고
응답이 있는 IP 목록을 반환합니다.
"""
try:
import ipaddress
import platform
import concurrent.futures
data = request.get_json(force=True, silent=True) or {}
start_ip_str = data.get('start_ip')
end_ip_str = data.get('end_ip')
if not start_ip_str or not end_ip_str:
return jsonify({"success": False, "error": "시작 IP와 종료 IP를 모두 입력해주세요."}), 400
try:
start_ip = ipaddress.IPv4Address(start_ip_str)
end_ip = ipaddress.IPv4Address(end_ip_str)
if start_ip > end_ip:
return jsonify({"success": False, "error": "시작 IP가 종료 IP보다 큽니다."}), 400
# IP 개수 제한 (너무 많은 스캔 방지, 예: C클래스 2개 분량 512개)
if int(end_ip) - int(start_ip) > 512:
return jsonify({"success": False, "error": "스캔 범위가 너무 넓습니다. (최대 512개)"}), 400
except ValueError:
return jsonify({"success": False, "error": "유효하지 않은 IP 주소 형식입니다."}), 400
# Ping 함수 정의
def ping_ip(ip_obj):
ip = str(ip_obj)
param = '-n' if platform.system().lower() == 'windows' else '-c'
timeout_param = '-w' if platform.system().lower() == 'windows' else '-W'
# Windows: -w 200 (ms), Linux: -W 1 (s)
timeout_val = '200' if platform.system().lower() == 'windows' else '1'
command = ['ping', param, '1', timeout_param, timeout_val, ip]
try:
# shell=False로 보안 강화, stdout/stderr 무시
res = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return ip if res.returncode == 0 else None
except Exception:
return None
active_ips = []
# IP 리스트 생성
target_ips = []
temp_ip = start_ip
while temp_ip <= end_ip:
target_ips.append(temp_ip)
temp_ip += 1
# 병렬 처리 (최대 50 쓰레드)
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
results = executor.map(ping_ip, target_ips)
# 결과 수집 (None 제외)
active_ips = [ip for ip in results if ip is not None]
return jsonify({
"success": True,
"active_ips": active_ips,
"count": len(active_ips),
"message": f"스캔 완료: {len(active_ips)}개의 활성 IP 발견"
})
except Exception as e:
logging.error(f"Scan network fatal error: {e}")
return jsonify({"success": False, "error": f"서버 내부 오류: {str(e)}"}), 500

Binary file not shown.

View File

@@ -0,0 +1,353 @@
"""
Dell 펌웨어 카탈로그 대안 방법들
backend/services/dell_catalog_alternatives.py
Dell의 공식 Catalog.xml이 차단된 경우 사용할 수 있는 대안들
"""
import requests
from typing import List, Dict, Optional
from backend.models.firmware_version import FirmwareVersion, db
from datetime import datetime
class DellFirmwareCatalogAlternatives:
"""Dell 펌웨어 정보를 가져오는 대안 방법들"""
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
# ========================================
# 방법 1: Dell Support API (공식 API)
# ========================================
def fetch_from_dell_support_api(self, model: str = "PowerEdge R750") -> List[Dict]:
"""
Dell Support API를 통한 펌웨어 정보 조회
Dell의 공식 Support API 엔드포인트:
https://www.dell.com/support/home/api/
참고: API 키가 필요할 수 있음
"""
try:
# Dell Support API 엔드포인트 (예시)
# 실제 API는 Dell 개발자 포털에서 확인 필요
url = f"https://www.dell.com/support/home/api/products/{model}/drivers"
response = self.session.get(url, timeout=30)
if response.status_code == 200:
data = response.json()
return self._parse_support_api_response(data, model)
else:
print(f"Dell Support API 오류: {response.status_code}")
return []
except Exception as e:
print(f"Dell Support API 조회 실패: {str(e)}")
return []
# ========================================
# 방법 2: Dell TechDirect (파트너 전용)
# ========================================
def fetch_from_techdirect(self, model: str = "PowerEdge R750") -> List[Dict]:
"""
Dell TechDirect API를 통한 펌웨어 정보 조회
TechDirect는 Dell 파트너용 플랫폼
API 키 필요: https://techdirect.dell.com/
"""
# TechDirect API 구현
# 실제 사용 시 API 키 필요
pass
# ========================================
# 방법 3: 로컬 카탈로그 파일 사용
# ========================================
def load_from_local_catalog(self, catalog_path: str) -> List[Dict]:
"""
로컬에 저장된 Catalog.xml 파일 사용
사용법:
1. Dell 공식 사이트에서 수동으로 Catalog.xml 다운로드
2. 로컬에 저장
3. 이 함수로 파싱
Args:
catalog_path: 로컬 Catalog.xml 파일 경로
"""
try:
from xml.etree import ElementTree as ET
with open(catalog_path, 'r', encoding='utf-8') as f:
content = f.read()
root = ET.fromstring(content)
return self._parse_catalog_xml(root)
except Exception as e:
print(f"로컬 카탈로그 파일 로드 실패: {str(e)}")
return []
# ========================================
# 방법 4: iDRAC에서 직접 조회
# ========================================
def fetch_from_idrac(self, idrac_ip: str, username: str, password: str) -> List[Dict]:
"""
iDRAC Redfish API를 통해 사용 가능한 업데이트 조회
iDRAC는 Dell Update Service를 통해 사용 가능한 업데이트를 확인할 수 있음
장점:
- 실제 서버에 맞는 정확한 펌웨어 정보
- 외부 카탈로그 불필요
단점:
- 각 서버마다 조회 필요
- 네트워크 연결 필요
"""
try:
from backend.services.idrac_redfish_client import DellRedfishClient
client = DellRedfishClient(idrac_ip, username, password)
# UpdateService에서 사용 가능한 업데이트 조회
url = f"https://{idrac_ip}/redfish/v1/UpdateService"
response = client.session.get(url, timeout=30)
if response.status_code == 200:
data = response.json()
# UpdateService의 Actions 확인
# SimpleUpdate 또는 CheckForUpdate 액션 사용
return self._parse_idrac_updates(data)
return []
except Exception as e:
print(f"iDRAC 업데이트 조회 실패: {str(e)}")
return []
# ========================================
# 방법 5: 수동 입력 (가장 간단하고 확실)
# ========================================
def create_manual_catalog(self) -> List[Dict]:
"""
수동으로 작성한 펌웨어 버전 정보
Dell 공식 사이트에서 확인한 최신 버전을 수동으로 입력
https://www.dell.com/support/
장점:
- 가장 확실하고 안정적
- 외부 의존성 없음
- 검증된 버전만 사용
단점:
- 수동 업데이트 필요
"""
manual_catalog = [
{
'component_name': 'BIOS',
'latest_version': '2.15.2',
'server_model': 'PowerEdge R750',
'vendor': 'Dell',
'release_date': '2024-03-15',
'download_url': 'https://www.dell.com/support/home/drivers/driversdetails?driverid=...',
'notes': 'PowerEdge R750 BIOS - 2024년 3월 릴리즈',
'is_critical': False
},
{
'component_name': 'iDRAC',
'latest_version': '7.00.00.00',
'server_model': None, # 모든 모델
'vendor': 'Dell',
'release_date': '2024-02-20',
'download_url': 'https://www.dell.com/support/home/drivers/driversdetails?driverid=...',
'notes': 'iDRAC9 최신 펌웨어 (14G/15G/16G 공용)',
'is_critical': True
},
{
'component_name': 'PERC H755',
'latest_version': '25.5.9.0001',
'server_model': 'PowerEdge R750',
'vendor': 'Dell',
'release_date': '2024-01-10',
'notes': 'PERC H755 RAID 컨트롤러',
'is_critical': False
},
# 더 많은 컴포넌트 추가...
]
return manual_catalog
# ========================================
# 방법 6: Dell Repository Manager (DRM)
# ========================================
def fetch_from_drm_export(self, drm_export_path: str) -> List[Dict]:
"""
Dell Repository Manager (DRM)에서 내보낸 데이터 사용
DRM은 Dell의 공식 펌웨어 관리 도구
https://www.dell.com/support/kbdoc/en-us/000177083/
사용법:
1. DRM 설치 및 실행
2. 필요한 서버 모델 선택
3. 카탈로그 내보내기
4. 내보낸 파일을 이 함수로 파싱
"""
try:
# DRM 내보내기 파일 파싱 로직
# XML 또는 CSV 형식일 수 있음
pass
except Exception as e:
print(f"DRM 내보내기 파일 로드 실패: {str(e)}")
return []
# ========================================
# 헬퍼 함수들
# ========================================
def _parse_support_api_response(self, data: Dict, model: str) -> List[Dict]:
"""Dell Support API 응답 파싱"""
# API 응답 구조에 따라 구현
return []
def _parse_catalog_xml(self, root) -> List[Dict]:
"""Catalog.xml 파싱"""
from xml.etree import ElementTree as ET
firmware_list = []
for pkg in root.findall(".//SoftwareComponent"):
name = pkg.findtext("Name")
version = pkg.findtext("Version")
release_date = pkg.findtext("ReleaseDate")
path = pkg.findtext("path")
if name and version:
firmware_list.append({
'component_name': name,
'latest_version': version,
'release_date': release_date,
'download_url': f"https://downloads.dell.com/{path}" if path else None,
'vendor': 'Dell'
})
return firmware_list
def _parse_idrac_updates(self, data: Dict) -> List[Dict]:
"""iDRAC UpdateService 응답 파싱"""
# UpdateService 데이터 파싱
return []
# ========================================
# DB에 저장
# ========================================
def save_to_database(self, firmware_list: List[Dict]) -> int:
"""
펌웨어 목록을 데이터베이스에 저장
Returns:
저장된 항목 수
"""
count = 0
for fw in firmware_list:
# 중복 확인
existing = FirmwareVersion.query.filter_by(
component_name=fw.get('component_name'),
latest_version=fw.get('latest_version'),
server_model=fw.get('server_model')
).first()
if not existing:
version = FirmwareVersion(
component_name=fw.get('component_name'),
latest_version=fw.get('latest_version'),
server_model=fw.get('server_model'),
vendor=fw.get('vendor', 'Dell'),
release_date=fw.get('release_date'),
download_url=fw.get('download_url'),
notes=fw.get('notes'),
is_critical=fw.get('is_critical', False)
)
db.session.add(version)
count += 1
db.session.commit()
return count
# ========================================
# 사용 예시
# ========================================
def sync_firmware_alternative(method: str = 'manual', **kwargs) -> Dict:
"""
대안 방법으로 펌웨어 정보 동기화
Args:
method: 'manual', 'local_catalog', 'idrac', 'support_api' 중 선택
**kwargs: 각 방법에 필요한 추가 인자
Returns:
{'success': bool, 'count': int, 'message': str}
"""
catalog = DellFirmwareCatalogAlternatives()
firmware_list = []
try:
if method == 'manual':
# 가장 권장: 수동으로 작성한 카탈로그
firmware_list = catalog.create_manual_catalog()
elif method == 'local_catalog':
# 로컬 Catalog.xml 파일 사용
catalog_path = kwargs.get('catalog_path')
if not catalog_path:
return {'success': False, 'count': 0, 'message': 'catalog_path 필요'}
firmware_list = catalog.load_from_local_catalog(catalog_path)
elif method == 'idrac':
# iDRAC에서 직접 조회
idrac_ip = kwargs.get('idrac_ip')
username = kwargs.get('username')
password = kwargs.get('password')
if not all([idrac_ip, username, password]):
return {'success': False, 'count': 0, 'message': 'iDRAC 정보 필요'}
firmware_list = catalog.fetch_from_idrac(idrac_ip, username, password)
elif method == 'support_api':
# Dell Support API 사용
model = kwargs.get('model', 'PowerEdge R750')
firmware_list = catalog.fetch_from_dell_support_api(model)
else:
return {'success': False, 'count': 0, 'message': f'알 수 없는 방법: {method}'}
# DB에 저장
count = catalog.save_to_database(firmware_list)
return {
'success': True,
'count': count,
'message': f'{count}개 펌웨어 정보 동기화 완료'
}
except Exception as e:
return {
'success': False,
'count': 0,
'message': f'오류: {str(e)}'
}

View File

@@ -1,58 +1,178 @@
import requests
from xml.etree import ElementTree as ET
from backend.models.firmware_version import FirmwareVersion, db
from datetime import datetime
def get_manual_firmware_catalog(model="PowerEdge R750"):
"""
수동으로 작성한 펌웨어 카탈로그
Dell 공식 사이트에서 확인한 최신 버전 정보
https://www.dell.com/support/
Dell Catalog.xml이 차단된 경우 이 데이터 사용
"""
# 2024년 11월 기준 최신 버전 (정기적으로 업데이트 필요)
catalog = {
"PowerEdge R750": [
{
'component_name': 'BIOS',
'latest_version': '2.15.2',
'release_date': '2024-03-15',
'notes': 'PowerEdge R750 BIOS - 보안 업데이트 포함',
'is_critical': False
},
{
'component_name': 'iDRAC',
'latest_version': '7.00.00.00',
'release_date': '2024-02-20',
'notes': 'iDRAC9 최신 펌웨어',
'is_critical': True
},
{
'component_name': 'PERC H755',
'latest_version': '25.5.9.0001',
'release_date': '2024-01-10',
'notes': 'PERC H755 RAID 컨트롤러',
'is_critical': False
},
{
'component_name': 'CPLD',
'latest_version': '1.0.6',
'release_date': '2023-12-15',
'notes': '시스템 보드 CPLD',
'is_critical': False
},
],
"PowerEdge R640": [
{
'component_name': 'BIOS',
'latest_version': '2.19.2',
'release_date': '2024-02-01',
'notes': 'PowerEdge R640 BIOS',
'is_critical': False
},
{
'component_name': 'iDRAC',
'latest_version': '7.00.00.00',
'release_date': '2024-02-20',
'notes': 'iDRAC9 최신 펌웨어',
'is_critical': True
},
]
}
return catalog.get(model, [])
def sync_dell_catalog(model="PowerEdge R750"):
"""
Dell 공식 Catalog.xml에서 지정된 모델(model)에 해당하는 펌웨어 정보만 추출 후 DB 저장.
Dell 펌웨어 정보 동기화
1차: Dell 공식 Catalog.xml 시도
2차: 수동 카탈로그 사용 (Fallback)
"""
url = "https://downloads.dell.com/catalog/Catalog.xml"
print(f"[INFO] Dell Catalog 다운로드 중... ({url})")
response = requests.get(url, timeout=60)
response.raise_for_status()
root = ET.fromstring(response.content)
count = 0
# 1차 시도: Dell 공식 Catalog.xml
try:
url = "https://downloads.dell.com/catalog/Catalog.xml"
print(f"[INFO] Dell Catalog 다운로드 시도... ({url})")
response = requests.get(url, timeout=30)
response.raise_for_status()
root = ET.fromstring(response.content)
for pkg in root.findall(".//SoftwareComponent"):
# 이 컴포넌트가 지정된 모델에 해당하는지 확인
supported = [
sys.text.strip()
for sys in pkg.findall(".//SupportedSystems/Brand/Model/Display")
if sys.text
]
if model not in supported:
continue
for pkg in root.findall(".//SoftwareComponent"):
# 이 컴포넌트가 지정된 모델에 해당하는지 확인
supported = [
sys.text.strip()
for sys in pkg.findall(".//SupportedSystems/Brand/Model/Display")
if sys.text
]
if model not in supported:
continue
name = pkg.findtext("Name")
version = pkg.findtext("Version")
release_date = pkg.findtext("ReleaseDate")
path = pkg.findtext("path")
vendor = "Dell"
name = pkg.findtext("Name")
version = pkg.findtext("Version")
release_date = pkg.findtext("ReleaseDate")
path = pkg.findtext("path")
vendor = "Dell"
if not name or not version:
continue
if not name or not version:
continue
# 중복 방지
existing = FirmwareVersion.query.filter_by(
component_name=name,
latest_version=version,
server_model=model
).first()
# 중복 방지
existing = FirmwareVersion.query.filter_by(
component_name=name,
latest_version=version,
server_model=model
).first()
if not existing:
db.session.add(
FirmwareVersion(
component_name=name,
latest_version=version,
release_date=release_date,
vendor=vendor,
server_model=model,
download_url=f"https://downloads.dell.com/{path}",
if not existing:
db.session.add(
FirmwareVersion(
component_name=name,
latest_version=version,
release_date=release_date,
vendor=vendor,
server_model=model,
download_url=f"https://downloads.dell.com/{path}",
)
)
)
count += 1
count += 1
db.session.commit()
print(f"{model} 관련 펌웨어 {count}개 동기화 완료 (Dell Catalog)")
return count
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"[경고] Dell Catalog.xml 접근 불가 (404). 수동 카탈로그 사용...")
else:
print(f"[경고] Dell Catalog 다운로드 실패: {e}. 수동 카탈로그 사용...")
except Exception as e:
print(f"[경고] Dell Catalog 처리 중 오류: {e}. 수동 카탈로그 사용...")
# 2차 시도: 수동 카탈로그 사용
try:
print(f"[INFO] 수동 카탈로그에서 {model} 펌웨어 정보 로드 중...")
manual_catalog = get_manual_firmware_catalog(model)
if not manual_catalog:
print(f"[경고] {model}에 대한 수동 카탈로그 데이터가 없습니다")
return 0
for fw in manual_catalog:
# 중복 방지
existing = FirmwareVersion.query.filter_by(
component_name=fw['component_name'],
latest_version=fw['latest_version'],
server_model=model
).first()
if not existing:
db.session.add(
FirmwareVersion(
component_name=fw['component_name'],
latest_version=fw['latest_version'],
release_date=fw.get('release_date'),
vendor='Dell',
server_model=model,
notes=fw.get('notes'),
is_critical=fw.get('is_critical', False)
)
)
count += 1
db.session.commit()
print(f"{model} 관련 펌웨어 {count}개 동기화 완료 (수동 카탈로그)")
return count
except Exception as e:
print(f"[오류] 수동 카탈로그 처리 실패: {e}")
db.session.rollback()
return 0
db.session.commit()
print(f"{model} 관련 펌웨어 {count}개 동기화 완료")
return count

View File

@@ -0,0 +1,308 @@
"""
Dell Repository Manager (DRM) 연동 모듈
backend/services/drm_catalog_sync.py
Dell Repository Manager에서 생성한 로컬 리포지토리 카탈로그를 파싱하여
펌웨어 정보를 가져오는 기능
"""
import os
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import List, Dict, Optional
from backend.models.firmware_version import FirmwareVersion, db
from datetime import datetime
class DRMCatalogSync:
"""Dell Repository Manager 카탈로그 동기화"""
def __init__(self, drm_repository_path: str = None):
"""
Args:
drm_repository_path: DRM 리포지토리 경로
예: C:/Dell/Repository 또는 네트워크 경로
"""
self.repository_path = drm_repository_path
self.catalog_file = None
if drm_repository_path:
# DRM은 보통 catalog 폴더에 XML 파일 생성
possible_catalogs = [
os.path.join(drm_repository_path, 'catalog', 'Catalog.xml'),
os.path.join(drm_repository_path, 'Catalog.xml'),
os.path.join(drm_repository_path, 'catalog.xml'),
]
for catalog_path in possible_catalogs:
if os.path.exists(catalog_path):
self.catalog_file = catalog_path
print(f"[INFO] DRM 카탈로그 파일 발견: {catalog_path}")
break
def sync_from_drm_repository(self, model: str = "PowerEdge R750") -> int:
"""
DRM 리포지토리에서 펌웨어 정보 동기화
Args:
model: 서버 모델명
Returns:
동기화된 펌웨어 개수
"""
if not self.catalog_file or not os.path.exists(self.catalog_file):
print(f"[오류] DRM 카탈로그 파일을 찾을 수 없습니다: {self.repository_path}")
return 0
try:
print(f"[INFO] DRM 카탈로그 파싱 중: {self.catalog_file}")
# XML 파싱
tree = ET.parse(self.catalog_file)
root = tree.getroot()
count = 0
# DRM 카탈로그 구조 파싱
for pkg in root.findall(".//SoftwareComponent"):
# 모델 필터링
supported_models = self._get_supported_models(pkg)
if model and model not in supported_models:
continue
# 펌웨어 정보 추출
firmware_info = self._extract_firmware_info(pkg, model)
if firmware_info:
# DB에 저장
if self._save_firmware_to_db(firmware_info):
count += 1
print(f"✓ DRM에서 {model} 관련 펌웨어 {count}개 동기화 완료")
return count
except Exception as e:
print(f"[오류] DRM 카탈로그 동기화 실패: {str(e)}")
import traceback
traceback.print_exc()
return 0
def _get_supported_models(self, pkg_element) -> List[str]:
"""패키지가 지원하는 서버 모델 목록 추출"""
models = []
# Dell 카탈로그 XML 구조
for display in pkg_element.findall(".//SupportedSystems/Brand/Model/Display"):
if display.text:
models.append(display.text.strip())
return models
def _extract_firmware_info(self, pkg_element, model: str) -> Optional[Dict]:
"""패키지에서 펌웨어 정보 추출"""
try:
name = pkg_element.findtext("Name")
version = pkg_element.findtext("vendorVersion") # DRM은 vendorVersion 사용
if not version:
version = pkg_element.findtext("dellVersion") # 또는 dellVersion
release_date = pkg_element.findtext("dateTime")
path = pkg_element.findtext("path")
category = pkg_element.findtext("Category")
# 중요도 판단
importance = pkg_element.findtext("ImportanceDisplay")
is_critical = importance and "Critical" in importance
# 파일 정보
file_name = None
file_size_mb = None
if path:
file_name = os.path.basename(path)
# 실제 파일이 있으면 크기 확인
if self.repository_path:
full_path = os.path.join(self.repository_path, path)
if os.path.exists(full_path):
file_size_mb = os.path.getsize(full_path) // (1024 * 1024)
if not name or not version:
return None
return {
'component_name': name,
'latest_version': version,
'server_model': model,
'vendor': 'Dell',
'release_date': self._parse_date(release_date),
'download_url': f"https://downloads.dell.com/{path}" if path else None,
'file_name': file_name,
'file_size_mb': file_size_mb,
'component_type': category,
'is_critical': is_critical,
'notes': f"DRM에서 동기화 - {category}" if category else "DRM에서 동기화"
}
except Exception as e:
print(f"[경고] 펌웨어 정보 추출 실패: {str(e)}")
return None
def _parse_date(self, date_str: str) -> Optional[str]:
"""날짜 문자열 파싱"""
if not date_str:
return None
try:
# DRM 날짜 형식: 2024-03-15T10:30:00
dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
return dt.strftime('%Y-%m-%d')
except:
return date_str[:10] if len(date_str) >= 10 else None
def _save_firmware_to_db(self, firmware_info: Dict) -> bool:
"""펌웨어 정보를 DB에 저장"""
try:
# 중복 확인
existing = FirmwareVersion.query.filter_by(
component_name=firmware_info['component_name'],
latest_version=firmware_info['latest_version'],
server_model=firmware_info['server_model']
).first()
if existing:
# 이미 존재하면 업데이트
for key, value in firmware_info.items():
if hasattr(existing, key) and value is not None:
setattr(existing, key, value)
db.session.commit()
return False # 새로 추가된 것은 아님
else:
# 새로 추가
version = FirmwareVersion(**firmware_info)
db.session.add(version)
db.session.commit()
return True
except Exception as e:
print(f"[오류] DB 저장 실패: {str(e)}")
db.session.rollback()
return False
def list_available_models(self) -> List[str]:
"""DRM 리포지토리에서 사용 가능한 모델 목록 조회"""
if not self.catalog_file or not os.path.exists(self.catalog_file):
return []
try:
tree = ET.parse(self.catalog_file)
root = tree.getroot()
models = set()
for display in root.findall(".//SupportedSystems/Brand/Model/Display"):
if display.text:
models.add(display.text.strip())
return sorted(list(models))
except Exception as e:
print(f"[오류] 모델 목록 조회 실패: {str(e)}")
return []
def get_repository_info(self) -> Dict:
"""DRM 리포지토리 정보 조회"""
if not self.catalog_file or not os.path.exists(self.catalog_file):
return {
'exists': False,
'path': self.repository_path,
'catalog_file': None
}
try:
tree = ET.parse(self.catalog_file)
root = tree.getroot()
# 카탈로그 메타데이터
base_location = root.findtext(".//BaseLocation")
release_id = root.findtext(".//ReleaseID")
# 패키지 수 계산
total_packages = len(root.findall(".//SoftwareComponent"))
return {
'exists': True,
'path': self.repository_path,
'catalog_file': self.catalog_file,
'base_location': base_location,
'release_id': release_id,
'total_packages': total_packages,
'available_models': self.list_available_models()
}
except Exception as e:
return {
'exists': True,
'path': self.repository_path,
'catalog_file': self.catalog_file,
'error': str(e)
}
# ========================================
# 편의 함수
# ========================================
def sync_from_drm(repository_path: str, model: str = "PowerEdge R750") -> Dict:
"""
DRM 리포지토리에서 펌웨어 동기화 (간편 함수)
Args:
repository_path: DRM 리포지토리 경로
model: 서버 모델명
Returns:
{'success': bool, 'count': int, 'message': str}
"""
try:
drm = DRMCatalogSync(repository_path)
# 리포지토리 확인
info = drm.get_repository_info()
if not info['exists']:
return {
'success': False,
'count': 0,
'message': f'DRM 리포지토리를 찾을 수 없습니다: {repository_path}'
}
# 동기화
count = drm.sync_from_drm_repository(model)
return {
'success': True,
'count': count,
'message': f'{model} 펌웨어 {count}개 동기화 완료',
'repository_info': info
}
except Exception as e:
return {
'success': False,
'count': 0,
'message': f'DRM 동기화 실패: {str(e)}'
}
def check_drm_repository(repository_path: str) -> Dict:
"""
DRM 리포지토리 상태 확인
Args:
repository_path: DRM 리포지토리 경로
Returns:
리포지토리 정보
"""
drm = DRMCatalogSync(repository_path)
return drm.get_repository_info()

View File

@@ -60,6 +60,19 @@ def setup_logging(app: Optional[object] = None) -> logging.Logger:
# Flask 앱 로거에도 동일 핸들러 바인딩
app.logger.handlers = root.handlers
app.logger.setLevel(root.level)
# 루트 로거로 전파되면 메시지가 두 번 출력되므로 방지
app.logger.propagate = False
# 제3자 라이브러리 로그 레벨 조정 (너무 시끄러운 경우)
# werkzeug: 기본적인 HTTP 요청 로그(GET/POST 등)를 숨김 (WARNING 이상만 표시)
logging.getLogger("werkzeug").setLevel(logging.WARNING)
logging.getLogger("socketio").setLevel(logging.WARNING)
logging.getLogger("engineio").setLevel(logging.WARNING)
# httpx, telegram 라이브러리의 HTTP 요청 로그 숨기기
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logging.getLogger("telegram").setLevel(logging.WARNING)
root.info("Logger initialized | level=%s | file=%s", _DEF_LEVEL, log_path)
return root

View File

@@ -45,18 +45,31 @@ class RedfishClient:
ip: str,
username: str,
password: str,
timeout: int = 15,
verify_ssl: bool = False
timeout: Optional[int] = None,
verify_ssl: Optional[bool] = None
):
self.ip = ip
self.base_url = f"https://{ip}/redfish/v1"
self.host_url = f"https://{ip}" # ← 추가: 호스트 URL
self.timeout = timeout
self.verify_ssl = verify_ssl
self.host_url = f"https://{ip}"
# Config defaults
default_timeout = 15
default_verify = False
try:
from flask import current_app
if current_app:
default_timeout = current_app.config.get("REDFISH_TIMEOUT", 15)
default_verify = current_app.config.get("REDFISH_VERIFY_SSL", False)
except ImportError:
pass
self.timeout = timeout if timeout is not None else default_timeout
self.verify_ssl = verify_ssl if verify_ssl is not None else default_verify
self.session = requests.Session()
self.session.auth = (username, password)
self.session.verify = verify_ssl
self.session.verify = self.verify_ssl
self.session.headers.update({
"Content-Type": "application/json",
"Accept": "application/json"
@@ -214,11 +227,77 @@ class RedfishClient:
"PercentComplete": str(percent),
"Message": message_text,
"ScheduledStartTime": job_data.get("ScheduledStartTime", ""),
"StartTime": job_data.get("StartTime", ""),
"EndTime": job_data.get("EndTime", ""),
"LastUpdateTime": job_data.get("EndTime") or job_data.get("StartTime", ""),
}
def export_system_configuration(self, share_parameters: Dict[str, Any], target: str = "ALL") -> str:
"""
시스템 설정 내보내기 (SCP Export)
:param share_parameters: 네트워크 공유 설정 (IP, ShareName, FileName, UserName, Password 등)
:param target: 내보낼 대상 (ALL, IDRAC, BIOS, NIC, RAID)
:return: Job ID
"""
url = f"{self.host_url}/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/DellManager.ExportSystemConfiguration"
payload = {
"ExportFormat": "XML",
"ShareParameters": share_parameters,
"Target": target
}
logger.info(f"{self.ip}: Exporting system configuration to {share_parameters.get('FileName')}")
response = self.session.post(url, json=payload, verify=False)
response.raise_for_status()
# Job ID 추출 (Location 헤더 또는 응답 본문)
job_id = ""
if response.status_code == 202:
location = response.headers.get("Location")
if location:
job_id = location.split("/")[-1]
if not job_id:
# 응답 본문에서 시도 (일부 펌웨어 버전 대응)
try:
data = response.json()
# 일반적인 JID 포맷 확인 필요
# 여기서는 간단히 헤더 우선으로 처리하고 없으면 에러 처리하거나 데이터 파싱
pass
except:
pass
return job_id
def import_system_configuration(self, share_parameters: Dict[str, Any], import_mode: str = "Replace", target: str = "ALL") -> str:
"""
시스템 설정 가져오기 (SCP Import)
:param share_parameters: 네트워크 공유 설정
:param import_mode: Replace, Append 등
:param target: 가져올 대상
:return: Job ID
"""
url = f"{self.host_url}/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/DellManager.ImportSystemConfiguration"
payload = {
"ImportSystemConfigurationXMLFile": share_parameters.get("FileName"),
"ShareParameters": share_parameters,
"ImportMode": import_mode,
"Target": target,
"ShutdownType": "Graceful" # 적용 후 재부팅 방식
}
logger.info(f"{self.ip}: Importing system configuration from {share_parameters.get('FileName')}")
response = self.session.post(url, json=payload, verify=False)
response.raise_for_status()
job_id = ""
if response.status_code == 202:
location = response.headers.get("Location")
if location:
job_id = location.split("/")[-1]
return job_id
def close(self):
"""세션 종료"""
self.session.close()

View File

@@ -0,0 +1,18 @@
/* Hover and Transition Effects */
.hover-shadow:hover {
transform: translateY(-3px);
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !important;
}
.transition-all {
transition: all 0.3s ease;
}
/* Text Truncation */
.text-truncate-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}

View File

@@ -0,0 +1,55 @@
/* Scrollbar Styles */
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background-color: #888;
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #555;
}
html {
scrollbar-width: thin;
scrollbar-color: #888 #f1f1f1;
}
/* Textarea Styles */
textarea {
width: 100%;
height: 600px;
padding: 10px;
font-size: 14px;
line-height: 1.5;
background-color: #f9f9f9;
border: 1px solid #ccc;
transition: background-color 0.3s ease;
}
textarea:hover {
background-color: #f0f0f0;
}
/* XML List Item Styles */
.xml-list-item {
padding: 10px;
background-color: #ffffff;
transition: background-color 0.3s ease;
}
.xml-list-item:hover {
background-color: #e9ecef;
cursor: pointer;
}
/* Button Styles */
.btn {
margin-top: 20px;
}

View File

@@ -27,7 +27,7 @@ header {
background: white;
padding: 30px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
margin-bottom: 30px;
text-align: center;
}
@@ -48,7 +48,7 @@ header .subtitle {
background: white;
padding: 30px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
margin-bottom: 30px;
}
@@ -290,7 +290,7 @@ header .subtitle {
top: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
@@ -302,7 +302,7 @@ header .subtitle {
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.modal-header {
@@ -423,8 +423,15 @@ header .subtitle {
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
.progress-message {
@@ -531,20 +538,20 @@ footer {
flex-direction: column;
align-items: flex-start;
}
.header-actions {
margin-top: 10px;
width: 100%;
}
.header-actions button {
flex: 1;
}
.bulk-actions {
flex-wrap: wrap;
}
.modal-content {
width: 95%;
}
@@ -556,3 +563,217 @@ input[type="checkbox"] {
height: 18px;
cursor: pointer;
}
/* ========================================
탭 메뉴 스타일
======================================== */
.tab-menu {
display: flex;
gap: 10px;
margin-bottom: 20px;
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.tab-button {
flex: 1;
padding: 12px 24px;
border: 2px solid #ddd;
background: white;
border-radius: 8px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.tab-button.active {
background: #667eea;
color: white;
border-color: #667eea;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.tab-button:hover:not(.active) {
background: #f8f9fa;
border-color: #667eea;
transform: translateY(-2px);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ========================================
토스트 알림 스타일
======================================== */
.toast {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
border-radius: 8px;
color: white;
font-weight: 600;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
transform: translateX(400px);
transition: transform 0.3s ease-out;
z-index: 2000;
max-width: 400px;
word-wrap: break-word;
}
.toast.show {
transform: translateX(0);
}
.toast-success {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
}
.toast-error {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
}
.toast-warning {
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
color: #333;
}
.toast-info {
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
}
/* ========================================
로딩 스피너
======================================== */
.loading-spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid rgba(102, 126, 234, 0.2);
border-radius: 50%;
border-top-color: #667eea;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
#comparison-loading {
text-align: center;
padding: 60px 20px;
}
#comparison-loading p {
margin-top: 20px;
color: #666;
font-size: 1.1em;
}
/* ========================================
비교 결과 개선 스타일
======================================== */
.comparison-grid {
display: grid;
gap: 30px;
margin-top: 20px;
}
.server-comparison-section {
background: #f8f9fa;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.server-comparison-section h3 {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #ddd;
}
.comparison-card {
background: white;
padding: 20px;
border-radius: 10px;
border-left: 4px solid #667eea;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
margin-bottom: 15px;
transition: all 0.3s;
}
.comparison-card:hover {
transform: translateX(5px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
}
.comparison-card.outdated {
border-left-color: #dc3545;
background: linear-gradient(to right, #fff5f5 0%, white 100%);
}
.comparison-card.latest {
border-left-color: #28a745;
background: linear-gradient(to right, #f0fff4 0%, white 100%);
}
.comparison-card.unknown {
border-left-color: #ffc107;
background: linear-gradient(to right, #fffbf0 0%, white 100%);
}
.comparison-card code {
font-family: 'Courier New', monospace;
font-size: 0.95em;
}
/* ========================================
반응형 개선
======================================== */
@media (max-width: 768px) {
.tab-menu {
flex-direction: column;
}
.tab-button {
width: 100%;
}
.toast {
right: 10px;
left: 10px;
max-width: none;
}
.comparison-card {
margin-bottom: 10px;
}
}

View File

@@ -0,0 +1,120 @@
/* ===== 공통 파일 카드 컴팩트 스타일 ===== */
.file-card-compact {
transition: all 0.2s ease;
background: #fff;
min-width: 120px;
max-width: 200px;
}
.file-card-compact:hover {
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.file-card-compact a {
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
/* ===== 목록별 버튼 분리 규칙 ===== */
/* 처리된 파일 목록 전용 컨테이너(보기/삭제 2열) */
.processed-list .file-card-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: .5rem;
}
/* 보기(처리된) */
.processed-list .btn-view-processed {
border-color: #3b82f6;
color: #1d4ed8;
padding: .425rem .6rem;
font-size: .8125rem;
font-weight: 600;
}
.processed-list .btn-view-processed:hover {
background: rgba(59, 130, 246, .08);
}
/* 삭제(처리된) — 더 작게 */
.processed-list .btn-delete-processed {
border-color: #ef4444;
color: #b91c1c;
padding: .3rem .5rem;
font-size: .75rem;
font-weight: 600;
}
.processed-list .btn-delete-processed:hover {
background: rgba(239, 68, 68, .08);
}
/* 백업 파일 목록 전용 컨테이너(단일 버튼) */
.backup-list .file-card-single-button {
display: flex;
margin-top: .25rem;
}
/* 보기(백업) — 강조 색상 */
.backup-list .btn-view-backup {
width: 100%;
border-color: #10b981;
color: #047857;
padding: .45rem .75rem;
font-size: .8125rem;
font-weight: 700;
}
.backup-list .btn-view-backup:hover {
background: rgba(16, 185, 129, .08);
}
/* ===== 백업 파일 날짜 헤더 ===== */
.list-group-item .bg-light {
transition: background-color 0.2s ease;
}
.list-group-item:hover .bg-light {
background-color: #e9ecef !important;
}
/* ===== 진행바 애니메이션 ===== */
.progress {
border-radius: 10px;
overflow: hidden;
}
.progress-bar {
transition: width 0.6s ease;
}
/* ===== 반응형 텍스트 ===== */
@media (max-width: 768px) {
.card-body {
padding: 1.5rem !important;
}
}
/* ===== 스크롤바 스타일링(모달) ===== */
.modal-body pre::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.modal-body pre::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.modal-body pre::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.modal-body pre::-webkit-scrollbar-thumb:hover {
background: #555;
}

View File

@@ -0,0 +1,86 @@
/* Status Dot Styles */
.status-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #6c757d;
}
.status-dot.active {
background-color: #198754;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
/* Table Text Handling */
#jobs-table {
table-layout: fixed;
width: 100%;
}
#jobs-table td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300px;
}
/* Column Width Fixed */
#jobs-table td:nth-child(1) {
max-width: 110px;
}
/* IP */
#jobs-table td:nth-child(2) {
max-width: 160px;
font-size: 0.85rem;
}
/* JID */
#jobs-table td:nth-child(3) {
max-width: 200px;
}
/* 작업명 */
#jobs-table td:nth-child(4) {
max-width: 180px;
}
/* 상태 */
#jobs-table td:nth-child(5) {
max-width: 120px;
}
/* 진행률 */
#jobs-table td:nth-child(6) {
max-width: 300px;
}
/* 메시지 */
#jobs-table td:nth-child(7) {
max-width: 150px;
}
/* 시간 */
/* Hover to Show Full Text */
#jobs-table td:hover {
white-space: normal;
overflow: visible;
position: relative;
z-index: 1000;
background-color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

296
backend/static/css/scp.css Normal file
View File

@@ -0,0 +1,296 @@
/* Custom styles for XML Management (manage_xml.html) */
.main-title {
font-size: 1.8rem;
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
font-size: 0.95rem;
margin-bottom: 30px;
}
.card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
background: white;
}
.card-header-custom {
background-color: #007bff;
color: white;
padding: 12px 20px;
font-weight: 600;
border-radius: 8px 8px 0 0;
font-size: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
padding: 20px;
}
.form-label {
font-weight: 500;
color: #555;
margin-bottom: 8px;
font-size: 0.9rem;
}
.form-control {
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px 12px;
font-size: 0.9rem;
}
.upload-section {
background-color: #f8f9fa;
padding: 15px;
border-radius: 4px;
}
/* File Input Styling */
.custom-file {
position: relative;
display: inline-block;
width: 100%;
margin-bottom: 0;
}
.custom-file-input {
position: relative;
z-index: 2;
width: 100%;
height: calc(1.5em + .75rem + 2px);
margin: 0;
opacity: 0;
}
.custom-file-label {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 1;
height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
border: 1px solid #ced4da;
border-radius: .25rem;
}
.custom-file-label::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 3;
display: block;
height: calc(1.5em + .75rem);
padding: .375rem .75rem;
line-height: 1.5;
color: #495057;
content: "Browse";
background-color: #e9ecef;
border-left: inherit;
border-radius: 0 .25rem .25rem 0;
}
/* 아이콘 + 뱃지 스타일 */
.file-list {
max-height: 600px;
overflow-y: auto;
padding-right: 5px;
/* 스크롤바 공간 확보 */
}
.file-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb:hover {
background: #555;
}
.icon-badge-item {
border: 1px solid #e9ecef;
border-radius: 8px;
/* 둥글게 */
padding: 12px 16px;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.2s ease-in-out;
background: white;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
}
.icon-badge-item:hover {
background-color: #f1f8ff;
/* 아주 연한 파랑 */
border-color: #cce5ff;
transform: translateY(-2px);
/* 살짝 위로 */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.icon-badge-left {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
min-width: 0;
/* 텍스트 말줄임 필수 */
margin-right: 15px;
/* 버튼과 간격 확보 */
}
.select-checkbox {
width: 18px;
height: 18px;
margin-right: 10px;
cursor: pointer;
}
.file-icon-small {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #007bff, #0056b3);
/* 그라데이션 */
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 123, 255, 0.2);
}
.file-name-section {
display: flex;
flex-direction: column;
/* 이름과 뱃지를 위아래로 */
justify-content: center;
min-width: 0;
flex: 1;
}
.file-name-badge {
font-weight: 600;
color: #333;
font-size: 0.95rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
}
.badge-custom {
background-color: #e7f3ff;
color: #007bff;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 600;
display: inline-block;
width: fit-content;
}
.action-buttons {
display: flex;
gap: 8px;
flex-shrink: 0;
/* 절대 줄어들지 않음 */
align-items: center;
}
/* 버튼 스타일 개선 */
.action-buttons .btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 6px 12px;
font-size: 0.85rem;
font-weight: 500;
border-radius: 6px;
transition: all 0.2s;
}
.action-buttons .btn i {
margin-right: 4px;
/* 아이콘과 텍스트 사이 간격 */
}
/* 모바일 대응: 화면이 좁을 땐 텍스트 숨기기 */
@media (max-width: 768px) {
.action-buttons .btn span {
display: none;
}
.action-buttons .btn i {
margin-right: 0;
}
.action-buttons .btn {
padding: 6px 10px;
}
}
.empty-message {
text-align: center;
color: #999;
padding: 40px;
font-size: 1rem;
background: #f8f9fa;
border-radius: 8px;
border: 1px dashed #ddd;
}
/* Diff View Styles (scp_diff.html) */
.diff-container {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
overflow-x: auto;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.9rem;
white-space: pre;
}
.diff-line {
display: block;
}
.diff-add {
background-color: #e6ffec;
color: #24292e;
}
.diff-del {
background-color: #ffebe9;
color: #24292e;
}
.diff-header {
color: #6f42c1;
font-weight: bold;
}

View File

@@ -0,0 +1,51 @@
(function () {
// Bootstrap 5을 사용한다고 가정. data-bs-* 이벤트로 처리.
const changePasswordModal = document.getElementById('changePasswordModal');
const modalUserInfo = document.getElementById('modalUserInfo');
const changePasswordForm = document.getElementById('changePasswordForm');
const newPasswordInput = document.getElementById('newPasswordInput');
const confirmPasswordInput = document.getElementById('confirmPasswordInput');
const pwMismatch = document.getElementById('pwMismatch');
if (!changePasswordModal) return;
changePasswordModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget; // 버튼 that triggered the modal
const userId = button.getAttribute('data-user-id');
const username = button.getAttribute('data-username') || ('ID ' + userId);
// 표시 텍스트 세팅
modalUserInfo.textContent = username + ' (ID: ' + userId + ')';
// 폼 action 동적 설정: admin.reset_password 라우트 기대
// 예: /admin/users/123/reset_password
// Note: This assumes the URL pattern exists. Adjust if needed.
const baseUrl = changePasswordForm.getAttribute('data-base-url') || '/admin/users/0/reset_password';
changePasswordForm.action = baseUrl.replace('/0/', '/' + userId + '/');
// 폼 내부 비밀번호 필드 초기화
newPasswordInput.value = '';
confirmPasswordInput.value = '';
confirmPasswordInput.classList.remove('is-invalid');
pwMismatch.style.display = 'none';
});
// 폼 제출 전 클라이언트에서 비밀번호 일치 검사
changePasswordForm.addEventListener('submit', function (e) {
const a = newPasswordInput.value || '';
const b = confirmPasswordInput.value || '';
if (a.length < 8) {
newPasswordInput.focus();
e.preventDefault();
return;
}
if (a !== b) {
e.preventDefault();
confirmPasswordInput.classList.add('is-invalid');
pwMismatch.style.display = 'block';
confirmPasswordInput.focus();
return;
}
// 제출 허용 (서버측에서도 반드시 검증)
});
})();

View File

@@ -58,6 +58,9 @@ document.addEventListener('DOMContentLoaded', function () {
refreshServers();
loadGroups();
setupSocketIO();
// 초기 탭 설정
showTab('servers');
});
// ========================================
@@ -299,27 +302,27 @@ function closeUploadModal() {
async function startMultiUpload() {
const fileInput = document.getElementById('firmware-file');
const serverIds = getSelectedServerIds();
if (!fileInput.files[0]) {
showMessage('파일을 선택하세요', 'warning');
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('server_ids', serverIds.join(','));
try {
closeUploadModal();
showUploadProgress(serverIds);
const response = await fetchWithCSRF('/idrac/api/upload-multi', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showMessage(data.message, 'success');
} else {
@@ -335,9 +338,9 @@ async function startMultiUpload() {
function showUploadProgress(serverIds) {
const section = document.getElementById('upload-progress-section');
const list = document.getElementById('upload-progress-list');
section.style.display = 'block';
// 각 서버별 진행 바 생성
list.innerHTML = serverIds.map(id => {
const server = servers.find(s => s.id === id);
@@ -354,7 +357,7 @@ function showUploadProgress(serverIds) {
</div>
`;
}).join('');
// 요약 초기화
document.getElementById('progress-summary').innerHTML = `
<div class="summary-stats">
@@ -375,13 +378,13 @@ function hideUploadProgress() {
function setupSocketIO() {
// 업로드 진행 상황
socket.on('upload_progress', function(data) {
socket.on('upload_progress', function (data) {
console.log('Upload progress:', data);
const statusEl = document.getElementById(`status-${data.server_id}`);
const barEl = document.getElementById(`bar-${data.server_id}`);
const messageEl = document.getElementById(`message-${data.server_id}`);
if (statusEl) statusEl.textContent = data.message;
if (barEl) {
barEl.style.width = data.progress + '%';
@@ -389,18 +392,18 @@ function setupSocketIO() {
}
if (messageEl) messageEl.textContent = data.job_id ? `Job ID: ${data.job_id}` : '';
});
// 업로드 완료
socket.on('upload_complete', function(data) {
socket.on('upload_complete', function (data) {
console.log('Upload complete:', data);
const summary = data.summary;
document.getElementById('completed-count').textContent = `완료: ${summary.success}`;
document.getElementById('failed-count').textContent = `실패: ${summary.failed}`;
showMessage(`업로드 완료: 성공 ${summary.success}대, 실패 ${summary.failed}`, 'success');
showResults(data.results, '업로드 결과');
refreshServers();
});
}
@@ -412,9 +415,9 @@ function setupSocketIO() {
function showResults(results, title) {
const section = document.getElementById('result-section');
const content = document.getElementById('result-content');
section.style.display = 'block';
content.innerHTML = `
<h3>${title}</h3>
<table class="result-table">
@@ -448,30 +451,30 @@ function showResults(results, title) {
async function rebootSelected() {
const serverIds = getSelectedServerIds();
if (serverIds.length === 0) {
showMessage('서버를 선택하세요', 'warning');
return;
}
if (!confirm(`선택한 ${serverIds.length}대 서버를 재부팅하시겠습니까?\n\n⚠️ 업로드된 펌웨어가 적용됩니다!`)) {
return;
}
try {
const response = await fetchWithCSRF('/idrac/api/servers/reboot-multi', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
body: JSON.stringify({
server_ids: serverIds,
type: 'GracefulRestart'
})
});
const data = await response.json();
if (data.success) {
const summary = data.summary;
showMessage(`재부팅 시작: 성공 ${summary.success}대, 실패 ${summary.failed}`, 'success');
@@ -479,7 +482,7 @@ async function rebootSelected() {
} else {
showMessage(data.message, 'error');
}
refreshServers();
} catch (error) {
showMessage('재부팅 실패: ' + error, 'error');
@@ -499,11 +502,38 @@ function importExcel() {
// ========================================
function showMessage(message, type = 'info') {
// 간단한 알림 표시
alert(message);
// 토스트 알림으로 변경
showToast(message, type);
console.log(`[${type}] ${message}`);
}
// ========================================
// 토스트 알림 시스템
// ========================================
function showToast(message, type = 'info') {
// 기존 토스트 제거
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
// 새 토스트 생성
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
// 애니메이션
setTimeout(() => toast.classList.add('show'), 100);
// 3초 후 제거
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// 편의 함수들
function editServer(serverId) {
showMessage('서버 수정 기능은 개발 중입니다', 'info');
@@ -511,20 +541,20 @@ function editServer(serverId) {
function getSelectedFirmware() {
const serverIds = getSelectedServerIds();
if (serverIds.length === 0) {
showMessage('서버를 선택하세요', 'warning');
return;
}
showMessage('선택한 서버의 펌웨어 조회 중...', 'info');
// 각 서버별로 펌웨어 조회
serverIds.forEach(async (serverId) => {
try {
const response = await fetchWithCSRF(`/idrac/api/servers/${serverId}/firmware`);
const data = await response.json();
if (data.success) {
console.log(`Server ${serverId} firmware:`, data.data);
}
@@ -532,7 +562,7 @@ function getSelectedFirmware() {
console.error(`Server ${serverId} firmware query failed:`, error);
}
});
// 새로고침
setTimeout(() => {
refreshServers();
@@ -580,11 +610,19 @@ async function compareSelectedFirmware() {
const serverIds = getSelectedServerIds();
if (serverIds.length === 0) {
showMessage('서버를 선택하세요', 'warning');
showToast('서버를 선택하세요', 'warning');
return;
}
showMessage(`선택된 ${serverIds.length}대 서버 버전 비교 중...`, 'info');
// 비교 탭으로 전환
showTab('comparison');
// 로딩 표시
const loadingEl = document.getElementById('comparison-loading');
const resultEl = document.getElementById('comparison-result');
if (loadingEl) loadingEl.style.display = 'block';
if (resultEl) resultEl.innerHTML = '';
try {
const res = await fetchWithCSRF('/idrac/api/servers/firmware/compare-multi', {
@@ -594,16 +632,90 @@ async function compareSelectedFirmware() {
});
const data = await res.json();
// 로딩 숨기기
if (loadingEl) loadingEl.style.display = 'none';
if (data.success) {
showResults(data.results, '버전 비교 결과');
displayComparisonResults(data.results);
showToast(`${serverIds.length}대 서버 비교 완료`, 'success');
} else {
showMessage(data.message, 'error');
if (resultEl) {
resultEl.innerHTML = `<div class="warning-text">⚠️ ${data.message}</div>`;
}
showToast(data.message, 'error');
}
} catch (error) {
showMessage('버전 비교 실패: ' + error, 'error');
if (loadingEl) loadingEl.style.display = 'none';
if (resultEl) {
resultEl.innerHTML = `<div class="warning-text">⚠️ 버전 비교 실패: ${error}</div>`;
}
showToast('버전 비교 실패: ' + error, 'error');
}
}
// ========================================
// 비교 결과 표시 (개선된 버전)
// ========================================
function displayComparisonResults(results) {
const resultEl = document.getElementById('comparison-result');
if (!resultEl) return;
if (!results || results.length === 0) {
resultEl.innerHTML = '<div class="info-text">비교 결과가 없습니다</div>';
return;
}
let html = '<div class="comparison-grid">';
results.forEach(serverResult => {
html += `
<div class="server-comparison-section">
<h3 style="margin-bottom: 15px; color: #333;">
🖥️ ${serverResult.server_name}
<span style="font-size: 0.8em; color: #666;">(${serverResult.server_ip || ''})</span>
</h3>
`;
if (serverResult.success && serverResult.comparisons) {
serverResult.comparisons.forEach(comp => {
const statusClass = comp.status === 'outdated' ? 'outdated' :
comp.status === 'latest' ? 'latest' : 'unknown';
const statusIcon = comp.status === 'outdated' ? '⚠️' :
comp.status === 'latest' ? '✅' : '❓';
html += `
<div class="comparison-card ${statusClass}">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
<strong style="font-size: 1.1em;">${comp.component_name}</strong>
<span style="font-size: 1.5em;">${statusIcon}</span>
</div>
<div style="margin-bottom: 8px;">
<span style="color: #666;">현재:</span>
<code style="background: #f0f0f0; padding: 2px 8px; border-radius: 4px;">${comp.current_version}</code>
</div>
<div style="margin-bottom: 8px;">
<span style="color: #666;">최신:</span>
<code style="background: #f0f0f0; padding: 2px 8px; border-radius: 4px;">${comp.latest_version || 'N/A'}</code>
</div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #ddd; font-size: 0.9em; color: #555;">
${comp.recommendation}
</div>
</div>
`;
});
} else {
html += `<div class="warning-text">⚠️ ${serverResult.message || '비교 실패'}</div>`;
}
html += '</div>';
});
html += '</div>';
resultEl.innerHTML = html;
}
// ========================================
// 펌웨어 버전 추가 모달
// ========================================
@@ -681,7 +793,7 @@ async function refreshFirmwareVersionList() {
// Dell Catalog에서 최신 버전 자동 가져오기
// ========================================
async function syncDellCatalog(model = "PowerEdge R750") {
showMessage(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info");
showToast(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info");
try {
const response = await fetchWithCSRF("/catalog/sync", {
@@ -692,13 +804,95 @@ async function syncDellCatalog(model = "PowerEdge R750") {
const data = await response.json();
if (data.success) {
showMessage(data.message, "success");
showToast(data.message, "success");
await refreshFirmwareVersionList();
} else {
showMessage(data.message, "error");
showToast(data.message, "error");
}
} catch (error) {
showMessage("버전 정보 동기화 실패: " + error, "error");
showToast("버전 정보 동기화 실패: " + error, "error");
}
}
// ========================================
// DRM 리포지토리 동기화
// ========================================
async function syncFromDRM() {
// DRM 리포지토리 경로 입력 받기
const repositoryPath = prompt(
'DRM 리포지토리 경로를 입력하세요:\n예: C:\\Dell\\Repository 또는 \\\\network\\share\\DellRepo',
'C:\\Dell\\Repository'
);
if (!repositoryPath) return;
const model = prompt('서버 모델을 입력하세요:', 'PowerEdge R750');
if (!model) return;
showToast('DRM 리포지토리에서 펌웨어 정보 가져오는 중...', 'info');
try {
const response = await fetchWithCSRF('/drm/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
repository_path: repositoryPath,
model: model
})
});
const data = await response.json();
if (data.success) {
showToast(`${data.message}`, 'success');
await refreshFirmwareVersionList();
} else {
showToast(data.message, 'error');
}
} catch (error) {
showToast('DRM 동기화 실패: ' + error, 'error');
}
}
async function checkDRMRepository() {
const repositoryPath = prompt(
'DRM 리포지토리 경로를 입력하세요:',
'C:\\Dell\\Repository'
);
if (!repositoryPath) return;
try {
const response = await fetchWithCSRF('/drm/check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ repository_path: repositoryPath })
});
const data = await response.json();
if (data.success && data.info) {
const info = data.info;
let message = `DRM 리포지토리 정보:\n\n`;
message += `경로: ${info.path}\n`;
message += `카탈로그 파일: ${info.catalog_file || '없음'}\n`;
message += `총 패키지 수: ${info.total_packages || 0}\n`;
if (info.available_models && info.available_models.length > 0) {
message += `\n사용 가능한 모델 (${info.available_models.length}개):\n`;
message += info.available_models.slice(0, 10).join(', ');
if (info.available_models.length > 10) {
message += ` ... 외 ${info.available_models.length - 10}`;
}
}
alert(message);
} else {
showToast('DRM 리포지토리를 찾을 수 없습니다', 'error');
}
} catch (error) {
showToast('DRM 확인 실패: ' + error, 'error');
}
}
@@ -716,8 +910,8 @@ async function refreshFirmwareVersionList() {
tbody.innerHTML = data.versions.length
? data.versions
.map(
(v) => `
.map(
(v) => `
<tr>
<td>${v.component_name}</td>
<td>${v.latest_version}</td>
@@ -728,13 +922,79 @@ async function refreshFirmwareVersionList() {
<button class="btn btn-danger" onclick="deleteFirmwareVersion(${v.id})">삭제</button>
</td>
</tr>`
)
.join("")
)
.join("")
: `<tr><td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td></tr>`;
} else {
showMessage(data.message, "error");
showToast(data.message, "error");
}
} catch (error) {
showMessage("버전 목록 로드 실패: " + error, "error");
showToast("버전 목록 로드 실패: " + error, "error");
}
}
// ========================================
// 탭 전환 기능
// ========================================
function showTab(tabName) {
// 모든 탭 콘텐츠 숨기기
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// 모든 탭 버튼 비활성화
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
// 선택된 탭 표시
const selectedTab = document.getElementById(tabName + '-tab');
if (selectedTab) {
selectedTab.classList.add('active');
}
// 클릭된 버튼 활성화 (이벤트에서 호출된 경우)
if (event && event.target) {
event.target.classList.add('active');
} else {
// 직접 호출된 경우 해당 버튼 찾아서 활성화
const buttons = document.querySelectorAll('.tab-button');
buttons.forEach((btn, index) => {
if ((tabName === 'servers' && index === 0) ||
(tabName === 'versions' && index === 1) ||
(tabName === 'comparison' && index === 2)) {
btn.classList.add('active');
}
});
}
// 버전 탭 선택 시 목록 로드
if (tabName === 'versions') {
refreshFirmwareVersionList();
}
}
// ========================================
// 펌웨어 버전 삭제
// ========================================
async function deleteFirmwareVersion(versionId) {
if (!confirm('이 펌웨어 버전 정보를 삭제하시겠습니까?')) return;
try {
const response = await fetchWithCSRF(`/idrac/api/firmware-versions/${versionId}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
showToast(data.message, 'success');
refreshFirmwareVersionList();
} else {
showToast(data.message, 'error');
}
} catch (error) {
showToast('삭제 실패: ' + error, 'error');
}
}

298
backend/static/js/index.js Normal file
View File

@@ -0,0 +1,298 @@
document.addEventListener('DOMContentLoaded', () => {
// ─────────────────────────────────────────────────────────────
// 스크립트 선택 시 XML 드롭다운 토글
// ─────────────────────────────────────────────────────────────
const TARGET_SCRIPT = "02-set_config.py";
const scriptSelect = document.getElementById('script');
const xmlGroup = document.getElementById('xmlFileGroup');
function toggleXml() {
if (!scriptSelect || !xmlGroup) return;
if (scriptSelect.value === TARGET_SCRIPT) {
xmlGroup.style.display = 'block';
xmlGroup.classList.add('fade-in');
} else {
xmlGroup.style.display = 'none';
}
}
if (scriptSelect) {
toggleXml();
scriptSelect.addEventListener('change', toggleXml);
}
// ─────────────────────────────────────────────────────────────
// 파일 보기 모달
// ─────────────────────────────────────────────────────────────
const modalEl = document.getElementById('fileViewModal');
const titleEl = document.getElementById('fileViewModalLabel');
const contentEl = document.getElementById('fileViewContent');
if (modalEl) {
modalEl.addEventListener('show.bs.modal', async (ev) => {
const btn = ev.relatedTarget;
const folder = btn?.getAttribute('data-folder') || '';
const date = btn?.getAttribute('data-date') || '';
const filename = btn?.getAttribute('data-filename') || '';
titleEl.innerHTML = `<i class="bi bi-file-text me-2"></i>${filename || '파일'}`;
contentEl.textContent = '불러오는 중...';
const params = new URLSearchParams();
if (folder) params.set('folder', folder);
if (date) params.set('date', date);
if (filename) params.set('filename', filename);
try {
const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' });
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
contentEl.textContent = data?.content ?? '(빈 파일)';
} catch (e) {
contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e);
}
});
}
// ─────────────────────────────────────────────────────────────
// 진행바 업데이트
// ─────────────────────────────────────────────────────────────
window.updateProgress = function (val) {
const bar = document.getElementById('progressBar');
if (!bar) return;
const v = Math.max(0, Math.min(100, Number(val) || 0));
bar.style.width = v + '%';
bar.setAttribute('aria-valuenow', v);
bar.innerHTML = `<span class="fw-semibold">${v}%</span>`;
};
// ─────────────────────────────────────────────────────────────
// CSRF 토큰
// ─────────────────────────────────────────────────────────────
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
// ─────────────────────────────────────────────────────────────
// IP 입력 데이터 보존 (Local Storage)
// ─────────────────────────────────────────────────────────────
const ipTextarea = document.getElementById('ips');
const ipForm = document.getElementById('ipForm');
const STORAGE_KEY_IP = 'ip_input_draft';
if (ipTextarea) {
// 1. 페이지 로드 시 저장된 값 복원
const savedIps = localStorage.getItem(STORAGE_KEY_IP);
if (savedIps) {
ipTextarea.value = savedIps;
// 라인 수 업데이트 트리거
if (window.updateIpCount) window.updateIpCount();
}
// 2. 입력 시마다 저장
ipTextarea.addEventListener('input', () => {
localStorage.setItem(STORAGE_KEY_IP, ipTextarea.value);
// script.js에 있는 updateIpCount 호출 (있다면)
if (window.updateIpCount) window.updateIpCount();
});
// 3. 폼 제출 성공 시 초기화?
// 사용자의 의도에 따라 다름: "변경이 되지 않는 이상 계속 가지고 있게"
// -> 제출 후에도 유지하는 것이 요청 사항에 부합함.
// 만약 '성공적으로 작업이 끝나면 지워달라'는 요청이 있으면 여기를 수정.
// 현재 요청: "페이지가 리셋이되도 변경이 되지 않는이상 계속 가지고있게" -> 유지.
}
// ─────────────────────────────────────────────────────────────
// 공통 POST 함수
// ─────────────────────────────────────────────────────────────
async function postFormAndHandle(url) {
const res = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'X-CSRFToken': csrfToken,
'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8',
},
});
const ct = (res.headers.get('content-type') || '').toLowerCase();
if (ct.includes('application/json')) {
const data = await res.json();
if (data.success === false) {
throw new Error(data.error || ('HTTP ' + res.status));
}
return data;
}
return { success: true, html: true };
}
// ─────────────────────────────────────────────────────────────
// MAC 파일 이동
// ─────────────────────────────────────────────────────────────
const macForm = document.getElementById('macMoveForm');
if (macForm) {
macForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = macForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(macForm.action);
location.reload();
} catch (err) {
alert('MAC 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// ─────────────────────────────────────────────────────────────
// GUID 파일 이동
// ─────────────────────────────────────────────────────────────
const guidForm = document.getElementById('guidMoveForm');
if (guidForm) {
guidForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = guidForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(guidForm.action);
location.reload();
} catch (err) {
alert('GUID 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// ─────────────────────────────────────────────────────────────
// 알림 자동 닫기
// ─────────────────────────────────────────────────────────────
setTimeout(() => {
document.querySelectorAll('.alert').forEach(alert => {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
});
}, 5000);
// ─────────────────────────────────────────────────────────────
// IP 스캔 로직 (Modal)
// ─────────────────────────────────────────────────────────────
const btnScan = document.getElementById('btnStartScan');
if (btnScan) {
btnScan.addEventListener('click', async () => {
const startIp = '10.10.0.2';
const endIp = '10.10.0.255';
const ipsTextarea = document.getElementById('ips');
const progressBar = document.getElementById('progressBar');
// UI 상태 변경 (로딩 중)
const originalIcon = btnScan.innerHTML;
btnScan.disabled = true;
btnScan.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
// 메인 진행바 활용
if (progressBar) {
const progressContainer = progressBar.closest('.progress');
if (progressContainer) {
progressContainer.parentElement.classList.remove('d-none');
}
progressBar.style.width = '100%';
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
progressBar.textContent = 'IP 스캔 중...';
}
try {
const res = await fetch('/utils/scan_network', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({ start_ip: startIp, end_ip: endIp })
});
// 1. 세션 만료로 인한 리다이렉트 감지
if (res.redirected) {
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
window.location.reload();
return;
}
// 2. JSON 응답인지 확인
const contentType = res.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
const text = await res.text();
if (text.includes("CSRF")) {
throw new Error("보안 토큰(CSRF)이 만료되었습니다. 페이지를 새로고침해주세요.");
}
throw new Error(`서버 응답 오류 (HTTP ${res.status}): ${text.substring(0, 100)}...`);
}
const data = await res.json();
if (data.success) {
if (data.active_ips && data.active_ips.length > 0) {
ipsTextarea.value = data.active_ips.join('\n');
// 이벤트 트리거
ipsTextarea.dispatchEvent(new Event('input'));
alert(`스캔 완료: ${data.active_ips.length}개의 활성 IP를 찾았습니다.`);
} else {
alert('활성 IP를 발견하지 못했습니다.');
}
} else {
throw new Error(data.error || 'Unknown error');
}
} catch (err) {
console.error(err);
alert('오류가 발생했습니다: ' + (err.message || err));
} finally {
// 상태 복구
btnScan.disabled = false;
btnScan.innerHTML = originalIcon;
if (progressBar) {
// 진행바 초기화
progressBar.style.width = '0%';
progressBar.textContent = '0%';
progressBar.classList.remove('progress-bar-striped', 'progress-bar-animated');
}
}
});
}
// ─────────────────────────────────────────────────────────────
// IP 입력 지우기 버튼
// ─────────────────────────────────────────────────────────────
const btnClear = document.getElementById('btnClearIps');
if (btnClear) {
btnClear.addEventListener('click', () => {
const ipsTextarea = document.getElementById('ips');
if (ipsTextarea) {
ipsTextarea.value = '';
ipsTextarea.dispatchEvent(new Event('input')); // 로컬 스토리지 업데이트 및 카운트 갱신 트리거
}
});
}
});

419
backend/static/js/jobs.js Normal file
View File

@@ -0,0 +1,419 @@
// Note: This script expects csrfToken to be available globally
// It should be set in the HTML template before this script loads
// ========== 전역 변수 ==========
let CONFIG = {
grace_minutes: 60,
recency_hours: 24,
poll_interval_ms: 10000
};
let monitoringOn = false;
let pollTimer = null;
let lastRenderHash = "";
// ========== Elements ==========
const $ = id => document.getElementById(id);
const $body = $('jobs-body');
const $last = $('last-updated');
const $loading = $('loading-indicator');
const $btn = $('btn-refresh');
const $auto = $('autoRefreshSwitch');
const $ipInput = $('ipInput');
const $ipCount = $('ip-count');
const $btnLoad = $('btn-load-file');
const $btnApply = $('btn-apply');
const $monSw = $('monitorSwitch');
const $statusDot = $('status-dot');
const $statusText = $('status-text');
const $showCompleted = $('showCompletedSwitch');
const $pollInterval = $('poll-interval');
// Stats
const $statTotal = $('stat-total');
const $statRunning = $('stat-running');
const $statCompleted = $('stat-completed');
const $statError = $('stat-error');
// ========== LocalStorage Keys ==========
const LS_IPS = 'idrac_job_ips';
const LS_MON = 'idrac_monitor_on';
const LS_AUTO = 'idrac_monitor_auto';
const LS_SHOW_COMPLETED = 'idrac_show_completed';
// ========== 유틸리티 ==========
function parseIps(text) {
if (!text) return [];
const raw = text.replace(/[,;]+/g, '\n');
const out = [], seen = new Set();
raw.split('\n').forEach(line => {
const parts = line.trim().split(/\s+/);
parts.forEach(p => {
if (!p || p.startsWith('#')) return;
if (!seen.has(p)) {
seen.add(p);
out.push(p);
}
});
});
return out;
}
function getIpsFromUI() { return parseIps($ipInput.value); }
function setIpsToUI(ips) {
$ipInput.value = (ips || []).join('\n');
updateIpCount();
}
function updateIpCount() {
const ips = getIpsFromUI();
$ipCount.textContent = ips.length;
}
function saveIps() {
localStorage.setItem(LS_IPS, JSON.stringify(getIpsFromUI()));
updateIpCount();
}
function loadIps() {
try {
const v = JSON.parse(localStorage.getItem(LS_IPS) || "[]");
return Array.isArray(v) ? v : [];
} catch {
return [];
}
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, m => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;',
'"': '&quot;', "'": '&#39;'
}[m]));
}
function progressBar(pc) {
const n = parseInt(String(pc ?? "").toString().replace('%', '').trim(), 10);
if (isNaN(n)) return `<span class="text-muted small">${escapeHtml(pc ?? "")}</span>`;
let bgClass = 'bg-info';
if (n === 100) bgClass = 'bg-success';
else if (n < 30) bgClass = 'bg-warning';
return `<div class="progress" style="height:8px;">
<div class="progress-bar ${bgClass}" role="progressbar"
style="width:${n}%;" aria-valuenow="${n}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">${n}%</small>`;
}
function badgeStatus(status, pc, recently = false) {
const raw = String(status || "");
const s = raw.toLowerCase();
let cls = "bg-secondary";
let icon = "info-circle";
if (recently) {
cls = "bg-success";
icon = "check-circle";
} else if (s.includes("completed")) {
cls = "bg-success";
icon = "check-circle";
} else if (s.includes("running") || s.includes("progress")) {
cls = "bg-info";
icon = "arrow-repeat";
} else if (s.includes("scheduled") || s.includes("pending")) {
cls = "bg-warning text-dark";
icon = "clock";
} else if (s.includes("failed") || s.includes("error")) {
cls = "bg-danger";
icon = "x-circle";
}
const pct = parseInt(String(pc ?? "").toString().replace('%', '').trim(), 10);
const pctText = isNaN(pct) ? "" : ` (${pct}%)`;
const text = recently ? `${raw || "Completed"} (최근${pctText})` : `${raw || "-"}${pctText}`;
return `<span class="badge ${cls}">
<i class="bi bi-${icon}"></i> ${escapeHtml(text)}
</span>`;
}
function updateStats(items) {
let total = 0, running = 0, completed = 0, error = 0;
items.forEach(it => {
if (!it.ok) {
error++;
return;
}
if (it.jobs && it.jobs.length) {
total++;
it.jobs.forEach(j => {
const s = (j.Status || "").toLowerCase();
if (s.includes("running") || s.includes("progress") || s.includes("starting")) {
running++;
} else if (s.includes("completed") || s.includes("success")) {
completed++;
} else if (s.includes("failed") || s.includes("error")) {
error++;
}
});
}
});
$statTotal.textContent = items.length;
$statRunning.textContent = running;
$statCompleted.textContent = completed;
$statError.textContent = error;
}
// ========== 렌더링 ==========
function renderTable(items) {
if (!items) return;
const hash = JSON.stringify(items);
if (hash === lastRenderHash) return;
lastRenderHash = hash;
if (!items.length) {
$body.innerHTML = `<tr><td colspan="7" class="text-center text-muted py-4">
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
현재 모니터링 중인 Job이 없습니다.
</td></tr>`;
updateStats([]);
return;
}
const rows = [];
for (const it of items) {
if (!it.ok) {
rows.push(`<tr class="table-danger">
<td><code>${escapeHtml(it.ip)}</code></td>
<td colspan="6">
<i class="bi bi-exclamation-triangle"></i>
오류: ${escapeHtml(it.error || "Unknown")}
</td>
</tr>`);
continue;
}
if (!it.jobs || !it.jobs.length) continue;
for (const j of it.jobs) {
const recent = !!j.RecentlyCompleted;
const timeText = j.CompletedAt
? `완료: ${escapeHtml(j.CompletedAt.split('T')[1]?.split('.')[0] || j.CompletedAt)}`
: escapeHtml(j.LastUpdateTime || "");
rows.push(`<tr ${recent ? 'class="table-success"' : ''}>
<td><code>${escapeHtml(it.ip)}</code></td>
<td><small class="font-monospace">${escapeHtml(j.JID || "")}</small></td>
<td><strong>${escapeHtml(j.Name || "")}</strong></td>
<td>${badgeStatus(j.Status || "", j.PercentComplete || "", recent)}</td>
<td>${progressBar(j.PercentComplete || "0")}</td>
<td><small>${escapeHtml(j.Message || "")}</small></td>
<td><small class="text-muted">${timeText}</small></td>
</tr>`);
}
}
$body.innerHTML = rows.length
? rows.join("")
: `<tr><td colspan="7" class="text-center text-success py-4">
<i class="bi bi-check-circle fs-2 d-block mb-2"></i>
현재 진행 중인 Job이 없습니다. ✅
</td></tr>`;
updateStats(items);
}
// ========== 서버 요청 ==========
async function fetchJobs(auto = false) {
if (!monitoringOn) {
$body.innerHTML = `<tr><td colspan="7" class="text-center text-muted py-4">
<i class="bi bi-power fs-2 d-block mb-2"></i>
모니터링이 꺼져 있습니다. 상단 스위치를 켜면 조회가 시작됩니다.
</td></tr>`;
$last.textContent = "";
updateStats([]);
return;
}
const ips = getIpsFromUI();
if (!ips.length) {
$body.innerHTML = `<tr><td colspan="7" class="text-center text-warning py-4">
<i class="bi bi-exclamation-triangle fs-2 d-block mb-2"></i>
IP 목록이 비어 있습니다.
</td></tr>`;
$last.textContent = "";
updateStats([]);
return;
}
try {
$loading.classList.remove('d-none');
$last.textContent = "조회 중… " + new Date().toLocaleTimeString();
const res = await fetch("/jobs/scan", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken
},
body: JSON.stringify({
ips,
method: "redfish", // Redfish 사용
recency_hours: CONFIG.recency_hours,
grace_minutes: CONFIG.grace_minutes,
include_tracked_done: $showCompleted.checked
})
});
const data = await res.json();
if (!data.ok) throw new Error(data.error || "Scan failed");
renderTable(data.items);
$last.textContent = "업데이트: " + new Date().toLocaleString();
} catch (e) {
$body.innerHTML = `<tr><td colspan="7" class="text-danger text-center py-4">
<i class="bi bi-exclamation-circle fs-2 d-block mb-2"></i>
로드 실패: ${escapeHtml(e.message)}
<br><button class="btn btn-sm btn-outline-primary mt-2" onclick="fetchJobs(false)">
<i class="bi bi-arrow-clockwise"></i> 재시도
</button>
</td></tr>`;
$last.textContent = "에러: " + new Date().toLocaleString();
console.error(e);
} finally {
$loading.classList.add('d-none');
}
}
// ========== 모니터링 제어 ==========
function startAuto() {
stopAuto();
pollTimer = setInterval(() => fetchJobs(true), CONFIG.poll_interval_ms);
}
function stopAuto() {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
}
function updateMonitorUI() {
$btn.disabled = !monitoringOn;
$auto.disabled = !monitoringOn;
if (monitoringOn) {
$statusDot.classList.add('active');
$statusText.textContent = '모니터링 중';
} else {
$statusDot.classList.remove('active');
$statusText.textContent = '모니터링 꺼짐';
}
}
async function setMonitoring(on) {
monitoringOn = !!on;
localStorage.setItem(LS_MON, monitoringOn ? "1" : "0");
updateMonitorUI();
if (!monitoringOn) {
stopAuto();
$last.textContent = "";
$body.innerHTML = `<tr><td colspan="7" class="text-center text-muted py-4">
<i class="bi bi-power fs-2 d-block mb-2"></i>
모니터링이 꺼져 있습니다.
</td></tr>`;
lastRenderHash = "";
updateStats([]);
return;
}
await fetchJobs(false);
if ($auto.checked) startAuto();
}
// ========== 초기화 ==========
document.addEventListener('DOMContentLoaded', async () => {
// 설정 로드
try {
const res = await fetch('/jobs/config');
const data = await res.json();
if (data.ok) {
CONFIG = data.config;
$pollInterval.textContent = Math.round(CONFIG.poll_interval_ms / 1000);
}
} catch (e) {
console.error("Failed to load config:", e);
}
// IP 복원
const savedIps = loadIps();
if (savedIps.length) {
setIpsToUI(savedIps);
} else {
try {
const res = await fetch('/jobs/iplist', {
headers: { 'X-CSRFToken': csrfToken }
});
const data = await res.json();
if (data.ok && data.ips) {
setIpsToUI(data.ips);
saveIps();
}
} catch (e) {
console.error(e);
}
}
// 설정 복원
const savedMon = localStorage.getItem(LS_MON);
const savedAuto = localStorage.getItem(LS_AUTO);
const savedShowCompleted = localStorage.getItem(LS_SHOW_COMPLETED);
$monSw.checked = savedMon === "1";
$auto.checked = savedAuto === "1";
$showCompleted.checked = savedShowCompleted !== "0";
// 이벤트
$ipInput.addEventListener('input', updateIpCount);
$btn.addEventListener('click', () => { if (monitoringOn) fetchJobs(false); });
$auto.addEventListener('change', e => {
localStorage.setItem(LS_AUTO, e.target.checked ? "1" : "0");
if (!monitoringOn) return;
if (e.target.checked) startAuto();
else stopAuto();
});
$monSw.addEventListener('click', e => setMonitoring(e.target.checked));
$showCompleted.addEventListener('change', e => {
localStorage.setItem(LS_SHOW_COMPLETED, e.target.checked ? "1" : "0");
if (monitoringOn) fetchJobs(false);
});
$btnLoad.addEventListener('click', async () => {
try {
const res = await fetch('/jobs/iplist', {
headers: { 'X-CSRFToken': csrfToken }
});
const data = await res.json();
if (data.ok) {
setIpsToUI(data.ips || []);
saveIps();
if (monitoringOn) await fetchJobs(false);
}
} catch (e) {
alert('IP 목록을 불러오는 중 오류가 발생했습니다: ' + e.message);
}
});
$btnApply.addEventListener('click', () => {
saveIps();
if (monitoringOn) fetchJobs(false);
});
// 초기 상태
updateMonitorUI();
updateIpCount();
if ($monSw.checked) {
setMonitoring(true);
}
});

30
backend/static/js/scp.js Normal file
View File

@@ -0,0 +1,30 @@
function updateFileName(input) {
const fileName = input.files[0]?.name || '파일 선택';
document.getElementById('fileLabel').textContent = fileName;
}
function openDeployModal(filename) {
document.getElementById('deployFilename').value = filename;
var myModal = new bootstrap.Modal(document.getElementById('deployModal'));
myModal.show();
}
function compareSelected() {
const checkboxes = document.querySelectorAll('.file-selector:checked');
if (checkboxes.length !== 2) {
alert('비교할 파일을 정확히 2개 선택해주세요.');
return;
}
const file1 = checkboxes[0].value;
const file2 = checkboxes[1].value;
// HTML 버튼의 data-url 속성에서 base URL을 가져옴
const btn = document.getElementById('compareBtn');
if (!btn) {
console.error('compareBtn not found');
return;
}
const baseUrl = btn.dataset.url;
window.location.href = `${baseUrl}?file1=${encodeURIComponent(file1)}&file2=${encodeURIComponent(file2)}`;
}

View File

@@ -1,71 +1,151 @@
{# backend/templates/admin.html #}
{% extends "base.html" %}
{% block title %}관리자 패널 - Dell Server Info{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="card">
<div class="card-body">
<h3 class="card-title">Admin Page</h3>
<div class="container py-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-1">
<i class="bi bi-shield-lock text-primary me-2"></i>관리자 패널
</h2>
<p class="text-muted mb-0">사용자 관리 및 시스템 설정을 수행합니다.</p>
</div>
<div class="d-flex gap-2">
<a href="{{ url_for('admin.view_logs') }}" class="btn btn-outline-secondary">
<i class="bi bi-journal-text me-1"></i>로그 보기
</a>
<a href="{{ url_for('admin.settings') }}" class="btn btn-primary">
<i class="bi bi-gear-fill me-1"></i>시스템 설정
</a>
</div>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mt-2">
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert">
{{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
<!-- Dashboard Stats -->
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card border-0 shadow-sm h-100 bg-primary bg-opacity-10">
<div class="card-body d-flex align-items-center">
<div class="rounded-circle bg-primary text-white p-3 me-3">
<i class="bi bi-people-fill fs-4"></i>
</div>
{% endif %}
{% endwith %}
<div>
<h6 class="text-primary fw-bold mb-1">총 사용자</h6>
<h3 class="mb-0 fw-bold">{{ users|length }}</h3>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm h-100 bg-success bg-opacity-10">
<div class="card-body d-flex align-items-center">
<div class="rounded-circle bg-success text-white p-3 me-3">
<i class="bi bi-person-check-fill fs-4"></i>
</div>
<div>
<h6 class="text-success fw-bold mb-1">활성 사용자</h6>
{% set active_users = users | selectattr("is_active") | list %}
<h3 class="mb-0 fw-bold">{{ active_users|length }}</h3>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm h-100 bg-warning bg-opacity-10">
<div class="card-body d-flex align-items-center">
<div class="rounded-circle bg-warning text-white p-3 me-3">
<i class="bi bi-person-dash-fill fs-4"></i>
</div>
<div>
<h6 class="text-warning fw-bold mb-1">승인 대기</h6>
<h3 class="mb-0 fw-bold">{{ (users|length) - (active_users|length) }}</h3>
</div>
</div>
</div>
</div>
</div>
<!-- User Management Table -->
<div class="card border shadow-sm">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 fw-bold">
<i class="bi bi-person-lines-fill text-primary me-2"></i>사용자 목록
</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped align-middle">
<thead>
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th style="width:60px">ID</th>
<th>Username</th>
<th>Email</th>
<th style="width:80px">Active</th>
<th style="width:260px">Action</th>
<th class="ps-4 py-3 text-secondary text-uppercase small fw-bold" style="width: 60px;">NO</th>
<th class="py-3 text-secondary text-uppercase small fw-bold">이름</th>
<th class="py-3 text-secondary text-uppercase small fw-bold">ID (Email)</th>
<th class="py-3 text-secondary text-uppercase small fw-bold">상태</th>
<th class="py-3 text-secondary text-uppercase small fw-bold text-end pe-4">관리</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td class="ps-4 fw-bold text-secondary">{{ loop.index }}</td>
<td>
<div class="d-flex align-items-center">
<div
class="avatar-initial rounded-circle bg-light text-primary fw-bold me-2 d-flex align-items-center justify-content-center border"
style="width: 32px; height: 32px; font-size: 0.9rem;">
{{ user.username[:1] | upper }}
</div>
<span class="fw-bold text-dark">{{ user.username }}</span>
</div>
</td>
<td class="text-secondary small font-monospace">{{ user.email }}</td>
<td>
{% if user.is_active %}
<span class="badge bg-success">Yes</span>
<span class="badge bg-success-subtle text-success border border-success-subtle rounded-pill px-3">
<i class="bi bi-check-circle-fill me-1"></i>Active
</span>
{% else %}
<span class="badge bg-secondary">No</span>
<span class="badge bg-warning-subtle text-warning border border-warning-subtle rounded-pill px-3">
<i class="bi bi-hourglass-split me-1"></i>Pending
</span>
{% endif %}
{% if user.is_admin %}
<span
class="badge bg-primary-subtle text-primary border border-primary-subtle rounded-pill px-2 ms-1">Admin</span>
{% endif %}
</td>
<td>
{% if not user.is_active %}
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}" class="btn btn-success btn-sm me-1">Approve</a>
{% endif %}
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}"
class="btn btn-danger btn-sm me-1"
onclick="return confirm('사용자 {{ user.username }} (ID={{ user.id }}) 를 삭제하시겠습니까?');">
Delete
</a>
<td class="text-end pe-4">
<div class="d-flex justify-content-end gap-2">
{% if not user.is_active %}
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}"
class="btn btn-sm btn-success text-white d-flex align-items-center gap-1" title="가입 승인">
<i class="bi bi-check-lg"></i>승인
</a>
{% endif %}
<!-- Change Password 버튼: 모달 오픈 -->
<button type="button"
class="btn btn-primary btn-sm"
data-user-id="{{ user.id }}"
data-username="{{ user.username | e }}"
data-bs-toggle="modal"
data-bs-target="#changePasswordModal">
Change Password
</button>
<button type="button" class="btn btn-sm btn-outline-secondary d-flex align-items-center gap-1"
data-user-id="{{ user.id }}" data-username="{{ user.username | e }}" data-bs-toggle="modal"
data-bs-target="#changePasswordModal">
<i class="bi bi-key"></i>비밀번호
</button>
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}"
class="btn btn-sm btn-outline-danger d-flex align-items-center gap-1"
onclick="return confirm('⚠️ 경고: 사용자 [{{ user.username }}]님을 정말 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.');">
<i class="bi bi-trash"></i>삭제
</a>
</div>
</td>
</tr>
{% endfor %}
{% if not users %}
<tr>
<td colspan="4" class="text-center py-5 text-muted">사용자가 없습니다.</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
@@ -74,96 +154,52 @@
</div>
{# ========== Change Password Modal ========== #}
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-labelledby="changePasswordModalLabel" aria-hidden="true">
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-content border-0 shadow">
<form id="changePasswordForm" method="post" action="">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="modal-header">
<h5 class="modal-title" id="changePasswordModalLabel">Change Password</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">
<i class="bi bi-key-fill me-2"></i>비밀번호 변경
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<small class="text-muted">User:</small>
<div id="modalUserInfo" class="fw-bold"></div>
<div class="modal-body p-4">
<div class="alert alert-light border mb-4 d-flex align-items-center">
<i class="bi bi-person-circle fs-4 me-3 text-secondary"></i>
<div>
<small class="text-muted d-block">대상 사용자</small>
<span id="modalUserInfo" class="fw-bold text-dark fs-5"></span>
</div>
</div>
<div class="mb-3">
<label for="newPasswordInput" class="form-label">New password</label>
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8" placeholder="Enter new password">
<div class="form-text">최소 8자 이상을 권장합니다.</div>
<label for="newPasswordInput" class="form-label fw-semibold">새 비밀번호</label>
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8"
placeholder="최소 8자 이상">
</div>
<div class="mb-3">
<label for="confirmPasswordInput" class="form-label">Confirm password</label>
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required minlength="8" placeholder="Confirm new password">
<label for="confirmPasswordInput" class="form-label fw-semibold">비밀번호 확인</label>
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required
minlength="8" placeholder="비밀번호 재입력">
<div id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button id="modalSubmitBtn" type="submit" class="btn btn-primary">Change Password</button>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button id="modalSubmitBtn" type="submit" class="btn btn-primary px-4">변경 저장</button>
</div>
</form>
</div>
</div>
</div>
{# ========== 스크립트: 모달에 사용자 정보 채우기 + 클라이언트 확인 ========== #}
{% block scripts %}
{{ super() }}
<script>
(function () {
// Bootstrap 5을 사용한다고 가정. data-bs-* 이벤트로 처리.
const changePasswordModal = document.getElementById('changePasswordModal');
const modalUserInfo = document.getElementById('modalUserInfo');
const changePasswordForm = document.getElementById('changePasswordForm');
const newPasswordInput = document.getElementById('newPasswordInput');
const confirmPasswordInput = document.getElementById('confirmPasswordInput');
const pwMismatch = document.getElementById('pwMismatch');
if (!changePasswordModal) return;
changePasswordModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget; // 버튼 that triggered the modal
const userId = button.getAttribute('data-user-id');
const username = button.getAttribute('data-username') || ('ID ' + userId);
// 표시 텍스트 세팅
modalUserInfo.textContent = username + ' (ID: ' + userId + ')';
// 폼 action 동적 설정: admin.reset_password 라우트 기대
// 예: /admin/users/123/reset_password
changePasswordForm.action = "{{ url_for('admin.reset_password', user_id=0) }}".replace('/0/', '/' + userId + '/');
// 폼 내부 비밀번호 필드 초기화
newPasswordInput.value = '';
confirmPasswordInput.value = '';
confirmPasswordInput.classList.remove('is-invalid');
pwMismatch.style.display = 'none';
});
// 폼 제출 전 클라이언트에서 비밀번호 일치 검사
changePasswordForm.addEventListener('submit', function (e) {
const a = newPasswordInput.value || '';
const b = confirmPasswordInput.value || '';
if (a.length < 8) {
newPasswordInput.focus();
e.preventDefault();
return;
}
if (a !== b) {
e.preventDefault();
confirmPasswordInput.classList.add('is-invalid');
pwMismatch.style.display = 'block';
confirmPasswordInput.focus();
return;
}
// 제출 허용 (서버측에서도 반드시 검증)
});
})();
</script>
{% endblock %}
{{ super() }}
<script src="{{ url_for('static', filename='js/admin.js') }}"></script>
{% endblock %}
{% endblock %}

View File

@@ -0,0 +1,245 @@
{% extends "base.html" %}
{% block title %}시스템 로그 - Dell Server Info{% endblock %}
{% block extra_css %}
<style>
/* 전체 레이아웃 */
.editor-container {
display: flex;
flex-direction: column;
height: 600px;
background: #1e1e1e;
border: 1px solid #333;
border-radius: 6px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
/* 툴바 (헤더) */
.editor-toolbar {
background-color: #252526;
border-bottom: 1px solid #333;
padding: 0.5rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
/* 에디터 본문 */
#monaco-editor-root {
flex: 1;
width: 100%;
height: 100%;
}
/* 로딩 인디케이터 */
.editor-loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: #d4d4d4;
font-size: 1.1rem;
background: #1e1e1e;
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h2 class="fw-bold mb-1">
<i class="bi bi-terminal text-dark me-2"></i>시스템 로그
</h2>
<p class="text-muted mb-0 small">최근 생성된 1000줄의 시스템 로그를 실시간으로 확인합니다.</p>
</div>
<div>
<a href="{{ url_for('admin.admin_panel') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>돌아가기
</a>
</div>
</div>
<div class="editor-container">
<!-- Toolbar -->
<div class="editor-toolbar">
<div class="d-flex gap-2 align-items-center flex-wrap">
<div class="input-group input-group-sm" style="width: 250px;">
<span class="input-group-text bg-dark border-secondary text-light"><i
class="bi bi-search"></i></span>
<input type="text" id="logSearch" class="form-control bg-dark border-secondary text-light"
placeholder="검색어 입력...">
</div>
<div class="btn-group btn-group-sm" role="group">
<input type="checkbox" class="btn-check" id="checkInfo" checked autocomplete="off">
<label class="btn btn-outline-secondary text-light" for="checkInfo">INFO</label>
<input type="checkbox" class="btn-check" id="checkWarn" checked autocomplete="off">
<label class="btn btn-outline-warning" for="checkWarn">WARN</label>
<input type="checkbox" class="btn-check" id="checkError" checked autocomplete="off">
<label class="btn btn-outline-danger" for="checkError">ERROR</label>
</div>
</div>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-light" id="btnScrollBottom">
<i class="bi bi-arrow-down-circle me-1"></i>맨 아래로
</button>
<a href="{{ url_for('admin.view_logs') }}" class="btn btn-primary btn-sm">
<i class="bi bi-arrow-clockwise me-1"></i>새로고침
</a>
</div>
</div>
<!-- Editor Area -->
<div id="monaco-editor-root">
<div class="editor-loading">
<div class="spinner-border text-light me-3" role="status"></div>
<div>로그 뷰어를 불러오는 중...</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<!-- Monaco Editor Loader -->
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
// 서버에서 전달된 로그 데이터 (Python list -> JS array)
// tojson safe 필터 사용
const allLogs = {{ logs | tojson | safe }};
document.addEventListener('DOMContentLoaded', function () {
if (typeof require === 'undefined') {
document.querySelector('.editor-loading').innerHTML =
'<div class="text-danger"><i class="bi bi-exclamation-triangle me-2"></i>Monaco Editor를 로드할 수 없습니다.</div>';
return;
}
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], function () {
var container = document.getElementById('monaco-editor-root');
container.innerHTML = ''; // 로딩 제거
// 1. 커스텀 로그 언어 정의 (간단한 하이라이팅)
monaco.languages.register({ id: 'simpleLog' });
monaco.languages.setMonarchTokensProvider('simpleLog', {
tokenizer: {
root: [
[/\[INFO\]|INFO:/, 'info-token'],
[/\[WARNING\]|\[WARN\]|WARNING:|WARN:/, 'warn-token'],
[/\[ERROR\]|ERROR:|Traceback/, 'error-token'],
[/\[DEBUG\]|DEBUG:/, 'debug-token'],
[/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}/, 'date-token'],
[/".*?"/, 'string']
]
}
});
// 2. 테마 정의
monaco.editor.defineTheme('logTheme', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'info-token', foreground: '4ec9b0' },
{ token: 'warn-token', foreground: 'cca700', fontStyle: 'bold' },
{ token: 'error-token', foreground: 'f44747', fontStyle: 'bold' },
{ token: 'debug-token', foreground: '808080' },
{ token: 'date-token', foreground: '569cd6' },
],
colors: {
'editor.background': '#1e1e1e'
}
});
// 3. 에디터 생성
var editor = monaco.editor.create(container, {
value: allLogs.join('\n'),
language: 'simpleLog',
theme: 'logTheme',
readOnly: true,
automaticLayout: true,
minimap: { enabled: true },
fontSize: 13,
lineHeight: 19, // 밀도 조절
scrollBeyondLastLine: false,
lineNumbers: 'on',
wordWrap: 'on',
renderLineHighlight: 'all',
contextmenu: false,
padding: { top: 10, bottom: 10 }
});
// 4. 필터링 로직
function updateLogs() {
const query = document.getElementById('logSearch').value.toLowerCase();
const showInfo = document.getElementById('checkInfo').checked;
const showWarn = document.getElementById('checkWarn').checked;
const showError = document.getElementById('checkError').checked;
const filtered = allLogs.filter(line => {
const lower = line.toLowerCase();
// 레벨 체크 (매우 단순화)
let levelMatch = false;
const isError = lower.includes('[error]') || lower.includes('error:') || lower.includes('traceback');
const isWarn = lower.includes('[warning]') || lower.includes('[warn]') || lower.includes('warn:');
const isInfo = lower.includes('[info]') || lower.includes('info:');
if (isError) {
if (showError) levelMatch = true;
} else if (isWarn) {
if (showWarn) levelMatch = true;
} else if (isInfo) {
if (showInfo) levelMatch = true;
} else {
// 레벨 키워드가 없는 줄은 기본적으로 표시 (맥락 유지)
levelMatch = true;
}
if (!levelMatch) return false;
// 검색어 체크
if (query && !lower.includes(query)) return false;
return true;
});
// 현재 스크롤 위치 저장? 아니면 항상 아래로? -> 보통 필터링하면 아래로 가는게 편함
const currentModel = editor.getModel();
if (currentModel) {
currentModel.setValue(filtered.join('\n'));
}
// editor.revealLine(editor.getModel().getLineCount());
}
// 이벤트 연결
document.getElementById('logSearch').addEventListener('keyup', updateLogs);
document.getElementById('checkInfo').addEventListener('change', updateLogs);
document.getElementById('checkWarn').addEventListener('change', updateLogs);
document.getElementById('checkError').addEventListener('change', updateLogs);
// 맨 아래로 버튼
document.getElementById('btnScrollBottom').addEventListener('click', function () {
editor.revealLine(editor.getModel().getLineCount());
});
// 초기 스크롤 (약간의 지연 후)
setTimeout(() => {
editor.revealLine(editor.getModel().getLineCount());
}, 100);
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,328 @@
{% extends "base.html" %}
{% block title %}시스템 설정 - Dell Server Info{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin_settings.css') }}">
{% endblock %}
{% block content %}
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0">
<i class="bi bi-gear-fill text-primary me-2"></i>시스템 설정
</h2>
<a href="{{ url_for('admin.admin_panel') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i>관리자 패널로 돌아가기
</a>
</div>
<!-- 텔레그램 봇 설정 섹션 -->
<div class="card border shadow-sm mb-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">
<i class="bi bi-telegram text-info me-2"></i>텔레그램 봇 관리
</h5>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addBotModal">
<i class="bi bi-plus-lg me-1"></i>봇 추가
</button>
</div>
<div class="card-body">
<div class="alert alert-info d-flex align-items-center" role="alert">
<i class="bi bi-info-circle-fill me-2 flex-shrink-0 fs-5"></i>
<div>
등록된 모든 <strong>활성(Active)</strong> 봇에게 알림이 동시에 전송됩니다.<br>
<small class="text-muted">알림 종류: 로그인, 회원가입, 로그아웃, 서버 작업 등</small>
</div>
</div>
{% if bots %}
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-4">
{% for bot in bots %}
<div class="col">
<div class="card h-100 border-0 shadow-sm hover-shadow transition-all">
<div
class="card-header bg-white border-0 pt-3 pb-0 d-flex justify-content-between align-items-start">
<div class="d-flex align-items-center">
<div class="avatar-circle bg-primary bg-opacity-10 text-primary me-2 rounded-circle d-flex align-items-center justify-content-center"
style="width: 40px; height: 40px;">
<i class="bi bi-robot fs-5"></i>
</div>
<div>
<h5 class="card-title fw-bold mb-0 text-truncate" style="max-width: 150px;"
title="{{ bot.name }}">
{{ bot.name }}
</h5>
<small class="text-muted" style="font-size: 0.75rem;">ID: {{ bot.id }}</small>
</div>
</div>
{% if bot.is_active %}
<span
class="badge bg-success-subtle text-success border border-success-subtle rounded-pill px-2">
<i class="bi bi-check-circle-fill me-1"></i>Active
</span>
{% else %}
<span
class="badge bg-secondary-subtle text-secondary border border-secondary-subtle rounded-pill px-2">
<i class="bi bi-dash-circle me-1"></i>Inactive
</span>
{% endif %}
</div>
<div class="card-body">
<p class="card-text text-muted small mb-3 text-truncate-2" style="min-height: 2.5em;">
{{ bot.description or "설명이 없습니다." }}
</p>
<div class="bg-light rounded p-2 mb-2">
<div class="d-flex align-items-center text-secondary small mb-1">
<i class="bi bi-key me-2 text-primary"></i>
<span class="fw-semibold me-1">Token:</span>
<span class="font-monospace text-truncate">{{ bot.token[:10] }}...</span>
</div>
<div class="d-flex align-items-center text-secondary small">
<i class="bi bi-chat-dots me-2 text-success"></i>
<span class="fw-semibold me-1">Chat ID:</span>
<span class="font-monospace">{{ bot.chat_id }}</span>
</div>
</div>
<div class="mb-3">
{% set types = (bot.notification_types or "").split(",") %}
{% if 'auth' in types %}
<span
class="badge bg-info-subtle text-info border border-info-subtle rounded-pill me-1"><i
class="bi bi-shield-lock-fill me-1"></i>인증</span>
{% endif %}
{% if 'activity' in types %}
<span
class="badge bg-warning-subtle text-warning border border-warning-subtle rounded-pill me-1"><i
class="bi bi-activity me-1"></i>활동</span>
{% endif %}
{% if 'system' in types %}
<span
class="badge bg-danger-subtle text-danger border border-danger-subtle rounded-pill me-1"><i
class="bi bi-gear-fill me-1"></i>시스템</span>
{% endif %}
</div>
</div>
<div class="card-footer bg-white border-0 pt-0 pb-3">
<div class="btn-group w-100 shadow-sm" role="group">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal"
data-bs-target="#editBotModal{{ bot.id }}">
<i class="bi bi-pencil me-1"></i>수정
</button>
<button type="submit" form="testForm{{ bot.id }}"
class="btn btn-outline-primary btn-sm">
<i class="bi bi-send me-1"></i>테스트
</button>
<button type="submit" form="deleteForm{{ bot.id }}"
class="btn btn-outline-danger btn-sm" onclick="return confirm('정말 삭제하시겠습니까?');">
<i class="bi bi-trash me-1"></i>삭제
</button>
</div>
<!-- Hidden Forms -->
<form id="testForm{{ bot.id }}" action="{{ url_for('admin.test_bot', bot_id=bot.id) }}"
method="post" class="d-none">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
</form>
<form id="deleteForm{{ bot.id }}" action="{{ url_for('admin.delete_bot', bot_id=bot.id) }}"
method="post" class="d-none">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
</form>
</div>
</div>
</div>
<!-- 수정 모달 -->
<div class="modal fade" id="editBotModal{{ bot.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<form action="{{ url_for('admin.edit_bot', bot_id=bot.id) }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">
<i class="bi bi-pencil-square me-2"></i>봇 설정 수정
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold">이름 (식별용)</label>
<input type="text" class="form-control" name="name" value="{{ bot.name }}"
required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Bot Token</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-key"></i></span>
<input type="text" class="form-control font-monospace" name="token"
value="{{ bot.token }}" required>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Chat ID</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-chat-dots"></i></span>
<input type="text" class="form-control font-monospace" name="chat_id"
value="{{ bot.chat_id }}" required>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">알림 유형</label>
<div class="d-flex gap-3">
{% set types = (bot.notification_types or "").split(",") %}
<div class="form-check">
<input class="form-check-input" type="checkbox" name="notify_types"
value="auth" id="edit_auth_{{ bot.id }}" {{ 'checked' if 'auth' in
types else '' }}>
<label class="form-check-label" for="edit_auth_{{ bot.id }}">
인증
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="notify_types"
value="activity" id="edit_activity_{{ bot.id }}" {{ 'checked'
if 'activity' in types else '' }}>
<label class="form-check-label" for="edit_activity_{{ bot.id }}">
활동
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="notify_types"
value="system" id="edit_system_{{ bot.id }}" {{ 'checked'
if 'system' in types else '' }}>
<label class="form-check-label" for="edit_system_{{ bot.id }}">
시스템
</label>
</div>
</div>
<small class="text-muted">선택한 알림 유형만 전송됩니다</small>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">설명</label>
<textarea class="form-control" name="description"
rows="2">{{ bot.description or '' }}</textarea>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_active"
id="activeCheck{{ bot.id }}" {{ 'checked' if bot.is_active else '' }}>
<label class="form-check-label" for="activeCheck{{ bot.id }}">
활성화
</label>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-primary px-4">저장</button>
</div>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<div class="mb-3">
<div class="d-inline-flex align-items-center justify-content-center bg-light rounded-circle"
style="width: 80px; height: 80px;">
<i class="bi bi-robot fs-1 text-secondary"></i>
</div>
</div>
<h5 class="text-muted fw-normal">등록된 텔레그램 봇이 없습니다.</h5>
<p class="text-muted small mb-4">우측 상단의 '봇 추가' 버튼을 눌러 알림을 설정하세요.</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addBotModal">
<i class="bi bi-plus-lg me-1"></i>봇 추가
</button>
</div>
{% endif %}
</div>
</div>
<!-- 봇 추가 모달 -->
<div class="modal fade" id="addBotModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<form action="{{ url_for('admin.add_bot') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">
<i class="bi bi-plus-circle me-2"></i>새 텔레그램 봇 추가
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold">이름 (식별용)</label>
<input type="text" class="form-control" name="name" placeholder="예: 알림용 봇" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Bot Token</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-key"></i></span>
<input type="text" class="form-control font-monospace" name="token"
placeholder="123456:ABC..." required>
</div>
<div class="form-text">BotFather에게 받은 API Token</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Chat ID</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-chat-dots"></i></span>
<input type="text" class="form-control font-monospace" name="chat_id"
placeholder="-100..." required>
</div>
<div class="form-text">메시지를 받을 채팅방 ID (그룹은 음수)</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">알림 유형</label>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="notify_types" value="auth"
id="add_auth" checked>
<label class="form-check-label" for="add_auth">
인증
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="notify_types" value="activity"
id="add_activity" checked>
<label class="form-check-label" for="add_activity">
활동
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="notify_types" value="system"
id="add_system" checked>
<label class="form-check-label" for="add_system">
시스템
</label>
</div>
</div>
<small class="text-muted">선택한 알림 유형만 전송됩니다</small>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">설명</label>
<textarea class="form-control" name="description" rows="2" placeholder="선택 사항"></textarea>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-primary px-4">추가</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,35 +1,58 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Dell Server 정보 및 MAC 주소 처리 시스템">
<title>{% block title %}Dell Server Info, MAC 정보 처리{% endblock %}</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Bootstrap 5.3.3 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
integrity="sha384-tViUnnbYAV00FLIhhi3v/dWt3Jxw4gZQcNoSCxCIFNJVCx7/D55/wXsrNIRANwdD"
crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
integrity="sha384-tViUnnbYAV00FLIhhi3v/dWt3Jxw4gZQcNoSCxCIFNJVCx7/D55/wXsrNIRANwdD" crossorigin="anonymous">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Skip to main content (접근성) -->
<a href="#main-content" class="visually-hidden-focusable">본문으로 건너뛰기</a>
{# 플래시 메시지 (좌측 상단 토스트 스타일) #}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 2000; margin-top: 60px;">
{% for cat, msg in messages %}
<div class="toast align-items-center text-white bg-{{ 'success' if cat == 'success' else 'danger' if cat == 'error' else 'primary' }} border-0 fade show"
role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="3000">
<div class="d-flex">
<div class="toast-body d-flex align-items-center">
<i
class="bi bi-{{ 'check-circle-fill' if cat == 'success' else 'exclamation-diamond-fill' }} me-2 fs-5"></i>
<div>{{ msg }}</div>
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container-fluid">
@@ -37,12 +60,8 @@
<i class="bi bi-server me-2"></i>
Dell Server Info
</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="네비게이션 토글">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="네비게이션 토글">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
@@ -53,44 +72,44 @@
</a>
</li>
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">
<i class="bi bi-hdd-network me-1"></i>ServerInfo
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('xml.xml_management') }}">
<i class="bi bi-file-code me-1"></i>XML Management
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('jobs.jobs_page') }}">
<i class="bi bi-list-task me-1"></i>Job Monitor
</a>
</li>
{% if current_user.is_admin %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.admin_panel') }}">
<i class="bi bi-shield-lock me-1"></i>Admin
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">
<i class="bi bi-box-arrow-right me-1"></i>Logout
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">
<i class="bi bi-hdd-network me-1"></i>ServerInfo
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('xml.xml_management') }}">
<i class="bi bi-file-code me-1"></i>XML Management
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('jobs.jobs_page') }}">
<i class="bi bi-list-task me-1"></i>Job Monitor
</a>
</li>
{% if current_user.is_admin %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.admin_panel') }}">
<i class="bi bi-shield-lock me-1"></i>Admin
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">
<i class="bi bi-box-arrow-right me-1"></i>Logout
</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">
<i class="bi bi-box-arrow-in-right me-1"></i>Login
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">
<i class="bi bi-person-plus me-1"></i>Register
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">
<i class="bi bi-box-arrow-in-right me-1"></i>Login
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">
<i class="bi bi-person-plus me-1"></i>Register
</a>
</li>
{% endif %}
</ul>
</div>
@@ -98,7 +117,12 @@
</nav>
<!-- Main Content -->
<main id="main-content" class="{% if request.endpoint in ['auth.login', 'auth.register', 'auth.reset_password'] %}container mt-5{% else %}container mt-4 container-card{% endif %}">
<main id="main-content"
class="{% if request.endpoint in ['auth.login', 'auth.register', 'auth.reset_password'] %}container mt-5{% else %}container mt-4 container-card{% endif %}">
{# 플래시 메시지 (컨텐츠 상단 표시) #}
{% block content %}{% endblock %}
</main>
@@ -110,17 +134,36 @@
</footer>
<!-- Bootstrap 5.3.3 JS Bundle (Popper.js 포함) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<!-- Socket.IO (필요한 경우만) -->
{% if config.USE_SOCKETIO %}
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"
integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4"
crossorigin="anonymous"></script>
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"
integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4"
crossorigin="anonymous"></script>
{% endif %}
{% block scripts %}{% endblock %}
<!-- Auto-hide Toasts -->
<script>
document.addEventListener('DOMContentLoaded', function () {
var toastElList = [].slice.call(document.querySelectorAll('.toast'));
var toastList = toastElList.map(function (toastEl) {
// 부트스트랩 토스트 인스턴스 생성 (autohide: true 기본값)
var toast = new bootstrap.Toast(toastEl, { delay: 3000 });
toast.show();
// 3초 후 자동으로 DOM에서 제거하고 싶다면 이벤트 리스너 추가 가능
toastEl.addEventListener('hidden.bs.toast', function () {
// toastEl.remove(); // 필요시 제거
});
return toast;
});
});
</script>
</body>
</html>
</html>

View File

@@ -1,51 +1,170 @@
{% extends "base.html" %}
{% block title %}XML 편집: {{ filename }} - Dell Server Info{% endblock %}
{% block extra_css %}
<style>
/* 전체 레이아웃 */
.editor-container {
display: flex;
flex-direction: column;
height: calc(100vh - 160px);
/* 헤더/푸터 제외 높이 (조정 가능) */
min-height: 600px;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
/* 툴바 (헤더) */
.editor-toolbar {
background-color: #f8fafc;
border-bottom: 1px solid #e2e8f0;
padding: 0.75rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.editor-title {
font-size: 1.1rem;
font-weight: 600;
color: #1e293b;
display: flex;
align-items: center;
gap: 0.5rem;
}
.editor-actions {
display: flex;
gap: 0.5rem;
}
/* 에디터 본문 */
#monaco-editor-root {
flex: 1;
width: 100%;
height: 100%;
}
/* 로딩 인디케이터 */
.editor-loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: #64748b;
font-size: 1.2rem;
background: #f1f5f9;
}
</style>
{% endblock %}
{% block content %}
<head>
<meta charset="UTF-8">
<title>Edit XML File</title>
<style>
::-webkit-scrollbar { width: 12px; }
::-webkit-scrollbar-track { background: #f1f1f1; }
::-webkit-scrollbar-thumb { background-color: #888; border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background-color: #555; }
html { scrollbar-width: thin; scrollbar-color: #888 #f1f1f1; }
textarea {
width: 100%;
height: 600px;
padding: 10px;
font-size: 14px;
line-height: 1.5;
background-color: #f9f9f9;
border: 1px solid #ccc;
transition: background-color 0.3s ease;
}
textarea:hover { background-color: #f0f0f0; }
.xml-list-item {
padding: 10px;
background-color: #ffffff;
transition: background-color 0.3s ease;
}
.xml-list-item:hover { background-color: #e9ecef; cursor: pointer; }
.btn { margin-top: 20px; }
</style>
</head>
<body>
<div class="card shadow-lg">
<div class="card-header bg-primary text-white">
<h3>Edit XML File: <strong>{{ filename }}</strong></h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="xmlContent">XML Content</label>
<textarea id="xmlContent" name="content" class="form-control" rows="20">{{ content }}</textarea>
</div>
<button type="submit" class="btn btn-success mt-3">Save Changes</button>
<a href="{{ url_for('xml.xml_management') }}" class="btn btn-secondary mt-3">Cancel</a>
</form>
</div>
<div class="container-fluid py-4 h-100">
<!-- Breadcrumb / Navigation -->
<div class="mb-3 d-flex align-items-center">
<a href="{{ url_for('xml.xml_management') }}" class="text-decoration-none text-muted small fw-bold">
<i class="bi bi-arrow-left me-1"></i>목록으로 돌아가기
</a>
</div>
</body>
<form id="editorForm" method="post" style="height: 100%;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<!-- Monaco Editor의 내용은 submit 시 이 textarea에 동기화됨 -->
<textarea name="content" id="hiddenContent" style="display:none;">{{ content }}</textarea>
<div class="editor-container">
<!-- Toolbar -->
<div class="editor-toolbar">
<div class="editor-title">
<i class="bi bi-filetype-xml text-primary fs-4"></i>
<span>{{ filename }}</span>
<span class="badge bg-light text-secondary border ms-2">XML</span>
</div>
<div class="editor-actions">
<!-- 포맷팅 버튼 (Monaco 기능 호출) -->
<button type="button" class="btn btn-white border text-dark btn-sm fw-bold" id="btnFormat">
<i class="bi bi-magic me-1 text-info"></i> 자동 정렬
</button>
<!-- 저장 버튼 -->
<button type="submit" class="btn btn-primary btn-sm fw-bold px-4">
<i class="bi bi-save me-1"></i> 저장하기
</button>
</div>
</div>
<!-- Editor Area -->
<div id="monaco-editor-root">
<div class="editor-loading">
<div class="spinner-border text-primary me-3" role="status"></div>
<div>에디터를 불러오는 중...</div>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
<!-- Monaco Editor Loader -->
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (typeof require === 'undefined') {
document.querySelector('.editor-loading').innerHTML =
'<div class="text-danger"><i class="bi bi-exclamation-triangle me-2"></i>Monaco Editor를 로드할 수 없습니다. 인터넷 연결을 확인하거나 CDN 차단을 확인하세요.</div>';
return;
}
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], function () {
// 초기 컨텐츠 가져오기
var initialContent = document.getElementById('hiddenContent').value;
var container = document.getElementById('monaco-editor-root');
// 기존 로딩 메시지 제거
container.innerHTML = '';
// 에디터 생성
var editor = monaco.editor.create(container, {
value: initialContent,
language: 'xml',
theme: 'vs', // or 'vs-dark'
automaticLayout: true,
minimap: { enabled: true },
fontSize: 14,
scrollBeyondLastLine: false,
lineNumbers: 'on',
formatOnPaste: true,
formatOnType: true,
wordWrap: 'on'
});
// 1. 폼 제출 시 에디터 내용을 textarea에 동기화
document.getElementById('editorForm').addEventListener('submit', function () {
document.getElementById('hiddenContent').value = editor.getValue();
});
// 2. 자동 정렬(Format) 버튼 기능 연결
document.getElementById('btnFormat').addEventListener('click', function () {
editor.getAction('editor.action.formatDocument').run();
});
// 3. Ctrl+S 저장 단축키 지원
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
document.getElementById('editorForm').requestSubmit();
});
}, function (err) {
// 로드 실패 시 에러 표시
document.querySelector('.editor-loading').innerHTML =
'<div class="text-danger"><i class="bi bi-exclamation-triangle me-2"></i>에디터 리소스 로드 실패: ' + err.message + '</div>';
});
});
</script>
{% endblock %}

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -7,6 +8,7 @@
<title>Dell iDRAC 멀티 서버 펌웨어 관리</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/idrac_style.css') }}">
</head>
<body>
<div class="container">
<header>
@@ -84,43 +86,66 @@
</div>
<!-- 펌웨어 버전 관리 탭 -->
<div id="versions-tab" class="tab-content">
<section class="card">
<div class="card-header">
<h2>📦 펌웨어 최신 버전 관리</h2>
<div class="header-actions">
<button onclick="syncDellCatalog('PowerEdge R750')" class="btn btn-success">🔄 Dell에서 버전 가져오기</button>
<button onclick="showAddVersionModal()" class="btn btn-primary">+ 버전 추가</button>
</div>
</div>
<div id="versions-tab" class="tab-content">
<section class="card">
<div class="card-header">
<h2>📦 펌웨어 최신 버전 관리</h2>
<div class="header-actions">
<button onclick="syncDellCatalog('PowerEdge R750')" class="btn btn-success">🔄 Dell에서 버전
가져오기</button>
<button onclick="syncFromDRM()" class="btn btn-info">📁 DRM에서 가져오기</button>
<button onclick="showAddVersionModal()" class="btn btn-primary">+ 버전 추가</button>
</div>
</div>
<div class="table-container">
<table class="server-table">
<thead>
<tr>
<th>컴포넌트</th>
<th>최신 버전</th>
<th>서버 모델</th>
<th>릴리즈 날짜</th>
<th>중요</th>
<th width="150">작업</th>
</tr>
</thead>
<tbody id="version-list">
<tr>
<td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td>
</tr>
</tbody>
</table>
<div class="table-container">
<table class="server-table">
<thead>
<tr>
<th>컴포넌트</th>
<th>최신 버전</th>
<th>서버 모델</th>
<th>릴리즈 날짜</th>
<th>중요</th>
<th width="150">작업</th>
</tr>
</thead>
<tbody id="version-list">
<tr>
<td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
</section>
</div>
<!-- 버전 비교 결과 탭 -->
<div id="comparison-tab" class="tab-content">
<section class="card">
<h2>🔍 펌웨어 버전 비교 결과</h2>
<div id="comparison-result"></div>
<div class="card-header">
<h2>🔍 펌웨어 버전 비교 결과</h2>
<button onclick="compareSelectedFirmware()" class="btn btn-primary">🔄 다시 비교</button>
</div>
<!-- 로딩 상태 -->
<div id="comparison-loading" style="display: none;">
<div class="loading-spinner"></div>
<p>버전 비교 중...</p>
</div>
<!-- 비교 결과 -->
<div id="comparison-result">
<div class="info-text">
<p><strong>📋 사용 방법:</strong></p>
<ol style="margin-top: 10px; margin-left: 20px; line-height: 1.8;">
<li>"서버 관리" 탭에서 비교할 서버를 선택하세요</li>
<li>"버전 비교" 버튼을 클릭하세요</li>
<li>현재 설치된 펌웨어 버전과 최신 버전을 자동으로 비교합니다</li>
<li>업데이트가 필요한 컴포넌트를 확인할 수 있습니다</li>
</ol>
</div>
</div>
</section>
</div>
@@ -242,11 +267,12 @@
</div>
</div>
</div>
<section class="card" id="result-section" style="display:none;">
<div id="result-content"></div>
</section>
<section class="card" id="result-section" style="display:none;">
<div id="result-content"></div>
</section>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="{{ url_for('static', filename='js/idrac_main.js') }}"></script>
</body>
</html>
</html>

View File

@@ -3,20 +3,7 @@
{% block content %}
<div class="container-fluid py-4">
{# 플래시 메시지 #}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="position-fixed top-0 end-0 p-3" style="z-index: 1050">
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }} alert-dismissible fade show shadow-lg" role="alert">
<i class="bi bi-{{ 'check-circle' if cat == 'success' else 'exclamation-triangle' }} me-2"></i>
{{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{# 헤더 섹션 #}
<div class="row mb-4">
@@ -34,51 +21,78 @@
{# IP 처리 카드 #}
<div class="col-lg-6">
<div class="card border shadow-sm h-100">
<div class="card-header bg-primary text-white border-0 py-2">
<h6 class="mb-0 fw-semibold">
<div class="card-header bg-light border-0 py-2">
<h6 class="mb-0">
<i class="bi bi-hdd-network me-2"></i>
IP 처리
</h6>
</div>
<div class="card-body p-4">
<form id="ipForm" method="post" action="{{ url_for('main.process_ips') }}">
<div class="card-body p-4 h-100 d-flex flex-column">
<form id="ipForm" method="post" action="{{ url_for('main.process_ips') }}" class="h-100 d-flex flex-column">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{# 스크립트 선택 #}
<div class="mb-3">
<label for="script" class="form-label">스크립트 선택</label>
<select id="script" name="script" class="form-select" required>
<select id="script" name="script" class="form-select" required autocomplete="off">
<option value="">스크립트를 선택하세요</option>
{% for script in scripts %}
{% if grouped_scripts %}
{% for category, s_list in grouped_scripts.items() %}
<optgroup label="{{ category }}">
{% for script in s_list %}
<option value="{{ script }}">{{ script }}</option>
{% endfor %}
</optgroup>
{% endfor %}
{% else %}
{# 만약 grouped_scripts가 없는 경우(하위 호환) #}
{% for script in scripts %}
<option value="{{ script }}">{{ script }}</option>
{% endfor %}
{% endif %}
</select>
</div>
{# XML 파일 선택 (조건부) #}
<div class="mb-3" id="xmlFileGroup" style="display:none;">
<label for="xmlFile" class="form-label">XML 파일 선택</label>
<select id="xmlFile" name="xmlFile" class="form-select">
<option value="">XML 파일 선택</option>
{% for xml_file in xml_files %}
<option value="{{ xml_file }}">{{ xml_file }}</option>
<option value="{{ xml_file }}">{{ xml_file }}</option>
{% endfor %}
</select>
</div>
{# IP 주소 입력 #}
<div class="mb-3">
<label for="ips" class="form-label">
IP 주소 (각 줄에 하나)
<span class="badge bg-secondary ms-2" id="ipLineCount">0 대설정</span>
<div class="mb-3 flex-grow-1 d-flex flex-column">
<label for="ips" class="form-label w-100 d-flex justify-content-between align-items-end mb-2">
<span class="mb-1">
IP 주소
<span class="badge bg-secondary ms-1" id="ipLineCount">0</span>
</span>
<div class="d-flex align-items-center gap-1">
<button type="button" class="btn btn-sm btn-outline-secondary px-2 py-1" id="btnClearIps"
title="입력 내용 지우기" style="font-size: 0.75rem;">
<i class="bi bi-trash me-1"></i>지우기
</button>
<button type="button" class="btn btn-sm btn-outline-primary px-2 py-1" id="btnStartScan"
title="10.10.0.1 ~ 255 자동 스캔" style="font-size: 0.75rem;">
<i class="bi bi-search me-1"></i>IP 스캔
</button>
</div>
</label>
<textarea id="ips" name="ips" rows="4" class="form-control font-monospace"
placeholder="예:&#10;192.168.1.1&#10;192.168.1.2&#10;192.168.1.3" required></textarea>
<textarea id="ips" name="ips" class="form-control font-monospace flex-grow-1"
placeholder="예:&#10;192.168.1.1&#10;192.168.1.2" required style="resize: none;"></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">
처리
</button>
<div class="mt-auto">
<button type="submit"
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move">
<div class="rounded-circle bg-primary bg-opacity-10 text-primary p-1">
<i class="bi bi-play-circle-fill fs-5"></i>
</div>
<span class="fw-medium text-dark" style="font-size: 0.8rem;">처리 시작</span>
</button>
</div>
</form>
</div>
</div>
@@ -87,8 +101,8 @@
{# 공유 작업 카드 #}
<div class="col-lg-6">
<div class="card border shadow-sm h-100">
<div class="card-header bg-success text-white border-0 py-2">
<h6 class="mb-0 fw-semibold">
<div class="card-header bg-light border-0 py-2">
<h6 class="mb-0">
<i class="bi bi-share me-2"></i>
공유 작업
</h6>
@@ -102,24 +116,38 @@
서버 리스트 (덮어쓰기)
<span class="badge bg-secondary ms-2" id="serverLineCount">0 대설정</span>
</label>
<textarea id="server_list_content" name="server_list_content" rows="8"
class="form-control font-monospace" style="font-size: 0.95rem;"
placeholder="서버 리스트를 입력하세요..."></textarea>
<textarea id="server_list_content" name="server_list_content" rows="8" class="form-control font-monospace"
style="font-size: 0.95rem;" placeholder="서버 리스트를 입력하세요..."></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}"
class="btn btn-secondary">
MAC to Excel
</button>
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}"
class="btn btn-success">
GUID to Excel
</button>
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}"
class="btn btn-warning">
GPU to Excel
</button>
<div class="row g-2">
<div class="col-4">
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}"
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move h-100">
<div class="rounded-circle bg-primary bg-opacity-10 text-primary p-1">
<i class="bi bi-file-earmark-spreadsheet fs-5"></i>
</div>
<span class="fw-medium text-dark" style="font-size: 0.8rem;">MAC to Excel</span>
</button>
</div>
<div class="col-4">
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}"
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move h-100">
<div class="rounded-circle bg-success bg-opacity-10 text-success p-1">
<i class="bi bi-file-earmark-excel fs-5"></i>
</div>
<span class="fw-medium text-dark" style="font-size: 0.8rem;">GUID to Excel</span>
</button>
</div>
<div class="col-4">
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}"
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move h-100">
<div class="rounded-circle bg-danger bg-opacity-10 text-danger p-1">
<i class="bi bi-gpu-card fs-5"></i>
</div>
<span class="fw-medium text-dark" style="font-size: 0.8rem;">GPU to Excel</span>
</button>
</div>
</div>
</form>
</div>
@@ -137,8 +165,8 @@
<span class="fw-semibold">처리 진행률</span>
</div>
<div class="progress" style="height: 25px;">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success"
role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success"
role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span class="fw-semibold">0%</span>
</div>
</div>
@@ -159,104 +187,152 @@
</div>
<div class="card-body p-4 file-tools">
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xxl-5 g-3 align-items-end">
<div class="d-flex flex-column gap-3">
<!-- ZIP 다운로드 -->
<div class="col">
<label class="form-label text-nowrap">ZIP 다운로드</label>
<form method="post" action="{{ url_for('main.download_zip') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="input-group">
<input type="text" class="form-control" name="zip_filename" placeholder="파일명" required>
<button class="btn btn-primary" type="submit">다운로드</button>
<!-- 상단: 입력형 도구 (다운로드/백업) -->
<div class="row g-2">
<!-- ZIP 다운로드 -->
<div class="col-6">
<div class="card h-100 border-primary-subtle bg-primary-subtle bg-opacity-10">
<div class="card-body p-2 d-flex flex-column justify-content-center">
<h6 class="card-title fw-bold text-primary mb-1 small" style="font-size: 0.75rem;">
<i class="bi bi-file-earmark-zip me-1"></i>ZIP
</h6>
<form method="post" action="{{ url_for('main.download_zip') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="input-group input-group-sm">
<input type="text" class="form-control border-primary-subtle form-control-sm"
name="zip_filename" placeholder="파일명" required
style="font-size: 0.75rem; padding: 0.2rem 0.5rem;">
<button class="btn btn-primary btn-sm px-2" type="submit">
<i class="bi bi-download" style="font-size: 0.75rem;"></i>
</button>
</div>
</form>
</div>
</div>
</form>
</div>
</div>
<!-- 파일 백업 -->
<div class="col">
<label class="form-label text-nowrap">파일 백업</label>
<form method="post" action="{{ url_for('main.backup_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="input-group">
<input type="text" class="form-control" name="backup_prefix" placeholder="PO로 시작">
<button class="btn btn-success" type="submit">백업</button>
<!-- 파일 백업 -->
<div class="col-6">
<div class="card h-100 border-success-subtle bg-success-subtle bg-opacity-10">
<div class="card-body p-2 d-flex flex-column justify-content-center">
<h6 class="card-title fw-bold text-success mb-1 small" style="font-size: 0.75rem;">
<i class="bi bi-hdd-network me-1"></i>백업
</h6>
<form method="post" action="{{ url_for('main.backup_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="input-group input-group-sm">
<input type="text" class="form-control border-success-subtle form-control-sm"
name="backup_prefix" placeholder="ex)PO-20251117-0015_20251223_판교_R6615(TY1A)"
style="font-size: 0.75rem; padding: 0.2rem 0.5rem;">
<button class="btn btn-success btn-sm px-2" type="submit">
<i class="bi bi-save" style="font-size: 0.75rem;"></i>
</button>
</div>
</form>
</div>
</div>
</form>
</div>
</div>
<!-- MAC 파일 이동 -->
<div class="col">
<label class="form-label text-nowrap">MAC 파일 이동</label>
<form id="macMoveForm" method="post" action="{{ url_for('utils.move_mac_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-warning w-100" type="submit">MAC Move</button>
</form>
</div>
<!-- 하단: 원클릭 액션 (파일 정리) -->
<div class="card bg-light border-0">
<div class="card-body p-3">
<small class="text-muted fw-bold text-uppercase mb-2 d-block">
<i class="bi bi-folder-symlink me-1"></i>파일 정리 (Quick Move)
</small>
<div class="row g-2">
<!-- MAC Move -->
<div class="col-4">
<form id="macMoveForm" method="post" action="{{ url_for('utils.move_mac_files') }}" class="h-100">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button
class="btn btn-white bg-white border shadow-sm w-100 h-100 py-1 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move"
type="submit">
<div class="rounded-circle bg-primary bg-opacity-10 text-primary p-1">
<i class="bi bi-cpu fs-6"></i>
</div>
<span class="fw-medium text-dark" style="font-size: 0.75rem;">MAC</span>
</button>
</form>
</div>
<!-- GUID 파일 이동 -->
<div class="col">
<label class="form-label text-nowrap">GUID 파일 이동</label>
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-info w-100" type="submit">GUID Move</button>
</form>
</div>
<!-- GUID Move -->
<div class="col-4">
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}" class="h-100">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button
class="btn btn-white bg-white border shadow-sm w-100 h-100 py-1 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move"
type="submit">
<div class="rounded-circle bg-success bg-opacity-10 text-success p-1">
<i class="bi bi-fingerprint fs-6"></i>
</div>
<span class="fw-medium text-dark" style="font-size: 0.75rem;">GUID</span>
</button>
</form>
</div>
<!-- GPU 파일 이동 -->
<div class="col">
<label class="form-label text-nowrap">GPU 파일 이동</label>
<form id="gpuMoveForm" method="post" action="{{ url_for('utils.move_gpu_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-secondary w-100" type="submit">GPU Move</button>
</form>
<div class="col-4">
<form id="gpuMoveForm" method="post" action="{{ url_for('utils.move_gpu_files') }}" class="h-100">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button
class="btn btn-white bg-white border shadow-sm w-100 h-100 py-1 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move"
type="submit">
<div class="rounded-circle bg-danger bg-opacity-10 text-danger p-1">
<i class="bi bi-gpu-card fs-6"></i>
</div>
<span class="fw-medium text-dark" style="font-size: 0.75rem;">GPU</span>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{# 처리된 파일 목록 #}
<div class="row mb-4 processed-list">
<div class="col">
<div class="card border shadow-sm">
<div class="card-header bg-light border-0 py-2 d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="bi bi-files me-2"></i>
처리된 파일 목록
</h6>
</div>
<div class="card-body p-4">
{% if files_to_display and files_to_display|length > 0 %}
{# 처리된 파일 목록 #}
<div class="row mb-4 processed-list">
<div class="col">
<div class="card border shadow-sm">
<div class="card-header bg-light border-0 py-2 d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="bi bi-files me-2"></i>
처리된 파일 목록
</h6>
</div>
<div class="card-body p-4">
{% if files_to_display and files_to_display|length > 0 %}
<div class="row g-3">
{% for file_info in files_to_display %}
<div class="col-auto">
<div class="file-card-compact border rounded p-2 text-center">
<a href="{{ url_for('main.download_file', filename=file_info.file) }}"
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
download title="{{ file_info.name or file_info.file }}">
{{ file_info.name or file_info.file }}
</a>
<div class="file-card-buttons d-flex gap-2 justify-content-center">
<button type="button" class="btn btn-sm btn-outline btn-view-processed flex-fill"
data-bs-toggle="modal" data-bs-target="#fileViewModal"
data-folder="idrac_info"
data-filename="{{ file_info.file }}">
보기
<div class="col-auto">
<div class="file-card-compact border rounded p-2 text-center">
<a href="{{ url_for('main.download_file', filename=file_info.file) }}"
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2" download
title="{{ file_info.name or file_info.file }}">
{{ file_info.name or file_info.file }}
</a>
<div class="file-card-buttons d-flex gap-2 justify-content-center">
<button type="button" class="btn btn-sm btn-outline btn-view-processed flex-fill"
data-bs-toggle="modal" data-bs-target="#fileViewModal" data-folder="idrac_info"
data-filename="{{ file_info.file }}">
보기
</button>
<form action="{{ url_for('main.delete_file', filename=file_info.file) }}" method="post"
class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline btn-delete-processed flex-fill"
onclick="return confirm('삭제하시겠습니까?');">
삭제
</button>
<form action="{{ url_for('main.delete_file', filename=file_info.file) }}"
method="post" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline btn-delete-processed flex-fill"
onclick="return confirm('삭제하시겠습니까?');">
삭제
</button>
</form>
</div>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
@@ -267,54 +343,53 @@
<!-- 이전 페이지 -->
{% if page > 1 %}
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=page-1) }}">
<i class="bi bi-chevron-left"></i> 이전
</a>
</li>
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=page-1) }}">
<i class="bi bi-chevron-left"></i> 이전
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link"><i class="bi bi-chevron-left"></i> 이전</span>
</li>
<li class="page-item disabled">
<span class="page-link"><i class="bi bi-chevron-left"></i> 이전</span>
</li>
{% endif %}
<!-- 페이지 번호 (최대 10개 표시) -->
{% set start_page = ((page - 1) // 10) * 10 + 1 %}
{% set end_page = [start_page + 9, total_pages]|min %}
{% for p in range(start_page, end_page + 1) %}
<li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('main.index', page=p) }}">{{ p }}</a>
</li>
<li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('main.index', page=p) }}">{{ p }}</a>
</li>
{% endfor %}
<!-- 다음 페이지 -->
{% if page < total_pages %}
<li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=page+1) }}">
다음 <i class="bi bi-chevron-right"></i>
</a>
{% if page < total_pages %} <li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=page+1) }}">
다음 <i class="bi bi-chevron-right"></i>
</a>
</li>
{% else %}
{% else %}
<li class="page-item disabled">
<span class="page-link">다음 <i class="bi bi-chevron-right"></i></span>
</li>
{% endif %}
{% endif %}
</ul>
</nav>
{% endif %}
<!-- /페이지네이션 -->
{% else %}
{% else %}
<div class="text-center py-5">
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">표시할 파일이 없습니다.</p>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
</div>
{# 백업된 파일 목록 #}
<div class="row backup-list">
@@ -328,55 +403,52 @@
</div>
<div class="card-body p-4">
{% if backup_files and backup_files|length > 0 %}
<div class="list-group">
{% for date, info in backup_files.items() %}
<div class="list-group-item border rounded mb-2 p-0 overflow-hidden">
<div class="d-flex justify-content-between align-items-center p-3 bg-light">
<div class="d-flex align-items-center">
<i class="bi bi-calendar3 text-primary me-2"></i>
<strong>{{ date }}</strong>
<span class="badge bg-primary ms-3">{{ info.count }} 파일</span>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button"
data-bs-toggle="collapse" data-bs-target="#collapse-{{ loop.index }}"
aria-expanded="false">
<i class="bi bi-chevron-down"></i>
</button>
</div>
<div id="collapse-{{ loop.index }}" class="collapse">
<div class="p-3">
<div class="row g-3">
{% for file in info.files %}
<div class="col-auto">
<div class="file-card-compact border rounded p-2 text-center">
<a href="{{ url_for('main.download_backup_file', date=date, filename=file) }}"
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
download title="{{ file }}">
{{ file.rsplit('.', 1)[0] }}
</a>
<div class="file-card-single-button">
<button type="button" class="btn btn-sm btn-outline btn-view-backup w-100"
data-bs-toggle="modal" data-bs-target="#fileViewModal"
data-folder="backup"
data-date="{{ date }}"
data-filename="{{ file }}">
보기
</button>
</div>
</div>
</div>
{% endfor %}
<div class="list-group">
{% for date, info in backup_files.items() %}
<div class="list-group-item border rounded mb-2 p-0 overflow-hidden">
<div class="d-flex justify-content-between align-items-center p-3 bg-light">
<div class="d-flex align-items-center">
<i class="bi bi-calendar3 text-primary me-2"></i>
<strong>{{ date }}</strong>
<span class="badge bg-primary ms-3">{{ info.count }} 파일</span>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse"
data-bs-target="#collapse-{{ loop.index }}" aria-expanded="false">
<i class="bi bi-chevron-down"></i>
</button>
</div>
<div id="collapse-{{ loop.index }}" class="collapse">
<div class="p-3">
<div class="row g-3">
{% for file in info.files %}
<div class="col-auto">
<div class="file-card-compact border rounded p-2 text-center">
<a href="{{ url_for('main.download_backup_file', date=date, filename=file) }}"
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2" download
title="{{ file }}">
{{ file.rsplit('.', 1)[0] }}
</a>
<div class="file-card-single-button">
<button type="button" class="btn btn-sm btn-outline btn-view-backup w-100"
data-bs-toggle="modal" data-bs-target="#fileViewModal" data-folder="backup"
data-date="{{ date }}" data-filename="{{ file }}">
보기
</button>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">백업된 파일이 없습니다.</p>
</div>
<div class="text-center py-5">
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">백업된 파일이 없습니다.</p>
</div>
{% endif %}
</div>
</div>
@@ -397,8 +469,8 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<div class="modal-body bg-light">
<pre id="fileViewContent" class="mb-0 p-3 bg-white border rounded font-monospace"
style="white-space:pre-wrap;word-break:break-word;max-height:70vh;">불러오는 중...</pre>
<pre id="fileViewContent" class="mb-0 p-3 bg-white border rounded font-monospace"
style="white-space:pre-wrap;word-break:break-word;max-height:70vh;">불러오는 중...</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
@@ -408,279 +480,76 @@
</div>
</div>
</div>
{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
<!-- Tom Select CSS (Bootstrap 5 theme) -->
<link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.bootstrap5.min.css" rel="stylesheet">
<style>
/* Tom Select 미세 조정 */
.ts-wrapper.form-select {
padding: 0 !important;
border: none !important;
}
.ts-control {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 0.375rem 0.75rem;
}
.ts-wrapper.focus .ts-control {
border-color: #86b7fe;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
/* Quick Move 버튼 호버 효과 */
.btn-quick-move {
transition: all 0.2s ease-in-out;
}
.btn-quick-move:hover {
transform: translateY(-3px);
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .1) !important;
background-color: #f8f9fa !important;
border-color: #dee2e6 !important;
}
.btn-quick-move:active {
transform: translateY(0);
}
</style>
{% endblock %}
{% block scripts %}
<style>
/* ===== 공통 파일 카드 컴팩트 스타일 ===== */
.file-card-compact {
transition: all 0.2s ease;
background: #fff;
min-width: 120px;
max-width: 200px;
}
{{ super() }}
.file-card-compact:hover {
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.file-card-compact a {
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
/* ===== 목록별 버튼 분리 규칙 ===== */
/* 처리된 파일 목록 전용 컨테이너(보기/삭제 2열) */
.processed-list .file-card-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: .5rem;
}
/* 보기(처리된) */
.processed-list .btn-view-processed {
border-color: #3b82f6;
color: #1d4ed8;
padding: .425rem .6rem;
font-size: .8125rem;
font-weight: 600;
}
.processed-list .btn-view-processed:hover {
background: rgba(59,130,246,.08);
}
/* 삭제(처리된) — 더 작게 */
.processed-list .btn-delete-processed {
border-color: #ef4444;
color: #b91c1c;
padding: .3rem .5rem;
font-size: .75rem;
font-weight: 600;
}
.processed-list .btn-delete-processed:hover {
background: rgba(239,68,68,.08);
}
/* 백업 파일 목록 전용 컨테이너(단일 버튼) */
.backup-list .file-card-single-button {
display: flex;
margin-top: .25rem;
}
/* 보기(백업) — 강조 색상 */
.backup-list .btn-view-backup {
width: 100%;
border-color: #10b981;
color: #047857;
padding: .45rem .75rem;
font-size: .8125rem;
font-weight: 700;
}
.backup-list .btn-view-backup:hover {
background: rgba(16,185,129,.08);
}
/* ===== 백업 파일 날짜 헤더 ===== */
.list-group-item .bg-light {
transition: background-color 0.2s ease;
}
.list-group-item:hover .bg-light {
background-color: #e9ecef !important;
}
/* ===== 진행바 애니메이션 ===== */
.progress {
border-radius: 10px;
overflow: hidden;
}
.progress-bar {
transition: width 0.6s ease;
}
/* ===== 반응형 텍스트 ===== */
@media (max-width: 768px) {
.card-body {
padding: 1.5rem !important;
}
}
/* ===== 스크롤바 스타일링(모달) ===== */
.modal-body pre::-webkit-scrollbar { width: 8px; height: 8px; }
.modal-body pre::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
.modal-body pre::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }
.modal-body pre::-webkit-scrollbar-thumb:hover { background: #555; }
</style>
<!-- Tom Select JS -->
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
// ─────────────────────────────────────────────────────────────
// 스크립트 선택 시 XML 드롭다운 토글
// ─────────────────────────────────────────────────────────────
const TARGET_SCRIPT = "02-set_config.py";
const scriptSelect = document.getElementById('script');
const xmlGroup = document.getElementById('xmlFileGroup');
function toggleXml() {
if (!scriptSelect || !xmlGroup) return;
if (scriptSelect.value === TARGET_SCRIPT) {
xmlGroup.style.display = 'block';
xmlGroup.classList.add('fade-in');
} else {
xmlGroup.style.display = 'none';
document.addEventListener('DOMContentLoaded', function () {
// Tom Select 초기화
// 모바일 등 환경 고려, 검색 가능하게 설정
if (document.getElementById('script')) {
new TomSelect("#script", {
create: false,
sortField: {
field: "text",
direction: "asc"
},
placeholder: "스크립트를 검색하거나 선택하세요...",
plugins: ['clear_button'],
allowEmptyOption: true,
});
}
}
if (scriptSelect) {
toggleXml();
scriptSelect.addEventListener('change', toggleXml);
}
// ─────────────────────────────────────────────────────────────
// 파일 보기 모달
// ─────────────────────────────────────────────────────────────
const modalEl = document.getElementById('fileViewModal');
const titleEl = document.getElementById('fileViewModalLabel');
const contentEl = document.getElementById('fileViewContent');
if (modalEl) {
modalEl.addEventListener('show.bs.modal', async (ev) => {
const btn = ev.relatedTarget;
const folder = btn?.getAttribute('data-folder') || '';
const date = btn?.getAttribute('data-date') || '';
const filename = btn?.getAttribute('data-filename') || '';
titleEl.innerHTML = `<i class="bi bi-file-text me-2"></i>${filename || '파일'}`;
contentEl.textContent = '불러오는 중...';
const params = new URLSearchParams();
if (folder) params.set('folder', folder);
if (date) params.set('date', date);
if (filename) params.set('filename', filename);
try {
const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' });
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
contentEl.textContent = data?.content ?? '(빈 파일)';
} catch (e) {
contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e);
}
});
}
// ─────────────────────────────────────────────────────────────
// 진행바 업데이트
// ─────────────────────────────────────────────────────────────
window.updateProgress = function (val) {
const bar = document.getElementById('progressBar');
if (!bar) return;
const v = Math.max(0, Math.min(100, Number(val) || 0));
bar.style.width = v + '%';
bar.setAttribute('aria-valuenow', v);
bar.innerHTML = `<span class="fw-semibold">${v}%</span>`;
};
// ─────────────────────────────────────────────────────────────
// CSRF 토큰
// ─────────────────────────────────────────────────────────────
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
// ─────────────────────────────────────────────────────────────
// 공통 POST 함수
// ─────────────────────────────────────────────────────────────
async function postFormAndHandle(url) {
const res = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'X-CSRFToken': csrfToken,
'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8',
},
});
const ct = (res.headers.get('content-type') || '').toLowerCase();
if (ct.includes('application/json')) {
const data = await res.json();
if (data.success === false) {
throw new Error(data.error || ('HTTP ' + res.status));
}
return data;
}
return { success: true, html: true };
}
// ─────────────────────────────────────────────────────────────
// MAC 파일 이동
// ─────────────────────────────────────────────────────────────
const macForm = document.getElementById('macMoveForm');
if (macForm) {
macForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = macForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(macForm.action);
location.reload();
} catch (err) {
alert('MAC 이동 중 오류: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// ─────────────────────────────────────────────────────────────
// GUID 파일 이동
// ─────────────────────────────────────────────────────────────
const guidForm = document.getElementById('guidMoveForm');
if (guidForm) {
guidForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = guidForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(guidForm.action);
location.reload();
} catch (err) {
alert('GUID 이동 중 오류: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// ─────────────────────────────────────────────────────────────
// 알림 자동 닫기
// ─────────────────────────────────────────────────────────────
setTimeout(() => {
document.querySelectorAll('.alert').forEach(alert => {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
});
}, 5000);
});
});
</script>
<script src="{{ url_for('static', filename='js/index.js') }}?v={{ range(1, 100000) | random }}"></script>
<!-- 외부 script.js 파일 (IP 폼 처리 로직 포함) -->
<script src="{{ url_for('static', filename='script.js') }}"></script>
{% endblock %}
<script src="{{ url_for('static', filename='script.js') }}?v={{ range(1, 100000) | random }}"></script>
{% endblock %}

View File

@@ -1,6 +1,18 @@
{% extends "base.html" %}
{% block title %}iDRAC Job Queue 모니터링 (Redfish){% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/jobs.css') }}">
{% endblock %}
{% block scripts %}
<!-- CSRF Token for JavaScript -->
<script>
const csrfToken = "{{ csrf_token() }}";
</script>
<script src="{{ url_for('static', filename='js/jobs.js') }}"></script>
{% endblock %}
{% block content %}
<div class="container-fluid py-3">
<!-- 헤더 -->
@@ -40,8 +52,8 @@
<small class="text-muted d-block mb-2">
한 줄에 하나씩 입력 (쉼표/세미콜론/공백 구분 가능, # 주석 지원)
</small>
<textarea id="ipInput" class="form-control font-monospace" rows="4"
placeholder="10.10.0.11&#10;10.10.0.12&#10;# 주석 가능"></textarea>
<textarea id="ipInput" class="form-control font-monospace" rows="4"
placeholder="10.10.0.11&#10;10.10.0.12&#10;# 주석 가능"></textarea>
<div class="mt-2">
<small class="text-muted"><strong id="ip-count">0</strong>개 IP</small>
</div>
@@ -60,13 +72,13 @@
</label>
</div>
</div>
<div class="col-auto">
<button id="btn-refresh" class="btn btn-primary btn-sm" disabled>
<i class="bi bi-arrow-clockwise"></i> 지금 새로고침
</button>
</div>
<div class="col-auto">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch">
@@ -75,7 +87,7 @@
</label>
</div>
</div>
<div class="col-auto">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="showCompletedSwitch" checked>
@@ -150,494 +162,4 @@
</div>
</div>
<style>
.status-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #6c757d;
}
.status-dot.active {
background-color: #198754;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ↓↓↓ 여기부터 추가 ↓↓↓ */
/* 테이블 텍스트 한 줄 처리 */
#jobs-table {
table-layout: fixed;
width: 100%;
}
#jobs-table td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300px;
}
/* 열별 너비 고정 */
#jobs-table td:nth-child(1) { max-width: 110px; } /* IP */
#jobs-table td:nth-child(2) { max-width: 160px; font-size: 0.85rem; } /* JID */
#jobs-table td:nth-child(3) { max-width: 200px; } /* 작업명 */
#jobs-table td:nth-child(4) { max-width: 180px; } /* 상태 */
#jobs-table td:nth-child(5) { max-width: 120px; } /* 진행률 */
#jobs-table td:nth-child(6) { max-width: 300px; } /* 메시지 */
#jobs-table td:nth-child(7) { max-width: 150px; } /* 시간 */
/* 마우스 올리면 전체 텍스트 보기 */
#jobs-table td:hover {
white-space: normal;
overflow: visible;
position: relative;
z-index: 1000;
background-color: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
/* ↑↑↑ 여기까지 추가 ↑↑↑ */
.status-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #6c757d;
}
.status-dot.active {
background-color: #198754;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
<script>
const csrfToken = "{{ csrf_token() }}";
// ========== 전역 변수 ==========
let CONFIG = {
grace_minutes: 60,
recency_hours: 24,
poll_interval_ms: 10000
};
let monitoringOn = false;
let pollTimer = null;
let lastRenderHash = "";
// ========== Elements ==========
const $ = id => document.getElementById(id);
const $body = $('jobs-body');
const $last = $('last-updated');
const $loading = $('loading-indicator');
const $btn = $('btn-refresh');
const $auto = $('autoRefreshSwitch');
const $ipInput = $('ipInput');
const $ipCount = $('ip-count');
const $btnLoad = $('btn-load-file');
const $btnApply = $('btn-apply');
const $monSw = $('monitorSwitch');
const $statusDot = $('status-dot');
const $statusText = $('status-text');
const $showCompleted = $('showCompletedSwitch');
const $pollInterval = $('poll-interval');
// Stats
const $statTotal = $('stat-total');
const $statRunning = $('stat-running');
const $statCompleted = $('stat-completed');
const $statError = $('stat-error');
// ========== LocalStorage Keys ==========
const LS_IPS = 'idrac_job_ips';
const LS_MON = 'idrac_monitor_on';
const LS_AUTO = 'idrac_monitor_auto';
const LS_SHOW_COMPLETED = 'idrac_show_completed';
// ========== 유틸리티 ==========
function parseIps(text) {
if (!text) return [];
const raw = text.replace(/[,;]+/g, '\n');
const out = [], seen = new Set();
raw.split('\n').forEach(line => {
const parts = line.trim().split(/\s+/);
parts.forEach(p => {
if (!p || p.startsWith('#')) return;
if (!seen.has(p)) {
seen.add(p);
out.push(p);
}
});
});
return out;
}
function getIpsFromUI() { return parseIps($ipInput.value); }
function setIpsToUI(ips) {
$ipInput.value = (ips || []).join('\n');
updateIpCount();
}
function updateIpCount() {
const ips = getIpsFromUI();
$ipCount.textContent = ips.length;
}
function saveIps() {
localStorage.setItem(LS_IPS, JSON.stringify(getIpsFromUI()));
updateIpCount();
}
function loadIps() {
try {
const v = JSON.parse(localStorage.getItem(LS_IPS) || "[]");
return Array.isArray(v) ? v : [];
} catch {
return [];
}
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, m => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;',
'"': '&quot;', "'": '&#39;'
}[m]));
}
function progressBar(pc) {
const n = parseInt(String(pc ?? "").toString().replace('%', '').trim(), 10);
if (isNaN(n)) return `<span class="text-muted small">${escapeHtml(pc ?? "")}</span>`;
let bgClass = 'bg-info';
if (n === 100) bgClass = 'bg-success';
else if (n < 30) bgClass = 'bg-warning';
return `<div class="progress" style="height:8px;">
<div class="progress-bar ${bgClass}" role="progressbar"
style="width:${n}%;" aria-valuenow="${n}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">${n}%</small>`;
}
function badgeStatus(status, pc, recently = false) {
const raw = String(status || "");
const s = raw.toLowerCase();
let cls = "bg-secondary";
let icon = "info-circle";
if (recently) {
cls = "bg-success";
icon = "check-circle";
} else if (s.includes("completed")) {
cls = "bg-success";
icon = "check-circle";
} else if (s.includes("running") || s.includes("progress")) {
cls = "bg-info";
icon = "arrow-repeat";
} else if (s.includes("scheduled") || s.includes("pending")) {
cls = "bg-warning text-dark";
icon = "clock";
} else if (s.includes("failed") || s.includes("error")) {
cls = "bg-danger";
icon = "x-circle";
}
const pct = parseInt(String(pc ?? "").toString().replace('%', '').trim(), 10);
const pctText = isNaN(pct) ? "" : ` (${pct}%)`;
const text = recently ? `${raw || "Completed"} (최근${pctText})` : `${raw || "-"}${pctText}`;
return `<span class="badge ${cls}">
<i class="bi bi-${icon}"></i> ${escapeHtml(text)}
</span>`;
}
function updateStats(items) {
let total = 0, running = 0, completed = 0, error = 0;
items.forEach(it => {
if (!it.ok) {
error++;
return;
}
if (it.jobs && it.jobs.length) {
total++;
it.jobs.forEach(j => {
const s = (j.Status || "").toLowerCase();
if (s.includes("running") || s.includes("progress") || s.includes("starting")) {
running++;
} else if (s.includes("completed") || s.includes("success")) {
completed++;
} else if (s.includes("failed") || s.includes("error")) {
error++;
}
});
}
});
$statTotal.textContent = items.length;
$statRunning.textContent = running;
$statCompleted.textContent = completed;
$statError.textContent = error;
}
// ========== 렌더링 ==========
function renderTable(items) {
if (!items) return;
const hash = JSON.stringify(items);
if (hash === lastRenderHash) return;
lastRenderHash = hash;
if (!items.length) {
$body.innerHTML = `<tr><td colspan="7" class="text-center text-muted py-4">
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
현재 모니터링 중인 Job이 없습니다.
</td></tr>`;
updateStats([]);
return;
}
const rows = [];
for (const it of items) {
if (!it.ok) {
rows.push(`<tr class="table-danger">
<td><code>${escapeHtml(it.ip)}</code></td>
<td colspan="6">
<i class="bi bi-exclamation-triangle"></i>
오류: ${escapeHtml(it.error || "Unknown")}
</td>
</tr>`);
continue;
}
if (!it.jobs || !it.jobs.length) continue;
for (const j of it.jobs) {
const recent = !!j.RecentlyCompleted;
const timeText = j.CompletedAt
? `완료: ${escapeHtml(j.CompletedAt.split('T')[1]?.split('.')[0] || j.CompletedAt)}`
: escapeHtml(j.LastUpdateTime || "");
rows.push(`<tr ${recent ? 'class="table-success"' : ''}>
<td><code>${escapeHtml(it.ip)}</code></td>
<td><small class="font-monospace">${escapeHtml(j.JID || "")}</small></td>
<td><strong>${escapeHtml(j.Name || "")}</strong></td>
<td>${badgeStatus(j.Status || "", j.PercentComplete || "", recent)}</td>
<td>${progressBar(j.PercentComplete || "0")}</td>
<td><small>${escapeHtml(j.Message || "")}</small></td>
<td><small class="text-muted">${timeText}</small></td>
</tr>`);
}
}
$body.innerHTML = rows.length
? rows.join("")
: `<tr><td colspan="7" class="text-center text-success py-4">
<i class="bi bi-check-circle fs-2 d-block mb-2"></i>
현재 진행 중인 Job이 없습니다. ✅
</td></tr>`;
updateStats(items);
}
// ========== 서버 요청 ==========
async function fetchJobs(auto = false) {
if (!monitoringOn) {
$body.innerHTML = `<tr><td colspan="7" class="text-center text-muted py-4">
<i class="bi bi-power fs-2 d-block mb-2"></i>
모니터링이 꺼져 있습니다. 상단 스위치를 켜면 조회가 시작됩니다.
</td></tr>`;
$last.textContent = "";
updateStats([]);
return;
}
const ips = getIpsFromUI();
if (!ips.length) {
$body.innerHTML = `<tr><td colspan="7" class="text-center text-warning py-4">
<i class="bi bi-exclamation-triangle fs-2 d-block mb-2"></i>
IP 목록이 비어 있습니다.
</td></tr>`;
$last.textContent = "";
updateStats([]);
return;
}
try {
$loading.classList.remove('d-none');
$last.textContent = "조회 중… " + new Date().toLocaleTimeString();
const res = await fetch("/jobs/scan", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken
},
body: JSON.stringify({
ips,
method: "redfish", // Redfish 사용
recency_hours: CONFIG.recency_hours,
grace_minutes: CONFIG.grace_minutes,
include_tracked_done: $showCompleted.checked
})
});
const data = await res.json();
if (!data.ok) throw new Error(data.error || "Scan failed");
renderTable(data.items);
$last.textContent = "업데이트: " + new Date().toLocaleString();
} catch (e) {
$body.innerHTML = `<tr><td colspan="7" class="text-danger text-center py-4">
<i class="bi bi-exclamation-circle fs-2 d-block mb-2"></i>
로드 실패: ${escapeHtml(e.message)}
<br><button class="btn btn-sm btn-outline-primary mt-2" onclick="fetchJobs(false)">
<i class="bi bi-arrow-clockwise"></i> 재시도
</button>
</td></tr>`;
$last.textContent = "에러: " + new Date().toLocaleString();
console.error(e);
} finally {
$loading.classList.add('d-none');
}
}
// ========== 모니터링 제어 ==========
function startAuto() {
stopAuto();
pollTimer = setInterval(() => fetchJobs(true), CONFIG.poll_interval_ms);
}
function stopAuto() {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
}
function updateMonitorUI() {
$btn.disabled = !monitoringOn;
$auto.disabled = !monitoringOn;
if (monitoringOn) {
$statusDot.classList.add('active');
$statusText.textContent = '모니터링 중';
} else {
$statusDot.classList.remove('active');
$statusText.textContent = '모니터링 꺼짐';
}
}
async function setMonitoring(on) {
monitoringOn = !!on;
localStorage.setItem(LS_MON, monitoringOn ? "1" : "0");
updateMonitorUI();
if (!monitoringOn) {
stopAuto();
$last.textContent = "";
$body.innerHTML = `<tr><td colspan="7" class="text-center text-muted py-4">
<i class="bi bi-power fs-2 d-block mb-2"></i>
모니터링이 꺼져 있습니다.
</td></tr>`;
lastRenderHash = "";
updateStats([]);
return;
}
await fetchJobs(false);
if ($auto.checked) startAuto();
}
// ========== 초기화 ==========
document.addEventListener('DOMContentLoaded', async () => {
// 설정 로드
try {
const res = await fetch('/jobs/config');
const data = await res.json();
if (data.ok) {
CONFIG = data.config;
$pollInterval.textContent = Math.round(CONFIG.poll_interval_ms / 1000);
}
} catch (e) {
console.error("Failed to load config:", e);
}
// IP 복원
const savedIps = loadIps();
if (savedIps.length) {
setIpsToUI(savedIps);
} else {
try {
const res = await fetch('/jobs/iplist', {
headers: { 'X-CSRFToken': csrfToken }
});
const data = await res.json();
if (data.ok && data.ips) {
setIpsToUI(data.ips);
saveIps();
}
} catch (e) {
console.error(e);
}
}
// 설정 복원
const savedMon = localStorage.getItem(LS_MON);
const savedAuto = localStorage.getItem(LS_AUTO);
const savedShowCompleted = localStorage.getItem(LS_SHOW_COMPLETED);
$monSw.checked = savedMon === "1";
$auto.checked = savedAuto === "1";
$showCompleted.checked = savedShowCompleted !== "0";
// 이벤트
$ipInput.addEventListener('input', updateIpCount);
$btn.addEventListener('click', () => { if (monitoringOn) fetchJobs(false); });
$auto.addEventListener('change', e => {
localStorage.setItem(LS_AUTO, e.target.checked ? "1" : "0");
if (!monitoringOn) return;
if (e.target.checked) startAuto();
else stopAuto();
});
$monSw.addEventListener('click', e => setMonitoring(e.target.checked));
$showCompleted.addEventListener('change', e => {
localStorage.setItem(LS_SHOW_COMPLETED, e.target.checked ? "1" : "0");
if (monitoringOn) fetchJobs(false);
});
$btnLoad.addEventListener('click', async () => {
try {
const res = await fetch('/jobs/iplist', {
headers: { 'X-CSRFToken': csrfToken }
});
const data = await res.json();
if (data.ok) {
setIpsToUI(data.ips || []);
saveIps();
if (monitoringOn) await fetchJobs(false);
}
} catch (e) {
alert('IP 목록 불러오기 실패: ' + e.message);
}
});
$btnApply.addEventListener('click', () => {
saveIps();
if (monitoringOn) fetchJobs(false);
});
// 초기 상태
updateMonitorUI();
updateIpCount();
if ($monSw.checked) {
setMonitoring(true);
}
});
</script>
{% endblock %}
{% endblock %}

View File

@@ -1,305 +1,525 @@
{% extends "base.html" %}
{% block title %}XML 설정 관리 & 배포 - Dell Server Info{% endblock %}
{% block extra_css %}
<!-- Existing SCP CSS for legacy support or specific components -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/scp.css') }}">
<!-- Overriding/New Styles for Modern Look -->
<style>
/* 드래그 앤 드롭 영역 스타일 */
.drop-zone {
border: 2px dashed #cbd5e1;
border-radius: 12px;
background-color: #f8fafc;
transition: all 0.2s ease;
text-align: center;
padding: 2rem;
cursor: pointer;
position: relative;
}
.drop-zone:hover,
.drop-zone.dragover {
border-color: #3b82f6;
background-color: #eff6ff;
}
.drop-zone-icon {
font-size: 2.5rem;
color: #64748b;
margin-bottom: 1rem;
}
.drop-zone-text {
font-weight: 500;
color: #334155;
margin-bottom: 0.5rem;
}
.drop-zone-hint {
font-size: 0.875rem;
color: #94a3b8;
}
.drop-zone input[type="file"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
/* 카드 그리드 스타일 (index.html과 유사) */
.xml-file-card {
background: white;
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 1rem;
transition: all 0.2s ease;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.xml-file-card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
border-color: #3b82f6;
}
.file-icon-wrapper {
width: 48px;
height: 48px;
border-radius: 10px;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
color: #2563eb;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.file-name {
font-weight: 600;
color: #1e293b;
margin-bottom: 0.5rem;
word-break: break-all;
line-height: 1.4;
}
.file-meta {
font-size: 0.75rem;
color: #64748b;
margin-bottom: 1rem;
}
.card-actions {
display: flex;
gap: 0.5rem;
margin-top: auto;
}
.btn-action {
flex: 1;
padding: 0.4rem;
font-size: 0.8rem;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
transition: all 0.15s;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e2e8f0;
}
.section-title {
font-size: 1.25rem;
font-weight: 700;
color: #0f172a;
margin: 0;
}
</style>
{% endblock %}
{% block content %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XML 파일 관리</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body {
background-color: #f5f5f5;
padding: 20px 0;
}
<div class="container-fluid py-4">
.main-title {
font-size: 1.8rem;
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
font-size: 0.95rem;
margin-bottom: 30px;
}
.card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
background: white;
}
.card-header-custom {
background-color: #007bff;
color: white;
padding: 12px 20px;
font-weight: 600;
border-radius: 8px 8px 0 0;
font-size: 1rem;
}
.card-body {
padding: 20px;
}
.form-label {
font-weight: 500;
color: #555;
margin-bottom: 8px;
font-size: 0.9rem;
}
.form-control {
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px 12px;
font-size: 0.9rem;
}
.btn-primary {
background-color: #007bff;
border: none;
padding: 8px 24px;
font-weight: 500;
border-radius: 4px;
font-size: 0.9rem;
}
.btn-success {
background-color: #28a745;
border: none;
padding: 6px 16px;
font-weight: 500;
border-radius: 4px;
font-size: 0.85rem;
}
.btn-danger {
background-color: #dc3545;
border: none;
padding: 6px 16px;
font-weight: 500;
border-radius: 4px;
font-size: 0.85rem;
}
.upload-section {
background-color: #f8f9fa;
padding: 15px;
border-radius: 4px;
}
.custom-file-label {
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.custom-file-label::after {
background-color: #007bff;
color: white;
border-radius: 0 3px 3px 0;
font-size: 0.85rem;
}
/* 아이콘 + 뱃지 스타일 */
.file-list {
max-height: 500px;
overflow-y: auto;
}
.icon-badge-item {
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 10px 15px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.3s;
background: white;
}
.icon-badge-item:hover {
background-color: #f8f9fa;
border-color: #007bff;
transform: translateX(3px);
}
.icon-badge-left {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.file-icon-small {
width: 36px;
height: 36px;
background: #007bff;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
flex-shrink: 0;
}
.file-name-section {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
}
.file-name-badge {
font-weight: 500;
color: #333;
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.badge-custom {
background-color: #e7f3ff;
color: #007bff;
padding: 3px 10px;
border-radius: 10px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
.action-buttons {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.empty-message {
text-align: center;
color: #999;
padding: 30px;
font-size: 0.9rem;
}
.container {
max-width: 1200px;
}
/* 스크롤바 스타일 */
.file-list::-webkit-scrollbar {
width: 6px;
}
.file-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>
</head>
<body>
<div class="container">
<h1 class="main-title">XML 파일 관리</h1>
<p class="subtitle">XML 파일을 업로드하고 관리할 수 있습니다</p>
<!-- XML 파일 업로드 폼 -->
<div class="card">
<div class="card-header-custom">
<i class="fas fa-cloud-upload-alt mr-2"></i>파일 업로드
</div>
<div class="card-body">
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="upload-section">
<div class="form-group mb-2">
<label for="xmlFile" class="form-label">XML 파일 선택</label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="xmlFile" name="xmlFile" accept=".xml" onchange="updateFileName(this)">
<label class="custom-file-label" for="xmlFile" id="fileLabel">파일을 선택하세요</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload mr-1"></i>업로드
</button>
</div>
</form>
</div>
<!-- Header Section -->
<div class="row mb-4">
<div class="col">
<h2 class="fw-bold mb-1">
<i class="bi bi-file-earmark-code text-primary me-2"></i>
설정 파일 관리
</h2>
<p class="text-muted mb-0">서버 설정(XML) 파일을 업로드, 관리 및 배포합니다.</p>
</div>
<!-- XML 파일 목록 -->
<div class="card">
<div class="card-header-custom">
<i class="fas fa-list mr-2"></i>파일 목록
</div>
<div class="card-body">
{% if xml_files %}
<div class="file-list">
{% for xml_file in xml_files %}
<div class="icon-badge-item">
<div class="icon-badge-left">
<div class="file-icon-small">
<i class="fas fa-file-code"></i>
</div>
<div class="file-name-section">
<span class="file-name-badge" title="{{ xml_file }}">{{ xml_file }}</span>
<span class="badge-custom">XML</span>
</div>
</div>
<div class="action-buttons">
<!-- 파일 편집 버튼 -->
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}" class="btn btn-success btn-sm">
<i class="fas fa-edit"></i> 편집
</a>
<!-- 파일 삭제 버튼 -->
<form action="{{ url_for('xml.delete_xml', filename=xml_file) }}" method="POST" style="display:inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('정말 삭제하시겠습니까?')">
<i class="fas fa-trash"></i> 삭제
</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-message">
<i class="fas fa-folder-open" style="font-size: 2rem; color: #ddd;"></i>
<p class="mt-2 mb-0">파일이 없습니다.</p>
</div>
{% endif %}
</div>
<div class="col-auto align-self-end">
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#exportModal">
<i class="bi bi-server me-2"></i>iDRAC에서 추출
</button>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
function updateFileName(input) {
const fileName = input.files[0]?.name || '파일을 선택하세요';
document.getElementById('fileLabel').textContent = fileName;
<div class="row g-4">
<!-- Left: Upload Section (30% on large screens) -->
<div class="col-lg-4 col-xl-3">
<div class="card border shadow-sm h-100">
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
<h6 class="fw-bold mb-0 text-dark">
<i class="bi bi-cloud-upload me-2 text-primary"></i>파일 업로드
</h6>
</div>
<div class="card-body">
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data"
id="uploadForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="drop-zone" id="dropZone">
<input type="file" name="xmlFile" id="xmlFile" accept=".xml"
onchange="handleFileSelect(this)">
<div class="drop-zone-icon">
<i class="bi bi-file-earmark-arrow-up"></i>
</div>
<div class="drop-zone-text" id="dropZoneText">
클릭하여 파일 선택<br>또는 파일을 여기로 드래그
</div>
<div class="drop-zone-hint">XML 파일만 지원됩니다.</div>
</div>
<button type="submit" class="btn btn-primary w-100 mt-3 shadow-sm">
<i class="bi bi-upload me-2"></i>업로드 시작
</button>
</form>
<div class="alert alert-light mt-4 border" role="alert">
<h6 class="alert-heading fs-6 fw-bold"><i class="bi bi-info-circle me-2"></i>도움말</h6>
<p class="mb-0 fs-small text-muted" style="font-size: 0.85rem;">
업로드된 XML 파일을 사용하여 여러 서버에 동일한 설정을 일괄 배포할 수 있습니다.
'비교' 기능을 사용하여 버전 간 차이를 확인하세요.
</p>
</div>
</div>
</div>
</div>
<!-- Right: File List (70%) -->
<div class="col-lg-8 col-xl-9">
<div class="card border shadow-sm h-100">
<div class="card-header bg-white border-bottom py-3 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<h6 class="fw-bold mb-0 text-dark me-3">
<i class="bi bi-list-check me-2 text-success"></i>파일 목록
</h6>
<span class="badge bg-light text-dark border">{{ xml_files|length }}개 파일</span>
</div>
<div>
<button class="btn btn-sm btn-outline-secondary" id="compareBtn"
data-url="{{ url_for('scp.diff_scp') }}" onclick="compareSelected()">
<i class="bi bi-arrow-left-right me-1"></i>선택 비교
</button>
</div>
</div>
<div class="card-body bg-light">
{% if xml_files %}
<!-- 카드 크기 조정: 한 줄에 4개(xxl), 3개(xl) 등으로 조금 더 키움 -->
<div class="row row-cols-1 row-cols-lg-2 row-cols-xl-3 row-cols-xxl-4 g-3">
{% for xml_file in xml_files %}
<div class="col">
<div class="xml-file-card position-relative p-3 h-100 d-flex flex-column">
<div class="position-absolute top-0 end-0 p-2 me-1">
<input type="checkbox" class="form-check-input file-selector border-secondary"
value="{{ xml_file }}" style="cursor: pointer;">
</div>
<div class="d-flex align-items-center mb-3">
<div class="file-icon-wrapper me-3 mb-0 shadow-sm"
style="width: 42px; height: 42px; font-size: 1.4rem;">
<i class="bi bi-filetype-xml"></i>
</div>
<div class="file-name text-truncate fw-bold mb-0 text-dark"
style="max-width: 140px; font-size: 0.95rem;" title="{{ xml_file }}">
{{ xml_file }}
</div>
</div>
<div class="mt-auto pt-3 border-top">
<div class="d-flex gap-2">
<!-- 배포 버튼 -->
<button type="button"
class="btn btn-sm btn-primary flex-fill d-flex align-items-center justify-content-center gap-1"
onclick="openDeployModal('{{ xml_file }}')" title="배포">
<i class="bi bi-send-fill"></i> <span class="small fw-bold">배포</span>
</button>
<!-- 편집 버튼 -->
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}"
class="btn btn-sm btn-white border flex-fill d-flex align-items-center justify-content-center gap-1 text-dark bg-white"
title="편집">
<i class="bi bi-pencil-fill text-secondary"></i> <span
class="small fw-bold">편집</span>
</a>
<!-- 삭제 버튼 -->
<form action="{{ url_for('xml.delete_xml', filename=xml_file) }}" method="POST"
class="d-flex flex-fill m-0" style="min-width: 0;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button type="submit"
class="btn btn-sm btn-white border w-100 d-flex align-items-center justify-content-center gap-1 text-danger bg-white"
onclick="return confirm('정말 삭제하시겠습니까?')" title="삭제">
<i class="bi bi-trash-fill"></i> <span class="small fw-bold">삭제</span>
</button>
</form>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5 my-5">
<div class="mb-3 text-secondary" style="font-size: 3rem; opacity: 0.3;">
<i class="bi bi-folder2-open"></i>
</div>
<h5 class="text-secondary fw-normal">등록된 파일이 없습니다.</h5>
<p class="text-muted">좌측 패널에서 XML 파일을 업로드해주세요.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Export Modal (Include existing modal logic but restyled) -->
<div class="modal fade" id="exportModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg">
<form action="{{ url_for('scp.export_scp') }}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fs-6 fw-bold"><i class="bi bi-download me-2"></i>iDRAC 설정 내보내기</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="alert alert-info py-2 small mb-4">
<i class="bi bi-info-circle-fill me-2"></i> CIFS 네트워크 공유 폴더가 필요합니다.
</div>
<h6 class="text-primary fw-bold mb-3 small text-uppercase">대상 iDRAC</h6>
<div class="form-floating mb-2">
<input type="text" class="form-control" id="targetIp" name="target_ip" placeholder="IP"
required>
<label for="targetIp">iDRAC IP Address</label>
</div>
<div class="row g-2 mb-4">
<div class="col">
<div class="form-floating">
<input type="text" class="form-control" id="targetUser" name="username"
placeholder="User" required>
<label for="targetUser">Username</label>
</div>
</div>
<div class="col">
<div class="form-floating">
<input type="password" class="form-control" id="targetPwd" name="password"
placeholder="Pwd" required>
<label for="targetPwd">Password</label>
</div>
</div>
</div>
<h6 class="text-success fw-bold mb-3 small text-uppercase">저장소 (CIFS Share)</h6>
<div class="row g-2 mb-2">
<div class="col-8">
<div class="form-floating">
<input type="text" class="form-control" name="share_ip" placeholder="IP" required>
<label>Share IP</label>
</div>
</div>
<div class="col-4">
<div class="form-floating">
<input type="text" class="form-control" name="share_name" placeholder="Name" required>
<label>Share Name</label>
</div>
</div>
</div>
<div class="form-floating mb-2">
<input type="text" class="form-control" name="filename" placeholder="Filename" required>
<label>저장할 파일명 (예: backup.xml)</label>
</div>
<div class="row g-2">
<div class="col">
<div class="form-floating">
<input type="text" class="form-control" name="share_user" placeholder="User">
<label>Share User</label>
</div>
</div>
<div class="col">
<div class="form-floating">
<input type="password" class="form-control" name="share_pwd" placeholder="Pwd">
<label>Share Password</label>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-primary px-4">내보내기 실행</button>
</div>
</form>
</div>
</div>
</div>
<!-- Deploy Modal -->
<div class="modal fade" id="deployModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg">
<form action="{{ url_for('scp.import_scp') }}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="modal-header bg-danger text-white">
<h5 class="modal-title fs-6 fw-bold"><i class="bi bi-send-fill me-2"></i>설정 배포 (Import)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="alert alert-warning py-2 small mb-4">
<i class="bi bi-exclamation-triangle-fill me-2"></i> 적용 후 서버가 재부팅될 수 있습니다.
</div>
<div class="mb-4">
<label class="form-label fw-bold small text-muted">배포 파일</label>
<div class="input-group">
<span class="input-group-text bg-light"><i class="bi bi-file-code"></i></span>
<input type="text" class="form-control fw-bold text-primary" id="deployFilename"
name="filename" readonly>
</div>
</div>
<h6 class="text-primary fw-bold mb-3 small text-uppercase">대상 iDRAC</h6>
<div class="form-floating mb-2">
<input type="text" class="form-control" name="target_ip" placeholder="IP" required>
<label>iDRAC IP</label>
</div>
<div class="row g-2 mb-4">
<div class="col">
<div class="form-floating">
<input type="text" class="form-control" name="username" placeholder="User" required>
<label>Username</label>
</div>
</div>
<div class="col">
<div class="form-floating">
<input type="password" class="form-control" name="password" placeholder="Pwd" required>
<label>Password</label>
</div>
</div>
</div>
<h6 class="text-success fw-bold mb-3 small text-uppercase">소스 위치 (CIFS Share)</h6>
<div class="row g-2 mb-2">
<div class="col-8">
<div class="form-floating">
<input type="text" class="form-control" name="share_ip" placeholder="IP" required>
<label>Share IP</label>
</div>
</div>
<div class="col-4">
<div class="form-floating">
<input type="text" class="form-control" name="share_name" placeholder="Name" required>
<label>Share Name</label>
</div>
</div>
</div>
<div class="row g-2 mb-4">
<div class="col">
<div class="form-floating">
<input type="text" class="form-control" name="share_user" placeholder="User">
<label>Share User</label>
</div>
</div>
<div class="col">
<div class="form-floating">
<input type="password" class="form-control" name="share_pwd" placeholder="Pwd">
<label>Share Password</label>
</div>
</div>
</div>
<div class="form-floating">
<select class="form-select" name="import_mode" id="importMode">
<option value="Replace">전체 교체 (Replace)</option>
<option value="Append">변경분만 적용 (Append)</option>
</select>
<label for="importMode">적용 모드</label>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-danger px-4">배포 시작</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/scp.js') }}"></script>
<script>
// 드래그 앤 드롭 파일 처리
function handleFileSelect(input) {
const fileName = input.files[0]?.name;
const dropZoneText = document.getElementById('dropZoneText');
if (fileName) {
dropZoneText.innerHTML = `<span class="text-primary fw-bold">${fileName}</span><br><span class="text-muted small">파일이 선택되었습니다.</span>`;
document.getElementById('dropZone').classList.add('border-primary', 'bg-light');
} else {
dropZoneText.innerHTML = '클릭하여 파일 선택<br>또는 파일을 여기로 드래그';
document.getElementById('dropZone').classList.remove('border-primary', 'bg-light');
}
</script>
</body>
</html>
{% endblock %}
}
// 드래그 효과
const dropZone = document.getElementById('dropZone');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
dropZone.classList.add('dragover');
}
function unhighlight(e) {
dropZone.classList.remove('dragover');
}
dropZone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
const input = document.getElementById('xmlFile');
if (files.length > 0) {
input.files = files; // input에 파일 할당
handleFileSelect(input);
}
}
// 툴팁 초기화 및 자동 닫기
document.addEventListener('DOMContentLoaded', function () {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
});
</script>
{% endblock %}
```

View File

@@ -0,0 +1,173 @@
{% extends "base.html" %}
{% block title %}설정 파일 비교: {{ file1 }} vs {{ file2 }}{% endblock %}
{% block extra_css %}
<!-- Monaco Diff Editor Styles -->
<style>
.diff-page-container {
display: flex;
flex-direction: column;
height: calc(100vh - 140px);
min-height: 600px;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.diff-toolbar {
background-color: #f8fafc;
border-bottom: 1px solid #e2e8f0;
padding: 0.75rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.diff-files {
display: flex;
align-items: center;
gap: 1.5rem;
font-weight: 600;
color: #334155;
}
.file-badge {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.75rem;
background: #fff;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 0.9rem;
}
.diff-arrow {
color: #94a3b8;
font-size: 1.2rem;
}
#monaco-diff-root {
flex: 1;
width: 100%;
height: 100%;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4 h-100">
<!-- Breadcrumb -->
<div class="mb-3">
<a href="{{ url_for('xml.xml_management') }}" class="text-decoration-none text-muted small fw-bold">
<i class="bi bi-arrow-left me-1"></i>목록으로 돌아가기
</a>
</div>
<div class="diff-page-container">
<!-- Toolbar -->
<div class="diff-toolbar">
<div class="diff-files">
<div class="file-badge text-danger border-danger-subtle bg-danger-subtle">
<i class="bi bi-file-earmark-minus"></i>
<span>{{ file1 }}</span> <!-- Original -->
</div>
<i class="bi bi-arrow-right diff-arrow"></i>
<div class="file-badge text-success border-success-subtle bg-success-subtle">
<i class="bi bi-file-earmark-plus"></i>
<span>{{ file2 }}</span> <!-- Modified -->
</div>
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary" onclick="toggleInlineDiff()" id="viewToggleBtn">
<i class="bi bi-layout-split me-1"></i>Inline View
</button>
</div>
</div>
<!-- Monaco Diff Editor -->
<div id="monaco-diff-root"></div>
</div>
<!-- Raw Content Hidden Inputs (Jinja2 will escape HTML entities automatically) -->
<textarea id="hidden_content1" style="display:none;">{{ content1 }}</textarea>
<textarea id="hidden_content2" style="display:none;">{{ content2 }}</textarea>
</div>
{% endblock %}
{% block scripts %}
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
let diffEditor = null;
let isInline = false;
document.addEventListener('DOMContentLoaded', function () {
// 1. Check for loader failure
if (typeof require === 'undefined') {
document.getElementById('monaco-diff-root').innerHTML =
'<div class="d-flex justify-content-center align-items-center h-100 text-danger">' +
'<i class="bi bi-exclamation-triangle me-2"></i>Monaco Editor 리소스를 불러올 수 없습니다. 인터넷 연결을 확인하세요.' +
'</div>';
return;
}
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], function () {
// 2. Read content from hidden textareas
// Jinja2가 HTML escaping을 처리하므로, .value를 통해 원본 XML을 얻을 수 있습니다.
const content1 = document.getElementById('hidden_content1').value;
const content2 = document.getElementById('hidden_content2').value;
const originalModel = monaco.editor.createModel(content1, 'xml');
const modifiedModel = monaco.editor.createModel(content2, 'xml');
const container = document.getElementById('monaco-diff-root');
// 3. Create Diff Editor
diffEditor = monaco.editor.createDiffEditor(container, {
theme: 'vs',
originalEditable: false,
readOnly: true,
renderSideBySide: true, // Default: Split View
automaticLayout: true,
minimap: { enabled: true },
diffWordWrap: 'off'
});
diffEditor.setModel({
original: originalModel,
modified: modifiedModel
});
// 네비게이션 기능 추가
diffEditor.getNavigator();
}, function (err) {
document.getElementById('monaco-diff-root').innerHTML =
'<div class="d-flex justify-content-center align-items-center h-100 text-danger">' +
'<i class="bi bi-exclamation-triangle me-2"></i>에디터 로드 실패: ' + err.message + '</div>';
});
});
function toggleInlineDiff() {
if (!diffEditor) return;
isInline = !isInline;
diffEditor.updateOptions({
renderSideBySide: !isInline
});
const btn = document.getElementById('viewToggleBtn');
if (isInline) {
btn.innerHTML = '<i class="bi bi-layout-sidebar me-1"></i>Split View';
} else {
btn.innerHTML = '<i class="bi bi-layout-split me-1"></i>Inline View';
}
}
</script>
{% endblock %}

View File

@@ -1,21 +1,19 @@
{% extends "base.html" %}
{% block content %}
<body>
<h1>Uploaded XML Files</h1>
<ul>
{% for file in files %}
<li>
{{ file }}
<a href="{{ url_for('download_xml', filename=file) }}">Download</a>
<a href="{{ url_for('edit_xml', filename=file) }}">Edit</a>
<form action="{{ url_for('delete_xml', filename=file) }}" method="post" style="display:inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit">Delete</button>
</form>
</li>
{% endfor %}
</ul>
<a href="{{ url_for('upload_xml') }}">Upload new file</a>
</body>
<h1>Uploaded XML Files</h1>
<ul>
{% for file in files %}
<li>
{{ file }}
<a href="{{ url_for('download_xml', filename=file) }}">Download</a>
<a href="{{ url_for('edit_xml', filename=file) }}">Edit</a>
<form action="{{ url_for('delete_xml', filename=file) }}" method="post" style="display:inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button type="submit">Delete</button>
</form>
</li>
{% endfor %}
</ul>
<a href="{{ url_for('upload_xml') }}">Upload new file</a>
{% endblock %}

10
check_telegram.py Normal file
View File

@@ -0,0 +1,10 @@
import sys
try:
import telegram
print(f"Telegram library found. Version: {telegram.__version__}")
from telegram import Bot
print("Bot class imported successfully.")
except ImportError as e:
print(f"ImportError: {e}")
except Exception as e:
print(f"Error during import: {e}")

View File

@@ -40,9 +40,22 @@ class Config:
# ── 보안
SECRET_KEY = os.environ.get("SECRET_KEY", "change-me") # 반드시 환경변수로 고정 권장
# ── Redfish
REDFISH_TIMEOUT = int(os.environ.get("REDFISH_TIMEOUT", 15))
REDFISH_VERIFY_SSL = os.environ.get("REDFISH_VERIFY_SSL", "false").lower() == "true"
# ── DB (환경변수 DATABASE_URL 있으면 그 값을 우선 사용)
sqlite_path = (INSTANCE_DIR / "site.db").as_posix()
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", f"sqlite:///{sqlite_path}")
# DB 연결 안정성 옵션 (SQLite 락/쓰레드 문제 완화)
SQLALCHEMY_ENGINE_OPTIONS = {
"pool_pre_ping": True,
"pool_recycle": 280,
}
if SQLALCHEMY_DATABASE_URI.startswith("sqlite"):
SQLALCHEMY_ENGINE_OPTIONS["connect_args"] = {"check_same_thread": False}
SQLALCHEMY_TRACK_MODIFICATIONS = False
# ── Telegram (미설정 시 기능 비활성처럼 동작)
@@ -74,6 +87,7 @@ class Config:
# ── 세션
PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.getenv("SESSION_MINUTES", 30)))
SESSION_PERMANENT = True # 브라우저 닫아도 세션 유지 (타임아웃까지)
# ── SocketIO
# threading / eventlet / gevent 중 선택. 기본은 threading (Windows 안정)

684
data/logs/2025-11-24.log Normal file
View File

@@ -0,0 +1,684 @@
2025-11-24 16:23:44,003 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:23:44,030 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:23:44,030 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:23:44,055 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:23:44,090 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 16:23:44,090 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 16:23:44,090 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 16:23:44,690 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:23:44,710 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:23:44,710 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:23:44,726 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:23:44,739 [WARNING] werkzeug: * Debugger is active!
2025-11-24 16:23:44,741 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 16:23:52,286 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:52] "GET / HTTP/1.1" 302 -
2025-11-24 16:23:52,316 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:52] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-24 16:23:52,359 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:52] "GET /static/style.css HTTP/1.1" 200 -
2025-11-24 16:23:52,414 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:52] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-24 16:23:58,612 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:23:58,612 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:23:58,676 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:23:58,676 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:23:58,676 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:23:58,676 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:23:58,677 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:58] "POST /login HTTP/1.1" 302 -
2025-11-24 16:23:58,717 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:58] "GET /index HTTP/1.1" 200 -
2025-11-24 16:23:58,735 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:58] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:23:58,745 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:23:58] "GET /static/script.js HTTP/1.1" 200 -
2025-11-24 16:24:08,700 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:08] "GET /home/ HTTP/1.1" 200 -
2025-11-24 16:24:08,711 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:08] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:10,139 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:10] "GET /home/ HTTP/1.1" 200 -
2025-11-24 16:24:10,154 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:10] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:10,632 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:10] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:24:10,645 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:10] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:14,477 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:14] "GET /edit_xml/T8A_R6625_RAID_A.xml HTTP/1.1" 200 -
2025-11-24 16:24:14,492 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:14] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:16,178 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:16] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:24:16,191 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:16] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:22,156 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:22] "GET /index HTTP/1.1" 200 -
2025-11-24 16:24:22,173 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:22] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:22,174 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:22] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:24:22,770 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:22] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:24:22,783 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:22] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:23,371 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:23] "GET /index HTTP/1.1" 200 -
2025-11-24 16:24:23,387 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:23] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:23,389 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:23] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:24:23,851 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:23] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:24:23,863 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:23] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:24,257 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:24] "GET /index HTTP/1.1" 200 -
2025-11-24 16:24:24,271 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:24] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:24:24,274 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:24] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:24:30,856 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:30] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:24:30,871 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:30] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:25:28,267 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:25:28] "GET /index HTTP/1.1" 200 -
2025-11-24 16:25:28,283 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:25:28] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:25:28,286 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:25:28] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:26:46,574 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:46] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:26:46,595 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:46] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:26:47,173 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:47] "GET /index HTTP/1.1" 200 -
2025-11-24 16:26:47,188 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:47] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:26:47,190 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:47] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:26:48,441 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:48] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:26:48,453 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:48] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:26:48,952 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:48] "GET /index HTTP/1.1" 200 -
2025-11-24 16:26:48,964 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:48] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:26:48,967 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:48] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:27:17,588 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:17] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:27:17,601 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:17] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:19,596 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:19] "GET /index HTTP/1.1" 200 -
2025-11-24 16:27:19,613 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:19] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:19,614 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:19] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:27:20,075 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:20] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:27:20,087 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:20] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:21,231 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:21] "GET /index HTTP/1.1" 200 -
2025-11-24 16:27:21,243 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:21] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:21,247 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:21] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:27:21,587 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:21] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:27:21,603 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:21] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:28,942 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:28] "GET /jobs HTTP/1.1" 200 -
2025-11-24 16:27:28,957 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:28] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:28,965 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:28] "GET /jobs/config HTTP/1.1" 200 -
2025-11-24 16:27:28,985 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-24 16:27:28,986 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:28] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-24 16:27:33,846 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:33] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:27:33,860 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:33] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:34,536 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:34] "GET /index HTTP/1.1" 200 -
2025-11-24 16:27:34,548 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:34] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:34,552 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:34] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:27:35,135 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:35] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:27:35,147 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:35] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:35,691 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:35] "GET /index HTTP/1.1" 200 -
2025-11-24 16:27:35,705 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:35] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:35,706 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:35] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:27:54,433 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:54] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 16:27:54,447 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:54] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:27:56,102 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:56] "GET /edit_xml/R6615_raid.xml HTTP/1.1" 200 -
2025-11-24 16:27:56,116 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:56] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:43:03,673 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:43:03,693 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:43:03,693 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:43:03,710 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:43:03,730 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 16:43:03,730 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 16:43:03,730 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 16:43:04,322 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:43:04,342 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:43:04,342 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:43:04,358 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:43:04,372 [WARNING] werkzeug: * Debugger is active!
2025-11-24 16:43:04,373 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 16:43:06,033 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:06] "GET /home/ HTTP/1.1" 200 -
2025-11-24 16:43:06,081 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:06] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:43:06,124 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:06] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 16:43:07,831 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:07] "GET /home/ HTTP/1.1" 200 -
2025-11-24 16:43:07,846 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:07] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:43:08,675 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:08] "GET /login HTTP/1.1" 200 -
2025-11-24 16:43:08,686 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:08] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:43:14,285 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:43:14,285 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:43:14,334 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:43:14,334 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:43:14,334 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:43:14,334 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:43:14,335 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:14] "POST /login HTTP/1.1" 302 -
2025-11-24 16:43:14,352 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:14] "GET /index HTTP/1.1" 200 -
2025-11-24 16:43:14,370 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:14] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:43:14,371 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:14] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:46:00,350 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:46:00,350 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:46:00,351 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:46:00] "GET /logout HTTP/1.1" 302 -
2025-11-24 16:46:00,354 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:46:00] "GET /login HTTP/1.1" 200 -
2025-11-24 16:46:00,370 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:46:00] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:47:44,807 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:47:44,827 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:47:44,827 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:47:44,841 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:47:44,862 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 16:47:44,862 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 16:47:44,862 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 16:47:45,441 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:47:45,460 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:47:45,460 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:47:45,479 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:47:45,494 [WARNING] werkzeug: * Debugger is active!
2025-11-24 16:47:45,496 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 16:47:47,414 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:47] "GET /login HTTP/1.1" 200 -
2025-11-24 16:47:47,454 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:47] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:47:47,464 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:47] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 16:47:51,936 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:47:51,936 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:47:51,984 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:47:51,984 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:47:51,985 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:47:51,985 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:47:51,985 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:51] "POST /login HTTP/1.1" 302 -
2025-11-24 16:47:52,003 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:52] "GET /index HTTP/1.1" 200 -
2025-11-24 16:47:52,022 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:47:52,022 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:52] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:49:41,046 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:49:41,046 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:49:41,048 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:41] "GET /logout HTTP/1.1" 302 -
2025-11-24 16:49:41,050 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:41] "GET /login HTTP/1.1" 200 -
2025-11-24 16:49:41,066 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:41] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:49:43,916 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:49:43,936 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:49:43,936 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:49:43,953 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:49:43,974 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 16:49:43,974 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 16:49:43,975 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 16:49:44,586 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:49:44,606 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:49:44,606 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:49:44,622 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:49:44,636 [WARNING] werkzeug: * Debugger is active!
2025-11-24 16:49:44,638 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 16:49:45,767 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:45] "GET /login HTTP/1.1" 200 -
2025-11-24 16:49:45,801 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:45] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:49:45,817 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:45] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 16:49:50,363 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:49:50,363 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:49:50,411 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:49:50,411 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:49:50,412 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:49:50,412 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:49:50,412 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:50] "POST /login HTTP/1.1" 302 -
2025-11-24 16:49:50,429 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:50] "GET /index HTTP/1.1" 200 -
2025-11-24 16:49:50,446 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:50] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:49:50,447 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:50] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:57:24,910 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:57:24,930 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:57:24,930 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:57:24,946 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:57:24,967 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 16:57:24,968 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 16:57:24,968 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 16:57:25,580 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:57:25,600 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:57:25,600 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:57:25,616 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:57:25,629 [WARNING] werkzeug: * Debugger is active!
2025-11-24 16:57:25,632 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 16:57:26,867 [INFO] app: LOGIN: already auth → /index
2025-11-24 16:57:26,867 [INFO] app: LOGIN: already auth → /index
2025-11-24 16:57:26,868 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:26] "GET /login HTTP/1.1" 302 -
2025-11-24 16:57:26,893 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:26] "GET /index HTTP/1.1" 200 -
2025-11-24 16:57:26,973 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:26] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:57:26,975 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:26] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:57:27,029 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:27] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 16:57:28,419 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:57:28,419 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:57:28,420 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:28] "GET /logout HTTP/1.1" 302 -
2025-11-24 16:57:28,425 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:28] "GET /login HTTP/1.1" 200 -
2025-11-24 16:57:28,441 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:28] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:57:34,916 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:57:34,916 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:57:34,968 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:57:34,968 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:57:34,968 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=MISSING
2025-11-24 16:57:34,968 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=MISSING
2025-11-24 16:57:34,968 [WARNING] app: Telegram notification skipped: Missing configuration or library.
2025-11-24 16:57:34,968 [WARNING] app: Telegram notification skipped: Missing configuration or library.
2025-11-24 16:57:34,970 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:57:34,970 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:57:34,970 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:34] "POST /login HTTP/1.1" 302 -
2025-11-24 16:57:34,975 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:34] "GET /index HTTP/1.1" 200 -
2025-11-24 16:57:34,993 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:34] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:57:34,994 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:34] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:58:17,994 [INFO] werkzeug: * Detected change in 'D:\\Code\\iDRAC_Info\\idrac_info\\check_telegram.py', reloading
2025-11-24 16:58:18,038 [INFO] werkzeug: * Detected change in 'D:\\Code\\iDRAC_Info\\idrac_info\\check_telegram.py', reloading
2025-11-24 16:58:18,039 [INFO] werkzeug: * Detected change in 'D:\\Code\\iDRAC_Info\\idrac_info\\check_telegram.py', reloading
2025-11-24 16:58:18,379 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 16:58:19,034 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:58:19,055 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:58:19,055 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:58:19,072 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:58:19,086 [WARNING] werkzeug: * Debugger is active!
2025-11-24 16:58:19,089 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 16:58:42,574 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:42] "GET /index HTTP/1.1" 200 -
2025-11-24 16:58:42,632 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:42] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:58:42,633 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:42] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:58:44,677 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:58:44,677 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:58:44,678 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:44] "GET /logout HTTP/1.1" 302 -
2025-11-24 16:58:44,684 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:44] "GET /login HTTP/1.1" 200 -
2025-11-24 16:58:44,702 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:44] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:58:49,916 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:58:49,916 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:58:49,966 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:58:49,966 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:58:49,966 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=MISSING
2025-11-24 16:58:49,966 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=MISSING
2025-11-24 16:58:49,966 [WARNING] app: Telegram notification skipped: Missing configuration or library.
2025-11-24 16:58:49,966 [WARNING] app: Telegram notification skipped: Missing configuration or library.
2025-11-24 16:58:49,966 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:58:49,966 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:58:49,967 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:49] "POST /login HTTP/1.1" 302 -
2025-11-24 16:58:49,972 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:49] "GET /index HTTP/1.1" 200 -
2025-11-24 16:58:49,993 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:49] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:58:49,993 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:49] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:59:34,417 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:59:34,438 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:59:34,438 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:59:34,453 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:59:34,473 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 16:59:34,473 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 16:59:34,474 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 16:59:35,084 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 16:59:35,104 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:59:35,104 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 16:59:35,121 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 16:59:35,136 [WARNING] werkzeug: * Debugger is active!
2025-11-24 16:59:35,138 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 16:59:36,736 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:59:36,736 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:59:36,737 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:36] "GET /logout HTTP/1.1" 302 -
2025-11-24 16:59:36,747 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:36] "GET /login HTTP/1.1" 200 -
2025-11-24 16:59:36,783 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:36] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:59:42,340 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:59:42,340 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 16:59:42,391 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:59:42,391 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 16:59:42,391 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=MISSING
2025-11-24 16:59:42,391 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=MISSING
2025-11-24 16:59:42,392 [WARNING] app: Telegram notification skipped: Missing configuration or library.
2025-11-24 16:59:42,392 [WARNING] app: Telegram notification skipped: Missing configuration or library.
2025-11-24 16:59:42,392 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:59:42,392 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 16:59:42,392 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:42] "POST /login HTTP/1.1" 302 -
2025-11-24 16:59:42,407 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:42] "GET /index HTTP/1.1" 200 -
2025-11-24 16:59:42,428 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:42] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 16:59:42,429 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:42] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 16:59:50,145 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:59:50,145 [INFO] app: LOGOUT: user=김강희
2025-11-24 16:59:50,146 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:50] "GET /logout HTTP/1.1" 302 -
2025-11-24 16:59:50,150 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:50] "GET /login HTTP/1.1" 200 -
2025-11-24 16:59:50,164 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:50] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:06:17,663 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:06:17,684 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:06:17,684 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:06:17,700 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:06:17,722 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 17:06:17,722 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 17:06:17,722 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 17:06:18,428 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:06:18,448 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:06:18,448 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:06:18,463 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:06:18,477 [WARNING] werkzeug: * Debugger is active!
2025-11-24 17:06:18,479 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 17:06:18,548 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:18] "GET /index HTTP/1.1" 302 -
2025-11-24 17:06:18,558 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:18] "GET /login?next=/index HTTP/1.1" 200 -
2025-11-24 17:06:18,602 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:18] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:06:18,643 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:18] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 17:06:24,340 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:06:24,340 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:06:24,396 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:06:24,396 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:06:24,396 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:06:24,396 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:06:24,397 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:06:24,397 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:06:24,398 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:24] "POST /login HTTP/1.1" 302 -
2025-11-24 17:06:24,418 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:24] "GET /index HTTP/1.1" 200 -
2025-11-24 17:06:24,437 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:24] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:06:24,438 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:24] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:06:26,931 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:09:29,579 [INFO] app: LOGOUT: user=김강희
2025-11-24 17:09:29,579 [INFO] app: LOGOUT: user=김강희
2025-11-24 17:09:29,580 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:29] "GET /logout HTTP/1.1" 302 -
2025-11-24 17:09:29,583 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:29] "GET /login HTTP/1.1" 200 -
2025-11-24 17:09:29,602 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:29] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:09:33,150 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:09:33,173 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:09:33,173 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:09:33,190 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:09:33,210 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 17:09:33,211 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 17:09:33,211 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 17:09:33,914 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:09:33,934 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:09:33,934 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:09:33,949 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:09:33,964 [WARNING] werkzeug: * Debugger is active!
2025-11-24 17:09:33,966 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 17:09:34,397 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:34] "GET /login HTTP/1.1" 200 -
2025-11-24 17:09:34,435 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:34] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:09:34,444 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:34] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 17:09:40,004 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:09:40,004 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:09:40,052 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:09:40,052 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:09:40,053 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:09:40,053 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:09:40,053 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:09:40,053 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:09:40,054 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:40] "POST /login HTTP/1.1" 302 -
2025-11-24 17:09:40,071 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:40] "GET /index HTTP/1.1" 200 -
2025-11-24 17:09:40,090 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:40] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:09:40,090 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:40] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:09:41,447 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:11:04,806 [INFO] werkzeug: * Detected change in 'D:\\Code\\iDRAC_Info\\idrac_info\\backend\\routes\\auth.py', reloading
2025-11-24 17:11:04,806 [INFO] werkzeug: * Detected change in 'D:\\Code\\iDRAC_Info\\idrac_info\\backend\\routes\\auth.py', reloading
2025-11-24 17:11:04,807 [INFO] werkzeug: * Detected change in 'D:\\Code\\iDRAC_Info\\idrac_info\\backend\\routes\\auth.py', reloading
2025-11-24 17:11:05,574 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 17:17:15,522 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:17:15,543 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:15,543 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:15,560 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:17:15,582 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 17:17:15,582 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 17:17:15,583 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 17:17:16,314 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:17:16,336 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:16,336 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:16,353 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:17:16,367 [WARNING] werkzeug: * Debugger is active!
2025-11-24 17:17:16,369 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 17:17:16,953 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:16,953 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:16,973 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:16] "GET /index HTTP/1.1" 200 -
2025-11-24 17:17:17,034 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:17] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:17:17,035 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:17] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:17:17,095 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:17] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 17:17:18,155 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:18,155 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:18,158 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:18] "GET /home/ HTTP/1.1" 200 -
2025-11-24 17:17:18,176 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:18] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:17:18,440 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:19,064 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:19,064 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:19,064 [INFO] app: LOGOUT: user=김강희
2025-11-24 17:17:19,064 [INFO] app: LOGOUT: user=김강희
2025-11-24 17:17:19,064 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:19,064 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:19,065 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:19] "GET /logout HTTP/1.1" 302 -
2025-11-24 17:17:19,070 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:19] "GET /login HTTP/1.1" 200 -
2025-11-24 17:17:19,085 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:19] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:17:19,454 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:20,491 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:20,517 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:25,147 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:17:25,167 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:25,167 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:25,183 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:17:25,203 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 17:17:25,204 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 17:17:25,204 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 17:17:25,903 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:17:25,924 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:25,924 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:17:25,940 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:17:25,953 [WARNING] werkzeug: * Debugger is active!
2025-11-24 17:17:25,955 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 17:17:36,901 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:17:36,901 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:17:36,954 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:17:36,954 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:17:36,954 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:36,954 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:36,956 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:17:36,956 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:17:36,957 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:36] "POST /login HTTP/1.1" 302 -
2025-11-24 17:17:36,961 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:36,961 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:36,982 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:36] "GET /index HTTP/1.1" 200 -
2025-11-24 17:17:37,034 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:37] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:17:37,035 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:37] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:17:38,456 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:38,490 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:48,010 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:48,010 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:48,013 [INFO] root: [AJAX] 작업 시작: 1763972268.0111287, script: 01-settings.py
2025-11-24 17:17:48,014 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:48] "POST /process_ips HTTP/1.1" 200 -
2025-11-24 17:17:48,025 [INFO] root: 🔧 실행 명령: D:\Code\imges_ocr\venv312\Scripts\python.exe D:\Code\iDRAC_Info\idrac_info\data\scripts\01-settings.py D:\Code\iDRAC_Info\idrac_info\data\temp_ip\ip_0.txt
2025-11-24 17:17:48,182 [INFO] root: [10.10.0.1] ✅ stdout:
오류 발생: IP 10.10.0.1 에 대한 SVC Tag 가져오기 실패
정보 수집 완료.
수집 완료 시간: 0 시간, 0 분, 0 초.
2025-11-24 17:17:48,519 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:48,519 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:48,519 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:48] "GET /progress_status/1763972268.0111287 HTTP/1.1" 200 -
2025-11-24 17:17:49,327 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:49,853 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:50,032 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:50,032 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:50,035 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:50] "GET /index HTTP/1.1" 200 -
2025-11-24 17:17:50,051 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:50] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:17:50,054 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:50] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:17:50,077 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:50] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 17:17:51,340 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:17:59,126 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:59,126 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:17:59,132 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:59] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 17:17:59,146 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:59] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:00,432 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:00,582 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:00,582 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:00,587 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:00] "GET /jobs HTTP/1.1" 200 -
2025-11-24 17:18:00,603 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:00] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:00,614 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:00,614 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:00,614 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:00] "GET /jobs/config HTTP/1.1" 200 -
2025-11-24 17:18:00,634 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:00,634 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:00,634 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-24 17:18:00,634 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:00] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-24 17:18:02,070 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:02,106 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:02,162 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:03,986 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:03,986 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:03,989 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:03] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 17:18:04,001 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:04] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:04,638 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:04,638 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:04,647 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:04] "GET /index HTTP/1.1" 200 -
2025-11-24 17:18:04,661 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:04] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:04,663 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:04] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:18:05,231 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,231 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,234 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:05] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 17:18:05,248 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:05] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:05,301 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:05,614 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,614 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,615 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:05] "GET /jobs HTTP/1.1" 200 -
2025-11-24 17:18:05,630 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:05] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:05,642 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,642 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,644 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:05] "GET /jobs/config HTTP/1.1" 200 -
2025-11-24 17:18:05,655 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,655 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:05,656 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-24 17:18:05,657 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:05] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-24 17:18:05,963 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:06,138 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:06,138 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:06,146 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:06] "GET /admin HTTP/1.1" 200 -
2025-11-24 17:18:06,159 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:06] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:06,547 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:07,125 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:07,155 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:07,180 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:07,474 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:09,491 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:09,491 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:09,495 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:09] "GET /index HTTP/1.1" 200 -
2025-11-24 17:18:09,508 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:09] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:09,512 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:09] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:18:11,282 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:17,413 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:17,413 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:17,414 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:17] "GET /moniter HTTP/1.1" 404 -
2025-11-24 17:18:17,452 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:17] "GET /favicon.ico HTTP/1.1" 404 -
2025-11-24 17:18:18,720 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:24,648 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:24,648 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:24,652 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:24] "GET /home/ HTTP/1.1" 200 -
2025-11-24 17:18:24,664 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:24] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:25,978 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:26,133 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,133 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,137 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:26] "GET /admin HTTP/1.1" 200 -
2025-11-24 17:18:26,149 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:26] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:26,463 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,463 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,464 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:26] "GET /jobs HTTP/1.1" 200 -
2025-11-24 17:18:26,479 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:26] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:26,490 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,490 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,491 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:26] "GET /jobs/config HTTP/1.1" 200 -
2025-11-24 17:18:26,502 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,502 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:26,502 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-24 17:18:26,503 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:26] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-24 17:18:27,440 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:27,941 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:27,971 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:28,010 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:29,254 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:29,254 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:29,257 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:29] "GET /xml_management HTTP/1.1" 200 -
2025-11-24 17:18:29,270 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:29] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:30,162 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:30,162 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:30,164 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:30] "GET /home/ HTTP/1.1" 200 -
2025-11-24 17:18:30,180 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:30] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:18:30,530 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:31,470 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:18:58,945 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:58,945 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:58,946 [INFO] app: LOGOUT: user=김강희
2025-11-24 17:18:58,946 [INFO] app: LOGOUT: user=김강희
2025-11-24 17:18:58,946 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:58,946 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:18:58,947 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:58] "GET /logout HTTP/1.1" 302 -
2025-11-24 17:18:58,953 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:58] "GET /login HTTP/1.1" 200 -
2025-11-24 17:18:58,965 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:58] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:19:02,424 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:19:02,444 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:19:02,444 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:19:02,459 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:19:02,482 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 17:19:02,482 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 17:19:02,482 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 17:19:03,191 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:19:03,210 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:19:03,210 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:19:03,226 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:19:03,240 [WARNING] werkzeug: * Debugger is active!
2025-11-24 17:19:03,242 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 17:36:28,877 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:36:28,899 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:36:28,899 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:36:28,919 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:36:28,943 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 17:36:28,943 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 17:36:28,943 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 17:36:29,684 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 17:36:29,704 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:36:29,704 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 17:36:29,722 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 17:36:29,736 [WARNING] werkzeug: * Debugger is active!
2025-11-24 17:36:29,739 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 17:36:31,166 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:31] "GET /login HTTP/1.1" 200 -
2025-11-24 17:36:31,209 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:31] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:36:31,230 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:31] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 17:36:37,567 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:36:37,567 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-24 17:36:37,621 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:36:37,621 [INFO] app: LOGIN: found id=1 active=True pass_ok=True
2025-11-24 17:36:37,621 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:36:37,621 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:36:37,621 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:36:37,621 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-24 17:36:37,623 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:37] "POST /login HTTP/1.1" 302 -
2025-11-24 17:36:37,626 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:36:37,626 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 17:36:37,643 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:37] "GET /index HTTP/1.1" 200 -
2025-11-24 17:36:37,662 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:37] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 17:36:37,665 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:37] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 17:36:39,139 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 17:36:39,167 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 18:05:50,478 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 18:05:50,499 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 18:05:50,499 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 18:05:50,515 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 18:05:50,535 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-24 18:05:50,535 [INFO] werkzeug: Press CTRL+C to quit
2025-11-24 18:05:50,536 [INFO] werkzeug: * Restarting with watchdog (windowsapi)
2025-11-24 18:05:51,256 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-24 18:05:51,276 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 18:05:51,276 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-24 18:05:51,291 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-24 18:05:51,305 [WARNING] werkzeug: * Debugger is active!
2025-11-24 18:05:51,308 [INFO] werkzeug: * Debugger PIN: 778-054-746
2025-11-24 18:05:51,329 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:51,329 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:51,337 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:51] "GET /home/ HTTP/1.1" 200 -
2025-11-24 18:05:51,377 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:51] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 18:05:51,409 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:51,409 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:51,411 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:51] "GET /home/ HTTP/1.1" 200 -
2025-11-24 18:05:51,427 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:51] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 18:05:51,446 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:51] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-24 18:05:52,762 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 18:05:52,797 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:52,797 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:52,801 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 18:05:52,816 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:52] "GET /index HTTP/1.1" 200 -
2025-11-24 18:05:52,832 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 18:05:52,833 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:52] "GET /static/script.js HTTP/1.1" 304 -
2025-11-24 18:05:54,028 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 18:05:54,426 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:54,426 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:54,427 [INFO] app: LOGOUT: user=김강희
2025-11-24 18:05:54,427 [INFO] app: LOGOUT: user=김강희
2025-11-24 18:05:54,427 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:54,427 [INFO] app: Telegram Debug: Token=OK, ChatID=OK, Lib=OK
2025-11-24 18:05:54,429 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:54] "GET /logout HTTP/1.1" 302 -
2025-11-24 18:05:54,434 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:54] "GET /login HTTP/1.1" 200 -
2025-11-24 18:05:54,451 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:54] "GET /static/style.css HTTP/1.1" 304 -
2025-11-24 18:05:55,766 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-24 18:05:55,801 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"

8939
data/logs/2025-11-26.log Normal file

File diff suppressed because it is too large Load Diff

557
data/logs/2025-11-27.log Normal file
View File

@@ -0,0 +1,557 @@
2025-11-27 18:53:56,188 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 18:53:56,211 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 18:53:56,211 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 18:53:56,236 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 18:53:56,270 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 18:53:56,270 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 18:54:04,239 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:04] "GET / HTTP/1.1" 302 -
2025-11-27 18:54:04,265 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:04] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-27 18:54:04,330 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:04] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 18:54:16,632 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 18:54:16,632 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 18:54:16,702 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 18:54:16,702 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 18:54:16,704 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 18:54:16,704 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 18:54:16,704 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:16] "POST /login HTTP/1.1" 302 -
2025-11-27 18:54:16,720 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:16] "GET /index HTTP/1.1" 200 -
2025-11-27 18:54:16,741 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:16] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 18:54:16,753 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:16] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 18:54:18,104 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-27 18:54:24,104 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:24] "GET /admin HTTP/1.1" 200 -
2025-11-27 18:54:24,119 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:24] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 18:54:25,237 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:25] "GET /admin/settings HTTP/1.1" 200 -
2025-11-27 18:54:25,251 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:25] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 18:54:37,033 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:37] "GET /home/ HTTP/1.1" 200 -
2025-11-27 18:54:37,047 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:37] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 18:54:38,345 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:38] "GET /admin HTTP/1.1" 200 -
2025-11-27 18:54:38,357 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:38] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 18:54:45,869 [INFO] app: LOGOUT: user=김강희
2025-11-27 18:54:45,869 [INFO] app: LOGOUT: user=김강희
2025-11-27 18:54:45,872 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:45] "GET /logout HTTP/1.1" 302 -
2025-11-27 18:54:45,877 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:45] "GET /login HTTP/1.1" 200 -
2025-11-27 18:54:45,899 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:45] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 18:54:47,218 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-27 19:49:49,532 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 19:49:49,554 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 19:49:49,554 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 19:49:49,571 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 19:49:49,593 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 19:49:49,593 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 19:49:52,485 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:49:52] "GET / HTTP/1.1" 302 -
2025-11-27 19:49:52,496 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:49:52] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-27 19:49:52,544 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:49:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:00,494 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 19:50:00,494 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 19:50:00,549 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 19:50:00,549 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 19:50:00,550 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 19:50:00,550 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 19:50:00,551 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:00] "POST /login HTTP/1.1" 302 -
2025-11-27 19:50:00,571 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:00] "GET /index HTTP/1.1" 200 -
2025-11-27 19:50:00,590 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:00] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:00,591 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:00] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 19:50:01,888 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-27 19:50:03,009 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:03] "GET /admin HTTP/1.1" 200 -
2025-11-27 19:50:03,022 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:03] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:04,715 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:04] "GET /admin/settings HTTP/1.1" 200 -
2025-11-27 19:50:04,727 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:04] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:06,249 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /jobs HTTP/1.1" 200 -
2025-11-27 19:50:06,263 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:06,276 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /jobs/config HTTP/1.1" 200 -
2025-11-27 19:50:06,292 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-27 19:50:06,292 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-27 19:50:06,939 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 19:50:06,952 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:06,956 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 19:50:06,956 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:06] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 19:50:22,779 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:22] "GET /scp/diff?file1=LinePlus_T1.xml&file2=PO-20250826-0158%20_가산3_XE9680_384EA.xml HTTP/1.1" 200 -
2025-11-27 19:50:22,795 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:22] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:22,796 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:22] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 19:50:37,550 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:37] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 19:50:37,576 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:37] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:37,577 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:37] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 19:50:37,578 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:37] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 19:50:46,123 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:46] "GET /edit_xml/LinePlus_T1.xml HTTP/1.1" 200 -
2025-11-27 19:50:46,138 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:46] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:49,706 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:49] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 19:50:49,723 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:49] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 19:50:49,724 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:49] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 19:50:49,724 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:49] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 19:55:01,647 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:55:01] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 19:55:01,665 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:55:01] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 19:55:01,669 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:55:01] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 19:55:01,669 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:55:01] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 19:55:01,718 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:55:01] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 19:59:23,834 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:59:23] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 19:59:23,858 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:59:23] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 19:59:23,859 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:59:23] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 19:59:23,859 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:59:23] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 19:59:23,891 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:59:23] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 20:03:11,961 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:03:11] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:03:11,984 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:03:11] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 20:03:11,986 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:03:11] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 20:03:11,987 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:03:11] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 20:03:12,018 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:03:12] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 20:05:25,592 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:05:25] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:05:25,617 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:05:25] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 20:05:25,618 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:05:25] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 20:05:25,618 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:05:25] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 20:05:25,650 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:05:25] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 20:06:41,550 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:06:41] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:06:41,572 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:06:41] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 20:06:41,573 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:06:41] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 20:06:41,574 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:06:41] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 20:06:41,660 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:06:41] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 20:07:01,417 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:01] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:07:01,437 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:01] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 20:07:01,437 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:01] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 20:07:01,438 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:01] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 20:07:01,466 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:01] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 20:07:42,817 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:42] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:07:42,837 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:42] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 20:07:42,838 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:42] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 20:07:42,838 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:42] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 20:07:42,872 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:42] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 20:07:55,087 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:07:55,108 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:07:55,108 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:07:55,128 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:07:55,151 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:07:55,151 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:07:56,391 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:56] "GET /xml_management HTTP/1.1" 404 -
2025-11-27 20:07:56,422 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:56] "GET /favicon.ico HTTP/1.1" 404 -
2025-11-27 20:07:57,096 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:57] "GET /xml_management HTTP/1.1" 404 -
2025-11-27 20:07:58,679 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:58] "GET /xml_management HTTP/1.1" 404 -
2025-11-27 20:07:59,693 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:59] "GET /xml_management HTTP/1.1" 404 -
2025-11-27 20:08:03,545 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "GET / HTTP/1.1" 500 -
2025-11-27 20:08:03,594 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:08:03,608 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:08:03,631 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1" 200 -
2025-11-27 20:08:03,638 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
2025-11-27 20:08:05,565 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET / HTTP/1.1" 500 -
2025-11-27 20:08:05,576 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:08:05,580 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:08:05,584 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1" 304 -
2025-11-27 20:08:05,591 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
2025-11-27 20:08:05,752 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET / HTTP/1.1" 500 -
2025-11-27 20:08:05,765 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:08:05,768 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:08:05,775 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1" 304 -
2025-11-27 20:08:05,792 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
2025-11-27 20:08:06,661 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:06] "GET / HTTP/1.1" 500 -
2025-11-27 20:08:06,674 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:06] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:08:06,678 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:06] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:08:06,685 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:06] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1" 200 -
2025-11-27 20:08:06,704 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:06] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:08:07,063 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET / HTTP/1.1" 500 -
2025-11-27 20:08:07,074 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:08:07,075 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:08:07,081 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1" 200 -
2025-11-27 20:08:07,100 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:08:07,213 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET / HTTP/1.1" 500 -
2025-11-27 20:08:07,227 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:08:07,228 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:08:07,246 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1" 200 -
2025-11-27 20:08:07,255 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:07] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:08:37,699 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:08:37,721 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:08:37,721 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:08:37,738 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:08:37,760 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:08:37,760 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:09:08,168 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:09:08,188 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:09:08,188 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:09:08,209 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:09:08,232 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:09:08,232 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:09:10,319 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:10,362 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:09:10,365 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:09:10,415 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:09:10,421 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
2025-11-27 20:09:11,297 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:11,308 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:09:11,311 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:09:11,318 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 304 -
2025-11-27 20:09:11,337 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
2025-11-27 20:09:11,859 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:11,870 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:09:11,874 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:09:11,880 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 304 -
2025-11-27 20:09:11,895 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
2025-11-27 20:09:12,026 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:12,038 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:09:12,042 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:09:12,048 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 304 -
2025-11-27 20:09:12,065 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
2025-11-27 20:09:13,701 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:13,713 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:09:13,713 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:09:13,720 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 304 -
2025-11-27 20:09:19,987 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:19] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:20,588 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:20,605 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-27 20:09:20,607 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-27 20:09:20,632 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 304 -
2025-11-27 20:09:33,685 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:09:33,707 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:09:33,707 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:09:33,729 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:09:33,751 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:09:33,751 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:09:35,916 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:35] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:35,929 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:35] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:09:35,932 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:35] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:09:35,941 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:35] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:09:35,960 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:35] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:09:36,395 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:36,406 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:09:36,407 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:09:36,419 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:09:36,435 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:09:36,568 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:36,584 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:09:36,584 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:09:36,592 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:09:36,608 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:09:36,721 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:36,734 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:09:36,736 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:09:36,743 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:09:36,763 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:09:36,863 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET / HTTP/1.1" 500 -
2025-11-27 20:09:36,876 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:09:36,877 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:09:36,882 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:09:36,901 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:36] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:09:52,809 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:52] "GET / HTTP/1.1" 302 -
2025-11-27 20:09:52,820 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:52] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-27 20:09:52,862 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:52] "GET /static/style.css HTTP/1.1" 200 -
2025-11-27 20:09:52,966 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:52] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-27 20:09:57,841 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 20:09:57,841 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 20:09:57,895 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 20:09:57,895 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 20:09:57,897 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 20:09:57,897 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 20:09:57,897 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:57] "POST /login HTTP/1.1" 302 -
2025-11-27 20:09:57,917 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:57] "GET /index HTTP/1.1" 500 -
2025-11-27 20:09:57,934 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:57] "GET /index?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:09:57,938 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:57] "GET /index?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:09:57,953 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:57] "GET /index?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:09:57,982 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:57] "GET /index?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:09:59,216 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-27 20:10:02,357 [INFO] app: LOGIN: already auth → /index
2025-11-27 20:10:02,357 [INFO] app: LOGIN: already auth → /index
2025-11-27 20:10:02,359 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:10:02] "GET /login?next=/ HTTP/1.1" 302 -
2025-11-27 20:10:19,124 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:10:19,143 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:10:19,143 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:10:19,163 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:10:19,184 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:10:19,184 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:10:21,639 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:10:21] "GET / HTTP/1.1" 500 -
2025-11-27 20:10:21,657 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:10:21] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-27 20:10:21,664 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:10:21] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-27 20:10:21,670 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:10:21] "GET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1" 200 -
2025-11-27 20:10:21,720 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:10:21] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-27 20:11:34,671 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:11:34,689 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:11:34,689 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:11:34,708 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:11:34,731 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:11:34,731 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:11:42,219 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:11:42,238 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:11:42,238 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:11:42,254 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:11:42,278 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:11:42,278 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:11:44,565 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:44] "GET / HTTP/1.1" 200 -
2025-11-27 20:11:44,635 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:44] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:11:44,637 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:44] "GET /static/script.js HTTP/1.1" 200 -
2025-11-27 20:11:46,595 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:46] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:11:46,611 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:46] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:11:46,618 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:46] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-27 20:11:46,619 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:46] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-27 20:11:54,440 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:54] "GET /edit_xml/LinePlus_T1.xml HTTP/1.1" 200 -
2025-11-27 20:11:54,458 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:54] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:11:56,650 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:56] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:11:56,668 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:56] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:11:56,671 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:56] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:11:56,672 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:56] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:12:12,744 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:12:12] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:12:12,762 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:12:12] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:12:14,222 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:12:14] "GET /index HTTP/1.1" 200 -
2025-11-27 20:12:14,239 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:12:14] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:12:14,244 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:12:14] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:17:16,167 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:16] "GET / HTTP/1.1" 200 -
2025-11-27 20:17:16,176 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:16] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:17:16,187 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:16] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:17:16,646 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:16] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-27 20:17:18,728 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:18] "GET /admin HTTP/1.1" 200 -
2025-11-27 20:17:18,744 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:18] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:17:20,726 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:20] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:17:20,739 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:20] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:17:21,701 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:21] "GET /index HTTP/1.1" 200 -
2025-11-27 20:17:21,714 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:21] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:17:21,718 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:21] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:17:29,307 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "GET /index HTTP/1.1" 200 -
2025-11-27 20:17:29,322 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:17:29,322 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:17:29,884 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:17:29,897 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:17:29,900 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:17:29,901 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:20:30,931 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:30] "GET / HTTP/1.1" 200 -
2025-11-27 20:20:30,941 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:30] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:20:30,952 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:30] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:20:33,122 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:33] "GET /admin HTTP/1.1" 200 -
2025-11-27 20:20:33,138 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:33] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:20:34,023 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:34] "GET /jobs HTTP/1.1" 200 -
2025-11-27 20:20:34,035 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:34] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:20:34,063 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:34] "GET /jobs/config HTTP/1.1" 200 -
2025-11-27 20:20:34,068 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-27 20:20:34,069 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:34] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-27 20:20:35,634 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:35] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:20:35,646 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:35] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:20:35,651 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:35] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:20:35,651 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:35] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:20:39,035 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:39] "GET /scp/diff?file1=LinePlus_T1.xml&file2=PO-20250826-0158%20_가산3_XE9680_384EA.xml HTTP/1.1" 200 -
2025-11-27 20:20:39,051 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:39] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:20:39,052 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:39] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:20:52,531 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:52] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:20:52,546 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:32,928 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:32] "GET /jobs HTTP/1.1" 200 -
2025-11-27 20:21:32,945 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:32] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:32,987 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:32] "GET /jobs/config HTTP/1.1" 200 -
2025-11-27 20:21:32,993 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-27 20:21:32,993 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:32] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-27 20:21:34,137 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:21:34,156 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:34,164 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:21:34,164 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:21:34,600 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:21:34,617 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:35,135 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "GET /index HTTP/1.1" 200 -
2025-11-27 20:21:35,151 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:35,154 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:21:35,599 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:21:35,614 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:35,622 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:21:35,622 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:21:36,236 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:36] "GET /jobs HTTP/1.1" 200 -
2025-11-27 20:21:36,251 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:36] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:36,265 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:36] "GET /jobs/config HTTP/1.1" 200 -
2025-11-27 20:21:36,279 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-27 20:21:36,279 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:36] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-27 20:21:36,700 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:36] "GET /admin HTTP/1.1" 200 -
2025-11-27 20:21:36,715 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:36] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:38,082 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /jobs HTTP/1.1" 200 -
2025-11-27 20:21:38,101 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:38,109 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /jobs/config HTTP/1.1" 200 -
2025-11-27 20:21:38,127 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-27 20:21:38,128 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-27 20:21:38,372 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:21:38,388 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:38,395 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:21:38,395 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:21:38,719 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /index HTTP/1.1" 200 -
2025-11-27 20:21:38,735 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:38,738 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:21:39,219 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:39] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:21:39,237 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:39] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:39,902 [INFO] app: LOGOUT: user=김강희
2025-11-27 20:21:39,902 [INFO] app: LOGOUT: user=김강희
2025-11-27 20:21:39,904 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:39] "GET /logout HTTP/1.1" 302 -
2025-11-27 20:21:39,910 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:39] "GET /login HTTP/1.1" 200 -
2025-11-27 20:21:39,926 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:39] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:21:41,234 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-27 20:21:42,246 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:42] "GET /register HTTP/1.1" 200 -
2025-11-27 20:21:42,262 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:42] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:22:04,167 [INFO] app: REGISTER: created id=3 email=rnfjrl@test.com token=jvoddlCPFQ
2025-11-27 20:22:04,167 [INFO] app: REGISTER: created id=3 email=rnfjrl@test.com token=jvoddlCPFQ
2025-11-27 20:22:04,168 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:22:04] "POST /register HTTP/1.1" 302 -
2025-11-27 20:22:04,172 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:22:04] "GET /login HTTP/1.1" 200 -
2025-11-27 20:22:04,190 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:22:04] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:22:05,431 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-27 20:25:48,870 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:25:48,891 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:25:48,891 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:25:48,908 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:25:48,931 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:25:48,931 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:25:53,316 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "GET /login HTTP/1.1" 200 -
2025-11-27 20:25:53,377 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:25:53,444 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-27 20:25:53,880 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "GET /login HTTP/1.1" 200 -
2025-11-27 20:25:53,893 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:25:53,913 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-27 20:28:38,443 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-27 20:28:38,463 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:28:38,463 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-27 20:28:38,481 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-27 20:28:38,494 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-27 20:28:38,494 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-27 20:28:38,494 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-27 20:28:38,494 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-27 20:28:38,525 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-27 20:28:38,525 [INFO] werkzeug: Press CTRL+C to quit
2025-11-27 20:28:39,629 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-27 20:28:39,861 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-27 20:28:39,861 [INFO] telegram.ext.Application: Application started
2025-11-27 20:28:43,484 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:28:43] "GET /login HTTP/1.1" 200 -
2025-11-27 20:28:43,527 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:28:43] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:28:43,542 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:28:43] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-27 20:28:50,542 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:28:50,849 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:28:51,707 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/answerCallbackQuery "HTTP/1.1 200 OK"
2025-11-27 20:28:51,707 [INFO] telegram_bot_service: Received callback: approve_jvoddlCPFQRfhbW_yZUPj3lzoaX9ByxZq6qJcrPtlHU
2025-11-27 20:28:52,115 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/editMessageText "HTTP/1.1 200 OK"
2025-11-27 20:28:52,115 [INFO] telegram_bot_service: User 꾸러기 approved
2025-11-27 20:29:01,076 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:29:11,305 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:29:21,550 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:29:31,778 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:29:42,004 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:29:46,006 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:46] "GET /login HTTP/1.1" 200 -
2025-11-27 20:29:46,027 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:46] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:29:46,051 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:46] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-27 20:29:51,337 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 20:29:51,337 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-27 20:29:51,385 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 20:29:51,385 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-27 20:29:51,385 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 20:29:51,385 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-27 20:29:51,387 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:51] "POST /login HTTP/1.1" 302 -
2025-11-27 20:29:51,403 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:51] "GET /index HTTP/1.1" 200 -
2025-11-27 20:29:51,424 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:51] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:29:51,426 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:51] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:29:52,238 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:29:52,602 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-27 20:29:52,831 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:52] "GET /admin HTTP/1.1" 200 -
2025-11-27 20:29:52,848 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:29:56,303 [INFO] root: 🗑 삭제된 사용자: 꾸러기 (id=3)
2025-11-27 20:29:56,304 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:56] "GET /admin/delete/3 HTTP/1.1" 302 -
2025-11-27 20:29:56,311 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:56] "GET /admin HTTP/1.1" 200 -
2025-11-27 20:29:56,327 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:56] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:29:59,804 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:59] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:29:59,821 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:59] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:30:02,465 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:30:12,699 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:30:22,951 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:30:33,178 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:30:40,127 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:30:40] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:30:40,150 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:30:40] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:30:40,157 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:30:40] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:30:40,158 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:30:40] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:30:43,407 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:30:53,647 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:31:03,882 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:31:14,109 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:31:24,340 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:31:34,589 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:31:44,816 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:31:55,041 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:32:05,272 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:32:15,514 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:32:25,741 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:32:35,967 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:32:46,197 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:32:56,422 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:32:58,550 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:32:58] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:32:58,570 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:32:58] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:06,650 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:33:16,876 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:33:27,104 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:33:29,679 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:29] "GET /home/ HTTP/1.1" 200 -
2025-11-27 20:33:29,699 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:29] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:37,329 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:33:47,556 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:33:48,168 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:48] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:33:48,184 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:48] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:48,192 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:48] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:33:48,192 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:48] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:33:49,273 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:49] "GET /index HTTP/1.1" 200 -
2025-11-27 20:33:49,291 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:49] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:49,295 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:49] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:33:50,150 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:33:50,167 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:50,172 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:33:50,172 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:33:50,728 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /jobs HTTP/1.1" 200 -
2025-11-27 20:33:50,747 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:50,761 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /jobs/config HTTP/1.1" 200 -
2025-11-27 20:33:50,778 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-27 20:33:50,779 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-27 20:33:51,185 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:51] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:33:51,205 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:51] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:51,210 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:51] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:33:51,211 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:51] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:33:52,127 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "GET /xml_management HTTP/1.1" 200 -
2025-11-27 20:33:52,144 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:52,148 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-27 20:33:52,149 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-27 20:33:52,556 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "GET /index HTTP/1.1" 200 -
2025-11-27 20:33:52,572 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:52,575 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:33:54,040 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:54] "GET /index HTTP/1.1" 200 -
2025-11-27 20:33:54,055 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:54] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:33:54,059 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:54] "GET /static/script.js HTTP/1.1" 304 -
2025-11-27 20:33:57,792 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:34:05,559 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /idrac HTTP/1.1" 308 -
2025-11-27 20:34:05,566 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /idrac/ HTTP/1.1" 200 -
2025-11-27 20:34:05,585 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /static/css/idrac_style.css HTTP/1.1" 200 -
2025-11-27 20:34:05,588 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /static/js/idrac_main.js HTTP/1.1" 200 -
2025-11-27 20:34:05,654 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /socket.io/?EIO=4&transport=polling&t=Ph55ufK HTTP/1.1" 200 -
2025-11-27 20:34:05,661 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /idrac/api/servers HTTP/1.1" 200 -
2025-11-27 20:34:05,662 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /idrac/api/groups HTTP/1.1" 200 -
2025-11-27 20:34:05,663 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "POST /socket.io/?EIO=4&transport=polling&t=Ph55ufT&sid=Kd7MSTztfydrG7iVAAAA HTTP/1.1" 200 -
2025-11-27 20:34:05,665 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /favicon.ico HTTP/1.1" 404 -
2025-11-27 20:34:05,666 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:05] "GET /socket.io/?EIO=4&transport=polling&t=Ph55ufT.0&sid=Kd7MSTztfydrG7iVAAAA HTTP/1.1" 200 -
2025-11-27 20:34:08,020 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:34:18,248 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:34:24,288 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:24] "GET /socket.io/?EIO=4&transport=websocket&sid=Kd7MSTztfydrG7iVAAAA HTTP/1.1" 200 -
2025-11-27 20:34:26,800 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:26] "GET /jobs HTTP/1.1" 200 -
2025-11-27 20:34:26,818 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:26] "GET /static/style.css HTTP/1.1" 304 -
2025-11-27 20:34:26,835 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:26] "GET /jobs/config HTTP/1.1" 200 -
2025-11-27 20:34:26,849 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-27 20:34:26,849 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:26] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-27 20:34:28,478 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-27 20:34:31,570 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:31] "GET /admin HTTP/1.1" 200 -
2025-11-27 20:34:31,586 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:34:31] "GET /static/style.css HTTP/1.1" 304 -

95
data/logs/2025-11-28.log Normal file
View File

@@ -0,0 +1,95 @@
2025-11-28 15:15:26,731 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-28 15:15:26,756 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-28 15:15:26,756 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-28 15:15:26,778 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-28 15:15:26,792 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-28 15:15:26,792 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-28 15:15:26,793 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-28 15:15:26,793 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-28 15:15:26,835 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-28 15:15:26,835 [INFO] werkzeug: Press CTRL+C to quit
2025-11-28 15:15:27,893 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-28 15:15:28,121 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-28 15:15:28,122 [INFO] telegram.ext.Application: Application started
2025-11-28 15:15:38,822 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:15:49,057 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:15:59,291 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:16:09,530 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:16:19,765 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:16:30,000 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:16:40,234 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:21:41,955 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-28 15:21:41,978 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-28 15:21:41,978 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-28 15:21:41,997 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-28 15:21:42,011 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-28 15:21:42,011 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-28 15:21:42,012 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-28 15:21:42,012 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-28 15:21:42,028 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-28 15:21:42,028 [INFO] werkzeug: Press CTRL+C to quit
2025-11-28 15:21:43,126 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-28 15:21:43,358 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-28 15:21:43,360 [INFO] telegram.ext.Application: Application started
2025-11-28 15:21:49,495 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:49] "GET / HTTP/1.1" 302 -
2025-11-28 15:21:49,520 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:49] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-28 15:21:49,602 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:49] "GET /static/style.css HTTP/1.1" 304 -
2025-11-28 15:21:54,039 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:21:58,823 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-28 15:21:58,823 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-28 15:21:58,900 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-28 15:21:58,900 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-28 15:21:58,901 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-28 15:21:58,901 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-28 15:21:58,901 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:58] "POST /login HTTP/1.1" 302 -
2025-11-28 15:21:58,918 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:58] "GET /index HTTP/1.1" 200 -
2025-11-28 15:21:58,935 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:58] "GET /static/style.css HTTP/1.1" 304 -
2025-11-28 15:21:58,946 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:58] "GET /static/script.js HTTP/1.1" 304 -
2025-11-28 15:22:00,150 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-28 15:22:01,767 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:01] "GET /admin HTTP/1.1" 200 -
2025-11-28 15:22:01,779 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:01] "GET /static/style.css HTTP/1.1" 304 -
2025-11-28 15:22:02,852 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:02] "GET /jobs HTTP/1.1" 200 -
2025-11-28 15:22:02,865 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:02] "GET /static/style.css HTTP/1.1" 304 -
2025-11-28 15:22:02,893 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:02] "GET /jobs/config HTTP/1.1" 200 -
2025-11-28 15:22:02,900 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-28 15:22:02,900 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:02] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-28 15:22:04,264 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:22:06,057 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:06] "GET /xml_management HTTP/1.1" 200 -
2025-11-28 15:22:06,070 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:06] "GET /static/style.css HTTP/1.1" 304 -
2025-11-28 15:22:06,076 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:06] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-28 15:22:06,076 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:06] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-28 15:22:14,492 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:22:24,718 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:22:25,589 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:25] "GET /edit_xml/LinePlus_T1.xml HTTP/1.1" 200 -
2025-11-28 15:22:25,607 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:25] "GET /static/style.css HTTP/1.1" 304 -
2025-11-28 15:22:29,679 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:29] "GET /xml_management HTTP/1.1" 200 -
2025-11-28 15:22:29,692 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:29] "GET /static/style.css HTTP/1.1" 304 -
2025-11-28 15:22:29,696 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:29] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-28 15:22:29,696 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:29] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-28 15:22:34,945 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 15:22:45,172 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-28 18:18:06,930 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-28 18:18:06,953 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-28 18:18:06,953 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-28 18:18:06,972 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-28 18:18:06,985 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-28 18:18:06,985 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-28 18:18:06,985 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-28 18:18:06,985 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-28 18:18:07,024 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-28 18:18:07,024 [INFO] werkzeug: Press CTRL+C to quit
2025-11-28 18:18:08,082 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-28 18:18:08,314 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-28 18:18:08,315 [INFO] telegram.ext.Application: Application started
2025-11-28 18:18:11,284 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 18:18:11] "GET / HTTP/1.1" 302 -
2025-11-28 18:18:11,304 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 18:18:11] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-28 18:18:11,334 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 18:18:11] "GET /static/style.css HTTP/1.1" 304 -

756
data/logs/2025-11-29.log Normal file
View File

@@ -0,0 +1,756 @@
2025-11-29 08:14:35,726 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-29 08:14:35,756 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 08:14:35,756 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 08:14:35,783 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-29 08:14:35,803 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 08:14:35,803 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 08:14:35,803 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 08:14:35,803 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 08:14:35,856 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-29 08:14:35,856 [INFO] werkzeug: Press CTRL+C to quit
2025-11-29 08:14:36,951 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-29 08:14:37,184 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-29 08:14:37,185 [INFO] telegram.ext.Application: Application started
2025-11-29 08:14:38,056 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "GET / HTTP/1.1" 302 -
2025-11-29 08:14:38,088 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-29 08:14:38,346 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "GET /static/style.css HTTP/1.1" 200 -
2025-11-29 08:14:38,694 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-29 08:14:43,883 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 08:14:43,883 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 08:14:43,954 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-29 08:14:43,954 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-29 08:14:43,955 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-29 08:14:43,955 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-29 08:14:43,956 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:43] "POST /login HTTP/1.1" 302 -
2025-11-29 08:14:44,191 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /index HTTP/1.1" 200 -
2025-11-29 08:14:44,517 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 08:14:44,522 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /static/css/index.css HTTP/1.1" 200 -
2025-11-29 08:14:44,525 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /static/script.js HTTP/1.1" 200 -
2025-11-29 08:14:44,529 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /static/js/index.js HTTP/1.1" 200 -
2025-11-29 08:14:45,195 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-29 08:14:47,867 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:14:49,161 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "GET /idrac HTTP/1.1" 308 -
2025-11-29 08:14:49,475 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "GET /idrac/ HTTP/1.1" 200 -
2025-11-29 08:14:49,797 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "GET /static/css/idrac_style.css HTTP/1.1" 200 -
2025-11-29 08:14:49,798 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "GET /static/js/idrac_main.js HTTP/1.1" 200 -
2025-11-29 08:14:50,054 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /socket.io/?EIO=4&transport=polling&t=PhClt5V HTTP/1.1" 200 -
2025-11-29 08:14:50,136 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "POST /socket.io/?EIO=4&transport=polling&t=PhClt98&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
2025-11-29 08:14:50,139 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /idrac/api/groups HTTP/1.1" 200 -
2025-11-29 08:14:50,140 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /favicon.ico HTTP/1.1" 404 -
2025-11-29 08:14:50,142 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /idrac/api/servers HTTP/1.1" 200 -
2025-11-29 08:14:50,367 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /socket.io/?EIO=4&transport=polling&t=PhClt99&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
2025-11-29 08:14:50,372 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /socket.io/?EIO=4&transport=polling&t=PhCltE3&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
2025-11-29 08:14:55,662 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:55] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 08:14:58,093 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:15:06,515 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:06] "POST /catalog/sync HTTP/1.1" 200 -
2025-11-29 08:15:08,327 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:15:13,235 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:13] "POST /catalog/sync HTTP/1.1" 200 -
2025-11-29 08:15:18,557 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:15:20,040 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:20] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 08:15:28,783 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:15:28,937 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:28] "POST /catalog/sync HTTP/1.1" 200 -
2025-11-29 08:15:34,670 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:34] "POST /catalog/sync HTTP/1.1" 200 -
2025-11-29 08:15:39,011 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:15:49,239 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:15:59,473 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:16:09,699 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:16:19,927 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:16:30,154 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:16:40,381 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:16:50,615 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:17:00,843 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:17:02,435 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:02] "GET /socket.io/?EIO=4&transport=websocket&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
2025-11-29 08:17:02,757 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:02] "GET /index HTTP/1.1" 200 -
2025-11-29 08:17:02,776 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:02] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 08:17:03,002 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /static/css/index.css HTTP/1.1" 304 -
2025-11-29 08:17:03,082 [INFO] app: LOGIN: already auth → /index
2025-11-29 08:17:03,082 [INFO] app: LOGIN: already auth → /index
2025-11-29 08:17:03,082 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /login?next=/ HTTP/1.1" 302 -
2025-11-29 08:17:03,083 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /static/js/index.js HTTP/1.1" 304 -
2025-11-29 08:17:03,191 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /index HTTP/1.1" 200 -
2025-11-29 08:17:03,390 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 08:17:03,515 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /static/js/index.js HTTP/1.1" 304 -
2025-11-29 08:17:03,516 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /static/script.js HTTP/1.1" 200 -
2025-11-29 08:17:03,517 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /static/css/index.css HTTP/1.1" 304 -
2025-11-29 08:17:11,069 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:17:21,296 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:17:31,524 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:17:41,752 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:17:51,985 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:18:02,214 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:18:12,440 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:18:22,665 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:18:32,897 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:18:43,122 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:18:53,349 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:19:03,575 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:19:13,803 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:19:24,030 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:19:34,258 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:19:44,487 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:19:54,717 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:19:58,781 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:58] "GET /idrac HTTP/1.1" 308 -
2025-11-29 08:19:58,783 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:58] "GET /idrac/ HTTP/1.1" 200 -
2025-11-29 08:19:59,032 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /static/css/idrac_style.css HTTP/1.1" 304 -
2025-11-29 08:19:59,107 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /static/js/idrac_main.js HTTP/1.1" 304 -
2025-11-29 08:19:59,421 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /idrac/api/groups HTTP/1.1" 200 -
2025-11-29 08:19:59,421 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /socket.io/?EIO=4&transport=polling&t=PhCn2c6 HTTP/1.1" 200 -
2025-11-29 08:19:59,422 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /idrac/api/servers HTTP/1.1" 200 -
2025-11-29 08:19:59,683 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "POST /socket.io/?EIO=4&transport=polling&t=PhCn2h1&sid=2pMufQvxsqmN2V1_AAAC HTTP/1.1" 200 -
2025-11-29 08:19:59,728 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /socket.io/?EIO=4&transport=polling&t=PhCn2h2&sid=2pMufQvxsqmN2V1_AAAC HTTP/1.1" 200 -
2025-11-29 08:19:59,731 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /socket.io/?EIO=4&transport=polling&t=PhCn2lp&sid=2pMufQvxsqmN2V1_AAAC HTTP/1.1" 200 -
2025-11-29 08:20:00,769 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:20:00] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 08:20:04,945 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:20:15,177 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:20:25,403 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:20:35,632 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:20:45,864 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:20:56,090 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:21:06,318 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:21:16,543 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:21:26,770 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:21:36,999 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:21:47,226 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:21:57,452 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:22:07,684 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:22:17,910 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:22:28,138 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:22:38,367 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:22:48,595 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:22:58,821 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:23:09,047 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:23:19,273 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:23:29,500 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:23:39,735 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:23:49,961 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:24:00,192 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:24:10,417 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:24:20,646 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:24:30,873 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:24:41,103 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:24:51,330 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:25:01,559 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:25:11,790 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:25:22,018 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:25:32,246 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:25:42,473 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:25:52,705 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:26:02,931 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:26:13,187 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:26:23,415 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:26:33,648 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:26:43,874 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:26:54,100 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:27:04,325 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:27:14,551 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:27:24,777 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:27:35,003 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:27:45,231 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:27:55,457 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:28:05,685 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:28:15,911 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:28:26,138 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:28:36,363 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:28:46,594 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:28:56,820 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:29:07,047 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:29:17,276 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:29:27,503 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:29:37,731 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:29:47,957 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:29:58,191 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:30:02,765 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:30:02] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 08:30:08,423 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:30:18,651 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:30:28,878 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:30:39,106 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:30:49,332 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:30:59,559 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:31:09,787 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:31:20,014 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:31:30,240 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:31:40,465 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:31:51,174 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:32:01,407 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:32:11,642 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:32:21,877 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:32:32,111 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:32:42,345 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:32:52,577 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:33:02,812 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:33:13,045 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:33:23,280 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:33:33,516 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:33:43,749 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:33:53,983 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:34:04,216 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:34:14,449 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:34:24,682 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:34:34,916 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:34:45,150 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:34:55,385 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:35:05,621 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:35:15,853 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:35:26,087 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:35:36,321 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:35:46,554 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:35:56,788 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:36:07,023 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:36:17,255 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:36:27,488 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:36:37,721 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:36:47,957 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:36:58,191 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:37:08,430 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:37:18,665 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:37:28,899 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:37:39,149 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:37:49,392 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:37:59,628 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:38:09,861 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:38:20,093 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:38:30,329 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:38:40,563 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:38:50,800 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:39:01,033 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:39:11,270 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:39:21,504 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:39:31,738 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:39:41,972 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:39:52,206 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:40:02,440 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:40:12,673 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:40:22,908 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:40:33,144 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:40:43,379 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:40:53,613 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:41:03,847 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:41:14,080 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:41:24,344 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:41:34,578 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:41:44,811 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:41:55,043 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:42:05,277 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:42:15,512 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:42:25,750 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:42:35,985 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:42:46,218 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:42:56,453 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:43:06,688 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:43:16,922 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:43:27,155 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:43:37,387 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:43:47,621 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:43:57,855 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:44:08,089 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:44:18,322 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:44:28,558 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:44:38,791 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:44:49,028 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:44:59,263 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:45:09,502 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:45:19,736 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:45:29,970 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:45:40,205 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:45:50,438 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:46:00,670 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:46:10,903 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:46:21,137 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:46:31,370 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:46:41,623 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:46:51,857 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:47:02,090 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:47:12,331 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:47:22,565 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:47:32,798 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:47:43,033 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:47:53,269 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:48:03,502 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:48:13,738 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:48:23,972 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:48:34,207 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:48:44,441 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:48:55,138 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:49:05,370 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:49:15,600 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:49:25,831 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:49:36,063 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:49:46,293 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:49:56,524 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:50:06,756 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:50:16,990 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:50:27,219 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:50:37,450 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:50:47,679 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:50:57,912 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:51:08,143 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:51:18,374 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:51:28,606 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:51:38,837 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:51:49,067 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:51:59,297 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:52:09,529 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:52:19,759 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:52:29,991 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:52:40,221 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:52:50,451 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:53:00,680 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:53:10,913 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:53:21,144 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:53:31,376 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:53:41,606 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:53:51,836 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:54:02,066 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:54:12,297 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:54:22,528 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:54:32,759 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:54:42,991 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:54:53,222 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:55:03,452 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:55:13,683 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:55:23,914 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:55:34,145 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:55:44,377 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:55:54,607 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:56:04,839 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:56:15,070 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:56:25,301 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:56:35,532 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:56:45,763 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:56:55,995 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:57:06,224 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:57:16,456 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:57:26,708 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:57:36,938 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:57:47,169 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:57:57,400 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:58:07,631 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:58:17,861 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:58:28,093 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:58:38,324 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:58:48,555 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:58:58,785 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:59:09,016 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:59:19,246 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:59:29,478 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:59:39,709 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 08:59:49,939 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:00:00,169 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:00:10,400 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:00:20,630 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:00:30,863 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:00:41,094 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:00:51,324 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:01:01,554 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:01:11,785 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:01:22,016 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:01:32,246 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:01:42,477 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:01:52,708 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:02:02,938 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:02:13,169 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:02:23,400 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:02:33,653 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:02:43,885 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:02:54,115 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:03:04,348 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:03:14,583 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:03:24,813 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:03:35,043 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:03:45,273 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:03:55,505 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:04:05,736 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:04:15,969 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:04:26,200 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:04:36,429 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:04:46,659 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:04:56,899 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:05:07,130 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:05:17,362 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:05:27,593 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:05:37,824 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:05:48,055 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:05:58,746 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:06:08,975 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:06:19,203 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:06:29,433 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:06:39,662 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:06:49,891 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:07:00,120 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:07:10,348 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:07:20,577 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:07:30,807 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:07:41,036 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:07:51,266 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:08:01,495 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:08:11,725 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:08:21,954 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:08:32,184 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:08:42,414 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:08:52,644 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:09:02,873 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:09:13,102 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:09:23,339 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:09:33,567 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:09:43,796 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:09:54,025 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:10:04,255 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:10:14,485 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:10:24,714 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:10:34,948 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:10:45,179 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:10:55,409 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:11:05,638 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:11:15,867 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:11:26,097 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:11:36,327 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:11:46,557 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:11:56,788 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:12:07,017 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:12:17,247 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:12:27,477 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:12:37,706 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:12:47,935 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:12:58,165 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:13:08,394 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:13:18,624 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:13:28,853 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:13:39,082 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:13:49,315 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:13:59,543 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:14:09,772 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:14:20,002 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:14:30,230 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:14:40,460 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:14:50,689 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:15:00,918 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:15:11,147 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:15:21,376 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:15:31,605 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:15:41,834 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:15:52,063 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:16:02,294 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:16:12,523 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:16:22,753 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:16:32,982 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:16:43,212 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:16:53,442 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:17:03,671 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:17:13,901 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:17:24,129 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:17:34,359 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:17:44,587 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:17:54,818 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:18:05,047 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:18:15,276 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:18:25,505 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:18:35,735 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:18:45,964 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:18:56,193 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:19:06,422 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:19:16,652 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:19:26,880 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:19:37,109 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:19:47,339 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:19:57,568 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:20:07,798 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:20:18,028 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 09:20:21,965 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:21] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 09:20:22,683 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:22] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 09:20:24,123 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:24] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 09:20:24,715 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:24] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 09:20:28,257 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:25:00,388 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-29 11:25:00,411 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 11:25:00,411 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 11:25:00,511 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-29 11:25:00,524 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 11:25:00,525 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 11:25:00,524 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 11:25:00,525 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 11:25:00,608 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-29 11:25:00,609 [INFO] werkzeug: Press CTRL+C to quit
2025-11-29 11:25:01,945 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-29 11:25:02,176 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-29 11:25:02,178 [INFO] telegram.ext.Application: Application started
2025-11-29 11:25:05,399 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:05] "GET / HTTP/1.1" 302 -
2025-11-29 11:25:05,453 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:05] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-29 11:25:05,487 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:05] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:25:12,748 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "POST /login HTTP/1.1" 500 -
2025-11-29 11:25:12,773 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-29 11:25:12,779 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-29 11:25:12,803 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=console.png&s=xoqKrRi6YNbxcjDr00Te HTTP/1.1" 200 -
2025-11-29 11:25:12,818 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-29 11:25:12,859 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:25:23,086 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:25:33,316 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:25:46,857 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-29 11:25:46,875 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 11:25:46,875 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 11:25:46,891 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-29 11:25:46,904 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 11:25:46,904 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 11:25:46,904 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 11:25:46,904 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 11:25:46,917 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-29 11:25:46,917 [INFO] werkzeug: Press CTRL+C to quit
2025-11-29 11:25:47,784 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:47] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-29 11:25:47,820 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:47] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:25:47,834 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:47] "GET /static/favicon.ico HTTP/1.1" 200 -
2025-11-29 11:25:47,981 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-29 11:25:48,215 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-29 11:25:48,217 [INFO] telegram.ext.Application: Application started
2025-11-29 11:25:55,161 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:25:55,161 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:25:55,164 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
2025-11-29 11:25:55,164 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:25:55,164 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:25:55,165 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:55] "POST /login HTTP/1.1" 200 -
2025-11-29 11:25:55,178 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:55] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:25:58,922 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:26:00,658 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:00,658 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:00,660 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
2025-11-29 11:26:00,660 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:00,660 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:00,661 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:00] "POST /login HTTP/1.1" 200 -
2025-11-29 11:26:00,680 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:00] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:09,154 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:26:14,556 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:14,556 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:14,557 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
2025-11-29 11:26:14,557 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:14,557 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:14,558 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:14] "POST /login HTTP/1.1" 200 -
2025-11-29 11:26:14,571 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:14] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:14,592 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:14] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-29 11:26:16,303 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:16,303 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:16,305 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
2025-11-29 11:26:16,305 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:16,305 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:16,306 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:16] "POST /login HTTP/1.1" 200 -
2025-11-29 11:26:16,318 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:16] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:16,338 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:16] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-29 11:26:19,385 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:26:26,537 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:26,537 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:26,538 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
2025-11-29 11:26:26,539 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:26,539 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:26,539 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:26] "POST /login HTTP/1.1" 200 -
2025-11-29 11:26:26,556 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:26] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:29,618 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:26:30,145 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:30,145 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:30,147 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
2025-11-29 11:26:30,147 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:30,147 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:30,148 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:30] "POST /login HTTP/1.1" 200 -
2025-11-29 11:26:30,163 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:30] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:39,849 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:26:46,795 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-29 11:26:46,814 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 11:26:46,814 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 11:26:46,830 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-29 11:26:46,843 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 11:26:46,843 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 11:26:46,843 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 11:26:46,843 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 11:26:46,856 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-29 11:26:46,856 [INFO] werkzeug: Press CTRL+C to quit
2025-11-29 11:26:47,893 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-29 11:26:48,119 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-29 11:26:48,121 [INFO] telegram.ext.Application: Application started
2025-11-29 11:26:48,563 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:48,563 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:48,614 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:48,614 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
2025-11-29 11:26:48,623 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:48] "POST /login HTTP/1.1" 200 -
2025-11-29 11:26:48,656 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:48] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:48,664 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:48] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-29 11:26:52,625 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:52,625 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 11:26:52,672 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-29 11:26:52,672 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-29 11:26:52,673 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-29 11:26:52,673 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-29 11:26:52,674 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "POST /login HTTP/1.1" 302 -
2025-11-29 11:26:52,700 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /index HTTP/1.1" 200 -
2025-11-29 11:26:52,716 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:52,728 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /static/css/index.css HTTP/1.1" 200 -
2025-11-29 11:26:52,731 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /static/script.js HTTP/1.1" 304 -
2025-11-29 11:26:52,734 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /static/js/index.js HTTP/1.1" 200 -
2025-11-29 11:26:54,110 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-29 11:26:58,026 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:58] "GET /admin HTTP/1.1" 200 -
2025-11-29 11:26:58,039 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:58] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 11:26:58,057 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:58] "GET /static/js/admin.js HTTP/1.1" 200 -
2025-11-29 11:26:58,801 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:26:59,260 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings HTTP/1.1" 500 -
2025-11-29 11:26:59,271 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2025-11-29 11:26:59,274 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2025-11-29 11:26:59,279 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=console.png&s=THSpWanxyHDEaEXGoJp8 HTTP/1.1" 200 -
2025-11-29 11:26:59,302 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2025-11-29 11:27:09,027 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:27:19,254 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:27:29,482 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:27:34,883 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "GET /admin/settings HTTP/1.1" 500 -
2025-11-29 11:27:34,895 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "GET /admin/settings?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-29 11:27:34,898 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "GET /admin/settings?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-29 11:27:34,904 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "GET /admin/settings?__debugger__=yes&cmd=resource&f=console.png&s=THSpWanxyHDEaEXGoJp8 HTTP/1.1" 304 -
2025-11-29 11:27:39,464 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /idrac HTTP/1.1" 308 -
2025-11-29 11:27:39,485 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /idrac/ HTTP/1.1" 200 -
2025-11-29 11:27:39,505 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /static/css/idrac_style.css HTTP/1.1" 200 -
2025-11-29 11:27:39,524 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /static/js/idrac_main.js HTTP/1.1" 200 -
2025-11-29 11:27:39,552 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /socket.io/?EIO=4&transport=polling&t=PhDR_kS HTTP/1.1" 200 -
2025-11-29 11:27:39,555 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /idrac/api/servers HTTP/1.1" 200 -
2025-11-29 11:27:39,558 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /idrac/api/groups HTTP/1.1" 200 -
2025-11-29 11:27:39,559 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /favicon.ico HTTP/1.1" 404 -
2025-11-29 11:27:39,561 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "POST /socket.io/?EIO=4&transport=polling&t=PhDR_kc&sid=Rrkjr7nmXxZXXV5rAAAA HTTP/1.1" 200 -
2025-11-29 11:27:39,563 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /socket.io/?EIO=4&transport=polling&t=PhDR_kd&sid=Rrkjr7nmXxZXXV5rAAAA HTTP/1.1" 200 -
2025-11-29 11:27:39,709 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:27:40,780 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:40] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 11:27:43,165 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:43] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
2025-11-29 11:27:47,382 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:47] "GET /socket.io/?EIO=4&transport=websocket&sid=Rrkjr7nmXxZXXV5rAAAA HTTP/1.1" 200 -
2025-11-29 11:27:49,936 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 11:28:00,164 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:19:05,980 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-29 16:19:06,006 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 16:19:06,006 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 16:19:06,078 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-29 16:19:06,096 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 16:19:06,096 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 16:19:06,096 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 16:19:06,096 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 16:19:06,145 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-29 16:19:06,146 [INFO] werkzeug: Press CTRL+C to quit
2025-11-29 16:19:07,200 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-29 16:19:07,434 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-29 16:19:07,436 [INFO] telegram.ext.Application: Application started
2025-11-29 16:19:12,329 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:12] "GET / HTTP/1.1" 302 -
2025-11-29 16:19:12,343 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:12] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-29 16:19:12,375 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:12] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:19:17,479 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 16:19:17,479 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-29 16:19:17,547 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-29 16:19:17,547 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-29 16:19:17,548 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-29 16:19:17,548 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-29 16:19:17,549 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "POST /login HTTP/1.1" 302 -
2025-11-29 16:19:17,564 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "GET /index HTTP/1.1" 200 -
2025-11-29 16:19:17,586 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:19:17,590 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "GET /static/css/index.css HTTP/1.1" 304 -
2025-11-29 16:19:17,590 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "GET /static/js/index.js HTTP/1.1" 304 -
2025-11-29 16:19:17,590 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "GET /static/script.js HTTP/1.1" 304 -
2025-11-29 16:19:18,134 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:19:18,818 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-29 16:19:19,001 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:19] "GET /admin HTTP/1.1" 200 -
2025-11-29 16:19:19,014 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:19] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:19:19,017 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:19] "GET /static/js/admin.js HTTP/1.1" 304 -
2025-11-29 16:19:20,031 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "GET /admin/settings HTTP/1.1" 500 -
2025-11-29 16:19:20,055 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "GET /admin/settings?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 304 -
2025-11-29 16:19:20,070 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "GET /admin/settings?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 304 -
2025-11-29 16:19:20,088 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "GET /admin/settings?__debugger__=yes&cmd=resource&f=console.png&s=nLGik2o7oqJf9jY3fsog HTTP/1.1" 200 -
2025-11-29 16:19:28,365 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:19:38,596 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:19:48,832 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:19:59,063 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:20:09,295 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:20:19,528 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:20:29,759 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:20:39,991 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:23:51,770 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-29 16:23:51,793 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 16:23:51,793 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-29 16:23:51,817 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-29 16:23:51,831 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 16:23:51,831 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-29 16:23:51,831 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 16:23:51,831 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-29 16:23:51,847 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-29 16:23:51,847 [INFO] werkzeug: Press CTRL+C to quit
2025-11-29 16:23:52,589 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "GET /admin/settings HTTP/1.1" 200 -
2025-11-29 16:23:52,671 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "GET /static/css/admin_settings.css HTTP/1.1" 200 -
2025-11-29 16:23:52,673 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:23:52,724 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "GET /static/favicon.ico HTTP/1.1" 304 -
2025-11-29 16:23:52,926 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-29 16:23:53,156 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-29 16:23:53,158 [INFO] telegram.ext.Application: Application started
2025-11-29 16:23:59,415 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-29 16:23:59,418 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "POST /admin/settings/bot/test/1 HTTP/1.1" 302 -
2025-11-29 16:23:59,422 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "GET /admin/settings HTTP/1.1" 200 -
2025-11-29 16:23:59,448 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:23:59,448 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "GET /static/css/admin_settings.css HTTP/1.1" 304 -
2025-11-29 16:24:03,847 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:24:14,079 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:24:14,607 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:14] "GET /admin HTTP/1.1" 200 -
2025-11-29 16:24:14,623 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:14] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:24:14,624 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:14] "GET /static/js/admin.js HTTP/1.1" 304 -
2025-11-29 16:24:15,836 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:15] "GET /admin/settings HTTP/1.1" 200 -
2025-11-29 16:24:15,852 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:15] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:24:15,855 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:15] "GET /static/css/admin_settings.css HTTP/1.1" 304 -
2025-11-29 16:24:17,060 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /admin HTTP/1.1" 200 -
2025-11-29 16:24:17,074 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:24:17,080 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /static/js/admin.js HTTP/1.1" 304 -
2025-11-29 16:24:17,684 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /index HTTP/1.1" 200 -
2025-11-29 16:24:17,703 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:24:17,705 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /static/css/index.css HTTP/1.1" 304 -
2025-11-29 16:24:17,708 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /static/js/index.js HTTP/1.1" 304 -
2025-11-29 16:24:17,709 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /static/script.js HTTP/1.1" 304 -
2025-11-29 16:24:18,247 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "GET /xml_management HTTP/1.1" 200 -
2025-11-29 16:24:18,262 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:24:18,266 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-29 16:24:18,267 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-29 16:24:20,336 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:20] "GET /edit_xml/LinePlus_T1.xml HTTP/1.1" 200 -
2025-11-29 16:24:20,365 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:20] "GET /static/css/edit_xml.css HTTP/1.1" 200 -
2025-11-29 16:24:20,368 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:20] "GET /static/style.css HTTP/1.1" 304 -
2025-11-29 16:24:24,311 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:24:34,542 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:24:44,774 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:24:55,005 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:25:05,234 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:25:15,464 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:25:25,695 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:25:35,928 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:25:46,157 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:25:56,389 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:26:06,619 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:26:16,849 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:26:27,079 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:26:37,310 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:26:47,540 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:26:57,770 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:27:08,002 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:27:18,232 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:27:28,463 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:27:38,693 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:27:48,923 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:27:59,154 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:28:09,384 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:28:19,614 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:28:29,845 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:28:40,076 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:28:50,307 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:29:00,537 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:29:10,768 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:29:20,998 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:29:31,228 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-29 16:29:41,458 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"

96
data/logs/2025-11-30.log Normal file
View File

@@ -0,0 +1,96 @@
2025-11-30 07:56:33,462 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
2025-11-30 07:56:33,483 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-30 07:56:33,483 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
2025-11-30 07:56:33,535 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
2025-11-30 07:56:33,548 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-30 07:56:33,548 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
2025-11-30 07:56:33,549 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-30 07:56:33,549 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
2025-11-30 07:56:33,595 [INFO] werkzeug: WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.0.122:5000
2025-11-30 07:56:33,595 [INFO] werkzeug: Press CTRL+C to quit
2025-11-30 07:56:34,624 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
2025-11-30 07:56:34,851 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
2025-11-30 07:56:34,853 [INFO] telegram.ext.Application: Application started
2025-11-30 07:56:36,685 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:36] "GET / HTTP/1.1" 302 -
2025-11-30 07:56:36,714 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:36] "GET /login?next=/ HTTP/1.1" 200 -
2025-11-30 07:56:36,751 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:36] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:56:43,202 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-30 07:56:43,202 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
2025-11-30 07:56:43,274 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-30 07:56:43,274 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
2025-11-30 07:56:43,275 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-30 07:56:43,275 [INFO] app: LOGIN: SUCCESS → redirect
2025-11-30 07:56:43,276 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "POST /login HTTP/1.1" 302 -
2025-11-30 07:56:43,301 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /index HTTP/1.1" 200 -
2025-11-30 07:56:43,321 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:56:43,326 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /static/css/index.css HTTP/1.1" 200 -
2025-11-30 07:56:43,334 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /static/script.js HTTP/1.1" 304 -
2025-11-30 07:56:43,339 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /static/js/index.js HTTP/1.1" 200 -
2025-11-30 07:56:44,539 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
2025-11-30 07:56:45,533 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:56:55,764 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:57:05,992 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:57:16,220 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:57:20,071 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:20] "GET /admin HTTP/1.1" 200 -
2025-11-30 07:57:20,087 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:20] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:20,091 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:20] "GET /static/js/admin.js HTTP/1.1" 200 -
2025-11-30 07:57:21,784 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /jobs HTTP/1.1" 200 -
2025-11-30 07:57:21,802 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:21,812 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /static/css/jobs.css HTTP/1.1" 200 -
2025-11-30 07:57:21,827 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /static/js/jobs.js HTTP/1.1" 200 -
2025-11-30 07:57:21,832 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /jobs/config HTTP/1.1" 200 -
2025-11-30 07:57:21,837 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-30 07:57:21,837 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-30 07:57:23,860 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:23] "GET /admin HTTP/1.1" 200 -
2025-11-30 07:57:23,873 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:23] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:23,877 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:23] "GET /static/js/admin.js HTTP/1.1" 304 -
2025-11-30 07:57:26,447 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:57:27,008 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:27] "GET /admin/settings HTTP/1.1" 200 -
2025-11-30 07:57:27,021 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:27] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:27,032 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:27] "GET /static/css/admin_settings.css HTTP/1.1" 200 -
2025-11-30 07:57:35,500 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /jobs HTTP/1.1" 200 -
2025-11-30 07:57:35,514 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:35,517 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /static/css/jobs.css HTTP/1.1" 304 -
2025-11-30 07:57:35,518 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /static/js/jobs.js HTTP/1.1" 304 -
2025-11-30 07:57:35,537 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /jobs/config HTTP/1.1" 200 -
2025-11-30 07:57:35,542 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
2025-11-30 07:57:35,542 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /jobs/iplist HTTP/1.1" 200 -
2025-11-30 07:57:36,597 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "GET /xml_management HTTP/1.1" 200 -
2025-11-30 07:57:36,610 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:36,615 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "GET /static/css/scp.css HTTP/1.1" 200 -
2025-11-30 07:57:36,615 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "GET /static/js/scp.js HTTP/1.1" 200 -
2025-11-30 07:57:36,674 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:57:40,721 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:40] "GET /edit_xml/LinePlus_T1.xml HTTP/1.1" 200 -
2025-11-30 07:57:40,734 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:40] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:40,738 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:40] "GET /static/css/edit_xml.css HTTP/1.1" 200 -
2025-11-30 07:57:42,310 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "GET /xml_management HTTP/1.1" 200 -
2025-11-30 07:57:42,322 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:57:42,326 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-30 07:57:42,327 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-30 07:57:46,902 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:57:57,128 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:58:07,122 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:07] "GET /scp/diff?file1=R6615.xml&file2=R6615_raid.xml HTTP/1.1" 200 -
2025-11-30 07:58:07,138 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:07] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:58:07,140 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:07] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-30 07:58:07,356 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:58:17,592 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:58:19,128 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /xml_management HTTP/1.1" 200 -
2025-11-30 07:58:19,142 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:58:19,146 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /static/css/scp.css HTTP/1.1" 304 -
2025-11-30 07:58:19,146 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /static/js/scp.js HTTP/1.1" 304 -
2025-11-30 07:58:19,647 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /index HTTP/1.1" 200 -
2025-11-30 07:58:19,661 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:58:19,667 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /static/css/index.css HTTP/1.1" 304 -
2025-11-30 07:58:19,667 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /static/js/index.js HTTP/1.1" 304 -
2025-11-30 07:58:19,668 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /static/script.js HTTP/1.1" 304 -
2025-11-30 07:58:24,733 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:24] "GET /home/ HTTP/1.1" 200 -
2025-11-30 07:58:24,746 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:24] "GET /static/style.css HTTP/1.1" 304 -
2025-11-30 07:58:27,818 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:58:38,046 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:58:48,273 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:58:58,501 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:59:08,734 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
2025-11-30 07:59:18,960 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"

3824
data/logs/2025-12-18.log Normal file

File diff suppressed because it is too large Load Diff

7623
data/logs/app.log Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,14 @@ import subprocess
import time
from dotenv import load_dotenv
from concurrent.futures import ThreadPoolExecutor
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# .env 파일 로드
load_dotenv()
@@ -99,9 +107,9 @@ def fetch_idrac_info(ip_address):
f.write(f"01. RAID Settings - Raid ProductName : {get_value(hwinventory, 'ProductName = PERC')}\n")
f.write(f"02. RAID Settings - Raid Types : No-Raid mode\n")
print(f"IP {ip_address} 에 대한 정보를 {output_file} 에 저장했습니다.")
logging.info(f"IP {ip_address} 에 대한 정보를 {output_file} 에 저장했습니다.")
except Exception as e:
print(f"오류 발생: {e}")
logging.error(f"오류 발생: {e}")
# 명령 결과에서 원하는 값 가져오기
def get_value(output, key):
@@ -117,7 +125,7 @@ start_time = time.time()
if __name__ == "__main__":
import sys
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <ip_file>")
logging.error(f"Usage: {sys.argv[0]} <ip_file>")
sys.exit(1)
ip_file_path = validate_ip_file(sys.argv[1])
@@ -138,5 +146,5 @@ elapsed_hours = int(elapsed_time // 3600)
elapsed_minutes = int((elapsed_time % 3600) // 60)
elapsed_seconds = int(elapsed_time % 60)
print("정보 수집 완료.")
print(f"수집 완료 시간: {elapsed_hours} 시간, {elapsed_minutes} 분, {elapsed_seconds} 초.")
logging.info("정보 수집 완료.")
logging.info(f"수집 완료 시간: {elapsed_hours} 시간, {elapsed_minutes} 분, {elapsed_seconds} 초.")

View File

@@ -16,7 +16,8 @@ RACADM = os.getenv("RACADM_PATH", "racadm") # PATH에 있으면 'racadm'
# 로깅
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def read_ip_list(ip_file: Path):
@@ -66,7 +67,7 @@ def apply_xml(ip: str, xml_path: Path) -> tuple[bool, str]:
# 대안:
# cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "set", "-t", "xml", "-f", str(safe_path)]
logging.info("실행 명령(리스트) → %s", " ".join(cmd))
logging.info(f"실행 명령(리스트) → {' '.join(cmd)}")
try:
p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=180)
stdout = (p.stdout or "").strip()
@@ -88,7 +89,7 @@ def apply_xml(ip: str, xml_path: Path) -> tuple[bool, str]:
def main():
if len(sys.argv) != 3:
print("Usage: python 02-set_config.py <ip_file> <xml_file>")
logging.error("Usage: python 02-set_config.py <ip_file> <xml_file>")
sys.exit(1)
ip_file = Path(sys.argv[1])

View File

@@ -4,6 +4,14 @@ import time
from dotenv import load_dotenv
import sys
from multiprocessing import Pool
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 환경 변수 로드
load_dotenv() # .env 파일에서 환경 변수 로드
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
# IP 주소 파일 로드 함수
def load_ip_file(ip_file_path):
if not os.path.isfile(ip_file_path):
sys.exit(f"IP file {ip_file_path} does not exist.")
logging.error(f"IP file {ip_file_path} does not exist.")
sys.exit(1)
with open(ip_file_path, "r") as file:
return [line.strip() for line in file if line.strip()]
# iDRAC 정보를 가져오는 함수 정의
def fetch_idrac_info(idrac_ip):
print(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
logging.info(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
try:
result = subprocess.run(
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "collect"],
capture_output=True,
text=True
)
print(
f"Successfully collected TSR report for {idrac_ip}"
if result.returncode == 0
else f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}"
)
if result.returncode == 0:
logging.info(f"Successfully collected TSR report for {idrac_ip}")
else:
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
except Exception as e:
print(f"Exception occurred for {idrac_ip}: {e}")
logging.error(f"Exception occurred for {idrac_ip}: {e}")
# 메인 함수
if __name__ == "__main__":
@@ -48,4 +57,4 @@ if __name__ == "__main__":
pool.map(fetch_idrac_info, ip_list)
elapsed_time = time.time() - start_time
print(f"설정 완료. 수집 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
logging.info(f"설정 완료. 수집 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")

View File

@@ -3,6 +3,14 @@ import subprocess
from dotenv import load_dotenv
import sys
from multiprocessing import Pool
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 환경 변수 로드
load_dotenv() # .env 파일에서 환경 변수 로드
@@ -16,13 +24,14 @@ OME_PASS = os.getenv("OME_PASS")
# IP 주소 파일 로드 및 유효성 검사
def load_ip_file(ip_file_path):
if not os.path.isfile(ip_file_path):
sys.exit(f"IP file {ip_file_path} does not exist.")
logging.error(f"IP file {ip_file_path} does not exist.")
sys.exit(1)
with open(ip_file_path, "r") as file:
return [line.strip() for line in file if line.strip()]
# iDRAC 정보를 가져오는 함수
def fetch_idrac_info(idrac_ip):
print(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
logging.info(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
try:
result = subprocess.run(
[
@@ -32,13 +41,13 @@ def fetch_idrac_info(idrac_ip):
capture_output=True,
text=True
)
print(
f"Successfully collected TSR report for {idrac_ip}"
if result.returncode == 0
else f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}"
)
if result.returncode == 0:
logging.info(f"Successfully collected TSR report for {idrac_ip}")
else:
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
except Exception as e:
print(f"Exception occurred for {idrac_ip}: {e}")
logging.error(f"Exception occurred for {idrac_ip}: {e}")
# 메인 함수
if __name__ == "__main__":
@@ -51,4 +60,4 @@ if __name__ == "__main__":
with Pool() as pool:
pool.map(fetch_idrac_info, ip_list)
print("설정 완료.")
logging.info("설정 완료.")

View File

@@ -4,6 +4,14 @@ import time
from dotenv import load_dotenv
import sys
from multiprocessing import Pool
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Load environment variables
load_dotenv() # Load variables from .env file
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
# Load IP addresses from file
def load_ip_file(ip_file_path):
if not os.path.isfile(ip_file_path):
sys.exit(f"IP file {ip_file_path} does not exist.")
logging.error(f"IP file {ip_file_path} does not exist.")
sys.exit(1)
with open(ip_file_path, "r") as file:
return [line.strip() for line in file if line.strip()]
# Power on the server for given iDRAC IP
def poweron_idrac_server(idrac_ip):
print(f"Powering on server for iDRAC IP: {idrac_ip}")
logging.info(f"Powering on server for iDRAC IP: {idrac_ip}")
try:
result = subprocess.run(
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerup"],
capture_output=True,
text=True
)
print(
f"Successfully powered on server for {idrac_ip}"
if result.returncode == 0
else f"Failed to power on server for {idrac_ip}: {result.stderr.strip()}"
)
if result.returncode == 0:
logging.info(f"Successfully powered on server for {idrac_ip}")
else:
logging.error(f"Failed to power on server for {idrac_ip}: {result.stderr.strip()}")
except Exception as e:
print(f"Exception occurred for {idrac_ip}: {e}")
logging.error(f"Exception occurred for {idrac_ip}: {e}")
# Main function
if __name__ == "__main__":
@@ -48,4 +57,4 @@ if __name__ == "__main__":
pool.map(poweron_idrac_server, ip_list)
elapsed_time = time.time() - start_time
print(f"Server Power On 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
logging.info(f"Server Power On 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")

View File

@@ -4,6 +4,14 @@ import time
from dotenv import load_dotenv
import sys
from multiprocessing import Pool
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Load environment variables
load_dotenv() # Load variables from .env file
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
# Load IP addresses from file
def load_ip_file(ip_file_path):
if not os.path.isfile(ip_file_path):
sys.exit(f"IP file {ip_file_path} does not exist.")
logging.error(f"IP file {ip_file_path} does not exist.")
sys.exit(1)
with open(ip_file_path, "r") as file:
return [line.strip() for line in file if line.strip()]
# Power off the server for given iDRAC IP
def poweroff_idrac_server(idrac_ip):
print(f"Powering off server for iDRAC IP: {idrac_ip}")
logging.info(f"Powering off server for iDRAC IP: {idrac_ip}")
try:
result = subprocess.run(
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerdown"],
capture_output=True,
text=True
)
print(
f"Successfully powered off server for {idrac_ip}"
if result.returncode == 0
else f"Failed to power off server for {idrac_ip}: {result.stderr.strip()}"
)
if result.returncode == 0:
logging.info(f"Successfully powered off server for {idrac_ip}")
else:
logging.error(f"Failed to power off server for {idrac_ip}: {result.stderr.strip()}")
except Exception as e:
print(f"Exception occurred for {idrac_ip}: {e}")
logging.error(f"Exception occurred for {idrac_ip}: {e}")
# Main function
if __name__ == "__main__":
@@ -48,4 +57,4 @@ if __name__ == "__main__":
pool.map(poweroff_idrac_server, ip_list)
elapsed_time = time.time() - start_time
print(f"Server Power Off 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
logging.info(f"Server Power Off 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")

View File

@@ -4,6 +4,14 @@ import time
from dotenv import load_dotenv
import sys
from multiprocessing import Pool
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Load environment variables
load_dotenv() # Load variables from .env file
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
# Load IP addresses from file
def load_ip_file(ip_file_path):
if not os.path.isfile(ip_file_path):
sys.exit(f"IP file {ip_file_path} does not exist.")
logging.error(f"IP file {ip_file_path} does not exist.")
sys.exit(1)
with open(ip_file_path, "r") as file:
return [line.strip() for line in file if line.strip()]
# Delete all jobs for given iDRAC IP
def delete_all_jobs(idrac_ip):
print(f"Deleting all jobs for iDRAC IP: {idrac_ip}")
logging.info(f"Deleting all jobs for iDRAC IP: {idrac_ip}")
try:
result = subprocess.run(
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "delete", "-i", "ALL"],
capture_output=True,
text=True
)
print(
f"Successfully deleted all jobs for {idrac_ip}"
if result.returncode == 0
else f"Failed to delete jobs for {idrac_ip}: {result.stderr.strip()}"
)
if result.returncode == 0:
logging.info(f"Successfully deleted all jobs for {idrac_ip}")
else:
logging.error(f"Failed to delete jobs for {idrac_ip}: {result.stderr.strip()}")
except Exception as e:
print(f"Exception occurred for {idrac_ip}: {e}")
logging.error(f"Exception occurred for {idrac_ip}: {e}")
# Main function
if __name__ == "__main__":
@@ -48,4 +57,4 @@ if __name__ == "__main__":
pool.map(delete_all_jobs, ip_list)
elapsed_time = time.time() - start_time
print(f"Job delete 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
logging.info(f"Job delete 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")

View File

@@ -9,7 +9,11 @@ from concurrent.futures import ThreadPoolExecutor
load_dotenv()
# 로깅 설정
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
# 사용자 이름 및 비밀번호 설정

View File

@@ -0,0 +1,206 @@
import subprocess
import sys
import re
import time
from pathlib import Path
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] root: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# ─────────────────────────────────────────────────────────────
# iDRAC 접속 설정
IDRAC_USER = "root"
IDRAC_PASS = "calvin"
# ─────────────────────────────────────────────────────────────
# 저장 위치 결정
def resolve_output_dir() -> Path:
here = Path(__file__).resolve().parent
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
base = here.parent
elif here.name.lower() == "scripts":
base = here.parent
else:
base = here.parent
out = base / "idrac_info"
out.mkdir(parents=True, exist_ok=True)
return out
# ─────────────────────────────────────────────────────────────
# 유틸리티 함수
def run_racadm(ip, *args):
"""racadm 명령어를 실행하고 결과를 반환합니다."""
cmd = ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS] + list(args)
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
return result.stdout
except Exception as e:
# 에러 발생 시 빈 문자열 반환 (스크립트 중단 방지)
return ""
def extract_value(text, pattern, delimiter='='):
"""텍스트에서 특정 키의 값을 추출하고 공백을 제거합니다."""
for line in text.splitlines():
if re.search(pattern, line, re.IGNORECASE):
if delimiter in line:
# '=' 기준으로 쪼갠 후 값만 가져와서 공백 제거
return line.split(delimiter, 1)[1].strip()
return "N/A"
# ─────────────────────────────────────────────────────────────
# 메인 수집 함수
def fetch_idrac_info(ip, output_dir):
logging.info(f">>> [{ip}] 데이터 수집 시작...")
# 1. 원본 데이터 덩어리 가져오기 (API 호출 최소화)
hwinventory = run_racadm(ip, "hwinventory")
getsysinfo = run_racadm(ip, "getsysinfo")
sys_profile = run_racadm(ip, "get", "bios.SysProfileSettings")
proc_settings = run_racadm(ip, "get", "bios.ProcSettings")
mem_settings = run_racadm(ip, "get", "bios.MemSettings")
storage_ctrl = run_racadm(ip, "get", "STORAGE.Controller.1")
# 서비스 태그 확인 (파일명 결정용)
svc_tag = extract_value(getsysinfo, "SVC Tag")
if svc_tag == "N/A":
logging.error(f"!!! [{ip}] SVC Tag 추출 실패. 작업을 건너뜁니다.")
return
output_file = output_dir / f"{svc_tag}.txt"
with open(output_file, "w", encoding="utf-8") as f:
# 헤더 작성
f.write(f"Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: {svc_tag})\n\n")
# --- 1. Firmware Version 정보 ---
f.write("-" * 42 + " Firmware Version 정보 " + "-" * 42 + "\n")
f.write(f"1. SVC Tag : {svc_tag}\n")
f.write(f"2. Bios Firmware : {extract_value(getsysinfo, 'System BIOS Version')}\n")
f.write(f"3. iDRAC Firmware Version : {extract_value(getsysinfo, 'Firmware Version')}\n")
# NIC 펌웨어 (별도 호출 필요)
nic1 = run_racadm(ip, "get", "NIC.FrmwImgMenu.1")
nic5 = run_racadm(ip, "get", "NIC.FrmwImgMenu.5")
f.write(f"4. NIC Integrated Firmware Version : {extract_value(nic1, '#FamilyVersion')}\n")
f.write(f"5. OnBoard NIC Firmware Version : {extract_value(nic5, '#FamilyVersion')}\n")
f.write(f"6. Raid Controller Firmware Version : {extract_value(hwinventory, 'ControllerFirmwareVersion')}\n\n")
# --- 2. Bios 설정 정보 ---
f.write("-" * 45 + " Bios 설정 정보 " + "-" * 45 + "\n")
boot_mode = run_racadm(ip, "get", "bios.BiosBootSettings")
f.write(f"01. Bios Boot Mode : {extract_value(boot_mode, 'BootMode')}\n")
f.write(f"02. System Profile Settings - System Profile : {extract_value(sys_profile, 'SysProfile=')}\n")
f.write(f"03. System Profile Settings - CPU Power Management : {extract_value(sys_profile, 'ProcPwrPerf')}\n")
f.write(f"04. System Profile Settings - Memory Frequency : {extract_value(sys_profile, 'MemFrequency')}\n")
f.write(f"05. System Profile Settings - Turbo Boost : {extract_value(sys_profile, 'ProcTurboMode')}\n")
f.write(f"06. System Profile Settings - PCI ASPM L1 Link Power Management : {extract_value(sys_profile, 'PcieAspmL1')}\n")
f.write(f"07. System Profile Settings - C-States : {extract_value(sys_profile, 'ProcCStates')}\n")
f.write(f"08. System Profile Settings - Determinism Slider : {extract_value(sys_profile, 'DeterminismSlider')}\n")
f.write(f"08-2. System Profile Settings - Dynamic Link Width Management (DLWM) : {extract_value(sys_profile, 'DynamicLinkWidthManagement')}\n")
f.write(f"09. Processor Settings - Logical Processor : {extract_value(proc_settings, 'LogicalProc')}\n")
f.write(f"10. Processor Settings - Virtualization Technology : {extract_value(proc_settings, 'ProcVirtualization')}\n")
f.write(f"11. Processor Settings - NUMA Nodes Per Socket : {extract_value(proc_settings, 'NumaNodesPerSocket')}\n")
f.write(f"12. Processor Settings - x2APIC Mode : {extract_value(proc_settings, 'ProcX2Apic')}\n")
f.write(f"13. Memory Settings - Dram Refresh Delay : {extract_value(mem_settings, 'DramRefreshDelay')}\n")
f.write(f"14. Memory Settings - DIMM Self Healing (PPR) : {extract_value(mem_settings, 'PPROnUCE')}\n")
f.write(f"15. Memory Settings - Correctable Error Logging : {extract_value(mem_settings, 'CECriticalSEL')}\n")
thermal = run_racadm(ip, "get", "System.ThermalSettings")
f.write(f"16. System Settings - Thermal Profile Optimization : {extract_value(thermal, 'ThermalProfile')}\n")
integrated = run_racadm(ip, "get", "Bios.IntegratedDevices")
f.write(f"17. Integrated Devices Settings - SR-IOV Global Enable : {extract_value(integrated, 'SriovGlobalEnable')}\n")
misc = run_racadm(ip, "get", "bios.MiscSettings")
f.write(f"18. Miscellaneous Settings - F1/F2 Prompt on Error : {extract_value(misc, 'ErrPrompt')}\n\n")
# --- 3. iDRAC 설정 정보 ---
f.write("-" * 45 + " iDRAC 설정 정보 " + "-" * 45 + "\n")
f.write(f"01. iDRAC Settings - Timezone : {extract_value(run_racadm(ip, 'get', 'iDRAC.Time.Timezone'), 'Timezone')}\n")
f.write(f"02. iDRAC Settings - IPMI LAN Selection : {extract_value(run_racadm(ip, 'get', 'iDRAC.CurrentNIC'), 'ActiveNIC')}\n")
f.write(f"03. iDRAC Settings - IPMI IP(IPv4) : {extract_value(run_racadm(ip, 'get', 'iDRAC.CurrentIPv4'), 'DHCPEnable')}\n")
f.write(f"04. iDRAC Settings - IPMI IP(IPv6) : {extract_value(run_racadm(ip, 'get', 'iDRAC.CurrentIPv6'), 'Enable=')}\n")
f.write(f"05. iDRAC Settings - Redfish Support : {extract_value(run_racadm(ip, 'get', 'iDRAC.Redfish.Enable'), 'Enable=')}\n")
f.write(f"06. iDRAC Settings - SSH Support : {extract_value(run_racadm(ip, 'get', 'iDRAC.SSH'), 'Enable=')}\n")
# AD 관련 설정 (AD Group 1, 2 포함)
f.write(f"07. iDRAC Settings - AD User Domain Name : {extract_value(run_racadm(ip, 'get', 'iDRAC.USERDomain.1.Name'), 'Name')}\n")
f.write(f"08. iDRAC Settings - SC Server Address : {extract_value(run_racadm(ip, 'get', 'iDRAC.ActiveDirectory.DomainController1'), 'DomainController1')}\n")
f.write(f"09. iDRAC Settings - SE AD RoleGroup Name : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.1.Name'), 'Name')}\n")
f.write(f"10. iDRAC Settings - SE AD RoleGroup Domain : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.1.Domain'), 'Domain')}\n")
f.write(f"11. iDRAC Settings - SE AD RoleGroup Privilege : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.1.Privilege'), 'Privilege')}\n")
f.write(f"12. iDRAC Settings - IDC AD RoleGroup Name : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.2.Name'), 'Name')}\n")
f.write(f"13. iDRAC Settings - IDC AD RoleGroup Domain : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.2.Domain'), 'Domain')}\n")
f.write(f"14. iDRAC Settings - IDC AD RoleGroup Privilege : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.2.Privilege'), 'Privilege')}\n")
# Syslog 및 기타
f.write(f"15. iDRAC Settings - Remote Log (syslog) : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.SysLogEnable'), 'SysLogEnable')}\n")
f.write(f"16. iDRAC Settings - syslog server address 1 : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.Server1'), 'Server1')}\n")
f.write(f"17. iDRAC Settings - syslog server address 2 : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.Server2'), 'Server2')}\n")
f.write(f"18. iDRAC Settings - syslog server port : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.Port'), 'Port')}\n")
f.write(f"19. iDRAC Settings - Remote KVM Nonsecure port : {extract_value(run_racadm(ip, 'get', 'iDRAC.VirtualConsole.Port'), 'Port')}\n\n")
# --- 4. Raid 설정 정보 (주석 해제 버전) ---
f.write("-" * 45 + " Raid 설정 정보 " + "-" * 45 + "\n")
f.write(f"01. RAID Settings - Raid ProductName : {extract_value(hwinventory, 'ProductName = PERC')}\n")
f.write(f"02. RAID Settings - Raid Types : {extract_value(hwinventory, 'RAIDTypes')}\n")
f.write(f"03. RAID Settings - StripeSize : {extract_value(hwinventory, 'StripeSize')}\n")
f.write(f"04. RAID Settings - ReadCachePolicy : {extract_value(hwinventory, 'ReadCachePolicy')}\n")
f.write(f"05. RAID Settings - WriteCachePolicy : {extract_value(hwinventory, 'WriteCachePolicy')}\n")
f.write(f"06. RAID Settings - CheckConsistencyRate : {extract_value(storage_ctrl, 'CheckConsistencyRate')}\n")
f.write(f"07. RAID Settings - PatrolReadMode : {extract_value(storage_ctrl, 'PatrolReadMode')}\n")
f.write(f"08. RAID Settings - period : 168h\n")
f.write(f"09. RAID Settings - Power Save : No\n")
f.write(f"10. RAID Settings - JBODMODE : Controller does not support JBOD\n")
f.write(f"11. RAID Settings - maxconcurrentpd : 240\n")
logging.info(f"✅ [{ip}] 저장 완료: {output_file.name}")
# ─────────────────────────────────────────────────────────────
# 실행 흐름 제어
def main():
if len(sys.argv) < 2:
logging.error(f"사용법: python {sys.argv[0]} <ip_file>")
sys.exit(1)
ip_file = Path(sys.argv[1])
if not ip_file.exists():
logging.error(f"오류: IP 파일 '{ip_file}'이 존재하지 않습니다.")
sys.exit(1)
output_dir = resolve_output_dir()
start_time = time.time()
# IP 목록 읽기
with open(ip_file, "r") as f:
ips = [line.strip() for line in f if line.strip()]
# 각 IP별로 수집 실행
for ip in ips:
try:
fetch_idrac_info(ip, output_dir)
except Exception as e:
logging.error(f"❌ [{ip}] 처리 중 예외 발생: {e}")
# 소요 시간 계산
end_time = time.time()
elapsed = end_time - start_time
hours, rem = divmod(elapsed, 3600)
minutes, seconds = divmod(rem, 60)
logging.info("="*50)
logging.info("정보 수집 완료.")
logging.info(f"총 소요 시간: {int(hours)}시간 {int(minutes)}{int(seconds)}")
logging.info(f"저장 경로: {output_dir}")
logging.info("="*50)
if __name__ == "__main__":
main()

Some files were not shown because too many files have changed in this diff Show More