This commit is contained in:
2025-11-28 18:27:15 +09:00
parent 2481d44eb8
commit c0d3312bca
52 changed files with 13363 additions and 1444 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_migrate import Migrate
from flask_socketio import SocketIO from flask_socketio import SocketIO
from flask_wtf import CSRFProtect from flask_wtf import CSRFProtect
from dotenv import load_dotenv
from config import Config from config import Config
from backend.models.user import db, load_user 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.logger import setup_logging
from backend.services import watchdog_handler 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 # structure: <project_root>/backend/templates, <project_root>/backend/static
# ─────────────────────────────────────────────────────────────
BASE_DIR = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent
TEMPLATE_DIR = (BASE_DIR / "backend" / "templates").resolve() TEMPLATE_DIR = (BASE_DIR / "backend" / "templates").resolve()
STATIC_DIR = (BASE_DIR / "backend" / "static").resolve() STATIC_DIR = (BASE_DIR / "backend" / "static").resolve()
@@ -32,9 +42,11 @@ setup_logging(app)
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
# CSRF 보호 + 템플릿에서 {{ csrf_token() }} 사용 가능하게 주입 # CSRF 보호 + 템플릿에서 {{ csrf_token() }} 사용 가능하게 주입
# ─────────────────────────────────────────────────────────────
csrf = CSRFProtect() csrf = CSRFProtect()
csrf.init_app(app) csrf.init_app(app)
@app.context_processor @app.context_processor
def inject_csrf(): def inject_csrf():
try: try:
@@ -44,9 +56,11 @@ def inject_csrf():
# Flask-WTF 미설치/에러 시에도 앱이 뜨도록 방어 # Flask-WTF 미설치/에러 시에도 앱이 뜨도록 방어
return dict(csrf_token=lambda: "") return dict(csrf_token=lambda: "")
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
# SocketIO: Windows 기본 threading, Linux는 eventlet 설치 시 eventlet 사용 # SocketIO: Windows 기본 threading, Linux는 eventlet 설치 시 eventlet 사용
# 환경변수 SOCKETIO_ASYNC_MODE 로 강제 지정 가능 ("threading"/"eventlet"/"gevent"/"auto") # 환경변수 SOCKETIO_ASYNC_MODE 로 강제 지정 가능 ("threading"/"eventlet"/"gevent"/"auto")
# ─────────────────────────────────────────────────────────────
async_mode = (app.config.get("SOCKETIO_ASYNC_MODE") or "threading").lower() async_mode = (app.config.get("SOCKETIO_ASYNC_MODE") or "threading").lower()
if async_mode == "auto": if async_mode == "auto":
async_mode = "threading" async_mode = "threading"
@@ -67,39 +81,86 @@ socketio = SocketIO(app, cors_allowed_origins="*", async_mode=async_mode)
# watchdog에서 socketio 사용 # watchdog에서 socketio 사용
watchdog_handler.socketio = socketio watchdog_handler.socketio = socketio
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
# DB / 마이그레이션 # DB / 마이그레이션
# ─────────────────────────────────────────────────────────────
app.logger.info("DB URI = %s", app.config.get("SQLALCHEMY_DATABASE_URI")) app.logger.info("DB URI = %s", app.config.get("SQLALCHEMY_DATABASE_URI"))
db.init_app(app) db.init_app(app)
Migrate(app, db) Migrate(app, db)
# (선택) 개발 편의용: 테이블 자동 부트스트랩 # (선택) 개발 편의용: 테이블 자동 부트스트랩
# 환경변수 AUTO_BOOTSTRAP_DB=true 일 때만 동작 (운영에서는 flask db upgrade 사용 권장) # 환경변수 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 from sqlalchemy import inspect
with app.app_context(): with app.app_context():
insp = inspect(db.engine) insp = inspect(db.engine)
if "user" not in insp.get_table_names(): if "user" not in insp.get_table_names():
db.create_all() db.create_all()
app.logger.info("DB bootstrap: created tables via create_all()") app.logger.info("DB bootstrap: created tables via create_all()")
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
# Login # Login
# ─────────────────────────────────────────────────────────────
login_manager = LoginManager() login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
login_manager.login_view = "auth.login" login_manager.login_view = "auth.login"
@login_manager.user_loader @login_manager.user_loader
def _load_user(user_id: str): def _load_user(user_id: str):
return load_user(user_id) return load_user(user_id)
# 라우트 등록 (Blueprints 등) # 라우트 등록 (Blueprints 등)
register_routes(app, socketio) 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__": if __name__ == "__main__":
host = os.getenv("FLASK_HOST", "0.0.0.0") host = os.getenv("FLASK_HOST", "0.0.0.0")
port = int(os.getenv("FLASK_PORT", 5000)) port = int(os.getenv("FLASK_PORT", 5000))
debug = os.getenv("FLASK_DEBUG", "true").lower() == "true" 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_admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
is_active: 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 def __repr__(self) -> str: # pragma: no cover
return f"<User id={self.id} email={self.email} active={self.is_active}>" return f"<User id={self.id} email={self.email} active={self.is_active}>"

View File

@@ -10,6 +10,7 @@ from .file_view import register_file_view
from .jobs import register_jobs_routes from .jobs import register_jobs_routes
from .idrac_routes import register_idrac_routes from .idrac_routes import register_idrac_routes
from .catalog_sync import catalog_bp from .catalog_sync import catalog_bp
from .scp_routes import scp_bp
def register_routes(app: Flask, socketio=None) -> None: def register_routes(app: Flask, socketio=None) -> None:
"""블루프린트 일괄 등록. socketio는 main 라우트에서만 사용.""" """블루프린트 일괄 등록. socketio는 main 라우트에서만 사용."""
@@ -23,3 +24,4 @@ def register_routes(app: Flask, socketio=None) -> None:
register_jobs_routes(app) register_jobs_routes(app)
register_idrac_routes(app) register_idrac_routes(app)
app.register_blueprint(catalog_bp) app.register_blueprint(catalog_bp)
app.register_blueprint(scp_bp)

Binary file not shown.

View File

@@ -18,6 +18,11 @@ from flask import (
from flask_login import login_required, current_user from flask_login import login_required, current_user
from backend.models.user import User, db 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__) admin_bp = Blueprint("admin", __name__)
@@ -124,3 +129,111 @@ def reset_password(user_id: int):
flash("비밀번호 변경 중 오류가 발생했습니다.", "danger") flash("비밀번호 변경 중 오류가 발생했습니다.", "danger")
return redirect(url_for("admin.admin_panel")) 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 logging
import threading import threading
import asyncio
import secrets
from typing import Optional from typing import Optional
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse, urljoin
from datetime import datetime
from flask import ( from flask import (
Blueprint, Blueprint,
@@ -23,11 +26,13 @@ from backend.models.user import User, db
# ── (선택) Telegram: 미설정이면 조용히 패스 # ── (선택) Telegram: 미설정이면 조용히 패스
try: try:
from telegram import Bot from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode from telegram.constants import ParseMode
except Exception: # 라이브러리 미설치/미사용 환경 except Exception: # 라이브러리 미설치/미사용 환경
Bot = None Bot = None
ParseMode = None ParseMode = None
InlineKeyboardButton = None
InlineKeyboardMarkup = None
auth_bp = Blueprint("auth", __name__) 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) return (test.scheme in ("http", "https")) and (ref.netloc == test.netloc)
def _notify(text: str) -> None: def _notify(text: str, category: str = "system") -> None:
"""텔레그램 알림 (설정 없으면 바로 return).""" """
token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip() 텔레그램 알림 전송
chat_id = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip() - DB(TelegramBot)에 등록된 활성 봇들에게 전송
if not (token and chat_id and Bot and ParseMode): - category: 'auth', 'activity', 'system'
return """
try:
from backend.models.telegram_bot import TelegramBot
def _send(): # 앱 컨텍스트 안에서 실행되므로 바로 DB 접근 가능
try: try:
bot = Bot(token=token) bots = TelegramBot.query.filter_by(is_active=True).all()
bot.send_message(chat_id=chat_id, text=text, parse_mode=ParseMode.HTML) except Exception:
except Exception as e: db.create_all()
current_app.logger.warning("Telegram send failed: %s", e) 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.register_blueprint(auth_bp)
@app.before_request @app.before_request
def _touch_session(): def _global_hooks():
# 요청마다 세션 갱신(만료 슬라이딩) + 로그아웃 플래그 정리 # 1. 세션 갱신 (요청마다 세션 타임아웃 연장)
session.modified = True session.modified = True
if current_user.is_authenticated and session.get("just_logged_out"):
session.pop("just_logged_out", None) # 2. 활동 알림 (로그인된 사용자)
flash("세션이 만료되어 자동 로그아웃 되었습니다.", "info") 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) current_app.logger.info("REGISTER: dup username %s", form.username.data)
return render_template("register.html", form=form) 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.add(user)
db.session.commit() db.session.commit()
_notify( # 텔레그램 알림 (인라인 버튼 포함)
f"🆕 <b>신규 가입 요청</b>\n" message = (
f"📛 사용자: <code>{user.username}</code>\n" f"🆕 <b>신규 가입 요청</b>\n\n"
f"📧 이메일: <code>{user.email}</code>" 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") flash("회원가입이 완료되었습니다. 관리자의 승인을 기다려주세요.", "success")
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
else: else:
@@ -136,24 +317,28 @@ def login():
current_app.logger.info("LOGIN: user not found") current_app.logger.info("LOGIN: user not found")
return render_template("login.html", form=form) 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( current_app.logger.info(
"LOGIN: found id=%s active=%s pass_ok=%s", "LOGIN: found id=%s active=%s approved=%s pass_ok=%s",
user.id, user.is_active, pass_ok user.id, user.is_active, user.is_approved, pass_ok
) )
if not pass_ok: if not pass_ok:
flash("이메일 또는 비밀번호가 올바르지 않습니다.", "danger") flash("이메일 또는 비밀번호가 올바르지 않습니다.", "danger")
return render_template("login.html", form=form) 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: if not user.is_active:
flash("계정이 아직 승인되지 않았습니다.", "warning") flash("계정이 비활성화되었습니다. 관리자에게 문의하세요.", "warning")
return render_template("login.html", form=form) return render_template("login.html", form=form)
# 성공 # 성공
login_user(user, remember=form.remember.data) login_user(user, remember=form.remember.data)
session.permanent = True 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") current_app.logger.info("LOGIN: SUCCESS → redirect")
nxt = request.args.get("next") nxt = request.args.get("next")
@@ -175,6 +360,7 @@ def login():
def logout(): def logout():
if current_user.is_authenticated: if current_user.is_authenticated:
current_app.logger.info("LOGOUT: user=%s", current_user.username) current_app.logger.info("LOGOUT: user=%s", current_user.username)
_notify(f"🚪 <b>로그아웃</b>\n👤 <code>{current_user.username}</code>", category="auth")
logout_user() logout_user()
session["just_logged_out"] = True flash("정상적으로 로그아웃 되었습니다.", "success")
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))

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

@@ -45,18 +45,31 @@ class RedfishClient:
ip: str, ip: str,
username: str, username: str,
password: str, password: str,
timeout: int = 15, timeout: Optional[int] = None,
verify_ssl: bool = False verify_ssl: Optional[bool] = None
): ):
self.ip = ip self.ip = ip
self.base_url = f"https://{ip}/redfish/v1" self.base_url = f"https://{ip}/redfish/v1"
self.host_url = f"https://{ip}" # ← 추가: 호스트 URL self.host_url = f"https://{ip}"
self.timeout = timeout
self.verify_ssl = verify_ssl # 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 = requests.Session()
self.session.auth = (username, password) self.session.auth = (username, password)
self.session.verify = verify_ssl self.session.verify = self.verify_ssl
self.session.headers.update({ self.session.headers.update({
"Content-Type": "application/json", "Content-Type": "application/json",
"Accept": "application/json" "Accept": "application/json"
@@ -214,11 +227,77 @@ class RedfishClient:
"PercentComplete": str(percent), "PercentComplete": str(percent),
"Message": message_text, "Message": message_text,
"ScheduledStartTime": job_data.get("ScheduledStartTime", ""), "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", ""), "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): def close(self):
"""세션 종료""" """세션 종료"""
self.session.close() 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

@@ -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;
}
// 제출 허용 (서버측에서도 반드시 검증)
});
})();

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="container mt-4">
<div class="card"> <div class="card">
<div class="card-body"> <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) %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
<div class="mt-2"> <div class="mt-2">
{% for cat, msg in messages %} {% for cat, msg in messages %}
<div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert"> <div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert">
{{ msg }} {{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<div class="table-responsive"> <div class="table-responsive">
@@ -39,28 +44,24 @@
<td>{{ user.email }}</td> <td>{{ user.email }}</td>
<td> <td>
{% if user.is_active %} {% if user.is_active %}
<span class="badge bg-success">Yes</span> <span class="badge bg-success">Yes</span>
{% else %} {% else %}
<span class="badge bg-secondary">No</span> <span class="badge bg-secondary">No</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if not user.is_active %} {% 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 %} {% endif %}
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}" <a href="{{ url_for('admin.delete_user', user_id=user.id) }}" class="btn btn-danger btn-sm me-1"
class="btn btn-danger btn-sm me-1" onclick="return confirm('사용자 {{ user.username }} (ID={{ user.id }}) 를 삭제하시겠습니까?');">
onclick="return confirm('사용자 {{ user.username }} (ID={{ user.id }}) 를 삭제하시겠습니까?');"> Delete
Delete
</a> </a>
<!-- Change Password 버튼: 모달 오픈 --> <!-- Change Password 버튼: 모달 오픈 -->
<button type="button" <button type="button" class="btn btn-primary btn-sm" data-user-id="{{ user.id }}"
class="btn btn-primary btn-sm" data-username="{{ user.username | e }}" data-bs-toggle="modal" data-bs-target="#changePasswordModal">
data-user-id="{{ user.id }}"
data-username="{{ user.username | e }}"
data-bs-toggle="modal"
data-bs-target="#changePasswordModal">
Change Password Change Password
</button> </button>
</td> </td>
@@ -74,7 +75,8 @@
</div> </div>
{# ========== Change Password Modal ========== #} {# ========== 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-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<form id="changePasswordForm" method="post" action=""> <form id="changePasswordForm" method="post" action="">
@@ -92,13 +94,15 @@
<div class="mb-3"> <div class="mb-3">
<label for="newPasswordInput" class="form-label">New password</label> <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 class="form-text">최소 8자 이상을 권장합니다.</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="confirmPasswordInput" class="form-label">Confirm password</label> <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 id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
</div> </div>
</div> </div>
@@ -112,58 +116,8 @@
</div> </div>
</div> </div>
{# ========== 스크립트: 모달에 사용자 정보 채우기 + 클라이언트 확인 ========== #}
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script> <script src="{{ url_for('static', filename='js/admin.js') }}"></script>
(function () {
// Bootstrap 5을 사용한다고 가정. data-bs-* 이벤트로 처리.
const changePasswordModal = document.getElementById('changePasswordModal');
const modalUserInfo = document.getElementById('modalUserInfo');
const changePasswordForm = document.getElementById('changePasswordForm');
const newPasswordInput = document.getElementById('newPasswordInput');
const confirmPasswordInput = document.getElementById('confirmPasswordInput');
const pwMismatch = document.getElementById('pwMismatch');
if (!changePasswordModal) return;
changePasswordModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget; // 버튼 that triggered the modal
const userId = button.getAttribute('data-user-id');
const username = button.getAttribute('data-username') || ('ID ' + userId);
// 표시 텍스트 세팅
modalUserInfo.textContent = username + ' (ID: ' + userId + ')';
// 폼 action 동적 설정: admin.reset_password 라우트 기대
// 예: /admin/users/123/reset_password
changePasswordForm.action = "{{ url_for('admin.reset_password', user_id=0) }}".replace('/0/', '/' + userId + '/');
// 폼 내부 비밀번호 필드 초기화
newPasswordInput.value = '';
confirmPasswordInput.value = '';
confirmPasswordInput.classList.remove('is-invalid');
pwMismatch.style.display = 'none';
});
// 폼 제출 전 클라이언트에서 비밀번호 일치 검사
changePasswordForm.addEventListener('submit', function (e) {
const a = newPasswordInput.value || '';
const b = confirmPasswordInput.value || '';
if (a.length < 8) {
newPasswordInput.focus();
e.preventDefault();
return;
}
if (a !== b) {
e.preventDefault();
confirmPasswordInput.classList.add('is-invalid');
pwMismatch.style.display = 'block';
confirmPasswordInput.focus();
return;
}
// 제출 허용 (서버측에서도 반드시 검증)
});
})();
</script>
{% endblock %} {% endblock %}
{% 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> <!DOCTYPE html>
<html lang="ko"> <html lang="ko">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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') }}"> <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Bootstrap 5.3.3 CSS --> <!-- Bootstrap 5.3.3 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
<!-- Bootstrap Icons --> <!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" <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" integrity="sha384-tViUnnbYAV00FLIhhi3v/dWt3Jxw4gZQcNoSCxCIFNJVCx7/D55/wXsrNIRANwdD" crossorigin="anonymous">
crossorigin="anonymous">
<!-- Custom CSS --> <!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
</head> </head>
<body> <body>
<!-- Skip to main content (접근성) --> <!-- Skip to main content (접근성) -->
<a href="#main-content" class="visually-hidden-focusable">본문으로 건너뛰기</a> <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 --> <!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container-fluid"> <div class="container-fluid">
@@ -37,12 +51,8 @@
<i class="bi bi-server me-2"></i> <i class="bi bi-server me-2"></i>
Dell Server Info Dell Server Info
</a> </a>
<button class="navbar-toggler" type="button" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
data-bs-toggle="collapse" aria-controls="navbarNav" aria-expanded="false" aria-label="네비게이션 토글">
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="네비게이션 토글">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
@@ -53,44 +63,44 @@
</a> </a>
</li> </li>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}"> <a class="nav-link" href="{{ url_for('main.index') }}">
<i class="bi bi-hdd-network me-1"></i>ServerInfo <i class="bi bi-hdd-network me-1"></i>ServerInfo
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('xml.xml_management') }}"> <a class="nav-link" href="{{ url_for('xml.xml_management') }}">
<i class="bi bi-file-code me-1"></i>XML Management <i class="bi bi-file-code me-1"></i>XML Management
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('jobs.jobs_page') }}"> <a class="nav-link" href="{{ url_for('jobs.jobs_page') }}">
<i class="bi bi-list-task me-1"></i>Job Monitor <i class="bi bi-list-task me-1"></i>Job Monitor
</a> </a>
</li> </li>
{% if current_user.is_admin %} {% if current_user.is_admin %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.admin_panel') }}"> <a class="nav-link" href="{{ url_for('admin.admin_panel') }}">
<i class="bi bi-shield-lock me-1"></i>Admin <i class="bi bi-shield-lock me-1"></i>Admin
</a> </a>
</li> </li>
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}"> <a class="nav-link" href="{{ url_for('auth.logout') }}">
<i class="bi bi-box-arrow-right me-1"></i>Logout <i class="bi bi-box-arrow-right me-1"></i>Logout
</a> </a>
</li> </li>
{% else %} {% else %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}"> <a class="nav-link" href="{{ url_for('auth.login') }}">
<i class="bi bi-box-arrow-in-right me-1"></i>Login <i class="bi bi-box-arrow-in-right me-1"></i>Login
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}"> <a class="nav-link" href="{{ url_for('auth.register') }}">
<i class="bi bi-person-plus me-1"></i>Register <i class="bi bi-person-plus me-1"></i>Register
</a> </a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
@@ -98,7 +108,8 @@
</nav> </nav>
<!-- Main Content --> <!-- 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 %} {% block content %}{% endblock %}
</main> </main>
@@ -111,16 +122,17 @@
<!-- Bootstrap 5.3.3 JS Bundle (Popper.js 포함) --> <!-- 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" <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<!-- Socket.IO (필요한 경우만) --> <!-- Socket.IO (필요한 경우만) -->
{% if config.USE_SOCKETIO %} {% if config.USE_SOCKETIO %}
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js" <script src="https://cdn.socket.io/4.7.4/socket.io.min.js"
integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4" integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
{% endif %} {% endif %}
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -1,51 +1,26 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block title %}Edit XML File - Dell Server Info{% endblock %}
<head>
<meta charset="UTF-8"> {% block extra_css %}
<title>Edit XML File</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/edit_xml.css') }}">
<style> {% endblock %}
::-webkit-scrollbar { width: 12px; }
::-webkit-scrollbar-track { background: #f1f1f1; } {% block content %}
::-webkit-scrollbar-thumb { background-color: #888; border-radius: 6px; } <div class="card shadow-lg">
::-webkit-scrollbar-thumb:hover { background-color: #555; } <div class="card-header bg-primary text-white">
html { scrollbar-width: thin; scrollbar-color: #888 #f1f1f1; } <h3>Edit XML File: <strong>{{ filename }}</strong></h3>
textarea { </div>
width: 100%; <div class="card-body">
height: 600px; <form method="post">
padding: 10px; <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
font-size: 14px; <div class="form-group">
line-height: 1.5; <label for="xmlContent">XML Content</label>
background-color: #f9f9f9; <textarea id="xmlContent" name="content" class="form-control" rows="20">{{ content }}</textarea>
border: 1px solid #ccc; </div>
transition: background-color 0.3s ease; <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>
textarea:hover { background-color: #f0f0f0; } </form>
.xml-list-item { </div>
padding: 10px; </div>
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>
{% endblock %} {% endblock %}

View File

@@ -3,20 +3,7 @@
{% block content %} {% block content %}
<div class="container-fluid py-4"> <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"> <div class="row mb-4">
@@ -50,7 +37,7 @@
<select id="script" name="script" class="form-select" required> <select id="script" name="script" class="form-select" required>
<option value="">스크립트를 선택하세요</option> <option value="">스크립트를 선택하세요</option>
{% for script in scripts %} {% for script in scripts %}
<option value="{{ script }}">{{ script }}</option> <option value="{{ script }}">{{ script }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@@ -61,7 +48,7 @@
<select id="xmlFile" name="xmlFile" class="form-select"> <select id="xmlFile" name="xmlFile" class="form-select">
<option value="">XML 파일 선택</option> <option value="">XML 파일 선택</option>
{% for xml_file in xml_files %} {% for xml_file in xml_files %}
<option value="{{ xml_file }}">{{ xml_file }}</option> <option value="{{ xml_file }}">{{ xml_file }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@@ -73,7 +60,7 @@
<span class="badge bg-secondary ms-2" id="ipLineCount">0 대설정</span> <span class="badge bg-secondary ms-2" id="ipLineCount">0 대설정</span>
</label> </label>
<textarea id="ips" name="ips" rows="4" class="form-control font-monospace" <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> </div>
<button type="submit" class="btn btn-primary w-100"> <button type="submit" class="btn btn-primary w-100">
@@ -102,22 +89,18 @@
서버 리스트 (덮어쓰기) 서버 리스트 (덮어쓰기)
<span class="badge bg-secondary ms-2" id="serverLineCount">0 대설정</span> <span class="badge bg-secondary ms-2" id="serverLineCount">0 대설정</span>
</label> </label>
<textarea id="server_list_content" name="server_list_content" rows="8" <textarea id="server_list_content" name="server_list_content" rows="8" class="form-control font-monospace"
class="form-control font-monospace" style="font-size: 0.95rem;" style="font-size: 0.95rem;" placeholder="서버 리스트를 입력하세요..."></textarea>
placeholder="서버 리스트를 입력하세요..."></textarea>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}" <button type="submit" formaction="{{ url_for('utils.update_server_list') }}" class="btn btn-secondary">
class="btn btn-secondary">
MAC to Excel MAC to Excel
</button> </button>
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}" <button type="submit" formaction="{{ url_for('utils.update_guid_list') }}" class="btn btn-success">
class="btn btn-success">
GUID to Excel GUID to Excel
</button> </button>
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}" <button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}" class="btn btn-warning">
class="btn btn-warning">
GPU to Excel GPU to Excel
</button> </button>
</div> </div>
@@ -138,7 +121,7 @@
</div> </div>
<div class="progress" style="height: 25px;"> <div class="progress" style="height: 25px;">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" <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> <span class="fw-semibold">0%</span>
</div> </div>
</div> </div>
@@ -218,45 +201,44 @@
</div> </div>
</div> </div>
{# 처리된 파일 목록 #} {# 처리된 파일 목록 #}
<div class="row mb-4 processed-list"> <div class="row mb-4 processed-list">
<div class="col"> <div class="col">
<div class="card border shadow-sm"> <div class="card border shadow-sm">
<div class="card-header bg-light border-0 py-2 d-flex justify-content-between align-items-center"> <div class="card-header bg-light border-0 py-2 d-flex justify-content-between align-items-center">
<h6 class="mb-0"> <h6 class="mb-0">
<i class="bi bi-files me-2"></i> <i class="bi bi-files me-2"></i>
처리된 파일 목록 처리된 파일 목록
</h6> </h6>
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
{% if files_to_display and files_to_display|length > 0 %} {% if files_to_display and files_to_display|length > 0 %}
<div class="row g-3"> <div class="row g-3">
{% for file_info in files_to_display %} {% for file_info in files_to_display %}
<div class="col-auto"> <div class="col-auto">
<div class="file-card-compact border rounded p-2 text-center"> <div class="file-card-compact border rounded p-2 text-center">
<a href="{{ url_for('main.download_file', filename=file_info.file) }}" <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" class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2" download
download title="{{ file_info.name or file_info.file }}"> title="{{ file_info.name or file_info.file }}">
{{ file_info.name or file_info.file }} {{ file_info.name or file_info.file }}
</a> </a>
<div class="file-card-buttons d-flex gap-2 justify-content-center"> <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" <button type="button" class="btn btn-sm btn-outline btn-view-processed flex-fill"
data-bs-toggle="modal" data-bs-target="#fileViewModal" data-bs-toggle="modal" data-bs-target="#fileViewModal" data-folder="idrac_info"
data-folder="idrac_info" data-filename="{{ file_info.file }}">
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> </button>
<form action="{{ url_for('main.delete_file', filename=file_info.file) }}" </form>
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>
</div> </div>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
@@ -267,54 +249,53 @@
<!-- 이전 페이지 --> <!-- 이전 페이지 -->
{% if page > 1 %} {% if page > 1 %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{{ url_for('main.index', page=page-1) }}"> <a class="page-link" href="{{ url_for('main.index', page=page-1) }}">
<i class="bi bi-chevron-left"></i> 이전 <i class="bi bi-chevron-left"></i> 이전
</a> </a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
<span class="page-link"><i class="bi bi-chevron-left"></i> 이전</span> <span class="page-link"><i class="bi bi-chevron-left"></i> 이전</span>
</li> </li>
{% endif %} {% endif %}
<!-- 페이지 번호 (최대 10개 표시) --> <!-- 페이지 번호 (최대 10개 표시) -->
{% set start_page = ((page - 1) // 10) * 10 + 1 %} {% set start_page = ((page - 1) // 10) * 10 + 1 %}
{% set end_page = [start_page + 9, total_pages]|min %} {% set end_page = [start_page + 9, total_pages]|min %}
{% for p in range(start_page, end_page + 1) %} {% for p in range(start_page, end_page + 1) %}
<li class="page-item {% if p == page %}active{% endif %}"> <li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('main.index', page=p) }}">{{ p }}</a> <a class="page-link" href="{{ url_for('main.index', page=p) }}">{{ p }}</a>
</li> </li>
{% endfor %} {% endfor %}
<!-- 다음 페이지 --> <!-- 다음 페이지 -->
{% if page < total_pages %} {% if page < total_pages %} <li class="page-item">
<li class="page-item"> <a class="page-link" href="{{ url_for('main.index', page=page+1) }}">
<a class="page-link" href="{{ url_for('main.index', page=page+1) }}"> 다음 <i class="bi bi-chevron-right"></i>
다음 <i class="bi bi-chevron-right"></i> </a>
</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
<span class="page-link">다음 <i class="bi bi-chevron-right"></i></span> <span class="page-link">다음 <i class="bi bi-chevron-right"></i></span>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</nav> </nav>
{% endif %} {% endif %}
<!-- /페이지네이션 --> <!-- /페이지네이션 -->
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-5">
<i class="bi bi-inbox fs-1 text-muted mb-3"></i> <i class="bi bi-inbox fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">표시할 파일이 없습니다.</p> <p class="text-muted mb-0">표시할 파일이 없습니다.</p>
</div> </div>
{% endif %} {% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
{# 백업된 파일 목록 #} {# 백업된 파일 목록 #}
<div class="row backup-list"> <div class="row backup-list">
@@ -328,55 +309,52 @@
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
{% if backup_files and backup_files|length > 0 %} {% if backup_files and backup_files|length > 0 %}
<div class="list-group"> <div class="list-group">
{% for date, info in backup_files.items() %} {% for date, info in backup_files.items() %}
<div class="list-group-item border rounded mb-2 p-0 overflow-hidden"> <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 justify-content-between align-items-center p-3 bg-light">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="bi bi-calendar3 text-primary me-2"></i> <i class="bi bi-calendar3 text-primary me-2"></i>
<strong>{{ date }}</strong> <strong>{{ date }}</strong>
<span class="badge bg-primary ms-3">{{ info.count }} 파일</span> <span class="badge bg-primary ms-3">{{ info.count }} 파일</span>
</div> </div>
<button class="btn btn-sm btn-outline-secondary" type="button" <button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse"
data-bs-toggle="collapse" data-bs-target="#collapse-{{ loop.index }}" data-bs-target="#collapse-{{ loop.index }}" aria-expanded="false">
aria-expanded="false"> <i class="bi bi-chevron-down"></i>
<i class="bi bi-chevron-down"></i> </button>
</button> </div>
</div> <div id="collapse-{{ loop.index }}" class="collapse">
<div id="collapse-{{ loop.index }}" class="collapse"> <div class="p-3">
<div class="p-3"> <div class="row g-3">
<div class="row g-3"> {% for file in info.files %}
{% for file in info.files %} <div class="col-auto">
<div class="col-auto"> <div class="file-card-compact border rounded p-2 text-center">
<div class="file-card-compact border rounded p-2 text-center"> <a href="{{ url_for('main.download_backup_file', date=date, filename=file) }}"
<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
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2" title="{{ file }}">
download title="{{ file }}"> {{ file.rsplit('.', 1)[0] }}
{{ file.rsplit('.', 1)[0] }} </a>
</a> <div class="file-card-single-button">
<div class="file-card-single-button"> <button type="button" class="btn btn-sm btn-outline btn-view-backup w-100"
<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-bs-toggle="modal" data-bs-target="#fileViewModal" data-date="{{ date }}" data-filename="{{ file }}">
data-folder="backup" 보기
data-date="{{ date }}" </button>
data-filename="{{ file }}"> </div>
보기
</button>
</div>
</div>
</div>
{% endfor %}
</div> </div>
</div> </div>
{% endfor %}
</div> </div>
</div> </div>
{% endfor %} </div>
</div> </div>
{% endfor %}
</div>
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-5">
<i class="bi bi-inbox fs-1 text-muted mb-3"></i> <i class="bi bi-inbox fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">백업된 파일이 없습니다.</p> <p class="text-muted mb-0">백업된 파일이 없습니다.</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -398,7 +376,7 @@
</div> </div>
<div class="modal-body bg-light"> <div class="modal-body bg-light">
<pre id="fileViewContent" class="mb-0 p-3 bg-white border rounded font-monospace" <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>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
@@ -410,276 +388,14 @@
</div> </div>
{% endblock %} {% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
{% endblock %}
{% block scripts %} {% block scripts %}
<style> {{ super() }}
/* ===== 공통 파일 카드 컴팩트 스타일 ===== */
.file-card-compact {
transition: all 0.2s ease;
background: #fff;
min-width: 120px;
max-width: 200px;
}
.file-card-compact:hover { <script src="{{ url_for('static', filename='js/index.js') }}"></script>
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.js 파일 (IP 폼 처리 로직 포함) --> <!-- 외부 script.js 파일 (IP 폼 처리 로직 포함) -->
<script src="{{ url_for('static', filename='script.js') }}"></script> <script src="{{ url_for('static', filename='script.js') }}"></script>

View File

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

View File

@@ -1,305 +1,223 @@
{% extends "base.html" %} {% 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 %} {% block content %}
<html lang="en"> <h1 class="main-title">설정 파일 관리 (SCP)</h1>
<head> <p class="subtitle">iDRAC 서버 설정(XML)을 내보내거나 가져오고, 버전을 비교할 수 있습니다.</p>
<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;
}
.main-title { <div class="row">
font-size: 1.8rem; <!-- 왼쪽: 파일 업로드 및 내보내기 -->
font-weight: 600; <div class="col-md-4">
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
font-size: 0.95rem;
margin-bottom: 30px;
}
.card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
background: white;
}
.card-header-custom {
background-color: #007bff;
color: white;
padding: 12px 20px;
font-weight: 600;
border-radius: 8px 8px 0 0;
font-size: 1rem;
}
.card-body {
padding: 20px;
}
.form-label {
font-weight: 500;
color: #555;
margin-bottom: 8px;
font-size: 0.9rem;
}
.form-control {
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px 12px;
font-size: 0.9rem;
}
.btn-primary {
background-color: #007bff;
border: none;
padding: 8px 24px;
font-weight: 500;
border-radius: 4px;
font-size: 0.9rem;
}
.btn-success {
background-color: #28a745;
border: none;
padding: 6px 16px;
font-weight: 500;
border-radius: 4px;
font-size: 0.85rem;
}
.btn-danger {
background-color: #dc3545;
border: none;
padding: 6px 16px;
font-weight: 500;
border-radius: 4px;
font-size: 0.85rem;
}
.upload-section {
background-color: #f8f9fa;
padding: 15px;
border-radius: 4px;
}
.custom-file-label {
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.custom-file-label::after {
background-color: #007bff;
color: white;
border-radius: 0 3px 3px 0;
font-size: 0.85rem;
}
/* 아이콘 + 뱃지 스타일 */
.file-list {
max-height: 500px;
overflow-y: auto;
}
.icon-badge-item {
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 10px 15px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.3s;
background: white;
}
.icon-badge-item:hover {
background-color: #f8f9fa;
border-color: #007bff;
transform: translateX(3px);
}
.icon-badge-left {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.file-icon-small {
width: 36px;
height: 36px;
background: #007bff;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
flex-shrink: 0;
}
.file-name-section {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
}
.file-name-badge {
font-weight: 500;
color: #333;
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.badge-custom {
background-color: #e7f3ff;
color: #007bff;
padding: 3px 10px;
border-radius: 10px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
.action-buttons {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.empty-message {
text-align: center;
color: #999;
padding: 30px;
font-size: 0.9rem;
}
.container {
max-width: 1200px;
}
/* 스크롤바 스타일 */
.file-list::-webkit-scrollbar {
width: 6px;
}
.file-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>
</head>
<body>
<div class="container">
<h1 class="main-title">XML 파일 관리</h1>
<p class="subtitle">XML 파일을 업로드하고 관리할 수 있습니다</p>
<!-- XML 파일 업로드 폼 -->
<div class="card"> <div class="card">
<div class="card-header-custom"> <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>
<div class="card-body"> <div class="card-body">
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data"> <!-- 1. PC에서 업로드 -->
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <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="upload-section">
<div class="form-group mb-2"> <div class="mb-2">
<label for="xmlFile" class="form-label">XML 파일 선택</label>
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" id="xmlFile" name="xmlFile" accept=".xml" onchange="updateFileName(this)"> <input type="file" class="custom-file-input" id="xmlFile" name="xmlFile" accept=".xml"
<label class="custom-file-label" for="xmlFile" id="fileLabel">파일을 선택하세요</label> onchange="updateFileName(this)">
<label class="custom-file-label" for="xmlFile" id="fileLabel">파일 선택</label>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary w-100">
<i class="fas fa-upload mr-1"></i>업로드 <i class="fas fa-upload me-1"></i>업로드
</button> </button>
</div> </div>
</form> </form>
</div>
</div>
<!-- XML 파일 목록 --> <hr>
<div class="card">
<div class="card-header-custom"> <!-- 2. iDRAC에서 내보내기 -->
<i class="fas fa-list mr-2"></i>파일 목록 <h6 class="mb-3"><i class="fas fa-server me-2"></i>iDRAC에서 추출 (Export)</h6>
</div> <button type="button" class="btn btn-outline-primary w-100" data-bs-toggle="modal"
<div class="card-body"> data-bs-target="#exportModal">
{% if xml_files %} <i class="fas fa-download me-1"></i>설정 추출하기
<div class="file-list"> </button>
{% for xml_file in xml_files %}
<div class="icon-badge-item">
<div class="icon-badge-left">
<div class="file-icon-small">
<i class="fas fa-file-code"></i>
</div>
<div class="file-name-section">
<span class="file-name-badge" title="{{ xml_file }}">{{ xml_file }}</span>
<span class="badge-custom">XML</span>
</div>
</div>
<div class="action-buttons">
<!-- 파일 편집 버튼 -->
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}" class="btn btn-success btn-sm">
<i class="fas fa-edit"></i> 편집
</a>
<!-- 파일 삭제 버튼 -->
<form action="{{ url_for('xml.delete_xml', filename=xml_file) }}" method="POST" style="display:inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('정말 삭제하시겠습니까?')">
<i class="fas fa-trash"></i> 삭제
</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-message">
<i class="fas fa-folder-open" style="font-size: 2rem; color: #ddd;"></i>
<p class="mt-2 mb-0">파일이 없습니다.</p>
</div>
{% endif %}
</div> </div>
</div> </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> <div class="col-md-8">
<script> <div class="card">
function updateFileName(input) { <div class="card-header-custom">
const fileName = input.files[0]?.name || '파일을 선택하세요'; <span><i class="fas fa-list me-2"></i>파일 목록</span>
document.getElementById('fileLabel').textContent = fileName; <button class="btn btn-light btn-sm text-primary" id="compareBtn"
} data-url="{{ url_for('scp.diff_scp') }}" onclick="compareSelected()">
</script> <i class="fas fa-exchange-alt me-1"></i>선택 비교
</body> </button>
</html> </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 %} {% 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" %} {% extends "base.html" %}
{% block content %} {% block content %}
<body> <h1>Uploaded XML Files</h1>
<h1>Uploaded XML Files</h1> <ul>
<ul> {% for file in files %}
{% for file in files %} <li>
<li> {{ file }}
{{ file }} <a href="{{ url_for('download_xml', filename=file) }}">Download</a>
<a href="{{ url_for('download_xml', filename=file) }}">Download</a> <a href="{{ url_for('edit_xml', filename=file) }}">Edit</a>
<a href="{{ url_for('edit_xml', filename=file) }}">Edit</a> <form action="{{ url_for('delete_xml', filename=file) }}" method="post" style="display:inline;">
<form action="{{ url_for('delete_xml', filename=file) }}" method="post" style="display:inline;"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <button type="submit">Delete</button>
<button type="submit">Delete</button> </form>
</form> </li>
</li> {% endfor %}
{% endfor %} </ul>
</ul> <a href="{{ url_for('upload_xml') }}">Upload new file</a>
<a href="{{ url_for('upload_xml') }}">Upload new file</a>
</body>
{% endblock %} {% 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") # 반드시 환경변수로 고정 권장 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 있으면 그 값을 우선 사용) # ── DB (환경변수 DATABASE_URL 있으면 그 값을 우선 사용)
sqlite_path = (INSTANCE_DIR / "site.db").as_posix() sqlite_path = (INSTANCE_DIR / "site.db").as_posix()
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", f"sqlite:///{sqlite_path}") 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/app.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 -

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) app.config.from_object(Config)
db.init_app(app) 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__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())

View File

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