Compare commits

...

3 Commits

Author SHA1 Message Date
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
62 changed files with 15365 additions and 1607 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

65
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,11 @@ setup_logging(app)
# ─────────────────────────────────────────────────────────────
# CSRF 보호 + 템플릿에서 {{ csrf_token() }} 사용 가능하게 주입
# ─────────────────────────────────────────────────────────────
csrf = CSRFProtect()
csrf.init_app(app)
@app.context_processor
def inject_csrf():
try:
@@ -44,9 +56,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 +81,86 @@ 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_polling_started = False # 전역 플래그로 중복 방지
def start_telegram_bot_polling() -> None:
"""텔레그램 봇 폴링을 백그라운드 스레드로 시작 (한 번만 실행)"""
import threading
global _bot_polling_started
if _bot_polling_started:
app.logger.warning("🤖 텔레그램 봇 폴링은 이미 시작됨 - 중복 요청 무시")
return
_bot_polling_started = True
def _runner():
try:
# telegram_bot_service.run_polling(app) 호출
telegram_run_polling(app)
except Exception as e:
app.logger.error("텔레그램 봇 폴링 서비스 오류: %s", e)
polling_thread = threading.Thread(target=_runner, daemon=True)
polling_thread.start()
app.logger.info("🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)")
# ─────────────────────────────────────────────────────────────
# 텔레그램 봇 폴링 자동 시작
# Flask 앱이 초기화되면 자동으로 봇 폴링 시작
# ─────────────────────────────────────────────────────────────
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)
socketio.run(app, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True, use_reloader=False)

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

@@ -39,6 +39,10 @@ class User(db.Model, UserMixin):
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
return f"<User id={self.id} email={self.email} active={self.is_active}>"

View File

@@ -10,6 +10,9 @@ 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 라우트에서만 사용."""
@@ -23,3 +26,5 @@ def register_routes(app: Flask, socketio=None) -> None:
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.

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,111 @@ 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"))

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 _notify(text: str, category: str = "system") -> None:
"""
텔레그램 알림 전송
- DB(TelegramBot)에 등록된 활성 봇들에게 전송
- category: 'auth', 'activity', 'system'
"""
try:
from backend.models.telegram_bot import TelegramBot
def _send():
# 앱 컨텍스트 안에서 실행되므로 바로 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,17 +259,36 @@ 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
# 승인 토큰 생성
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
)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
_notify(
f"🆕 <b>신규 가입 요청</b>\n"
f"📛 사용자: <code>{user.username}</code>\n"
f"📧 이메일: <code>{user.email}</code>"
# 텔레그램 알림 (인라인 버튼 포함)
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')}"
)
current_app.logger.info("REGISTER: created id=%s email=%s", user.id, user.email)
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:
@@ -136,24 +317,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 +360,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 not lv.server_model or lv.server_model == server.model:
latest = lv
break
if comparison.status == 'outdated':
outdated_count += 1
outdated_items.append({
'component': component_name,
'current': current_version,
'latest': lv.latest_version
})
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

@@ -0,0 +1,144 @@
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로 통일)
content1 = file1_path.read_text(encoding="utf-8").replace("\r\n", "\n").splitlines()
content2 = file2_path.read_text(encoding="utf-8").replace("\r\n", "\n").splitlines()
# Diff 생성
diff = difflib.unified_diff(
content1, content2,
fromfile=file1_name,
tofile=file2_name,
lineterm=""
)
diff_content = "\n".join(diff)
return render_template("scp_diff.html", file1=file1_name, file2=file2_name, diff_content=diff_content)
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/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

@@ -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 저장.
"""
url = "https://downloads.dell.com/catalog/Catalog.xml"
print(f"[INFO] Dell Catalog 다운로드 중... ({url})")
response = requests.get(url, timeout=60)
response.raise_for_status()
Dell 펌웨어 정보 동기화
root = ET.fromstring(response.content)
1차: Dell 공식 Catalog.xml 시도
2차: 수동 카탈로그 사용 (Fallback)
"""
count = 0
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
# 1차 시도: Dell 공식 Catalog.xml
try:
url = "https://downloads.dell.com/catalog/Catalog.xml"
print(f"[INFO] Dell Catalog 다운로드 시도... ({url})")
name = pkg.findtext("Name")
version = pkg.findtext("Version")
release_date = pkg.findtext("ReleaseDate")
path = pkg.findtext("path")
vendor = "Dell"
response = requests.get(url, timeout=30)
response.raise_for_status()
if not name or not version:
continue
root = ET.fromstring(response.content)
# 중복 방지
existing = FirmwareVersion.query.filter_by(
component_name=name,
latest_version=version,
server_model=model
).first()
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
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}",
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
# 중복 방지
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}",
)
)
)
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

@@ -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 {
@@ -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');
});
// ========================================
@@ -375,7 +378,7 @@ 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}`);
@@ -391,7 +394,7 @@ function setupSocketIO() {
});
// 업로드 완료
socket.on('upload_complete', function(data) {
socket.on('upload_complete', function (data) {
console.log('Upload complete:', data);
const summary = data.summary;
@@ -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');
@@ -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');
}
}

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

@@ -0,0 +1,163 @@
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 || '';
// ─────────────────────────────────────────────────────────────
// 공통 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);
});

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

@@ -5,19 +5,24 @@
<div class="container mt-4">
<div class="card">
<div class="card-body">
<h3 class="card-title">Admin Page</h3>
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="card-title mb-0">Admin Page</h3>
<a href="{{ url_for('admin.settings') }}" class="btn btn-outline-primary">
<i class="bi bi-gear-fill me-1"></i>시스템 설정
</a>
</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 %}
</div>
{% endif %}
{% 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 %}
</div>
{% endif %}
{% endwith %}
<div class="table-responsive">
@@ -39,28 +44,24 @@
<td>{{ user.email }}</td>
<td>
{% if user.is_active %}
<span class="badge bg-success">Yes</span>
<span class="badge bg-success">Yes</span>
{% else %}
<span class="badge bg-secondary">No</span>
<span class="badge bg-secondary">No</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>
<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 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>
<!-- 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">
<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>
</td>
@@ -74,7 +75,8 @@
</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-labelledby="changePasswordModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form id="changePasswordForm" method="post" action="">
@@ -92,13 +94,15 @@
<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">
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8"
placeholder="Enter new password">
<div class="form-text">최소 8자 이상을 권장합니다.</div>
</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">
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required
minlength="8" placeholder="Confirm new password">
<div id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
</div>
</div>
@@ -112,58 +116,8 @@
</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>
{{ super() }}
<script src="{{ url_for('static', filename='js/admin.js') }}"></script>
{% endblock %}
{% endblock %}

View File

@@ -0,0 +1,320 @@
{% 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">
<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>
{% 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">
@@ -11,25 +12,38 @@
<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">
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="position-fixed end-0 p-3" style="z-index: 2000; top: 70px;">
{% 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 %}
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container-fluid">
@@ -37,12 +51,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 +63,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 +108,8 @@
</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>
@@ -111,16 +122,17 @@
<!-- 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>
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>
integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4"
crossorigin="anonymous"></script>
{% endif %}
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -1,51 +1,26 @@
{% extends "base.html" %}
{% 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>
</body>
{% block title %}Edit XML File - Dell Server Info{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/edit_xml.css') }}">
{% endblock %}
{% block content %}
<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>
{% 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>

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">
@@ -50,7 +37,7 @@
<select id="script" name="script" class="form-select" required>
<option value="">스크립트를 선택하세요</option>
{% for script in scripts %}
<option value="{{ script }}">{{ script }}</option>
<option value="{{ script }}">{{ script }}</option>
{% endfor %}
</select>
</div>
@@ -61,7 +48,7 @@
<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>
@@ -73,7 +60,7 @@
<span class="badge bg-secondary ms-2" id="ipLineCount">0 대설정</span>
</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>
placeholder="예:&#10;192.168.1.1&#10;192.168.1.2&#10;192.168.1.3" required></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">
@@ -102,22 +89,18 @@
서버 리스트 (덮어쓰기)
<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">
<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">
<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">
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}" class="btn btn-warning">
GPU to Excel
</button>
</div>
@@ -138,7 +121,7 @@
</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">
role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span class="fw-semibold">0%</span>
</div>
</div>
@@ -218,45 +201,44 @@
</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 +249,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 +309,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>
@@ -398,7 +376,7 @@
</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>
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">
@@ -410,276 +388,14 @@
</div>
{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
{% 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>
<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';
}
}
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') }}"></script>
<!-- 외부 script.js 파일 (IP 폼 처리 로직 포함) -->
<script src="{{ url_for('static', filename='script.js') }}"></script>

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">
<!-- 헤더 -->
@@ -41,7 +53,7 @@
한 줄에 하나씩 입력 (쉼표/세미콜론/공백 구분 가능, # 주석 지원)
</small>
<textarea id="ipInput" class="form-control font-monospace" rows="4"
placeholder="10.10.0.11&#10;10.10.0.12&#10;# 주석 가능"></textarea>
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>
@@ -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 %}

View File

@@ -1,305 +1,223 @@
{% extends "base.html" %}
{% block title %}XML 파일 관리 & 배포 - Dell Server Info{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/scp.css') }}">
{% 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;
}
<h1 class="main-title">설정 파일 관리 (SCP)</h1>
<p class="subtitle">iDRAC 서버 설정(XML)을 내보내거나 가져오고, 버전을 비교할 수 있습니다.</p>
.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="row">
<!-- 왼쪽: 파일 업로드 및 내보내기 -->
<div class="col-md-4">
<div class="card">
<div class="card-header-custom">
<i class="fas fa-cloud-upload-alt mr-2"></i>파일 업로드
<span><i class="fas fa-cloud-upload-alt me-2"></i>파일 등록</span>
</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() }}"/>
<!-- 1. PC에서 업로드 -->
<h6 class="mb-3"><i class="fas fa-laptop me-2"></i>PC에서 업로드</h6>
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data" class="mb-4">
<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="mb-2">
<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>
<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 type="submit" class="btn btn-primary w-100">
<i class="fas fa-upload me-1"></i>업로드
</button>
</div>
</form>
</div>
</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 %}
<hr>
<!-- 2. iDRAC에서 내보내기 -->
<h6 class="mb-3"><i class="fas fa-server me-2"></i>iDRAC에서 추출 (Export)</h6>
<button type="button" class="btn btn-outline-primary w-100" data-bs-toggle="modal"
data-bs-target="#exportModal">
<i class="fas fa-download me-1"></i>설정 추출하기
</button>
</div>
</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;
}
</script>
</body>
</html>
<!-- 오른쪽: 파일 목록 및 작업 -->
<div class="col-md-8">
<div class="card">
<div class="card-header-custom">
<span><i class="fas fa-list me-2"></i>파일 목록</span>
<button class="btn btn-light btn-sm text-primary" id="compareBtn"
data-url="{{ url_for('scp.diff_scp') }}" onclick="compareSelected()">
<i class="fas fa-exchange-alt me-1"></i>선택 비교
</button>
</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">
<input type="checkbox" class="select-checkbox file-selector" value="{{ xml_file }}">
<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">
<!-- 배포 버튼 -->
<button type="button" class="btn btn-info btn-sm text-white"
onclick="openDeployModal('{{ xml_file }}')">
<i class="fas fa-plane-departure"></i> <span>배포</span>
</button>
<!-- 편집 버튼 -->
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}" class="btn btn-success btn-sm">
<i class="fas fa-edit"></i> <span>편집</span>
</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> <span>삭제</span>
</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>
</div>
</div>
<!-- Export Modal -->
<div class="modal fade" id="exportModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form action="{{ url_for('scp.export_scp') }}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="modal-header">
<h5 class="modal-title">iDRAC 설정 내보내기</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-info py-2" style="font-size: 0.9rem;">
<i class="fas fa-info-circle me-1"></i> 네트워크 공유 폴더(CIFS)가 필요합니다.
</div>
<h6>대상 iDRAC</h6>
<div class="mb-2"><input type="text" class="form-control" name="target_ip" placeholder="iDRAC IP"
required></div>
<div class="row mb-3">
<div class="col"><input type="text" class="form-control" name="username" placeholder="User"
required></div>
<div class="col"><input type="password" class="form-control" name="password"
placeholder="Password" required></div>
</div>
<hr>
<h6>네트워크 공유 (저장소)</h6>
<div class="mb-2"><input type="text" class="form-control" name="share_ip"
placeholder="Share Server IP" required></div>
<div class="mb-2"><input type="text" class="form-control" name="share_name"
placeholder="Share Name (e.g. public)" required></div>
<div class="mb-2"><input type="text" class="form-control" name="filename"
placeholder="Save Filename (e.g. backup.xml)" required></div>
<div class="row">
<div class="col"><input type="text" class="form-control" name="share_user"
placeholder="Share User"></div>
<div class="col"><input type="password" class="form-control" name="share_pwd"
placeholder="Share Password"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-primary">내보내기 시작</button>
</div>
</form>
</div>
</div>
</div>
<!-- Deploy (Import) Modal -->
<div class="modal fade" id="deployModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form action="{{ url_for('scp.import_scp') }}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="modal-header">
<h5 class="modal-title">설정 배포 (Import)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-warning py-2" style="font-size: 0.9rem;">
<i class="fas fa-exclamation-triangle me-1"></i> 적용 후 서버가 재부팅될 수 있습니다.
</div>
<div class="mb-3">
<label class="form-label">배포할 파일</label>
<input type="text" class="form-control" id="deployFilename" name="filename" readonly>
</div>
<h6>대상 iDRAC</h6>
<div class="mb-2"><input type="text" class="form-control" name="target_ip" placeholder="iDRAC IP"
required></div>
<div class="row mb-3">
<div class="col"><input type="text" class="form-control" name="username" placeholder="User"
required></div>
<div class="col"><input type="password" class="form-control" name="password"
placeholder="Password" required></div>
</div>
<hr>
<h6>네트워크 공유 (소스 위치)</h6>
<div class="mb-2"><input type="text" class="form-control" name="share_ip"
placeholder="Share Server IP" required></div>
<div class="mb-2"><input type="text" class="form-control" name="share_name" placeholder="Share Name"
required></div>
<div class="row mb-3">
<div class="col"><input type="text" class="form-control" name="share_user"
placeholder="Share User"></div>
<div class="col"><input type="password" class="form-control" name="share_pwd"
placeholder="Share Password"></div>
</div>
<div class="mb-3">
<label class="form-label">적용 모드</label>
<select class="form-select" name="import_mode">
<option value="Replace">전체 교체 (Replace)</option>
<option value="Append">변경분만 적용 (Append)</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-danger">배포 시작</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/scp.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}설정 파일 비교 - Dell Server Info{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/scp.css') }}">
{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>설정 파일 비교</h2>
<a href="{{ url_for('xml.xml_management') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i> 목록으로
</a>
</div>
<div class="card mb-4">
<div class="card-header bg-info text-white">
<i class="fas fa-exchange-alt me-2"></i>비교 대상
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-md-5">
<h5>{{ file1 }}</h5>
</div>
<div class="col-md-2">
<i class="fas fa-arrow-right text-muted"></i>
</div>
<div class="col-md-5">
<h5>{{ file2 }}</h5>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="fas fa-code me-2"></i>Diff 결과
</div>
<div class="card-body p-0">
<div class="diff-container">
{% for line in diff_content.splitlines() %}
{% if line.startswith('+++') or line.startswith('---') %}
<span class="diff-line diff-header">{{ line }}</span>
{% elif line.startswith('+') %}
<span class="diff-line diff-add">{{ line }}</span>
{% elif line.startswith('-') %}
<span class="diff-line diff-del">{{ line }}</span>
{% else %}
<span class="diff-line">{{ line }}</span>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
{% 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,6 +40,10 @@ 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}")

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 -

465
data/logs/app.log Normal file
View File

@@ -0,0 +1,465 @@
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"

1
data/temp_ip/ip_0.txt Normal file
View File

@@ -0,0 +1 @@
10.10.0.1

View File

@@ -21,21 +21,6 @@ def main() -> int:
app.config.from_object(Config)
db.init_app(app)
with app.app_context():
users = User.query.all()
updated_count = 0
for user in users:
if user.password and not is_hashed(user.password):
print(f"🔄 변환 대상: {user.username}")
user.password = generate_password_hash(user.password)
updated_count += 1
if updated_count:
db.session.commit()
print(f"✅ 완료: {updated_count}명 해시 처리")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -28,10 +28,15 @@ openpyxl==3.1.5
pandas==2.3.3
passlib==1.7.4
pycparser==2.23
pytest==8.0.0
pytest-mock==3.12.0
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-engineio==4.12.3
python-socketio==5.14.1
python-telegram-bot==22.5
pytz==2025.2
requests==2.32.3
simple-websocket==1.1.0
six==1.17.0
SQLAlchemy==2.0.43

157
telegram_bot_service.py Normal file
View File

@@ -0,0 +1,157 @@
"""
텔레그램 봇 폴링 서비스
- 백그라운드에서 텔레그램 봇의 업데이트를 폴링
- 인라인 버튼 클릭 처리 (가입 승인/거부)
"""
import logging
from typing import Optional
from telegram import Update
from telegram.ext import Application, CallbackQueryHandler, ContextTypes
from flask import Flask
from backend.models.telegram_bot import TelegramBot
from backend.models.user import User, db
logger = logging.getLogger(__name__)
async def handle_approval_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
텔레그램 인라인 버튼 클릭 처리
callback_data 형식: "approve_{token}" 또는 "reject_{token}"
"""
query = update.callback_query
await query.answer()
data = query.data or ""
logger.info("Received callback: %s", data)
# Flask app 객체는 bot_data에 저장해둔 것을 사용
flask_app: Optional[Flask] = context.application.bot_data.get("flask_app")
if flask_app is None:
logger.error("Flask app context is missing in bot_data")
await query.edit_message_text(
text="❌ 내부 설정 오류로 요청을 처리할 수 없습니다. 관리자에게 문의해 주세요."
)
return
# callback_data 형식 검증
if "_" not in data:
logger.warning("Invalid callback data format: %s", data)
await query.edit_message_text(
text="❌ 유효하지 않은 요청입니다."
)
return
try:
action, token = data.split("_", 1)
except ValueError:
logger.warning("Failed to split callback data: %s", data)
await query.edit_message_text(
text="❌ 유효하지 않은 요청입니다."
)
return
try:
with flask_app.app_context():
# 토큰으로 사용자 찾기
user = User.query.filter_by(approval_token=token).first()
if not user:
await query.edit_message_text(
text="❌ 유효하지 않은 승인 요청입니다.\n(이미 처리되었거나 만료된 요청)"
)
return
if action == "approve":
# 승인 처리
user.is_approved = True
user.is_active = True
user.approval_token = None # 토큰 무효화
db.session.commit()
await query.edit_message_text(
text=(
"✅ 승인 완료!\n\n"
f"👤 사용자: {user.username}\n"
f"📧 이메일: {user.email}\n\n"
"사용자가 이제 로그인할 수 있습니다."
)
)
logger.info("User %s approved", user.username)
elif action == "reject":
# 거부 처리 - 사용자 삭제
username = user.username
email = user.email
db.session.delete(user)
db.session.commit()
await query.edit_message_text(
text=(
"❌ 가입 거부됨\n\n"
f"👤 사용자: {username}\n"
f"📧 이메일: {email}\n\n"
"계정이 삭제되었습니다."
)
)
logger.info("User %s rejected and deleted", username)
else:
logger.warning("Unknown action in callback: %s", action)
await query.edit_message_text(
text="❌ 유효하지 않은 요청입니다."
)
except Exception as e:
logger.exception("Error handling callback: %s", e)
# 예외 내용은 사용자에게 직접 노출하지 않음
try:
db.session.rollback()
except Exception:
logger.exception("DB rollback failed")
await query.edit_message_text(
text="❌ 요청 처리 중 오류가 발생했습니다. 잠시 후 다시 시도하거나 관리자에게 문의해 주세요."
)
def run_polling(flask_app: Flask) -> None:
"""
동기 함수: 백그라운드 스레드에서 직접 호출됨
Application.run_polling() 이 내부에서 asyncio 이벤트 루프를 관리하므로
여기서는 asyncio.run 을 사용하지 않는다.
"""
if flask_app is None:
raise ValueError("flask_app is required for run_polling")
# DB에서 활성 봇 조회
with flask_app.app_context():
bots = TelegramBot.query.filter_by(is_active=True).all()
if not bots:
logger.warning("No active bots found")
return
# 첫 번째 활성 봇만 사용 (여러 봇이 동시에 폴링하면 충돌 가능)
bot = bots[0]
flask_app.logger.info("Starting polling for bot: %s (ID: %s)", bot.name, bot.id)
# Application 생성
application = Application.builder().token(bot.token).build()
# Flask app을 bot_data에 넣어서 핸들러에서 사용할 수 있게 함
application.bot_data["flask_app"] = flask_app
# 콜백 쿼리 핸들러 등록
application.add_handler(CallbackQueryHandler(handle_approval_callback))
try:
# v20 스타일: run_polling 은 동기 함수이고, 내부에서 이벤트 루프를 직접 관리함
application.run_polling(drop_pending_updates=True)
except Exception as e:
flask_app.logger.exception("Error in bot polling: %s", e)

View File

@@ -0,0 +1,64 @@
import pytest
from unittest.mock import MagicMock, patch
from flask import Flask
from backend.services.redfish_client import RedfishClient
@pytest.fixture
def app():
app = Flask(__name__)
app.config["REDFISH_TIMEOUT"] = 10
app.config["REDFISH_VERIFY_SSL"] = True
return app
@patch("backend.services.redfish_client.requests.Session")
def test_redfish_client_init_defaults(mock_session):
"""Test initialization with default values when no app context."""
client = RedfishClient("1.2.3.4", "user", "pass")
assert client.timeout == 15
assert client.verify_ssl == False
assert client.base_url == "https://1.2.3.4/redfish/v1"
@patch("backend.services.redfish_client.requests.Session")
def test_redfish_client_init_with_app_config(mock_session, app):
"""Test initialization picking up Flask app config."""
with app.app_context():
client = RedfishClient("1.2.3.4", "user", "pass")
assert client.timeout == 10
assert client.verify_ssl == True
@patch("backend.services.redfish_client.requests.Session")
def test_redfish_client_init_override(mock_session, app):
"""Test explicit arguments override config."""
with app.app_context():
client = RedfishClient("1.2.3.4", "user", "pass", timeout=30, verify_ssl=False)
assert client.timeout == 30
assert client.verify_ssl == False
@patch("backend.services.redfish_client.requests.Session")
def test_get_success(mock_session):
"""Test successful GET request."""
mock_response = MagicMock()
mock_response.json.return_value = {"key": "value"}
mock_response.status_code = 200
session_instance = mock_session.return_value
session_instance.get.return_value = mock_response
client = RedfishClient("1.2.3.4", "user", "pass")
result = client.get("/Some/Endpoint")
assert result == {"key": "value"}
session_instance.get.assert_called_with("https://1.2.3.4/redfish/v1/Some/Endpoint", timeout=15)
@patch("backend.services.redfish_client.requests.Session")
def test_get_absolute_path(mock_session):
"""Test GET request with absolute path."""
mock_response = MagicMock()
mock_response.json.return_value = {}
session_instance = mock_session.return_value
session_instance.get.return_value = mock_response
client = RedfishClient("1.2.3.4", "user", "pass")
client.get("/redfish/v1/Managers/1")
session_instance.get.assert_called_with("https://1.2.3.4/redfish/v1/Managers/1", timeout=15)

27
update_db.py Normal file
View File

@@ -0,0 +1,27 @@
import sqlite3
import os
db_path = r"D:\Code\iDRAC_Info\idrac_info\backend\instance\site.db"
if not os.path.exists(db_path):
print(f"Database not found at {db_path}")
else:
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if column exists
cursor.execute("PRAGMA table_info(telegram_bots)")
columns = [info[1] for info in cursor.fetchall()]
if 'notification_types' not in columns:
print("Adding notification_types column...")
cursor.execute("ALTER TABLE telegram_bots ADD COLUMN notification_types VARCHAR(255) DEFAULT 'auth,activity,system'")
conn.commit()
print("Column added successfully.")
else:
print("Column already exists.")
conn.close()
except Exception as e:
print(f"Error: {e}")

67
update_user_approval.py Normal file
View File

@@ -0,0 +1,67 @@
"""
DB 업데이트 스크립트: User 테이블에 승인 관련 필드 추가
- is_approved: 가입 승인 여부
- approval_token: 승인 토큰 (텔레그램 버튼 콜백용)
"""
import sys
import os
# 프로젝트 루트를 sys.path에 추가
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app import app
from backend.models.user import db
def update_user_table():
"""User 테이블에 승인 관련 컬럼 추가"""
with app.app_context():
try:
# 컬럼 존재 여부 확인
inspector = db.inspect(db.engine)
columns = [col['name'] for col in inspector.get_columns('user')]
if 'is_approved' not in columns:
print("✅ is_approved 컬럼 추가 중...")
db.session.execute(db.text(
"ALTER TABLE user ADD COLUMN is_approved BOOLEAN DEFAULT 0 NOT NULL"
))
print("✅ is_approved 컬럼 추가 완료")
else:
print(" is_approved 컬럼이 이미 존재합니다.")
if 'approval_token' not in columns:
print("✅ approval_token 컬럼 추가 중...")
# SQLite는 UNIQUE 제약조건을 ALTER TABLE에서 직접 추가할 수 없음
# 먼저 컬럼만 추가하고, 나중에 인덱스로 UNIQUE 처리
db.session.execute(db.text(
"ALTER TABLE user ADD COLUMN approval_token VARCHAR(100)"
))
print("✅ approval_token 컬럼 추가 완료")
# UNIQUE 인덱스 생성
try:
db.session.execute(db.text(
"CREATE UNIQUE INDEX idx_user_approval_token ON user(approval_token)"
))
print("✅ approval_token UNIQUE 인덱스 생성 완료")
except Exception as e:
print(f" UNIQUE 인덱스 생성 스킵 (이미 존재하거나 NULL 값 때문): {e}")
else:
print(" approval_token 컬럼이 이미 존재합니다.")
# 기존 사용자들은 자동 승인 처리
print("✅ 기존 사용자 자동 승인 처리 중...")
db.session.execute(db.text(
"UPDATE user SET is_approved = 1 WHERE is_approved = 0"
))
db.session.commit()
print("✅ 데이터베이스 업데이트 완료!")
except Exception as e:
print(f"❌ 오류 발생: {e}")
db.session.rollback()
raise
if __name__ == "__main__":
update_user_table()