Compare commits
8 Commits
45fa1fa162
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d5d2b8d99 | ||
|
|
b37c43ab86 | ||
|
|
b18412ecb2 | ||
| 804204ab97 | |||
| 25cbb6b8f8 | |||
| 2e4fc20523 | |||
| 19798cca66 | |||
| c0d3312bca |
BIN
__pycache__/app.cpython-312.pyc
Normal file
BIN
__pycache__/app.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/config.cpython-314.pyc
Normal file
BIN
__pycache__/config.cpython-314.pyc
Normal file
Binary file not shown.
BIN
__pycache__/telegram_bot_service.cpython-312.pyc
Normal file
BIN
__pycache__/telegram_bot_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/telegram_bot_service.cpython-314.pyc
Normal file
BIN
__pycache__/telegram_bot_service.cpython-314.pyc
Normal file
Binary file not shown.
87
app.py
87
app.py
@@ -8,6 +8,7 @@ from flask_login import LoginManager
|
||||
from flask_migrate import Migrate
|
||||
from flask_socketio import SocketIO
|
||||
from flask_wtf import CSRFProtect
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from config import Config
|
||||
from backend.models.user import db, load_user
|
||||
@@ -15,10 +16,19 @@ from backend.routes import register_routes
|
||||
from backend.services.logger import setup_logging
|
||||
from backend.services import watchdog_handler
|
||||
|
||||
# 텔레그램 서비스 (별도 모듈)에서 가져옴
|
||||
from telegram_bot_service import run_polling as telegram_run_polling
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# .env 파일 로드 (환경변수 우선순위 보장)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
load_dotenv()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 템플릿/정적 경로를 파일 위치 기준으로 안전하게 설정
|
||||
# structure: <project_root>/backend/templates, <project_root>/backend/static
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
TEMPLATE_DIR = (BASE_DIR / "backend" / "templates").resolve()
|
||||
STATIC_DIR = (BASE_DIR / "backend" / "static").resolve()
|
||||
@@ -32,9 +42,18 @@ setup_logging(app)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# CSRF 보호 + 템플릿에서 {{ csrf_token() }} 사용 가능하게 주입
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
csrf = CSRFProtect()
|
||||
csrf.init_app(app)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# ProxyFix: Nginx/NPM 등 리버스 프록시 뒤에서 실행 시 헤더 신뢰
|
||||
# (HTTPS 인식, 올바른 IP/Scheme 파악으로 CSRF/세션 문제 해결)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_csrf():
|
||||
try:
|
||||
@@ -44,9 +63,11 @@ def inject_csrf():
|
||||
# Flask-WTF 미설치/에러 시에도 앱이 뜨도록 방어
|
||||
return dict(csrf_token=lambda: "")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# SocketIO: Windows 기본 threading, Linux는 eventlet 설치 시 eventlet 사용
|
||||
# 환경변수 SOCKETIO_ASYNC_MODE 로 강제 지정 가능 ("threading"/"eventlet"/"gevent"/"auto")
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
async_mode = (app.config.get("SOCKETIO_ASYNC_MODE") or "threading").lower()
|
||||
if async_mode == "auto":
|
||||
async_mode = "threading"
|
||||
@@ -67,39 +88,101 @@ socketio = SocketIO(app, cors_allowed_origins="*", async_mode=async_mode)
|
||||
# watchdog에서 socketio 사용
|
||||
watchdog_handler.socketio = socketio
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# DB / 마이그레이션
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
app.logger.info("DB URI = %s", app.config.get("SQLALCHEMY_DATABASE_URI"))
|
||||
db.init_app(app)
|
||||
Migrate(app, db)
|
||||
|
||||
# (선택) 개발 편의용: 테이블 자동 부트스트랩
|
||||
# 환경변수 AUTO_BOOTSTRAP_DB=true 일 때만 동작 (운영에서는 flask db upgrade 사용 권장)
|
||||
if (os.getenv("AUTO_BOOTSTRAP_DB", "false").lower() == "true"):
|
||||
if os.getenv("AUTO_BOOTSTRAP_DB", "false").lower() == "true":
|
||||
from sqlalchemy import inspect
|
||||
|
||||
with app.app_context():
|
||||
insp = inspect(db.engine)
|
||||
if "user" not in insp.get_table_names():
|
||||
db.create_all()
|
||||
app.logger.info("DB bootstrap: created tables via create_all()")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Login
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "auth.login"
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def _load_user(user_id: str):
|
||||
return load_user(user_id)
|
||||
|
||||
|
||||
# 라우트 등록 (Blueprints 등)
|
||||
register_routes(app, socketio)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 텔레그램 봇 폴링 서비스 (중복 실행 방지 포함)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
_bot_socket_lock = None
|
||||
|
||||
def start_telegram_bot_polling() -> None:
|
||||
"""텔레그램 봇 폴링을 백그라운드 스레드로 시작 (TCP 소켓 락으로 중복 방지)"""
|
||||
import threading
|
||||
import socket
|
||||
|
||||
global _bot_socket_lock
|
||||
|
||||
if _bot_socket_lock:
|
||||
return
|
||||
|
||||
app.logger.info("🔒 봇 중복 실행 방지 락(TCP:50000) 획득 시도...")
|
||||
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind(("127.0.0.1", 50000))
|
||||
s.listen(1)
|
||||
_bot_socket_lock = s
|
||||
app.logger.info("🔒 락 획득 성공! 봇 폴링 스레드를 시작합니다.")
|
||||
except OSError:
|
||||
app.logger.warning("⛔ 락 획득 실패: 이미 다른 프로세스(또는 좀비 프로세스)가 포트 50000을 점유 중입니다. 봇 폴링을 건너뜁니다.")
|
||||
return
|
||||
|
||||
def _runner():
|
||||
try:
|
||||
telegram_run_polling(app)
|
||||
except Exception as e:
|
||||
app.logger.error("텔레그램 봇 폴링 서비스 오류: %s", e)
|
||||
|
||||
polling_thread = threading.Thread(target=_runner, daemon=True)
|
||||
polling_thread.start()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 텔레그램 봇 폴링 자동 시작
|
||||
# Flask 앱이 초기화되면 자동으로 봇 폴링 시작
|
||||
# 주의: Flask 리로더(Debug 모드) 사용 시 메인/워커 프로세스 중복 실행 방지
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 1. 리로더의 워커 프로세스인 경우 (WERKZEUG_RUN_MAIN = "true")
|
||||
# 2. 또는 디버그 모드가 꺼진 경우 (Production)
|
||||
if os.environ.get("WERKZEUG_RUN_MAIN") == "true" or not app.config.get("DEBUG"):
|
||||
start_telegram_bot_polling()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 엔트리포인트
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
if __name__ == "__main__":
|
||||
host = os.getenv("FLASK_HOST", "0.0.0.0")
|
||||
port = int(os.getenv("FLASK_PORT", 5000))
|
||||
debug = os.getenv("FLASK_DEBUG", "true").lower() == "true"
|
||||
socketio.run(app, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True)
|
||||
|
||||
# python app.py로 직접 실행 시(use_reloader=False)에는 위 조건문에서 실행되지 않을 수 있으므로
|
||||
# 여기서 명시적으로 실행 (중복 실행 방지 플래그가 있어 안전함)
|
||||
start_telegram_bot_polling()
|
||||
|
||||
socketio.run(app, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True, use_reloader=False)
|
||||
BIN
backend/forms/__pycache__/auth_forms.cpython-314.pyc
Normal file
BIN
backend/forms/__pycache__/auth_forms.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/models/__pycache__/firmware_version.cpython-314.pyc
Normal file
BIN
backend/models/__pycache__/firmware_version.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/models/__pycache__/idrac_server.cpython-314.pyc
Normal file
BIN
backend/models/__pycache__/idrac_server.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/models/__pycache__/telegram_bot.cpython-312.pyc
Normal file
BIN
backend/models/__pycache__/telegram_bot.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/models/__pycache__/telegram_bot.cpython-314.pyc
Normal file
BIN
backend/models/__pycache__/telegram_bot.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/models/__pycache__/user.cpython-314.pyc
Normal file
BIN
backend/models/__pycache__/user.cpython-314.pyc
Normal file
Binary file not shown.
24
backend/models/telegram_bot.py
Normal file
24
backend/models/telegram_bot.py
Normal 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
|
||||
}
|
||||
@@ -38,6 +38,10 @@ class User(db.Model, UserMixin):
|
||||
password: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
is_admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
|
||||
# 가입 승인 관련 필드
|
||||
is_approved: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
approval_token: Mapped[Optional[str]] = mapped_column(String(100), unique=True, nullable=True)
|
||||
|
||||
# ── 유틸 메서드
|
||||
def __repr__(self) -> str: # pragma: no cover
|
||||
@@ -109,14 +113,24 @@ class User(db.Model, UserMixin):
|
||||
q = (email or "").strip().lower()
|
||||
if not q:
|
||||
return None
|
||||
return User.query.filter_by(email=q).first()
|
||||
try:
|
||||
return User.query.filter_by(email=q).first()
|
||||
except Exception as e:
|
||||
logging.error(f"User find_by_email error: {e}")
|
||||
db.session.rollback()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def find_by_username(username: Optional[str]) -> Optional["User"]:
|
||||
q = (username or "").strip()
|
||||
if not q:
|
||||
return None
|
||||
return User.query.filter_by(username=q).first()
|
||||
try:
|
||||
return User.query.filter_by(username=q).first()
|
||||
except Exception as e:
|
||||
logging.error(f"User find_by_username error: {e}")
|
||||
db.session.rollback()
|
||||
return None
|
||||
|
||||
|
||||
# Flask-Login user_loader (SQLAlchemy 2.0 방식)
|
||||
|
||||
@@ -5,11 +5,14 @@ from .auth import register_auth_routes
|
||||
from .admin import register_admin_routes
|
||||
from .main import register_main_routes
|
||||
from .xml import register_xml_routes
|
||||
from .utilities import register_util_routes
|
||||
from .utilities import register_util_routes, utils_bp
|
||||
from .file_view import register_file_view
|
||||
from .jobs import register_jobs_routes
|
||||
from .idrac_routes import register_idrac_routes
|
||||
from .catalog_sync import catalog_bp
|
||||
from .scp_routes import scp_bp
|
||||
from .drm_sync import drm_sync_bp
|
||||
|
||||
|
||||
def register_routes(app: Flask, socketio=None) -> None:
|
||||
"""블루프린트 일괄 등록. socketio는 main 라우트에서만 사용."""
|
||||
@@ -18,8 +21,10 @@ def register_routes(app: Flask, socketio=None) -> None:
|
||||
register_admin_routes(app)
|
||||
register_main_routes(app, socketio)
|
||||
register_xml_routes(app)
|
||||
register_util_routes(app)
|
||||
app.register_blueprint(utils_bp, url_prefix="/utils")
|
||||
register_file_view(app)
|
||||
register_jobs_routes(app)
|
||||
register_idrac_routes(app)
|
||||
app.register_blueprint(catalog_bp)
|
||||
app.register_blueprint(scp_bp)
|
||||
app.register_blueprint(drm_sync_bp)
|
||||
|
||||
Binary file not shown.
BIN
backend/routes/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/routes/__pycache__/admin.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/admin.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/routes/__pycache__/auth.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/auth.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/catalog_sync.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/catalog_sync.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/drm_sync.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/drm_sync.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/file_view.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/file_view.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/home.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/home.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/routes/__pycache__/idrac_routes.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/idrac_routes.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/jobs.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/jobs.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/routes/__pycache__/main.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/main.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/scp_routes.cpython-312.pyc
Normal file
BIN
backend/routes/__pycache__/scp_routes.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/scp_routes.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/scp_routes.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/utilities.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/utilities.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/routes/__pycache__/xml.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/xml.cpython-314.pyc
Normal file
Binary file not shown.
@@ -18,6 +18,11 @@ from flask import (
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from backend.models.user import User, db
|
||||
from backend.models.telegram_bot import TelegramBot
|
||||
try:
|
||||
from telegram import Bot
|
||||
except ImportError:
|
||||
Bot = None
|
||||
|
||||
admin_bp = Blueprint("admin", __name__)
|
||||
|
||||
@@ -124,3 +129,163 @@ def reset_password(user_id: int):
|
||||
flash("비밀번호 변경 중 오류가 발생했습니다.", "danger")
|
||||
|
||||
return redirect(url_for("admin.admin_panel"))
|
||||
|
||||
|
||||
# ▼▼▼ 시스템 설정 (텔레그램 봇 관리) ▼▼▼
|
||||
@admin_bp.route("/admin/settings", methods=["GET"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def settings():
|
||||
# 테이블 생성 확인 (임시)
|
||||
try:
|
||||
bots = TelegramBot.query.all()
|
||||
except Exception:
|
||||
db.create_all()
|
||||
bots = []
|
||||
|
||||
return render_template("admin_settings.html", bots=bots)
|
||||
|
||||
|
||||
@admin_bp.route("/admin/settings/bot/add", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def add_bot():
|
||||
name = request.form.get("name")
|
||||
token = request.form.get("token")
|
||||
chat_id = request.form.get("chat_id")
|
||||
desc = request.form.get("description")
|
||||
|
||||
# 알림 유형 (체크박스 다중 선택)
|
||||
notify_types = request.form.getlist("notify_types")
|
||||
notify_str = ",".join(notify_types) if notify_types else ""
|
||||
|
||||
if not (name and token and chat_id):
|
||||
flash("필수 항목(이름, 토큰, Chat ID)을 입력하세요.", "warning")
|
||||
return redirect(url_for("admin.settings"))
|
||||
|
||||
bot = TelegramBot(
|
||||
name=name,
|
||||
token=token,
|
||||
chat_id=chat_id,
|
||||
description=desc,
|
||||
is_active=True,
|
||||
notification_types=notify_str
|
||||
)
|
||||
db.session.add(bot)
|
||||
db.session.commit()
|
||||
flash("텔레그램 봇이 추가되었습니다.", "success")
|
||||
return redirect(url_for("admin.settings"))
|
||||
|
||||
|
||||
@admin_bp.route("/admin/settings/bot/edit/<int:bot_id>", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def edit_bot(bot_id):
|
||||
bot = db.session.get(TelegramBot, bot_id)
|
||||
if not bot:
|
||||
abort(404)
|
||||
|
||||
bot.name = request.form.get("name")
|
||||
bot.token = request.form.get("token")
|
||||
bot.chat_id = request.form.get("chat_id")
|
||||
bot.description = request.form.get("description")
|
||||
bot.is_active = request.form.get("is_active") == "on"
|
||||
|
||||
# 알림 유형 업데이트
|
||||
notify_types = request.form.getlist("notify_types")
|
||||
bot.notification_types = ",".join(notify_types) if notify_types else ""
|
||||
|
||||
db.session.commit()
|
||||
flash("봇 설정이 수정되었습니다.", "success")
|
||||
return redirect(url_for("admin.settings"))
|
||||
|
||||
|
||||
@admin_bp.route("/admin/settings/bot/delete/<int:bot_id>", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_bot(bot_id):
|
||||
bot = db.session.get(TelegramBot, bot_id)
|
||||
if bot:
|
||||
db.session.delete(bot)
|
||||
db.session.commit()
|
||||
flash("봇이 삭제되었습니다.", "success")
|
||||
return redirect(url_for("admin.settings"))
|
||||
|
||||
|
||||
@admin_bp.route("/admin/settings/bot/test/<int:bot_id>", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def test_bot(bot_id):
|
||||
if not Bot:
|
||||
flash("python-telegram-bot 라이브러리가 설치되지 않았습니다.", "danger")
|
||||
return redirect(url_for("admin.settings"))
|
||||
|
||||
bot_obj = db.session.get(TelegramBot, bot_id)
|
||||
if not bot_obj:
|
||||
abort(404)
|
||||
|
||||
import asyncio
|
||||
|
||||
async def _send():
|
||||
bot = Bot(token=bot_obj.token)
|
||||
await bot.send_message(chat_id=bot_obj.chat_id, text="🔔 <b>테스트 메시지</b>\n이 메시지가 보이면 설정이 정상입니다.", parse_mode="HTML")
|
||||
|
||||
try:
|
||||
asyncio.run(_send())
|
||||
flash(f"'{bot_obj.name}' 봇으로 테스트 메시지를 보냈습니다.", "success")
|
||||
except Exception as e:
|
||||
flash(f"테스트 실패: {e}", "danger")
|
||||
|
||||
return redirect(url_for("admin.settings"))
|
||||
|
||||
|
||||
# ▼▼▼ 시스템 로그 뷰어 ▼▼▼
|
||||
@admin_bp.route("/admin/logs", methods=["GET"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def view_logs():
|
||||
import os
|
||||
import re
|
||||
from collections import deque
|
||||
|
||||
log_folder = current_app.config.get('LOG_FOLDER')
|
||||
log_file = os.path.join(log_folder, 'app.log') if log_folder else None
|
||||
|
||||
# 1. 실제 ANSI 이스케이프 코드 (\x1B로 시작)
|
||||
ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
|
||||
|
||||
# 2. 텍스트로 찍힌 ANSI 코드 패턴 (예: [36m, [0m 등) - Werkzeug가 이스케이프 된 상태로 로그에 남길 경우 대비
|
||||
literal_ansi = re.compile(r'\[[0-9;]+m')
|
||||
|
||||
# 3. 제어 문자 제거
|
||||
control_char_re = re.compile(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]')
|
||||
|
||||
logs = []
|
||||
if log_file and os.path.exists(log_file):
|
||||
try:
|
||||
with open(log_file, 'r', encoding='utf-8', errors='replace') as f:
|
||||
raw_lines = deque(f, 1000)
|
||||
|
||||
for line in raw_lines:
|
||||
# A. 실제 ANSI 코드 제거
|
||||
clean_line = ansi_escape.sub('', line)
|
||||
|
||||
# B. 리터럴 ANSI 패턴 제거 (사용자가 [36m 등을 텍스트로 보고 있다면 이것이 원인)
|
||||
clean_line = literal_ansi.sub('', clean_line)
|
||||
|
||||
# C. 제어 문자 제거
|
||||
clean_line = control_char_re.sub('', clean_line)
|
||||
|
||||
# D. 앞뒤 공백 제거
|
||||
clean_line = clean_line.strip()
|
||||
|
||||
# E. 빈 줄 제외
|
||||
if clean_line:
|
||||
logs.append(clean_line)
|
||||
|
||||
except Exception as e:
|
||||
logs = [f"Error reading log file: {str(e)}"]
|
||||
else:
|
||||
logs = [f"Log file not found at: {log_file}"]
|
||||
|
||||
return render_template("admin_logs.html", logs=logs)
|
||||
|
||||
@@ -3,8 +3,11 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import asyncio
|
||||
import secrets
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse, urljoin
|
||||
from datetime import datetime
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
@@ -23,11 +26,13 @@ from backend.models.user import User, db
|
||||
|
||||
# ── (선택) Telegram: 미설정이면 조용히 패스
|
||||
try:
|
||||
from telegram import Bot
|
||||
from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.constants import ParseMode
|
||||
except Exception: # 라이브러리 미설치/미사용 환경
|
||||
Bot = None
|
||||
ParseMode = None
|
||||
InlineKeyboardButton = None
|
||||
InlineKeyboardMarkup = None
|
||||
|
||||
auth_bp = Blueprint("auth", __name__)
|
||||
|
||||
@@ -42,21 +47,162 @@ def _is_safe_url(target: str) -> bool:
|
||||
return (test.scheme in ("http", "https")) and (ref.netloc == test.netloc)
|
||||
|
||||
|
||||
def _notify(text: str) -> None:
|
||||
"""텔레그램 알림 (설정 없으면 바로 return)."""
|
||||
token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip()
|
||||
chat_id = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip()
|
||||
if not (token and chat_id and Bot and ParseMode):
|
||||
return
|
||||
|
||||
def _send():
|
||||
def _notify(text: str, category: str = "system") -> None:
|
||||
"""
|
||||
텔레그램 알림 전송
|
||||
- DB(TelegramBot)에 등록된 활성 봇들에게 전송
|
||||
- category: 'auth', 'activity', 'system' 등
|
||||
"""
|
||||
try:
|
||||
from backend.models.telegram_bot import TelegramBot
|
||||
|
||||
# 앱 컨텍스트 안에서 실행되므로 바로 DB 접근 가능
|
||||
try:
|
||||
bot = Bot(token=token)
|
||||
bot.send_message(chat_id=chat_id, text=text, parse_mode=ParseMode.HTML)
|
||||
except Exception as e:
|
||||
current_app.logger.warning("Telegram send failed: %s", e)
|
||||
bots = TelegramBot.query.filter_by(is_active=True).all()
|
||||
except Exception:
|
||||
db.create_all()
|
||||
bots = []
|
||||
|
||||
threading.Thread(target=_send, daemon=True).start()
|
||||
# 1. DB에 봇이 없고, Config에 설정이 있는 경우 -> 자동 마이그레이션
|
||||
if not bots:
|
||||
cfg_token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip()
|
||||
cfg_chat = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip()
|
||||
|
||||
if cfg_token and cfg_chat:
|
||||
new_bot = TelegramBot(
|
||||
name="기본 봇 (Config)",
|
||||
token=cfg_token,
|
||||
chat_id=cfg_chat,
|
||||
is_active=True,
|
||||
description="config.py 설정에서 자동 가져옴",
|
||||
notification_types="auth,activity,system"
|
||||
)
|
||||
db.session.add(new_bot)
|
||||
db.session.commit()
|
||||
bots = [new_bot]
|
||||
current_app.logger.info("Telegram: Config settings migrated to DB.")
|
||||
|
||||
if not bots:
|
||||
return
|
||||
|
||||
# 카테고리 필터링
|
||||
target_bots = []
|
||||
for b in bots:
|
||||
allowed = (b.notification_types or "auth,activity,system").split(",")
|
||||
if category in allowed:
|
||||
target_bots.append(b)
|
||||
|
||||
if not target_bots:
|
||||
return
|
||||
|
||||
if not (Bot and ParseMode):
|
||||
current_app.logger.warning("Telegram: Library not installed.")
|
||||
return
|
||||
|
||||
app = current_app._get_current_object()
|
||||
|
||||
async def _send_to_bot(bot_obj, msg):
|
||||
try:
|
||||
t_bot = Bot(token=bot_obj.token)
|
||||
await t_bot.send_message(chat_id=bot_obj.chat_id, text=msg, parse_mode=ParseMode.HTML)
|
||||
except Exception as e:
|
||||
app.logger.error(f"Telegram fail ({bot_obj.name}): {e}")
|
||||
|
||||
async def _send_all():
|
||||
tasks = [_send_to_bot(b, text) for b in target_bots]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
def _run_thread():
|
||||
try:
|
||||
asyncio.run(_send_all())
|
||||
except Exception as e:
|
||||
app.logger.error(f"Telegram async loop error: {e}")
|
||||
|
||||
threading.Thread(target=_run_thread, daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Telegram notification error: {e}")
|
||||
|
||||
|
||||
def _notify_with_buttons(text: str, buttons: list, category: str = "auth") -> None:
|
||||
"""
|
||||
텔레그램 알림 전송 (인라인 버튼 포함)
|
||||
- buttons: [(text, callback_data), ...] 형식의 리스트
|
||||
"""
|
||||
try:
|
||||
from backend.models.telegram_bot import TelegramBot
|
||||
|
||||
try:
|
||||
bots = TelegramBot.query.filter_by(is_active=True).all()
|
||||
except Exception:
|
||||
db.create_all()
|
||||
bots = []
|
||||
|
||||
if not bots:
|
||||
cfg_token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip()
|
||||
cfg_chat = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip()
|
||||
|
||||
if cfg_token and cfg_chat:
|
||||
new_bot = TelegramBot(
|
||||
name="기본 봇 (Config)",
|
||||
token=cfg_token,
|
||||
chat_id=cfg_chat,
|
||||
is_active=True,
|
||||
description="config.py 설정에서 자동 가져옴",
|
||||
notification_types="auth,activity,system"
|
||||
)
|
||||
db.session.add(new_bot)
|
||||
db.session.commit()
|
||||
bots = [new_bot]
|
||||
|
||||
if not bots:
|
||||
return
|
||||
|
||||
target_bots = []
|
||||
for b in bots:
|
||||
allowed = (b.notification_types or "auth,activity,system").split(",")
|
||||
if category in allowed:
|
||||
target_bots.append(b)
|
||||
|
||||
if not target_bots:
|
||||
return
|
||||
|
||||
if not (Bot and ParseMode and InlineKeyboardButton and InlineKeyboardMarkup):
|
||||
current_app.logger.warning("Telegram: Library not installed.")
|
||||
return
|
||||
|
||||
# 인라인 키보드 생성
|
||||
keyboard = [[InlineKeyboardButton(btn[0], callback_data=btn[1]) for btn in buttons]]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
|
||||
app = current_app._get_current_object()
|
||||
|
||||
async def _send_to_bot(bot_obj, msg, markup):
|
||||
try:
|
||||
t_bot = Bot(token=bot_obj.token)
|
||||
await t_bot.send_message(
|
||||
chat_id=bot_obj.chat_id,
|
||||
text=msg,
|
||||
parse_mode=ParseMode.HTML,
|
||||
reply_markup=markup
|
||||
)
|
||||
except Exception as e:
|
||||
app.logger.error(f"Telegram fail ({bot_obj.name}): {e}")
|
||||
|
||||
async def _send_all():
|
||||
tasks = [_send_to_bot(b, text, reply_markup) for b in target_bots]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
def _run_thread():
|
||||
try:
|
||||
asyncio.run(_send_all())
|
||||
except Exception as e:
|
||||
app.logger.error(f"Telegram async loop error: {e}")
|
||||
|
||||
threading.Thread(target=_run_thread, daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Telegram notification with buttons error: {e}")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
@@ -67,12 +213,28 @@ def register_auth_routes(app):
|
||||
app.register_blueprint(auth_bp)
|
||||
|
||||
@app.before_request
|
||||
def _touch_session():
|
||||
# 요청마다 세션 갱신(만료 슬라이딩) + 로그아웃 플래그 정리
|
||||
def _global_hooks():
|
||||
# 1. 세션 갱신 (요청마다 세션 타임아웃 연장)
|
||||
session.modified = True
|
||||
if current_user.is_authenticated and session.get("just_logged_out"):
|
||||
session.pop("just_logged_out", None)
|
||||
flash("세션이 만료되어 자동 로그아웃 되었습니다.", "info")
|
||||
|
||||
# 2. 활동 알림 (로그인된 사용자)
|
||||
if current_user.is_authenticated:
|
||||
# 정적 리소스 및 불필요한 경로 제외
|
||||
if request.endpoint == 'static':
|
||||
return
|
||||
|
||||
# 제외할 확장자/경로 (필요 시 추가)
|
||||
ignored_exts = ('.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2')
|
||||
if request.path.lower().endswith(ignored_exts):
|
||||
return
|
||||
|
||||
# 활동 내용 구성
|
||||
msg = (
|
||||
f"👣 <b>활동 감지</b>\n"
|
||||
f"👤 <code>{current_user.username}</code>\n"
|
||||
f"📍 <code>{request.method} {request.path}</code>"
|
||||
)
|
||||
_notify(msg, category="activity")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
@@ -97,21 +259,54 @@ def register():
|
||||
current_app.logger.info("REGISTER: dup username %s", form.username.data)
|
||||
return render_template("register.html", form=form)
|
||||
|
||||
user = User(username=form.username.data, email=form.email.data, is_active=False)
|
||||
user.set_password(form.password.data) # passlib: 기본 Argon2id
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
_notify(
|
||||
f"🆕 <b>신규 가입 요청</b>\n"
|
||||
f"📛 사용자: <code>{user.username}</code>\n"
|
||||
f"📧 이메일: <code>{user.email}</code>"
|
||||
# 승인 토큰 생성
|
||||
approval_token = secrets.token_urlsafe(32)
|
||||
|
||||
user = User(
|
||||
username=form.username.data,
|
||||
email=form.email.data,
|
||||
is_active=False,
|
||||
is_approved=False,
|
||||
approval_token=approval_token
|
||||
)
|
||||
current_app.logger.info("REGISTER: created id=%s email=%s", user.id, user.email)
|
||||
user.set_password(form.password.data)
|
||||
|
||||
try:
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("REGISTER: DB commit failed: %s", e)
|
||||
flash("회원가입 처리 중 오류가 발생했습니다. (DB Error)", "danger")
|
||||
return render_template("register.html", form=form)
|
||||
|
||||
# 텔레그램 알림 (인라인 버튼 포함)
|
||||
message = (
|
||||
f"🆕 <b>신규 가입 요청</b>\n\n"
|
||||
f"<EFBFBD> 사용자: <code>{user.username}</code>\n"
|
||||
f"📧 이메일: <code>{user.email}</code>\n"
|
||||
f"🕐 신청시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
)
|
||||
|
||||
buttons = [
|
||||
("✅ 승인", f"approve_{approval_token}"),
|
||||
("❌ 거부", f"reject_{approval_token}")
|
||||
]
|
||||
|
||||
_notify_with_buttons(message, buttons, category="auth")
|
||||
|
||||
current_app.logger.info("REGISTER: created id=%s email=%s token=%s", user.id, user.email, approval_token[:10])
|
||||
flash("회원가입이 완료되었습니다. 관리자의 승인을 기다려주세요.", "success")
|
||||
return redirect(url_for("auth.login"))
|
||||
else:
|
||||
if request.method == "POST":
|
||||
# 폼 검증 실패 에러를 Flash 메시지로 출력
|
||||
for field_name, errors in form.errors.items():
|
||||
for error in errors:
|
||||
# 필드 객체 가져오기 (라벨 텍스트 확인용)
|
||||
field = getattr(form, field_name, None)
|
||||
label = field.label.text if field else field_name
|
||||
flash(f"{label}: {error}", "warning")
|
||||
current_app.logger.info("REGISTER: form errors=%s", form.errors)
|
||||
|
||||
return render_template("register.html", form=form)
|
||||
@@ -136,24 +331,28 @@ def login():
|
||||
current_app.logger.info("LOGIN: user not found")
|
||||
return render_template("login.html", form=form)
|
||||
|
||||
pass_ok = user.check_password(form.password.data) # passlib verify(+자동 재해시)
|
||||
pass_ok = user.check_password(form.password.data)
|
||||
current_app.logger.info(
|
||||
"LOGIN: found id=%s active=%s pass_ok=%s",
|
||||
user.id, user.is_active, pass_ok
|
||||
"LOGIN: found id=%s active=%s approved=%s pass_ok=%s",
|
||||
user.id, user.is_active, user.is_approved, pass_ok
|
||||
)
|
||||
|
||||
if not pass_ok:
|
||||
flash("이메일 또는 비밀번호가 올바르지 않습니다.", "danger")
|
||||
return render_template("login.html", form=form)
|
||||
|
||||
if not user.is_approved:
|
||||
flash("계정이 아직 승인되지 않았습니다. 관리자의 승인을 기다려주세요.", "warning")
|
||||
return render_template("login.html", form=form)
|
||||
|
||||
if not user.is_active:
|
||||
flash("계정이 아직 승인되지 않았습니다.", "warning")
|
||||
flash("계정이 비활성화되었습니다. 관리자에게 문의하세요.", "warning")
|
||||
return render_template("login.html", form=form)
|
||||
|
||||
# 성공
|
||||
login_user(user, remember=form.remember.data)
|
||||
session.permanent = True
|
||||
_notify(f"🔐 <b>로그인 성공</b>\n👤 <code>{user.username}</code>")
|
||||
_notify(f"🔐 <b>로그인 성공</b>\n👤 <code>{user.username}</code>", category="auth")
|
||||
current_app.logger.info("LOGIN: SUCCESS → redirect")
|
||||
|
||||
nxt = request.args.get("next")
|
||||
@@ -175,6 +374,7 @@ def login():
|
||||
def logout():
|
||||
if current_user.is_authenticated:
|
||||
current_app.logger.info("LOGOUT: user=%s", current_user.username)
|
||||
_notify(f"🚪 <b>로그아웃</b>\n👤 <code>{current_user.username}</code>", category="auth")
|
||||
logout_user()
|
||||
session["just_logged_out"] = True
|
||||
flash("정상적으로 로그아웃 되었습니다.", "success")
|
||||
return redirect(url_for("auth.login"))
|
||||
61
backend/routes/drm_sync.py
Normal file
61
backend/routes/drm_sync.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
DRM 카탈로그 동기화 라우트
|
||||
backend/routes/drm_sync.py
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
from backend.services.drm_catalog_sync import sync_from_drm, check_drm_repository
|
||||
|
||||
drm_sync_bp = Blueprint('drm_sync', __name__, url_prefix='/drm')
|
||||
|
||||
|
||||
@drm_sync_bp.route('/check', methods=['POST'])
|
||||
def check_repository():
|
||||
"""DRM 리포지토리 상태 확인"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
repository_path = data.get('repository_path')
|
||||
|
||||
if not repository_path:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'repository_path가 필요합니다'
|
||||
}), 400
|
||||
|
||||
info = check_drm_repository(repository_path)
|
||||
|
||||
return jsonify({
|
||||
'success': info.get('exists', False),
|
||||
'info': info
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@drm_sync_bp.route('/sync', methods=['POST'])
|
||||
def sync_repository():
|
||||
"""DRM 리포지토리에서 펌웨어 동기화"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
repository_path = data.get('repository_path')
|
||||
model = data.get('model', 'PowerEdge R750')
|
||||
|
||||
if not repository_path:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'repository_path가 필요합니다'
|
||||
}), 400
|
||||
|
||||
result = sync_from_drm(repository_path, model)
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
}), 500
|
||||
@@ -835,26 +835,37 @@ def delete_firmware_version(version_id):
|
||||
"""펌웨어 버전 정보 삭제"""
|
||||
try:
|
||||
version = FirmwareVersion.query.get(version_id)
|
||||
|
||||
if not version:
|
||||
from flask import current_app
|
||||
current_app.logger.warning(f"Firmware version not found: {version_id}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '버전 정보를 찾을 수 없습니다'
|
||||
})
|
||||
'message': '펌웨어 버전을 찾을 수 없습니다'
|
||||
}), 404
|
||||
|
||||
version.is_active = False
|
||||
component_name = version.component_name
|
||||
version_number = version.latest_version
|
||||
|
||||
db.session.delete(version)
|
||||
db.session.commit()
|
||||
|
||||
from flask import current_app
|
||||
current_app.logger.info(f"Firmware version deleted: {component_name} v{version_number} (ID: {version_id})")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{version.component_name} 버전 정보 삭제 완료'
|
||||
'message': f'{component_name} 버전 정보가 삭제되었습니다'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
from flask import current_app
|
||||
current_app.logger.error(f"Error deleting firmware version {version_id}: {str(e)}")
|
||||
db.session.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
})
|
||||
'message': f'삭제 실패: {str(e)}'
|
||||
}), 500
|
||||
|
||||
@idrac_bp.route('/api/servers/<int:server_id>/firmware/compare', methods=['GET'])
|
||||
def compare_server_firmware(server_id):
|
||||
@@ -962,6 +973,13 @@ def compare_multi_servers_firmware():
|
||||
for server_id in server_ids:
|
||||
server = IdracServer.query.get(server_id)
|
||||
if not server:
|
||||
results.append({
|
||||
'server_id': server_id,
|
||||
'server_name': 'Unknown',
|
||||
'server_ip': '',
|
||||
'success': False,
|
||||
'message': '서버를 찾을 수 없습니다'
|
||||
})
|
||||
continue
|
||||
|
||||
try:
|
||||
@@ -971,44 +989,47 @@ def compare_multi_servers_firmware():
|
||||
|
||||
latest_versions = FirmwareVersion.query.filter_by(is_active=True).all()
|
||||
|
||||
outdated_count = 0
|
||||
outdated_items = []
|
||||
comparisons = []
|
||||
|
||||
for current_fw in current_inventory:
|
||||
component_name = current_fw['Name']
|
||||
current_version = current_fw['Version']
|
||||
|
||||
# 최신 버전 찾기
|
||||
latest = None
|
||||
for lv in latest_versions:
|
||||
if lv.component_name.lower() in component_name.lower():
|
||||
comparison = FirmwareComparisonResult(
|
||||
component_name=component_name,
|
||||
current_version=current_version,
|
||||
latest_version=lv.latest_version
|
||||
)
|
||||
|
||||
if comparison.status == 'outdated':
|
||||
outdated_count += 1
|
||||
outdated_items.append({
|
||||
'component': component_name,
|
||||
'current': current_version,
|
||||
'latest': lv.latest_version
|
||||
})
|
||||
break
|
||||
# 서버 모델 확인
|
||||
if not lv.server_model or lv.server_model == server.model:
|
||||
latest = lv
|
||||
break
|
||||
|
||||
# 비교 결과 생성
|
||||
comparison = FirmwareComparisonResult(
|
||||
component_name=component_name,
|
||||
current_version=current_version,
|
||||
latest_version=latest.latest_version if latest else None
|
||||
)
|
||||
|
||||
comparisons.append(comparison.to_dict())
|
||||
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'outdated_count': outdated_count,
|
||||
'outdated_items': outdated_items,
|
||||
'status': 'needs_update' if outdated_count > 0 else 'up_to_date'
|
||||
'server_ip': server.ip_address,
|
||||
'success': True,
|
||||
'comparisons': comparisons,
|
||||
'message': f'{len(comparisons)}개 컴포넌트 비교 완료'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error comparing firmware for server {server.id}: {str(e)}")
|
||||
results.append({
|
||||
'server_id': server.id,
|
||||
'server_name': server.name,
|
||||
'error': str(e)
|
||||
'server_ip': server.ip_address,
|
||||
'success': False,
|
||||
'message': f'비교 실패: {str(e)}'
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
@@ -1017,6 +1038,7 @@ def compare_multi_servers_firmware():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error in compare_multi_servers_firmware: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'오류: {str(e)}'
|
||||
|
||||
@@ -47,11 +47,43 @@ def index():
|
||||
info_dir = Path(Config.IDRAC_INFO_FOLDER)
|
||||
backup_dir = Path(Config.BACKUP_FOLDER)
|
||||
|
||||
scripts = [f.name for f in script_dir.glob("*") if f.is_file() and f.name != ".env"]
|
||||
scripts = natsorted(scripts)
|
||||
# 1. 스크립트 목록 조회 및 카테고리 분류
|
||||
all_scripts = [f.name for f in script_dir.glob("*") if f.is_file() and f.name != ".env"]
|
||||
all_scripts = natsorted(all_scripts)
|
||||
|
||||
grouped_scripts = {}
|
||||
for script in all_scripts:
|
||||
upper = script.upper()
|
||||
category = "General"
|
||||
if upper.startswith("GPU"):
|
||||
category = "GPU"
|
||||
elif upper.startswith("LOM"):
|
||||
category = "LOM"
|
||||
elif upper.startswith("TYPE") or upper.startswith("XE"):
|
||||
category = "Server Models"
|
||||
elif "MAC" in upper:
|
||||
category = "MAC Info"
|
||||
elif "GUID" in upper:
|
||||
category = "GUID Info"
|
||||
elif "SET_" in upper or "CONFIG" in upper:
|
||||
category = "Configuration"
|
||||
|
||||
if category not in grouped_scripts:
|
||||
grouped_scripts[category] = []
|
||||
grouped_scripts[category].append(script)
|
||||
|
||||
# 카테고리 정렬 (General은 마지막에)
|
||||
sorted_categories = sorted(grouped_scripts.keys())
|
||||
if "General" in sorted_categories:
|
||||
sorted_categories.remove("General")
|
||||
sorted_categories.append("General")
|
||||
|
||||
grouped_scripts_sorted = {k: grouped_scripts[k] for k in sorted_categories}
|
||||
|
||||
# 2. XML 파일 목록
|
||||
xml_files = [f.name for f in xml_dir.glob("*.xml")]
|
||||
|
||||
# 페이지네이션
|
||||
# 3. 페이지네이션 및 파일 목록
|
||||
page = int(request.args.get("page", 1))
|
||||
info_files = [f.name for f in info_dir.glob("*") if f.is_file()]
|
||||
info_files = natsorted(info_files)
|
||||
@@ -62,11 +94,10 @@ def index():
|
||||
|
||||
total_pages = (len(info_files) + Config.FILES_PER_PAGE - 1) // Config.FILES_PER_PAGE
|
||||
|
||||
# ✅ 추가: 10개 단위로 표시될 페이지 범위 계산
|
||||
start_page = ((page - 1) // 10) * 10 + 1
|
||||
end_page = min(start_page + 9, total_pages)
|
||||
|
||||
# 백업 폴더 목록 (디렉터리만)
|
||||
# 4. 백업 폴더 목록
|
||||
backup_dirs = [d for d in backup_dir.iterdir() if d.is_dir()]
|
||||
backup_dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
|
||||
@@ -91,7 +122,8 @@ def index():
|
||||
backup_files=backup_files,
|
||||
total_backup_pages=total_backup_pages,
|
||||
backup_page=backup_page,
|
||||
scripts=scripts,
|
||||
scripts=all_scripts, # 기존 리스트 호환
|
||||
grouped_scripts=grouped_scripts_sorted, # 카테고리별 분류
|
||||
xml_files=xml_files,
|
||||
)
|
||||
|
||||
@@ -175,13 +207,13 @@ def delete_file(filename: str):
|
||||
if file_path.exists():
|
||||
try:
|
||||
file_path.unlink()
|
||||
flash(f"{filename} 삭제됨.")
|
||||
flash(f"'{filename}' 파일이 삭제되었습니다.", "success")
|
||||
logging.info(f"파일 삭제됨: {filename}")
|
||||
except Exception as e:
|
||||
logging.error(f"파일 삭제 오류: {e}")
|
||||
flash("파일 삭제 중 오류가 발생했습니다.", "danger")
|
||||
else:
|
||||
flash("파일이 존재하지 않습니다.")
|
||||
flash("파일이 존재하지 않습니다.", "warning")
|
||||
return redirect(url_for("main.index"))
|
||||
|
||||
|
||||
|
||||
166
backend/routes/scp_routes.py
Normal file
166
backend/routes/scp_routes.py
Normal file
@@ -0,0 +1,166 @@
|
||||
from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for
|
||||
from flask_login import login_required, current_user
|
||||
import logging
|
||||
import difflib
|
||||
from pathlib import Path
|
||||
from config import Config
|
||||
from backend.services.redfish_client import RedfishClient
|
||||
from backend.routes.xml import sanitize_preserve_unicode
|
||||
|
||||
scp_bp = Blueprint("scp", __name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@scp_bp.route("/scp/diff", methods=["GET"])
|
||||
@login_required
|
||||
def diff_scp():
|
||||
"""
|
||||
두 XML 파일의 차이점을 비교하여 보여줍니다.
|
||||
"""
|
||||
file1_name = request.args.get("file1")
|
||||
file2_name = request.args.get("file2")
|
||||
|
||||
if not file1_name or not file2_name:
|
||||
flash("비교할 두 파일을 선택해주세요.", "warning")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
try:
|
||||
file1_path = Path(Config.XML_FOLDER) / sanitize_preserve_unicode(file1_name)
|
||||
file2_path = Path(Config.XML_FOLDER) / sanitize_preserve_unicode(file2_name)
|
||||
|
||||
if not file1_path.exists() or not file2_path.exists():
|
||||
flash("파일을 찾을 수 없습니다.", "danger")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
# 파일 내용 읽기 (LF로 통일)
|
||||
# 파일 내용 읽기 (LF로 통일)
|
||||
# Monaco Editor에 원본 텍스트를 그대로 전달하기 위해 splitlines() 제거
|
||||
# 파일 내용 읽기 (LF로 통일)
|
||||
logger.info(f"Reading file1: {file1_path}")
|
||||
content1 = file1_path.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
|
||||
|
||||
logger.info(f"Reading file2: {file2_path}")
|
||||
content2 = file2_path.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
|
||||
|
||||
logger.info(f"Content1 length: {len(content1)}, Content2 length: {len(content2)}")
|
||||
|
||||
return render_template("scp_diff.html",
|
||||
file1=file1_name,
|
||||
file2=file2_name,
|
||||
content1=content1,
|
||||
content2=content2)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Diff error: {e}")
|
||||
flash(f"비교 중 오류가 발생했습니다: {str(e)}", "danger")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
@scp_bp.route("/scp/content/<path:filename>")
|
||||
@login_required
|
||||
def get_scp_content(filename):
|
||||
"""
|
||||
XML 파일 내용을 반환하는 API (Monaco Editor용)
|
||||
"""
|
||||
try:
|
||||
safe_name = sanitize_preserve_unicode(filename)
|
||||
path = Path(Config.XML_FOLDER) / safe_name
|
||||
|
||||
if not path.exists():
|
||||
return "File not found", 404
|
||||
|
||||
# 텍스트로 읽어서 반환
|
||||
content = path.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
|
||||
return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
||||
except Exception as e:
|
||||
logger.error(f"Content read error: {e}")
|
||||
return str(e), 500
|
||||
|
||||
@scp_bp.route("/scp/export", methods=["POST"])
|
||||
@login_required
|
||||
def export_scp():
|
||||
"""
|
||||
iDRAC에서 설정을 내보내기 (Export)
|
||||
네트워크 공유 설정이 필요합니다.
|
||||
"""
|
||||
data = request.form
|
||||
target_ip = data.get("target_ip")
|
||||
username = data.get("username")
|
||||
password = data.get("password")
|
||||
|
||||
# Share Parameters
|
||||
share_ip = data.get("share_ip")
|
||||
share_name = data.get("share_name")
|
||||
share_user = data.get("share_user")
|
||||
share_pwd = data.get("share_pwd")
|
||||
filename = data.get("filename")
|
||||
|
||||
if not all([target_ip, username, password, share_ip, share_name, filename]):
|
||||
flash("필수 정보가 누락되었습니다.", "warning")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
share_params = {
|
||||
"IPAddress": share_ip,
|
||||
"ShareName": share_name,
|
||||
"FileName": filename,
|
||||
"ShareType": "CIFS", # 기본값 CIFS
|
||||
"UserName": share_user,
|
||||
"Password": share_pwd
|
||||
}
|
||||
|
||||
try:
|
||||
with RedfishClient(target_ip, username, password) as client:
|
||||
job_id = client.export_system_configuration(share_params)
|
||||
if job_id:
|
||||
flash(f"내보내기 작업이 시작되었습니다. Job ID: {job_id}", "success")
|
||||
else:
|
||||
flash("작업을 시작했으나 Job ID를 받지 못했습니다.", "warning")
|
||||
except Exception as e:
|
||||
logger.error(f"Export failed: {e}")
|
||||
flash(f"내보내기 실패: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
@scp_bp.route("/scp/import", methods=["POST"])
|
||||
@login_required
|
||||
def import_scp():
|
||||
"""
|
||||
iDRAC로 설정 가져오기 (Import/Deploy)
|
||||
"""
|
||||
data = request.form
|
||||
target_ip = data.get("target_ip")
|
||||
username = data.get("username")
|
||||
password = data.get("password")
|
||||
|
||||
# Share Parameters
|
||||
share_ip = data.get("share_ip")
|
||||
share_name = data.get("share_name")
|
||||
share_user = data.get("share_user")
|
||||
share_pwd = data.get("share_pwd")
|
||||
filename = data.get("filename")
|
||||
|
||||
import_mode = data.get("import_mode", "Replace")
|
||||
|
||||
if not all([target_ip, username, password, share_ip, share_name, filename]):
|
||||
flash("필수 정보가 누락되었습니다.", "warning")
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
|
||||
share_params = {
|
||||
"IPAddress": share_ip,
|
||||
"ShareName": share_name,
|
||||
"FileName": filename,
|
||||
"ShareType": "CIFS",
|
||||
"UserName": share_user,
|
||||
"Password": share_pwd
|
||||
}
|
||||
|
||||
try:
|
||||
with RedfishClient(target_ip, username, password) as client:
|
||||
job_id = client.import_system_configuration(share_params, import_mode=import_mode)
|
||||
if job_id:
|
||||
flash(f"설정 적용(Import) 작업이 시작되었습니다. Job ID: {job_id}", "success")
|
||||
else:
|
||||
flash("작업을 시작했으나 Job ID를 받지 못했습니다.", "warning")
|
||||
except Exception as e:
|
||||
logger.error(f"Import failed: {e}")
|
||||
flash(f"설정 적용 실패: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for("xml.xml_management"))
|
||||
@@ -290,13 +290,83 @@ def update_gpu_list():
|
||||
|
||||
return redirect(url_for("main.index"))
|
||||
|
||||
@utils_bp.route("/download_excel")
|
||||
@login_required
|
||||
def download_excel():
|
||||
path = Path(Config.SERVER_LIST_FOLDER) / "mac_info.xlsx"
|
||||
if not path.is_file():
|
||||
flash("엑셀 파일을 찾을 수 없습니다.", "danger")
|
||||
return redirect(url_for("main.index"))
|
||||
|
||||
logging.info(f"엑셀 파일 다운로드: {path}")
|
||||
return send_file(str(path), as_attachment=True, download_name="mac_info.xlsx")
|
||||
return send_file(str(path), as_attachment=True, download_name="mac_info.xlsx")
|
||||
|
||||
|
||||
@utils_bp.route("/scan_network", methods=["POST"])
|
||||
@login_required
|
||||
def scan_network():
|
||||
"""
|
||||
지정된 IP 범위(Start ~ End)에 대해 Ping 테스트를 수행하고
|
||||
응답이 있는 IP 목록을 반환합니다.
|
||||
"""
|
||||
try:
|
||||
import ipaddress
|
||||
import platform
|
||||
import concurrent.futures
|
||||
|
||||
data = request.get_json(force=True, silent=True) or {}
|
||||
start_ip_str = data.get('start_ip')
|
||||
end_ip_str = data.get('end_ip')
|
||||
|
||||
if not start_ip_str or not end_ip_str:
|
||||
return jsonify({"success": False, "error": "시작 IP와 종료 IP를 모두 입력해주세요."}), 400
|
||||
|
||||
try:
|
||||
start_ip = ipaddress.IPv4Address(start_ip_str)
|
||||
end_ip = ipaddress.IPv4Address(end_ip_str)
|
||||
|
||||
if start_ip > end_ip:
|
||||
return jsonify({"success": False, "error": "시작 IP가 종료 IP보다 큽니다."}), 400
|
||||
|
||||
# IP 개수 제한 (너무 많은 스캔 방지, 예: C클래스 2개 분량 512개)
|
||||
if int(end_ip) - int(start_ip) > 512:
|
||||
return jsonify({"success": False, "error": "스캔 범위가 너무 넓습니다. (최대 512개)"}), 400
|
||||
|
||||
except ValueError:
|
||||
return jsonify({"success": False, "error": "유효하지 않은 IP 주소 형식입니다."}), 400
|
||||
|
||||
# Ping 함수 정의
|
||||
def ping_ip(ip_obj):
|
||||
ip = str(ip_obj)
|
||||
param = '-n' if platform.system().lower() == 'windows' else '-c'
|
||||
timeout_param = '-w' if platform.system().lower() == 'windows' else '-W'
|
||||
# Windows: -w 200 (ms), Linux: -W 1 (s)
|
||||
timeout_val = '200' if platform.system().lower() == 'windows' else '1'
|
||||
|
||||
command = ['ping', param, '1', timeout_param, timeout_val, ip]
|
||||
|
||||
try:
|
||||
# shell=False로 보안 강화, stdout/stderr 무시
|
||||
res = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return ip if res.returncode == 0 else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
active_ips = []
|
||||
|
||||
# IP 리스트 생성
|
||||
target_ips = []
|
||||
temp_ip = start_ip
|
||||
while temp_ip <= end_ip:
|
||||
target_ips.append(temp_ip)
|
||||
temp_ip += 1
|
||||
|
||||
# 병렬 처리 (최대 50 쓰레드)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
|
||||
results = executor.map(ping_ip, target_ips)
|
||||
|
||||
# 결과 수집 (None 제외)
|
||||
active_ips = [ip for ip in results if ip is not None]
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"active_ips": active_ips,
|
||||
"count": len(active_ips),
|
||||
"message": f"스캔 완료: {len(active_ips)}개의 활성 IP 발견"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Scan network fatal error: {e}")
|
||||
return jsonify({"success": False, "error": f"서버 내부 오류: {str(e)}"}), 500
|
||||
BIN
backend/services/__pycache__/dell_catalog_sync.cpython-314.pyc
Normal file
BIN
backend/services/__pycache__/dell_catalog_sync.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/services/__pycache__/drm_catalog_sync.cpython-314.pyc
Normal file
BIN
backend/services/__pycache__/drm_catalog_sync.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/services/__pycache__/idrac_jobs.cpython-314.pyc
Normal file
BIN
backend/services/__pycache__/idrac_jobs.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/services/__pycache__/ip_processor.cpython-314.pyc
Normal file
BIN
backend/services/__pycache__/ip_processor.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/services/__pycache__/logger.cpython-314.pyc
Normal file
BIN
backend/services/__pycache__/logger.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/services/__pycache__/redfish_client.cpython-314.pyc
Normal file
BIN
backend/services/__pycache__/redfish_client.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/services/__pycache__/watchdog_handler.cpython-314.pyc
Normal file
BIN
backend/services/__pycache__/watchdog_handler.cpython-314.pyc
Normal file
Binary file not shown.
353
backend/services/dell_catalog_alternatives.py
Normal file
353
backend/services/dell_catalog_alternatives.py
Normal file
@@ -0,0 +1,353 @@
|
||||
"""
|
||||
Dell 펌웨어 카탈로그 대안 방법들
|
||||
backend/services/dell_catalog_alternatives.py
|
||||
|
||||
Dell의 공식 Catalog.xml이 차단된 경우 사용할 수 있는 대안들
|
||||
"""
|
||||
|
||||
import requests
|
||||
from typing import List, Dict, Optional
|
||||
from backend.models.firmware_version import FirmwareVersion, db
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DellFirmwareCatalogAlternatives:
|
||||
"""Dell 펌웨어 정보를 가져오는 대안 방법들"""
|
||||
|
||||
def __init__(self):
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
})
|
||||
|
||||
# ========================================
|
||||
# 방법 1: Dell Support API (공식 API)
|
||||
# ========================================
|
||||
|
||||
def fetch_from_dell_support_api(self, model: str = "PowerEdge R750") -> List[Dict]:
|
||||
"""
|
||||
Dell Support API를 통한 펌웨어 정보 조회
|
||||
|
||||
Dell의 공식 Support API 엔드포인트:
|
||||
https://www.dell.com/support/home/api/
|
||||
|
||||
참고: API 키가 필요할 수 있음
|
||||
"""
|
||||
try:
|
||||
# Dell Support API 엔드포인트 (예시)
|
||||
# 실제 API는 Dell 개발자 포털에서 확인 필요
|
||||
url = f"https://www.dell.com/support/home/api/products/{model}/drivers"
|
||||
|
||||
response = self.session.get(url, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return self._parse_support_api_response(data, model)
|
||||
else:
|
||||
print(f"Dell Support API 오류: {response.status_code}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f"Dell Support API 조회 실패: {str(e)}")
|
||||
return []
|
||||
|
||||
# ========================================
|
||||
# 방법 2: Dell TechDirect (파트너 전용)
|
||||
# ========================================
|
||||
|
||||
def fetch_from_techdirect(self, model: str = "PowerEdge R750") -> List[Dict]:
|
||||
"""
|
||||
Dell TechDirect API를 통한 펌웨어 정보 조회
|
||||
|
||||
TechDirect는 Dell 파트너용 플랫폼
|
||||
API 키 필요: https://techdirect.dell.com/
|
||||
"""
|
||||
# TechDirect API 구현
|
||||
# 실제 사용 시 API 키 필요
|
||||
pass
|
||||
|
||||
# ========================================
|
||||
# 방법 3: 로컬 카탈로그 파일 사용
|
||||
# ========================================
|
||||
|
||||
def load_from_local_catalog(self, catalog_path: str) -> List[Dict]:
|
||||
"""
|
||||
로컬에 저장된 Catalog.xml 파일 사용
|
||||
|
||||
사용법:
|
||||
1. Dell 공식 사이트에서 수동으로 Catalog.xml 다운로드
|
||||
2. 로컬에 저장
|
||||
3. 이 함수로 파싱
|
||||
|
||||
Args:
|
||||
catalog_path: 로컬 Catalog.xml 파일 경로
|
||||
"""
|
||||
try:
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
with open(catalog_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
root = ET.fromstring(content)
|
||||
return self._parse_catalog_xml(root)
|
||||
|
||||
except Exception as e:
|
||||
print(f"로컬 카탈로그 파일 로드 실패: {str(e)}")
|
||||
return []
|
||||
|
||||
# ========================================
|
||||
# 방법 4: iDRAC에서 직접 조회
|
||||
# ========================================
|
||||
|
||||
def fetch_from_idrac(self, idrac_ip: str, username: str, password: str) -> List[Dict]:
|
||||
"""
|
||||
iDRAC Redfish API를 통해 사용 가능한 업데이트 조회
|
||||
|
||||
iDRAC는 Dell Update Service를 통해 사용 가능한 업데이트를 확인할 수 있음
|
||||
|
||||
장점:
|
||||
- 실제 서버에 맞는 정확한 펌웨어 정보
|
||||
- 외부 카탈로그 불필요
|
||||
|
||||
단점:
|
||||
- 각 서버마다 조회 필요
|
||||
- 네트워크 연결 필요
|
||||
"""
|
||||
try:
|
||||
from backend.services.idrac_redfish_client import DellRedfishClient
|
||||
|
||||
client = DellRedfishClient(idrac_ip, username, password)
|
||||
|
||||
# UpdateService에서 사용 가능한 업데이트 조회
|
||||
url = f"https://{idrac_ip}/redfish/v1/UpdateService"
|
||||
response = client.session.get(url, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
# UpdateService의 Actions 확인
|
||||
# SimpleUpdate 또는 CheckForUpdate 액션 사용
|
||||
return self._parse_idrac_updates(data)
|
||||
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f"iDRAC 업데이트 조회 실패: {str(e)}")
|
||||
return []
|
||||
|
||||
# ========================================
|
||||
# 방법 5: 수동 입력 (가장 간단하고 확실)
|
||||
# ========================================
|
||||
|
||||
def create_manual_catalog(self) -> List[Dict]:
|
||||
"""
|
||||
수동으로 작성한 펌웨어 버전 정보
|
||||
|
||||
Dell 공식 사이트에서 확인한 최신 버전을 수동으로 입력
|
||||
https://www.dell.com/support/
|
||||
|
||||
장점:
|
||||
- 가장 확실하고 안정적
|
||||
- 외부 의존성 없음
|
||||
- 검증된 버전만 사용
|
||||
|
||||
단점:
|
||||
- 수동 업데이트 필요
|
||||
"""
|
||||
manual_catalog = [
|
||||
{
|
||||
'component_name': 'BIOS',
|
||||
'latest_version': '2.15.2',
|
||||
'server_model': 'PowerEdge R750',
|
||||
'vendor': 'Dell',
|
||||
'release_date': '2024-03-15',
|
||||
'download_url': 'https://www.dell.com/support/home/drivers/driversdetails?driverid=...',
|
||||
'notes': 'PowerEdge R750 BIOS - 2024년 3월 릴리즈',
|
||||
'is_critical': False
|
||||
},
|
||||
{
|
||||
'component_name': 'iDRAC',
|
||||
'latest_version': '7.00.00.00',
|
||||
'server_model': None, # 모든 모델
|
||||
'vendor': 'Dell',
|
||||
'release_date': '2024-02-20',
|
||||
'download_url': 'https://www.dell.com/support/home/drivers/driversdetails?driverid=...',
|
||||
'notes': 'iDRAC9 최신 펌웨어 (14G/15G/16G 공용)',
|
||||
'is_critical': True
|
||||
},
|
||||
{
|
||||
'component_name': 'PERC H755',
|
||||
'latest_version': '25.5.9.0001',
|
||||
'server_model': 'PowerEdge R750',
|
||||
'vendor': 'Dell',
|
||||
'release_date': '2024-01-10',
|
||||
'notes': 'PERC H755 RAID 컨트롤러',
|
||||
'is_critical': False
|
||||
},
|
||||
# 더 많은 컴포넌트 추가...
|
||||
]
|
||||
|
||||
return manual_catalog
|
||||
|
||||
# ========================================
|
||||
# 방법 6: Dell Repository Manager (DRM)
|
||||
# ========================================
|
||||
|
||||
def fetch_from_drm_export(self, drm_export_path: str) -> List[Dict]:
|
||||
"""
|
||||
Dell Repository Manager (DRM)에서 내보낸 데이터 사용
|
||||
|
||||
DRM은 Dell의 공식 펌웨어 관리 도구
|
||||
https://www.dell.com/support/kbdoc/en-us/000177083/
|
||||
|
||||
사용법:
|
||||
1. DRM 설치 및 실행
|
||||
2. 필요한 서버 모델 선택
|
||||
3. 카탈로그 내보내기
|
||||
4. 내보낸 파일을 이 함수로 파싱
|
||||
"""
|
||||
try:
|
||||
# DRM 내보내기 파일 파싱 로직
|
||||
# XML 또는 CSV 형식일 수 있음
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"DRM 내보내기 파일 로드 실패: {str(e)}")
|
||||
return []
|
||||
|
||||
# ========================================
|
||||
# 헬퍼 함수들
|
||||
# ========================================
|
||||
|
||||
def _parse_support_api_response(self, data: Dict, model: str) -> List[Dict]:
|
||||
"""Dell Support API 응답 파싱"""
|
||||
# API 응답 구조에 따라 구현
|
||||
return []
|
||||
|
||||
def _parse_catalog_xml(self, root) -> List[Dict]:
|
||||
"""Catalog.xml 파싱"""
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
firmware_list = []
|
||||
|
||||
for pkg in root.findall(".//SoftwareComponent"):
|
||||
name = pkg.findtext("Name")
|
||||
version = pkg.findtext("Version")
|
||||
release_date = pkg.findtext("ReleaseDate")
|
||||
path = pkg.findtext("path")
|
||||
|
||||
if name and version:
|
||||
firmware_list.append({
|
||||
'component_name': name,
|
||||
'latest_version': version,
|
||||
'release_date': release_date,
|
||||
'download_url': f"https://downloads.dell.com/{path}" if path else None,
|
||||
'vendor': 'Dell'
|
||||
})
|
||||
|
||||
return firmware_list
|
||||
|
||||
def _parse_idrac_updates(self, data: Dict) -> List[Dict]:
|
||||
"""iDRAC UpdateService 응답 파싱"""
|
||||
# UpdateService 데이터 파싱
|
||||
return []
|
||||
|
||||
# ========================================
|
||||
# DB에 저장
|
||||
# ========================================
|
||||
|
||||
def save_to_database(self, firmware_list: List[Dict]) -> int:
|
||||
"""
|
||||
펌웨어 목록을 데이터베이스에 저장
|
||||
|
||||
Returns:
|
||||
저장된 항목 수
|
||||
"""
|
||||
count = 0
|
||||
|
||||
for fw in firmware_list:
|
||||
# 중복 확인
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=fw.get('component_name'),
|
||||
latest_version=fw.get('latest_version'),
|
||||
server_model=fw.get('server_model')
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
version = FirmwareVersion(
|
||||
component_name=fw.get('component_name'),
|
||||
latest_version=fw.get('latest_version'),
|
||||
server_model=fw.get('server_model'),
|
||||
vendor=fw.get('vendor', 'Dell'),
|
||||
release_date=fw.get('release_date'),
|
||||
download_url=fw.get('download_url'),
|
||||
notes=fw.get('notes'),
|
||||
is_critical=fw.get('is_critical', False)
|
||||
)
|
||||
db.session.add(version)
|
||||
count += 1
|
||||
|
||||
db.session.commit()
|
||||
return count
|
||||
|
||||
|
||||
# ========================================
|
||||
# 사용 예시
|
||||
# ========================================
|
||||
|
||||
def sync_firmware_alternative(method: str = 'manual', **kwargs) -> Dict:
|
||||
"""
|
||||
대안 방법으로 펌웨어 정보 동기화
|
||||
|
||||
Args:
|
||||
method: 'manual', 'local_catalog', 'idrac', 'support_api' 중 선택
|
||||
**kwargs: 각 방법에 필요한 추가 인자
|
||||
|
||||
Returns:
|
||||
{'success': bool, 'count': int, 'message': str}
|
||||
"""
|
||||
catalog = DellFirmwareCatalogAlternatives()
|
||||
firmware_list = []
|
||||
|
||||
try:
|
||||
if method == 'manual':
|
||||
# 가장 권장: 수동으로 작성한 카탈로그
|
||||
firmware_list = catalog.create_manual_catalog()
|
||||
|
||||
elif method == 'local_catalog':
|
||||
# 로컬 Catalog.xml 파일 사용
|
||||
catalog_path = kwargs.get('catalog_path')
|
||||
if not catalog_path:
|
||||
return {'success': False, 'count': 0, 'message': 'catalog_path 필요'}
|
||||
firmware_list = catalog.load_from_local_catalog(catalog_path)
|
||||
|
||||
elif method == 'idrac':
|
||||
# iDRAC에서 직접 조회
|
||||
idrac_ip = kwargs.get('idrac_ip')
|
||||
username = kwargs.get('username')
|
||||
password = kwargs.get('password')
|
||||
if not all([idrac_ip, username, password]):
|
||||
return {'success': False, 'count': 0, 'message': 'iDRAC 정보 필요'}
|
||||
firmware_list = catalog.fetch_from_idrac(idrac_ip, username, password)
|
||||
|
||||
elif method == 'support_api':
|
||||
# Dell Support API 사용
|
||||
model = kwargs.get('model', 'PowerEdge R750')
|
||||
firmware_list = catalog.fetch_from_dell_support_api(model)
|
||||
|
||||
else:
|
||||
return {'success': False, 'count': 0, 'message': f'알 수 없는 방법: {method}'}
|
||||
|
||||
# DB에 저장
|
||||
count = catalog.save_to_database(firmware_list)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'count': count,
|
||||
'message': f'{count}개 펌웨어 정보 동기화 완료'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'count': 0,
|
||||
'message': f'오류: {str(e)}'
|
||||
}
|
||||
@@ -1,58 +1,178 @@
|
||||
import requests
|
||||
from xml.etree import ElementTree as ET
|
||||
from backend.models.firmware_version import FirmwareVersion, db
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def get_manual_firmware_catalog(model="PowerEdge R750"):
|
||||
"""
|
||||
수동으로 작성한 펌웨어 카탈로그
|
||||
|
||||
Dell 공식 사이트에서 확인한 최신 버전 정보
|
||||
https://www.dell.com/support/
|
||||
|
||||
Dell Catalog.xml이 차단된 경우 이 데이터 사용
|
||||
"""
|
||||
# 2024년 11월 기준 최신 버전 (정기적으로 업데이트 필요)
|
||||
catalog = {
|
||||
"PowerEdge R750": [
|
||||
{
|
||||
'component_name': 'BIOS',
|
||||
'latest_version': '2.15.2',
|
||||
'release_date': '2024-03-15',
|
||||
'notes': 'PowerEdge R750 BIOS - 보안 업데이트 포함',
|
||||
'is_critical': False
|
||||
},
|
||||
{
|
||||
'component_name': 'iDRAC',
|
||||
'latest_version': '7.00.00.00',
|
||||
'release_date': '2024-02-20',
|
||||
'notes': 'iDRAC9 최신 펌웨어',
|
||||
'is_critical': True
|
||||
},
|
||||
{
|
||||
'component_name': 'PERC H755',
|
||||
'latest_version': '25.5.9.0001',
|
||||
'release_date': '2024-01-10',
|
||||
'notes': 'PERC H755 RAID 컨트롤러',
|
||||
'is_critical': False
|
||||
},
|
||||
{
|
||||
'component_name': 'CPLD',
|
||||
'latest_version': '1.0.6',
|
||||
'release_date': '2023-12-15',
|
||||
'notes': '시스템 보드 CPLD',
|
||||
'is_critical': False
|
||||
},
|
||||
],
|
||||
"PowerEdge R640": [
|
||||
{
|
||||
'component_name': 'BIOS',
|
||||
'latest_version': '2.19.2',
|
||||
'release_date': '2024-02-01',
|
||||
'notes': 'PowerEdge R640 BIOS',
|
||||
'is_critical': False
|
||||
},
|
||||
{
|
||||
'component_name': 'iDRAC',
|
||||
'latest_version': '7.00.00.00',
|
||||
'release_date': '2024-02-20',
|
||||
'notes': 'iDRAC9 최신 펌웨어',
|
||||
'is_critical': True
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return catalog.get(model, [])
|
||||
|
||||
|
||||
def sync_dell_catalog(model="PowerEdge R750"):
|
||||
"""
|
||||
Dell 공식 Catalog.xml에서 지정된 모델(model)에 해당하는 펌웨어 정보만 추출 후 DB 저장.
|
||||
Dell 펌웨어 정보 동기화
|
||||
|
||||
1차: Dell 공식 Catalog.xml 시도
|
||||
2차: 수동 카탈로그 사용 (Fallback)
|
||||
"""
|
||||
url = "https://downloads.dell.com/catalog/Catalog.xml"
|
||||
print(f"[INFO] Dell Catalog 다운로드 중... ({url})")
|
||||
response = requests.get(url, timeout=60)
|
||||
response.raise_for_status()
|
||||
|
||||
root = ET.fromstring(response.content)
|
||||
count = 0
|
||||
|
||||
# 1차 시도: Dell 공식 Catalog.xml
|
||||
try:
|
||||
url = "https://downloads.dell.com/catalog/Catalog.xml"
|
||||
print(f"[INFO] Dell Catalog 다운로드 시도... ({url})")
|
||||
|
||||
response = requests.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
root = ET.fromstring(response.content)
|
||||
|
||||
for pkg in root.findall(".//SoftwareComponent"):
|
||||
# 이 컴포넌트가 지정된 모델에 해당하는지 확인
|
||||
supported = [
|
||||
sys.text.strip()
|
||||
for sys in pkg.findall(".//SupportedSystems/Brand/Model/Display")
|
||||
if sys.text
|
||||
]
|
||||
if model not in supported:
|
||||
continue
|
||||
|
||||
for pkg in root.findall(".//SoftwareComponent"):
|
||||
# 이 컴포넌트가 지정된 모델에 해당하는지 확인
|
||||
supported = [
|
||||
sys.text.strip()
|
||||
for sys in pkg.findall(".//SupportedSystems/Brand/Model/Display")
|
||||
if sys.text
|
||||
]
|
||||
if model not in supported:
|
||||
continue
|
||||
name = pkg.findtext("Name")
|
||||
version = pkg.findtext("Version")
|
||||
release_date = pkg.findtext("ReleaseDate")
|
||||
path = pkg.findtext("path")
|
||||
vendor = "Dell"
|
||||
|
||||
name = pkg.findtext("Name")
|
||||
version = pkg.findtext("Version")
|
||||
release_date = pkg.findtext("ReleaseDate")
|
||||
path = pkg.findtext("path")
|
||||
vendor = "Dell"
|
||||
if not name or not version:
|
||||
continue
|
||||
|
||||
if not name or not version:
|
||||
continue
|
||||
# 중복 방지
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=name,
|
||||
latest_version=version,
|
||||
server_model=model
|
||||
).first()
|
||||
|
||||
# 중복 방지
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=name,
|
||||
latest_version=version,
|
||||
server_model=model
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
db.session.add(
|
||||
FirmwareVersion(
|
||||
component_name=name,
|
||||
latest_version=version,
|
||||
release_date=release_date,
|
||||
vendor=vendor,
|
||||
server_model=model,
|
||||
download_url=f"https://downloads.dell.com/{path}",
|
||||
if not existing:
|
||||
db.session.add(
|
||||
FirmwareVersion(
|
||||
component_name=name,
|
||||
latest_version=version,
|
||||
release_date=release_date,
|
||||
vendor=vendor,
|
||||
server_model=model,
|
||||
download_url=f"https://downloads.dell.com/{path}",
|
||||
)
|
||||
)
|
||||
)
|
||||
count += 1
|
||||
count += 1
|
||||
|
||||
db.session.commit()
|
||||
print(f"✓ {model} 관련 펌웨어 {count}개 동기화 완료 (Dell Catalog)")
|
||||
return count
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
print(f"[경고] Dell Catalog.xml 접근 불가 (404). 수동 카탈로그 사용...")
|
||||
else:
|
||||
print(f"[경고] Dell Catalog 다운로드 실패: {e}. 수동 카탈로그 사용...")
|
||||
except Exception as e:
|
||||
print(f"[경고] Dell Catalog 처리 중 오류: {e}. 수동 카탈로그 사용...")
|
||||
|
||||
# 2차 시도: 수동 카탈로그 사용
|
||||
try:
|
||||
print(f"[INFO] 수동 카탈로그에서 {model} 펌웨어 정보 로드 중...")
|
||||
manual_catalog = get_manual_firmware_catalog(model)
|
||||
|
||||
if not manual_catalog:
|
||||
print(f"[경고] {model}에 대한 수동 카탈로그 데이터가 없습니다")
|
||||
return 0
|
||||
|
||||
for fw in manual_catalog:
|
||||
# 중복 방지
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=fw['component_name'],
|
||||
latest_version=fw['latest_version'],
|
||||
server_model=model
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
db.session.add(
|
||||
FirmwareVersion(
|
||||
component_name=fw['component_name'],
|
||||
latest_version=fw['latest_version'],
|
||||
release_date=fw.get('release_date'),
|
||||
vendor='Dell',
|
||||
server_model=model,
|
||||
notes=fw.get('notes'),
|
||||
is_critical=fw.get('is_critical', False)
|
||||
)
|
||||
)
|
||||
count += 1
|
||||
|
||||
db.session.commit()
|
||||
print(f"✓ {model} 관련 펌웨어 {count}개 동기화 완료 (수동 카탈로그)")
|
||||
return count
|
||||
|
||||
except Exception as e:
|
||||
print(f"[오류] 수동 카탈로그 처리 실패: {e}")
|
||||
db.session.rollback()
|
||||
return 0
|
||||
|
||||
db.session.commit()
|
||||
print(f"✓ {model} 관련 펌웨어 {count}개 동기화 완료")
|
||||
return count
|
||||
|
||||
308
backend/services/drm_catalog_sync.py
Normal file
308
backend/services/drm_catalog_sync.py
Normal file
@@ -0,0 +1,308 @@
|
||||
"""
|
||||
Dell Repository Manager (DRM) 연동 모듈
|
||||
backend/services/drm_catalog_sync.py
|
||||
|
||||
Dell Repository Manager에서 생성한 로컬 리포지토리 카탈로그를 파싱하여
|
||||
펌웨어 정보를 가져오는 기능
|
||||
"""
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
from backend.models.firmware_version import FirmwareVersion, db
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DRMCatalogSync:
|
||||
"""Dell Repository Manager 카탈로그 동기화"""
|
||||
|
||||
def __init__(self, drm_repository_path: str = None):
|
||||
"""
|
||||
Args:
|
||||
drm_repository_path: DRM 리포지토리 경로
|
||||
예: C:/Dell/Repository 또는 네트워크 경로
|
||||
"""
|
||||
self.repository_path = drm_repository_path
|
||||
self.catalog_file = None
|
||||
|
||||
if drm_repository_path:
|
||||
# DRM은 보통 catalog 폴더에 XML 파일 생성
|
||||
possible_catalogs = [
|
||||
os.path.join(drm_repository_path, 'catalog', 'Catalog.xml'),
|
||||
os.path.join(drm_repository_path, 'Catalog.xml'),
|
||||
os.path.join(drm_repository_path, 'catalog.xml'),
|
||||
]
|
||||
|
||||
for catalog_path in possible_catalogs:
|
||||
if os.path.exists(catalog_path):
|
||||
self.catalog_file = catalog_path
|
||||
print(f"[INFO] DRM 카탈로그 파일 발견: {catalog_path}")
|
||||
break
|
||||
|
||||
def sync_from_drm_repository(self, model: str = "PowerEdge R750") -> int:
|
||||
"""
|
||||
DRM 리포지토리에서 펌웨어 정보 동기화
|
||||
|
||||
Args:
|
||||
model: 서버 모델명
|
||||
|
||||
Returns:
|
||||
동기화된 펌웨어 개수
|
||||
"""
|
||||
if not self.catalog_file or not os.path.exists(self.catalog_file):
|
||||
print(f"[오류] DRM 카탈로그 파일을 찾을 수 없습니다: {self.repository_path}")
|
||||
return 0
|
||||
|
||||
try:
|
||||
print(f"[INFO] DRM 카탈로그 파싱 중: {self.catalog_file}")
|
||||
|
||||
# XML 파싱
|
||||
tree = ET.parse(self.catalog_file)
|
||||
root = tree.getroot()
|
||||
|
||||
count = 0
|
||||
|
||||
# DRM 카탈로그 구조 파싱
|
||||
for pkg in root.findall(".//SoftwareComponent"):
|
||||
# 모델 필터링
|
||||
supported_models = self._get_supported_models(pkg)
|
||||
|
||||
if model and model not in supported_models:
|
||||
continue
|
||||
|
||||
# 펌웨어 정보 추출
|
||||
firmware_info = self._extract_firmware_info(pkg, model)
|
||||
|
||||
if firmware_info:
|
||||
# DB에 저장
|
||||
if self._save_firmware_to_db(firmware_info):
|
||||
count += 1
|
||||
|
||||
print(f"✓ DRM에서 {model} 관련 펌웨어 {count}개 동기화 완료")
|
||||
return count
|
||||
|
||||
except Exception as e:
|
||||
print(f"[오류] DRM 카탈로그 동기화 실패: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 0
|
||||
|
||||
def _get_supported_models(self, pkg_element) -> List[str]:
|
||||
"""패키지가 지원하는 서버 모델 목록 추출"""
|
||||
models = []
|
||||
|
||||
# Dell 카탈로그 XML 구조
|
||||
for display in pkg_element.findall(".//SupportedSystems/Brand/Model/Display"):
|
||||
if display.text:
|
||||
models.append(display.text.strip())
|
||||
|
||||
return models
|
||||
|
||||
def _extract_firmware_info(self, pkg_element, model: str) -> Optional[Dict]:
|
||||
"""패키지에서 펌웨어 정보 추출"""
|
||||
try:
|
||||
name = pkg_element.findtext("Name")
|
||||
version = pkg_element.findtext("vendorVersion") # DRM은 vendorVersion 사용
|
||||
if not version:
|
||||
version = pkg_element.findtext("dellVersion") # 또는 dellVersion
|
||||
|
||||
release_date = pkg_element.findtext("dateTime")
|
||||
path = pkg_element.findtext("path")
|
||||
category = pkg_element.findtext("Category")
|
||||
|
||||
# 중요도 판단
|
||||
importance = pkg_element.findtext("ImportanceDisplay")
|
||||
is_critical = importance and "Critical" in importance
|
||||
|
||||
# 파일 정보
|
||||
file_name = None
|
||||
file_size_mb = None
|
||||
|
||||
if path:
|
||||
file_name = os.path.basename(path)
|
||||
# 실제 파일이 있으면 크기 확인
|
||||
if self.repository_path:
|
||||
full_path = os.path.join(self.repository_path, path)
|
||||
if os.path.exists(full_path):
|
||||
file_size_mb = os.path.getsize(full_path) // (1024 * 1024)
|
||||
|
||||
if not name or not version:
|
||||
return None
|
||||
|
||||
return {
|
||||
'component_name': name,
|
||||
'latest_version': version,
|
||||
'server_model': model,
|
||||
'vendor': 'Dell',
|
||||
'release_date': self._parse_date(release_date),
|
||||
'download_url': f"https://downloads.dell.com/{path}" if path else None,
|
||||
'file_name': file_name,
|
||||
'file_size_mb': file_size_mb,
|
||||
'component_type': category,
|
||||
'is_critical': is_critical,
|
||||
'notes': f"DRM에서 동기화 - {category}" if category else "DRM에서 동기화"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"[경고] 펌웨어 정보 추출 실패: {str(e)}")
|
||||
return None
|
||||
|
||||
def _parse_date(self, date_str: str) -> Optional[str]:
|
||||
"""날짜 문자열 파싱"""
|
||||
if not date_str:
|
||||
return None
|
||||
|
||||
try:
|
||||
# DRM 날짜 형식: 2024-03-15T10:30:00
|
||||
dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
||||
return dt.strftime('%Y-%m-%d')
|
||||
except:
|
||||
return date_str[:10] if len(date_str) >= 10 else None
|
||||
|
||||
def _save_firmware_to_db(self, firmware_info: Dict) -> bool:
|
||||
"""펌웨어 정보를 DB에 저장"""
|
||||
try:
|
||||
# 중복 확인
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=firmware_info['component_name'],
|
||||
latest_version=firmware_info['latest_version'],
|
||||
server_model=firmware_info['server_model']
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
# 이미 존재하면 업데이트
|
||||
for key, value in firmware_info.items():
|
||||
if hasattr(existing, key) and value is not None:
|
||||
setattr(existing, key, value)
|
||||
db.session.commit()
|
||||
return False # 새로 추가된 것은 아님
|
||||
else:
|
||||
# 새로 추가
|
||||
version = FirmwareVersion(**firmware_info)
|
||||
db.session.add(version)
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[오류] DB 저장 실패: {str(e)}")
|
||||
db.session.rollback()
|
||||
return False
|
||||
|
||||
def list_available_models(self) -> List[str]:
|
||||
"""DRM 리포지토리에서 사용 가능한 모델 목록 조회"""
|
||||
if not self.catalog_file or not os.path.exists(self.catalog_file):
|
||||
return []
|
||||
|
||||
try:
|
||||
tree = ET.parse(self.catalog_file)
|
||||
root = tree.getroot()
|
||||
|
||||
models = set()
|
||||
|
||||
for display in root.findall(".//SupportedSystems/Brand/Model/Display"):
|
||||
if display.text:
|
||||
models.add(display.text.strip())
|
||||
|
||||
return sorted(list(models))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[오류] 모델 목록 조회 실패: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_repository_info(self) -> Dict:
|
||||
"""DRM 리포지토리 정보 조회"""
|
||||
if not self.catalog_file or not os.path.exists(self.catalog_file):
|
||||
return {
|
||||
'exists': False,
|
||||
'path': self.repository_path,
|
||||
'catalog_file': None
|
||||
}
|
||||
|
||||
try:
|
||||
tree = ET.parse(self.catalog_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# 카탈로그 메타데이터
|
||||
base_location = root.findtext(".//BaseLocation")
|
||||
release_id = root.findtext(".//ReleaseID")
|
||||
|
||||
# 패키지 수 계산
|
||||
total_packages = len(root.findall(".//SoftwareComponent"))
|
||||
|
||||
return {
|
||||
'exists': True,
|
||||
'path': self.repository_path,
|
||||
'catalog_file': self.catalog_file,
|
||||
'base_location': base_location,
|
||||
'release_id': release_id,
|
||||
'total_packages': total_packages,
|
||||
'available_models': self.list_available_models()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'exists': True,
|
||||
'path': self.repository_path,
|
||||
'catalog_file': self.catalog_file,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
# ========================================
|
||||
# 편의 함수
|
||||
# ========================================
|
||||
|
||||
def sync_from_drm(repository_path: str, model: str = "PowerEdge R750") -> Dict:
|
||||
"""
|
||||
DRM 리포지토리에서 펌웨어 동기화 (간편 함수)
|
||||
|
||||
Args:
|
||||
repository_path: DRM 리포지토리 경로
|
||||
model: 서버 모델명
|
||||
|
||||
Returns:
|
||||
{'success': bool, 'count': int, 'message': str}
|
||||
"""
|
||||
try:
|
||||
drm = DRMCatalogSync(repository_path)
|
||||
|
||||
# 리포지토리 확인
|
||||
info = drm.get_repository_info()
|
||||
if not info['exists']:
|
||||
return {
|
||||
'success': False,
|
||||
'count': 0,
|
||||
'message': f'DRM 리포지토리를 찾을 수 없습니다: {repository_path}'
|
||||
}
|
||||
|
||||
# 동기화
|
||||
count = drm.sync_from_drm_repository(model)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'count': count,
|
||||
'message': f'{model} 펌웨어 {count}개 동기화 완료',
|
||||
'repository_info': info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'count': 0,
|
||||
'message': f'DRM 동기화 실패: {str(e)}'
|
||||
}
|
||||
|
||||
|
||||
def check_drm_repository(repository_path: str) -> Dict:
|
||||
"""
|
||||
DRM 리포지토리 상태 확인
|
||||
|
||||
Args:
|
||||
repository_path: DRM 리포지토리 경로
|
||||
|
||||
Returns:
|
||||
리포지토리 정보
|
||||
"""
|
||||
drm = DRMCatalogSync(repository_path)
|
||||
return drm.get_repository_info()
|
||||
@@ -60,6 +60,19 @@ def setup_logging(app: Optional[object] = None) -> logging.Logger:
|
||||
# Flask 앱 로거에도 동일 핸들러 바인딩
|
||||
app.logger.handlers = root.handlers
|
||||
app.logger.setLevel(root.level)
|
||||
# 루트 로거로 전파되면 메시지가 두 번 출력되므로 방지
|
||||
app.logger.propagate = False
|
||||
|
||||
# 제3자 라이브러리 로그 레벨 조정 (너무 시끄러운 경우)
|
||||
# werkzeug: 기본적인 HTTP 요청 로그(GET/POST 등)를 숨김 (WARNING 이상만 표시)
|
||||
logging.getLogger("werkzeug").setLevel(logging.WARNING)
|
||||
logging.getLogger("socketio").setLevel(logging.WARNING)
|
||||
logging.getLogger("engineio").setLevel(logging.WARNING)
|
||||
|
||||
# httpx, telegram 라이브러리의 HTTP 요청 로그 숨기기
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
logging.getLogger("telegram").setLevel(logging.WARNING)
|
||||
|
||||
root.info("Logger initialized | level=%s | file=%s", _DEF_LEVEL, log_path)
|
||||
return root
|
||||
@@ -45,18 +45,31 @@ class RedfishClient:
|
||||
ip: str,
|
||||
username: str,
|
||||
password: str,
|
||||
timeout: int = 15,
|
||||
verify_ssl: bool = False
|
||||
timeout: Optional[int] = None,
|
||||
verify_ssl: Optional[bool] = None
|
||||
):
|
||||
self.ip = ip
|
||||
self.base_url = f"https://{ip}/redfish/v1"
|
||||
self.host_url = f"https://{ip}" # ← 추가: 호스트 URL
|
||||
self.timeout = timeout
|
||||
self.verify_ssl = verify_ssl
|
||||
self.host_url = f"https://{ip}"
|
||||
|
||||
# Config defaults
|
||||
default_timeout = 15
|
||||
default_verify = False
|
||||
|
||||
try:
|
||||
from flask import current_app
|
||||
if current_app:
|
||||
default_timeout = current_app.config.get("REDFISH_TIMEOUT", 15)
|
||||
default_verify = current_app.config.get("REDFISH_VERIFY_SSL", False)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
self.timeout = timeout if timeout is not None else default_timeout
|
||||
self.verify_ssl = verify_ssl if verify_ssl is not None else default_verify
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.auth = (username, password)
|
||||
self.session.verify = verify_ssl
|
||||
self.session.verify = self.verify_ssl
|
||||
self.session.headers.update({
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
@@ -214,11 +227,77 @@ class RedfishClient:
|
||||
"PercentComplete": str(percent),
|
||||
"Message": message_text,
|
||||
"ScheduledStartTime": job_data.get("ScheduledStartTime", ""),
|
||||
"StartTime": job_data.get("StartTime", ""),
|
||||
"EndTime": job_data.get("EndTime", ""),
|
||||
"LastUpdateTime": job_data.get("EndTime") or job_data.get("StartTime", ""),
|
||||
}
|
||||
|
||||
def export_system_configuration(self, share_parameters: Dict[str, Any], target: str = "ALL") -> str:
|
||||
"""
|
||||
시스템 설정 내보내기 (SCP Export)
|
||||
:param share_parameters: 네트워크 공유 설정 (IP, ShareName, FileName, UserName, Password 등)
|
||||
:param target: 내보낼 대상 (ALL, IDRAC, BIOS, NIC, RAID)
|
||||
:return: Job ID
|
||||
"""
|
||||
url = f"{self.host_url}/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/DellManager.ExportSystemConfiguration"
|
||||
|
||||
payload = {
|
||||
"ExportFormat": "XML",
|
||||
"ShareParameters": share_parameters,
|
||||
"Target": target
|
||||
}
|
||||
|
||||
logger.info(f"{self.ip}: Exporting system configuration to {share_parameters.get('FileName')}")
|
||||
response = self.session.post(url, json=payload, verify=False)
|
||||
response.raise_for_status()
|
||||
|
||||
# Job ID 추출 (Location 헤더 또는 응답 본문)
|
||||
job_id = ""
|
||||
if response.status_code == 202:
|
||||
location = response.headers.get("Location")
|
||||
if location:
|
||||
job_id = location.split("/")[-1]
|
||||
|
||||
if not job_id:
|
||||
# 응답 본문에서 시도 (일부 펌웨어 버전 대응)
|
||||
try:
|
||||
data = response.json()
|
||||
# 일반적인 JID 포맷 확인 필요
|
||||
# 여기서는 간단히 헤더 우선으로 처리하고 없으면 에러 처리하거나 데이터 파싱
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
return job_id
|
||||
|
||||
def import_system_configuration(self, share_parameters: Dict[str, Any], import_mode: str = "Replace", target: str = "ALL") -> str:
|
||||
"""
|
||||
시스템 설정 가져오기 (SCP Import)
|
||||
:param share_parameters: 네트워크 공유 설정
|
||||
:param import_mode: Replace, Append 등
|
||||
:param target: 가져올 대상
|
||||
:return: Job ID
|
||||
"""
|
||||
url = f"{self.host_url}/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/DellManager.ImportSystemConfiguration"
|
||||
|
||||
payload = {
|
||||
"ImportSystemConfigurationXMLFile": share_parameters.get("FileName"),
|
||||
"ShareParameters": share_parameters,
|
||||
"ImportMode": import_mode,
|
||||
"Target": target,
|
||||
"ShutdownType": "Graceful" # 적용 후 재부팅 방식
|
||||
}
|
||||
|
||||
logger.info(f"{self.ip}: Importing system configuration from {share_parameters.get('FileName')}")
|
||||
response = self.session.post(url, json=payload, verify=False)
|
||||
response.raise_for_status()
|
||||
|
||||
job_id = ""
|
||||
if response.status_code == 202:
|
||||
location = response.headers.get("Location")
|
||||
if location:
|
||||
job_id = location.split("/")[-1]
|
||||
|
||||
return job_id
|
||||
|
||||
def close(self):
|
||||
"""세션 종료"""
|
||||
self.session.close()
|
||||
|
||||
18
backend/static/css/admin_settings.css
Normal file
18
backend/static/css/admin_settings.css
Normal 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;
|
||||
}
|
||||
55
backend/static/css/edit_xml.css
Normal file
55
backend/static/css/edit_xml.css
Normal 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;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ header {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ header .subtitle {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ header .subtitle {
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -302,7 +302,7 @@ header .subtitle {
|
||||
max-width: 600px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
@@ -423,8 +423,15 @@ header .subtitle {
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-message {
|
||||
@@ -531,20 +538,20 @@ footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.header-actions {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.header-actions button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.bulk-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
}
|
||||
@@ -556,3 +563,217 @@ input[type="checkbox"] {
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
탭 메뉴 스타일
|
||||
======================================== */
|
||||
|
||||
.tab-menu {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
padding: 12px 24px;
|
||||
border: 2px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.tab-button:hover:not(.active) {
|
||||
background: #f8f9fa;
|
||||
border-color: #667eea;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
토스트 알림 스타일
|
||||
======================================== */
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 25px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
|
||||
transform: translateX(400px);
|
||||
transition: transform 0.3s ease-out;
|
||||
z-index: 2000;
|
||||
max-width: 400px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
||||
}
|
||||
|
||||
.toast-warning {
|
||||
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.toast-info {
|
||||
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
로딩 스피너
|
||||
======================================== */
|
||||
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(102, 126, 234, 0.2);
|
||||
border-radius: 50%;
|
||||
border-top-color: #667eea;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#comparison-loading {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
#comparison-loading p {
|
||||
margin-top: 20px;
|
||||
color: #666;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
비교 결과 개선 스타일
|
||||
======================================== */
|
||||
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
gap: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.server-comparison-section {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.server-comparison-section h3 {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.comparison-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #667eea;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.comparison-card:hover {
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.comparison-card.outdated {
|
||||
border-left-color: #dc3545;
|
||||
background: linear-gradient(to right, #fff5f5 0%, white 100%);
|
||||
}
|
||||
|
||||
.comparison-card.latest {
|
||||
border-left-color: #28a745;
|
||||
background: linear-gradient(to right, #f0fff4 0%, white 100%);
|
||||
}
|
||||
|
||||
.comparison-card.unknown {
|
||||
border-left-color: #ffc107;
|
||||
background: linear-gradient(to right, #fffbf0 0%, white 100%);
|
||||
}
|
||||
|
||||
.comparison-card code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
반응형 개선
|
||||
======================================== */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tab-menu {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toast {
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.comparison-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
120
backend/static/css/index.css
Normal file
120
backend/static/css/index.css
Normal 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;
|
||||
}
|
||||
86
backend/static/css/jobs.css
Normal file
86
backend/static/css/jobs.css
Normal 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
296
backend/static/css/scp.css
Normal 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;
|
||||
}
|
||||
51
backend/static/js/admin.js
Normal file
51
backend/static/js/admin.js
Normal 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;
|
||||
}
|
||||
// 제출 허용 (서버측에서도 반드시 검증)
|
||||
});
|
||||
})();
|
||||
@@ -58,6 +58,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
refreshServers();
|
||||
loadGroups();
|
||||
setupSocketIO();
|
||||
|
||||
// 초기 탭 설정
|
||||
showTab('servers');
|
||||
});
|
||||
|
||||
// ========================================
|
||||
@@ -299,27 +302,27 @@ function closeUploadModal() {
|
||||
async function startMultiUpload() {
|
||||
const fileInput = document.getElementById('firmware-file');
|
||||
const serverIds = getSelectedServerIds();
|
||||
|
||||
|
||||
if (!fileInput.files[0]) {
|
||||
showMessage('파일을 선택하세요', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
formData.append('server_ids', serverIds.join(','));
|
||||
|
||||
|
||||
try {
|
||||
closeUploadModal();
|
||||
showUploadProgress(serverIds);
|
||||
|
||||
|
||||
const response = await fetchWithCSRF('/idrac/api/upload-multi', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
if (data.success) {
|
||||
showMessage(data.message, 'success');
|
||||
} else {
|
||||
@@ -335,9 +338,9 @@ async function startMultiUpload() {
|
||||
function showUploadProgress(serverIds) {
|
||||
const section = document.getElementById('upload-progress-section');
|
||||
const list = document.getElementById('upload-progress-list');
|
||||
|
||||
|
||||
section.style.display = 'block';
|
||||
|
||||
|
||||
// 각 서버별 진행 바 생성
|
||||
list.innerHTML = serverIds.map(id => {
|
||||
const server = servers.find(s => s.id === id);
|
||||
@@ -354,7 +357,7 @@ function showUploadProgress(serverIds) {
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
|
||||
// 요약 초기화
|
||||
document.getElementById('progress-summary').innerHTML = `
|
||||
<div class="summary-stats">
|
||||
@@ -375,13 +378,13 @@ function hideUploadProgress() {
|
||||
|
||||
function setupSocketIO() {
|
||||
// 업로드 진행 상황
|
||||
socket.on('upload_progress', function(data) {
|
||||
socket.on('upload_progress', function (data) {
|
||||
console.log('Upload progress:', data);
|
||||
|
||||
|
||||
const statusEl = document.getElementById(`status-${data.server_id}`);
|
||||
const barEl = document.getElementById(`bar-${data.server_id}`);
|
||||
const messageEl = document.getElementById(`message-${data.server_id}`);
|
||||
|
||||
|
||||
if (statusEl) statusEl.textContent = data.message;
|
||||
if (barEl) {
|
||||
barEl.style.width = data.progress + '%';
|
||||
@@ -389,18 +392,18 @@ function setupSocketIO() {
|
||||
}
|
||||
if (messageEl) messageEl.textContent = data.job_id ? `Job ID: ${data.job_id}` : '';
|
||||
});
|
||||
|
||||
|
||||
// 업로드 완료
|
||||
socket.on('upload_complete', function(data) {
|
||||
socket.on('upload_complete', function (data) {
|
||||
console.log('Upload complete:', data);
|
||||
|
||||
|
||||
const summary = data.summary;
|
||||
document.getElementById('completed-count').textContent = `완료: ${summary.success}대`;
|
||||
document.getElementById('failed-count').textContent = `실패: ${summary.failed}대`;
|
||||
|
||||
|
||||
showMessage(`업로드 완료: 성공 ${summary.success}대, 실패 ${summary.failed}대`, 'success');
|
||||
showResults(data.results, '업로드 결과');
|
||||
|
||||
|
||||
refreshServers();
|
||||
});
|
||||
}
|
||||
@@ -412,9 +415,9 @@ function setupSocketIO() {
|
||||
function showResults(results, title) {
|
||||
const section = document.getElementById('result-section');
|
||||
const content = document.getElementById('result-content');
|
||||
|
||||
|
||||
section.style.display = 'block';
|
||||
|
||||
|
||||
content.innerHTML = `
|
||||
<h3>${title}</h3>
|
||||
<table class="result-table">
|
||||
@@ -448,30 +451,30 @@ function showResults(results, title) {
|
||||
|
||||
async function rebootSelected() {
|
||||
const serverIds = getSelectedServerIds();
|
||||
|
||||
|
||||
if (serverIds.length === 0) {
|
||||
showMessage('서버를 선택하세요', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!confirm(`선택한 ${serverIds.length}대 서버를 재부팅하시겠습니까?\n\n⚠️ 업로드된 펌웨어가 적용됩니다!`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetchWithCSRF('/idrac/api/servers/reboot-multi', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: JSON.stringify({
|
||||
server_ids: serverIds,
|
||||
type: 'GracefulRestart'
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
if (data.success) {
|
||||
const summary = data.summary;
|
||||
showMessage(`재부팅 시작: 성공 ${summary.success}대, 실패 ${summary.failed}대`, 'success');
|
||||
@@ -479,7 +482,7 @@ async function rebootSelected() {
|
||||
} else {
|
||||
showMessage(data.message, 'error');
|
||||
}
|
||||
|
||||
|
||||
refreshServers();
|
||||
} catch (error) {
|
||||
showMessage('재부팅 실패: ' + error, 'error');
|
||||
@@ -499,11 +502,38 @@ function importExcel() {
|
||||
// ========================================
|
||||
|
||||
function showMessage(message, type = 'info') {
|
||||
// 간단한 알림 표시
|
||||
alert(message);
|
||||
// 토스트 알림으로 변경
|
||||
showToast(message, type);
|
||||
console.log(`[${type}] ${message}`);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 토스트 알림 시스템
|
||||
// ========================================
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
// 기존 토스트 제거
|
||||
const existingToast = document.querySelector('.toast');
|
||||
if (existingToast) {
|
||||
existingToast.remove();
|
||||
}
|
||||
|
||||
// 새 토스트 생성
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 애니메이션
|
||||
setTimeout(() => toast.classList.add('show'), 100);
|
||||
|
||||
// 3초 후 제거
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 편의 함수들
|
||||
function editServer(serverId) {
|
||||
showMessage('서버 수정 기능은 개발 중입니다', 'info');
|
||||
@@ -511,20 +541,20 @@ function editServer(serverId) {
|
||||
|
||||
function getSelectedFirmware() {
|
||||
const serverIds = getSelectedServerIds();
|
||||
|
||||
|
||||
if (serverIds.length === 0) {
|
||||
showMessage('서버를 선택하세요', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
showMessage('선택한 서버의 펌웨어 조회 중...', 'info');
|
||||
|
||||
|
||||
// 각 서버별로 펌웨어 조회
|
||||
serverIds.forEach(async (serverId) => {
|
||||
try {
|
||||
const response = await fetchWithCSRF(`/idrac/api/servers/${serverId}/firmware`);
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
if (data.success) {
|
||||
console.log(`Server ${serverId} firmware:`, data.data);
|
||||
}
|
||||
@@ -532,7 +562,7 @@ function getSelectedFirmware() {
|
||||
console.error(`Server ${serverId} firmware query failed:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 새로고침
|
||||
setTimeout(() => {
|
||||
refreshServers();
|
||||
@@ -580,11 +610,19 @@ async function compareSelectedFirmware() {
|
||||
const serverIds = getSelectedServerIds();
|
||||
|
||||
if (serverIds.length === 0) {
|
||||
showMessage('서버를 선택하세요', 'warning');
|
||||
showToast('서버를 선택하세요', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage(`선택된 ${serverIds.length}대 서버 버전 비교 중...`, 'info');
|
||||
// 비교 탭으로 전환
|
||||
showTab('comparison');
|
||||
|
||||
// 로딩 표시
|
||||
const loadingEl = document.getElementById('comparison-loading');
|
||||
const resultEl = document.getElementById('comparison-result');
|
||||
|
||||
if (loadingEl) loadingEl.style.display = 'block';
|
||||
if (resultEl) resultEl.innerHTML = '';
|
||||
|
||||
try {
|
||||
const res = await fetchWithCSRF('/idrac/api/servers/firmware/compare-multi', {
|
||||
@@ -594,16 +632,90 @@ async function compareSelectedFirmware() {
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// 로딩 숨기기
|
||||
if (loadingEl) loadingEl.style.display = 'none';
|
||||
|
||||
if (data.success) {
|
||||
showResults(data.results, '버전 비교 결과');
|
||||
displayComparisonResults(data.results);
|
||||
showToast(`${serverIds.length}대 서버 비교 완료`, 'success');
|
||||
} else {
|
||||
showMessage(data.message, 'error');
|
||||
if (resultEl) {
|
||||
resultEl.innerHTML = `<div class="warning-text">⚠️ ${data.message}</div>`;
|
||||
}
|
||||
showToast(data.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('버전 비교 실패: ' + error, 'error');
|
||||
if (loadingEl) loadingEl.style.display = 'none';
|
||||
if (resultEl) {
|
||||
resultEl.innerHTML = `<div class="warning-text">⚠️ 버전 비교 실패: ${error}</div>`;
|
||||
}
|
||||
showToast('버전 비교 실패: ' + error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 비교 결과 표시 (개선된 버전)
|
||||
// ========================================
|
||||
|
||||
function displayComparisonResults(results) {
|
||||
const resultEl = document.getElementById('comparison-result');
|
||||
if (!resultEl) return;
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
resultEl.innerHTML = '<div class="info-text">비교 결과가 없습니다</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="comparison-grid">';
|
||||
|
||||
results.forEach(serverResult => {
|
||||
html += `
|
||||
<div class="server-comparison-section">
|
||||
<h3 style="margin-bottom: 15px; color: #333;">
|
||||
🖥️ ${serverResult.server_name}
|
||||
<span style="font-size: 0.8em; color: #666;">(${serverResult.server_ip || ''})</span>
|
||||
</h3>
|
||||
`;
|
||||
|
||||
if (serverResult.success && serverResult.comparisons) {
|
||||
serverResult.comparisons.forEach(comp => {
|
||||
const statusClass = comp.status === 'outdated' ? 'outdated' :
|
||||
comp.status === 'latest' ? 'latest' : 'unknown';
|
||||
const statusIcon = comp.status === 'outdated' ? '⚠️' :
|
||||
comp.status === 'latest' ? '✅' : '❓';
|
||||
|
||||
html += `
|
||||
<div class="comparison-card ${statusClass}">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
|
||||
<strong style="font-size: 1.1em;">${comp.component_name}</strong>
|
||||
<span style="font-size: 1.5em;">${statusIcon}</span>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span style="color: #666;">현재:</span>
|
||||
<code style="background: #f0f0f0; padding: 2px 8px; border-radius: 4px;">${comp.current_version}</code>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span style="color: #666;">최신:</span>
|
||||
<code style="background: #f0f0f0; padding: 2px 8px; border-radius: 4px;">${comp.latest_version || 'N/A'}</code>
|
||||
</div>
|
||||
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #ddd; font-size: 0.9em; color: #555;">
|
||||
${comp.recommendation}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
html += `<div class="warning-text">⚠️ ${serverResult.message || '비교 실패'}</div>`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
resultEl.innerHTML = html;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 펌웨어 버전 추가 모달
|
||||
// ========================================
|
||||
@@ -681,7 +793,7 @@ async function refreshFirmwareVersionList() {
|
||||
// Dell Catalog에서 최신 버전 자동 가져오기
|
||||
// ========================================
|
||||
async function syncDellCatalog(model = "PowerEdge R750") {
|
||||
showMessage(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info");
|
||||
showToast(`${model} 최신 버전 정보를 Dell에서 가져오는 중...`, "info");
|
||||
|
||||
try {
|
||||
const response = await fetchWithCSRF("/catalog/sync", {
|
||||
@@ -692,13 +804,95 @@ async function syncDellCatalog(model = "PowerEdge R750") {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showMessage(data.message, "success");
|
||||
showToast(data.message, "success");
|
||||
await refreshFirmwareVersionList();
|
||||
} else {
|
||||
showMessage(data.message, "error");
|
||||
showToast(data.message, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage("버전 정보 동기화 실패: " + error, "error");
|
||||
showToast("버전 정보 동기화 실패: " + error, "error");
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// DRM 리포지토리 동기화
|
||||
// ========================================
|
||||
|
||||
async function syncFromDRM() {
|
||||
// DRM 리포지토리 경로 입력 받기
|
||||
const repositoryPath = prompt(
|
||||
'DRM 리포지토리 경로를 입력하세요:\n예: C:\\Dell\\Repository 또는 \\\\network\\share\\DellRepo',
|
||||
'C:\\Dell\\Repository'
|
||||
);
|
||||
|
||||
if (!repositoryPath) return;
|
||||
|
||||
const model = prompt('서버 모델을 입력하세요:', 'PowerEdge R750');
|
||||
if (!model) return;
|
||||
|
||||
showToast('DRM 리포지토리에서 펌웨어 정보 가져오는 중...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetchWithCSRF('/drm/sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
repository_path: repositoryPath,
|
||||
model: model
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast(`${data.message}`, 'success');
|
||||
await refreshFirmwareVersionList();
|
||||
} else {
|
||||
showToast(data.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('DRM 동기화 실패: ' + error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkDRMRepository() {
|
||||
const repositoryPath = prompt(
|
||||
'DRM 리포지토리 경로를 입력하세요:',
|
||||
'C:\\Dell\\Repository'
|
||||
);
|
||||
|
||||
if (!repositoryPath) return;
|
||||
|
||||
try {
|
||||
const response = await fetchWithCSRF('/drm/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ repository_path: repositoryPath })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.info) {
|
||||
const info = data.info;
|
||||
let message = `DRM 리포지토리 정보:\n\n`;
|
||||
message += `경로: ${info.path}\n`;
|
||||
message += `카탈로그 파일: ${info.catalog_file || '없음'}\n`;
|
||||
message += `총 패키지 수: ${info.total_packages || 0}\n`;
|
||||
|
||||
if (info.available_models && info.available_models.length > 0) {
|
||||
message += `\n사용 가능한 모델 (${info.available_models.length}개):\n`;
|
||||
message += info.available_models.slice(0, 10).join(', ');
|
||||
if (info.available_models.length > 10) {
|
||||
message += ` ... 외 ${info.available_models.length - 10}개`;
|
||||
}
|
||||
}
|
||||
|
||||
alert(message);
|
||||
} else {
|
||||
showToast('DRM 리포지토리를 찾을 수 없습니다', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('DRM 확인 실패: ' + error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,8 +910,8 @@ async function refreshFirmwareVersionList() {
|
||||
|
||||
tbody.innerHTML = data.versions.length
|
||||
? data.versions
|
||||
.map(
|
||||
(v) => `
|
||||
.map(
|
||||
(v) => `
|
||||
<tr>
|
||||
<td>${v.component_name}</td>
|
||||
<td>${v.latest_version}</td>
|
||||
@@ -728,13 +922,79 @@ async function refreshFirmwareVersionList() {
|
||||
<button class="btn btn-danger" onclick="deleteFirmwareVersion(${v.id})">삭제</button>
|
||||
</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("")
|
||||
)
|
||||
.join("")
|
||||
: `<tr><td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td></tr>`;
|
||||
} else {
|
||||
showMessage(data.message, "error");
|
||||
showToast(data.message, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage("버전 목록 로드 실패: " + error, "error");
|
||||
showToast("버전 목록 로드 실패: " + error, "error");
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 탭 전환 기능
|
||||
// ========================================
|
||||
|
||||
function showTab(tabName) {
|
||||
// 모든 탭 콘텐츠 숨기기
|
||||
document.querySelectorAll('.tab-content').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
|
||||
// 모든 탭 버튼 비활성화
|
||||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
|
||||
// 선택된 탭 표시
|
||||
const selectedTab = document.getElementById(tabName + '-tab');
|
||||
if (selectedTab) {
|
||||
selectedTab.classList.add('active');
|
||||
}
|
||||
|
||||
// 클릭된 버튼 활성화 (이벤트에서 호출된 경우)
|
||||
if (event && event.target) {
|
||||
event.target.classList.add('active');
|
||||
} else {
|
||||
// 직접 호출된 경우 해당 버튼 찾아서 활성화
|
||||
const buttons = document.querySelectorAll('.tab-button');
|
||||
buttons.forEach((btn, index) => {
|
||||
if ((tabName === 'servers' && index === 0) ||
|
||||
(tabName === 'versions' && index === 1) ||
|
||||
(tabName === 'comparison' && index === 2)) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 버전 탭 선택 시 목록 로드
|
||||
if (tabName === 'versions') {
|
||||
refreshFirmwareVersionList();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 펌웨어 버전 삭제
|
||||
// ========================================
|
||||
|
||||
async function deleteFirmwareVersion(versionId) {
|
||||
if (!confirm('이 펌웨어 버전 정보를 삭제하시겠습니까?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetchWithCSRF(`/idrac/api/firmware-versions/${versionId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast(data.message, 'success');
|
||||
refreshFirmwareVersionList();
|
||||
} else {
|
||||
showToast(data.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('삭제 실패: ' + error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
298
backend/static/js/index.js
Normal file
298
backend/static/js/index.js
Normal file
@@ -0,0 +1,298 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 스크립트 선택 시 XML 드롭다운 토글
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const TARGET_SCRIPT = "02-set_config.py";
|
||||
const scriptSelect = document.getElementById('script');
|
||||
const xmlGroup = document.getElementById('xmlFileGroup');
|
||||
|
||||
function toggleXml() {
|
||||
if (!scriptSelect || !xmlGroup) return;
|
||||
if (scriptSelect.value === TARGET_SCRIPT) {
|
||||
xmlGroup.style.display = 'block';
|
||||
xmlGroup.classList.add('fade-in');
|
||||
} else {
|
||||
xmlGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (scriptSelect) {
|
||||
toggleXml();
|
||||
scriptSelect.addEventListener('change', toggleXml);
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 파일 보기 모달
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const modalEl = document.getElementById('fileViewModal');
|
||||
const titleEl = document.getElementById('fileViewModalLabel');
|
||||
const contentEl = document.getElementById('fileViewContent');
|
||||
|
||||
if (modalEl) {
|
||||
modalEl.addEventListener('show.bs.modal', async (ev) => {
|
||||
const btn = ev.relatedTarget;
|
||||
const folder = btn?.getAttribute('data-folder') || '';
|
||||
const date = btn?.getAttribute('data-date') || '';
|
||||
const filename = btn?.getAttribute('data-filename') || '';
|
||||
|
||||
titleEl.innerHTML = `<i class="bi bi-file-text me-2"></i>${filename || '파일'}`;
|
||||
contentEl.textContent = '불러오는 중...';
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (folder) params.set('folder', folder);
|
||||
if (date) params.set('date', date);
|
||||
if (filename) params.set('filename', filename);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
|
||||
const data = await res.json();
|
||||
contentEl.textContent = data?.content ?? '(빈 파일)';
|
||||
} catch (e) {
|
||||
contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 진행바 업데이트
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
window.updateProgress = function (val) {
|
||||
const bar = document.getElementById('progressBar');
|
||||
if (!bar) return;
|
||||
const v = Math.max(0, Math.min(100, Number(val) || 0));
|
||||
bar.style.width = v + '%';
|
||||
bar.setAttribute('aria-valuenow', v);
|
||||
bar.innerHTML = `<span class="fw-semibold">${v}%</span>`;
|
||||
};
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// CSRF 토큰
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// IP 입력 데이터 보존 (Local Storage)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const ipTextarea = document.getElementById('ips');
|
||||
const ipForm = document.getElementById('ipForm');
|
||||
const STORAGE_KEY_IP = 'ip_input_draft';
|
||||
|
||||
if (ipTextarea) {
|
||||
// 1. 페이지 로드 시 저장된 값 복원
|
||||
const savedIps = localStorage.getItem(STORAGE_KEY_IP);
|
||||
if (savedIps) {
|
||||
ipTextarea.value = savedIps;
|
||||
// 라인 수 업데이트 트리거
|
||||
if (window.updateIpCount) window.updateIpCount();
|
||||
}
|
||||
|
||||
// 2. 입력 시마다 저장
|
||||
ipTextarea.addEventListener('input', () => {
|
||||
localStorage.setItem(STORAGE_KEY_IP, ipTextarea.value);
|
||||
// script.js에 있는 updateIpCount 호출 (있다면)
|
||||
if (window.updateIpCount) window.updateIpCount();
|
||||
});
|
||||
|
||||
// 3. 폼 제출 성공 시 초기화?
|
||||
// 사용자의 의도에 따라 다름: "변경이 되지 않는 이상 계속 가지고 있게"
|
||||
// -> 제출 후에도 유지하는 것이 요청 사항에 부합함.
|
||||
// 만약 '성공적으로 작업이 끝나면 지워달라'는 요청이 있으면 여기를 수정.
|
||||
// 현재 요청: "페이지가 리셋이되도 변경이 되지 않는이상 계속 가지고있게" -> 유지.
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 공통 POST 함수
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
async function postFormAndHandle(url) {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8',
|
||||
},
|
||||
});
|
||||
|
||||
const ct = (res.headers.get('content-type') || '').toLowerCase();
|
||||
|
||||
if (ct.includes('application/json')) {
|
||||
const data = await res.json();
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || ('HTTP ' + res.status));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
return { success: true, html: true };
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// MAC 파일 이동
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const macForm = document.getElementById('macMoveForm');
|
||||
if (macForm) {
|
||||
macForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = macForm.querySelector('button');
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
|
||||
try {
|
||||
await postFormAndHandle(macForm.action);
|
||||
location.reload();
|
||||
} catch (err) {
|
||||
alert('MAC 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err));
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalHtml;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// GUID 파일 이동
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const guidForm = document.getElementById('guidMoveForm');
|
||||
if (guidForm) {
|
||||
guidForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = guidForm.querySelector('button');
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
|
||||
try {
|
||||
await postFormAndHandle(guidForm.action);
|
||||
location.reload();
|
||||
} catch (err) {
|
||||
alert('GUID 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err));
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalHtml;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 알림 자동 닫기
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.alert').forEach(alert => {
|
||||
const bsAlert = new bootstrap.Alert(alert);
|
||||
bsAlert.close();
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// IP 스캔 로직 (Modal)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const btnScan = document.getElementById('btnStartScan');
|
||||
if (btnScan) {
|
||||
btnScan.addEventListener('click', async () => {
|
||||
const startIp = '10.10.0.2';
|
||||
const endIp = '10.10.0.255';
|
||||
const ipsTextarea = document.getElementById('ips');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
|
||||
// UI 상태 변경 (로딩 중)
|
||||
const originalIcon = btnScan.innerHTML;
|
||||
btnScan.disabled = true;
|
||||
btnScan.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
|
||||
|
||||
// 메인 진행바 활용
|
||||
if (progressBar) {
|
||||
const progressContainer = progressBar.closest('.progress');
|
||||
if (progressContainer) {
|
||||
progressContainer.parentElement.classList.remove('d-none');
|
||||
}
|
||||
progressBar.style.width = '100%';
|
||||
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
|
||||
progressBar.textContent = 'IP 스캔 중...';
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/utils/scan_network', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify({ start_ip: startIp, end_ip: endIp })
|
||||
});
|
||||
|
||||
// 1. 세션 만료로 인한 리다이렉트 감지
|
||||
if (res.redirected) {
|
||||
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. JSON 응답인지 확인
|
||||
const contentType = res.headers.get("content-type");
|
||||
if (!contentType || !contentType.includes("application/json")) {
|
||||
const text = await res.text();
|
||||
if (text.includes("CSRF")) {
|
||||
throw new Error("보안 토큰(CSRF)이 만료되었습니다. 페이지를 새로고침해주세요.");
|
||||
}
|
||||
throw new Error(`서버 응답 오류 (HTTP ${res.status}): ${text.substring(0, 100)}...`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
if (data.active_ips && data.active_ips.length > 0) {
|
||||
ipsTextarea.value = data.active_ips.join('\n');
|
||||
// 이벤트 트리거
|
||||
ipsTextarea.dispatchEvent(new Event('input'));
|
||||
|
||||
alert(`스캔 완료: ${data.active_ips.length}개의 활성 IP를 찾았습니다.`);
|
||||
} else {
|
||||
alert('활성 IP를 발견하지 못했습니다.');
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Unknown error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('오류가 발생했습니다: ' + (err.message || err));
|
||||
} finally {
|
||||
// 상태 복구
|
||||
btnScan.disabled = false;
|
||||
btnScan.innerHTML = originalIcon;
|
||||
|
||||
if (progressBar) {
|
||||
// 진행바 초기화
|
||||
progressBar.style.width = '0%';
|
||||
progressBar.textContent = '0%';
|
||||
progressBar.classList.remove('progress-bar-striped', 'progress-bar-animated');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// IP 입력 지우기 버튼
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const btnClear = document.getElementById('btnClearIps');
|
||||
if (btnClear) {
|
||||
btnClear.addEventListener('click', () => {
|
||||
const ipsTextarea = document.getElementById('ips');
|
||||
if (ipsTextarea) {
|
||||
ipsTextarea.value = '';
|
||||
ipsTextarea.dispatchEvent(new Event('input')); // 로컬 스토리지 업데이트 및 카운트 갱신 트리거
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
419
backend/static/js/jobs.js
Normal file
419
backend/static/js/jobs.js
Normal 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 => ({
|
||||
'&': '&', '<': '<', '>': '>',
|
||||
'"': '"', "'": '''
|
||||
}[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
30
backend/static/js/scp.js
Normal 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)}`;
|
||||
}
|
||||
@@ -1,71 +1,151 @@
|
||||
{# backend/templates/admin.html #}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}관리자 패널 - Dell Server Info{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Admin Page</h3>
|
||||
<div class="container py-4">
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1">
|
||||
<i class="bi bi-shield-lock text-primary me-2"></i>관리자 패널
|
||||
</h2>
|
||||
<p class="text-muted mb-0">사용자 관리 및 시스템 설정을 수행합니다.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ url_for('admin.view_logs') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-journal-text me-1"></i>로그 보기
|
||||
</a>
|
||||
<a href="{{ url_for('admin.settings') }}" class="btn btn-primary">
|
||||
<i class="bi bi-gear-fill me-1"></i>시스템 설정
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="mt-2">
|
||||
{% for cat, msg in messages %}
|
||||
<div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert">
|
||||
{{ msg }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- Dashboard Stats -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card border-0 shadow-sm h-100 bg-primary bg-opacity-10">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="rounded-circle bg-primary text-white p-3 me-3">
|
||||
<i class="bi bi-people-fill fs-4"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div>
|
||||
<h6 class="text-primary fw-bold mb-1">총 사용자</h6>
|
||||
<h3 class="mb-0 fw-bold">{{ users|length }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-0 shadow-sm h-100 bg-success bg-opacity-10">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="rounded-circle bg-success text-white p-3 me-3">
|
||||
<i class="bi bi-person-check-fill fs-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-success fw-bold mb-1">활성 사용자</h6>
|
||||
{% set active_users = users | selectattr("is_active") | list %}
|
||||
<h3 class="mb-0 fw-bold">{{ active_users|length }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-0 shadow-sm h-100 bg-warning bg-opacity-10">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="rounded-circle bg-warning text-white p-3 me-3">
|
||||
<i class="bi bi-person-dash-fill fs-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-warning fw-bold mb-1">승인 대기</h6>
|
||||
<h3 class="mb-0 fw-bold">{{ (users|length) - (active_users|length) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Management Table -->
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white border-bottom py-3">
|
||||
<h5 class="mb-0 fw-bold">
|
||||
<i class="bi bi-person-lines-fill text-primary me-2"></i>사용자 목록
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle">
|
||||
<thead>
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th style="width:60px">ID</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th style="width:80px">Active</th>
|
||||
<th style="width:260px">Action</th>
|
||||
<th class="ps-4 py-3 text-secondary text-uppercase small fw-bold" style="width: 60px;">NO</th>
|
||||
<th class="py-3 text-secondary text-uppercase small fw-bold">이름</th>
|
||||
<th class="py-3 text-secondary text-uppercase small fw-bold">ID (Email)</th>
|
||||
<th class="py-3 text-secondary text-uppercase small fw-bold">상태</th>
|
||||
<th class="py-3 text-secondary text-uppercase small fw-bold text-end pe-4">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td class="ps-4 fw-bold text-secondary">{{ loop.index }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div
|
||||
class="avatar-initial rounded-circle bg-light text-primary fw-bold me-2 d-flex align-items-center justify-content-center border"
|
||||
style="width: 32px; height: 32px; font-size: 0.9rem;">
|
||||
{{ user.username[:1] | upper }}
|
||||
</div>
|
||||
<span class="fw-bold text-dark">{{ user.username }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-secondary small font-monospace">{{ user.email }}</td>
|
||||
<td>
|
||||
{% if user.is_active %}
|
||||
<span class="badge bg-success">Yes</span>
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle rounded-pill px-3">
|
||||
<i class="bi bi-check-circle-fill me-1"></i>Active
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">No</span>
|
||||
<span class="badge bg-warning-subtle text-warning border border-warning-subtle rounded-pill px-3">
|
||||
<i class="bi bi-hourglass-split me-1"></i>Pending
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_admin %}
|
||||
<span
|
||||
class="badge bg-primary-subtle text-primary border border-primary-subtle rounded-pill px-2 ms-1">Admin</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not user.is_active %}
|
||||
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}" class="btn btn-success btn-sm me-1">Approve</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}"
|
||||
class="btn btn-danger btn-sm me-1"
|
||||
onclick="return confirm('사용자 {{ user.username }} (ID={{ user.id }}) 를 삭제하시겠습니까?');">
|
||||
Delete
|
||||
</a>
|
||||
<td class="text-end pe-4">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
{% if not user.is_active %}
|
||||
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}"
|
||||
class="btn btn-sm btn-success text-white d-flex align-items-center gap-1" title="가입 승인">
|
||||
<i class="bi bi-check-lg"></i>승인
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Change Password 버튼: 모달 오픈 -->
|
||||
<button type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username | e }}"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#changePasswordModal">
|
||||
Change Password
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary d-flex align-items-center gap-1"
|
||||
data-user-id="{{ user.id }}" data-username="{{ user.username | e }}" data-bs-toggle="modal"
|
||||
data-bs-target="#changePasswordModal">
|
||||
<i class="bi bi-key"></i>비밀번호
|
||||
</button>
|
||||
|
||||
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}"
|
||||
class="btn btn-sm btn-outline-danger d-flex align-items-center gap-1"
|
||||
onclick="return confirm('⚠️ 경고: 사용자 [{{ user.username }}]님을 정말 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.');">
|
||||
<i class="bi bi-trash"></i>삭제
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not users %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5 text-muted">사용자가 없습니다.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -74,96 +154,52 @@
|
||||
</div>
|
||||
|
||||
{# ========== Change Password Modal ========== #}
|
||||
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-labelledby="changePasswordModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<form id="changePasswordForm" method="post" action="">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="changePasswordModalLabel">Change Password</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-header bg-light">
|
||||
<h5 class="modal-title fw-bold">
|
||||
<i class="bi bi-key-fill me-2"></i>비밀번호 변경
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">User:</small>
|
||||
<div id="modalUserInfo" class="fw-bold"></div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="alert alert-light border mb-4 d-flex align-items-center">
|
||||
<i class="bi bi-person-circle fs-4 me-3 text-secondary"></i>
|
||||
<div>
|
||||
<small class="text-muted d-block">대상 사용자</small>
|
||||
<span id="modalUserInfo" class="fw-bold text-dark fs-5"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="newPasswordInput" class="form-label">New password</label>
|
||||
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8" placeholder="Enter new password">
|
||||
<div class="form-text">최소 8자 이상을 권장합니다.</div>
|
||||
<label for="newPasswordInput" class="form-label fw-semibold">새 비밀번호</label>
|
||||
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8"
|
||||
placeholder="최소 8자 이상">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="confirmPasswordInput" class="form-label">Confirm password</label>
|
||||
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required minlength="8" placeholder="Confirm new password">
|
||||
<label for="confirmPasswordInput" class="form-label fw-semibold">비밀번호 확인</label>
|
||||
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required
|
||||
minlength="8" placeholder="비밀번호 재입력">
|
||||
<div id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button id="modalSubmitBtn" type="submit" class="btn btn-primary">Change Password</button>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
||||
<button id="modalSubmitBtn" type="submit" class="btn btn-primary px-4">변경 저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ========== 스크립트: 모달에 사용자 정보 채우기 + 클라이언트 확인 ========== #}
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
(function () {
|
||||
// Bootstrap 5을 사용한다고 가정. data-bs-* 이벤트로 처리.
|
||||
const changePasswordModal = document.getElementById('changePasswordModal');
|
||||
const modalUserInfo = document.getElementById('modalUserInfo');
|
||||
const changePasswordForm = document.getElementById('changePasswordForm');
|
||||
const newPasswordInput = document.getElementById('newPasswordInput');
|
||||
const confirmPasswordInput = document.getElementById('confirmPasswordInput');
|
||||
const pwMismatch = document.getElementById('pwMismatch');
|
||||
|
||||
if (!changePasswordModal) return;
|
||||
|
||||
changePasswordModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget; // 버튼 that triggered the modal
|
||||
const userId = button.getAttribute('data-user-id');
|
||||
const username = button.getAttribute('data-username') || ('ID ' + userId);
|
||||
|
||||
// 표시 텍스트 세팅
|
||||
modalUserInfo.textContent = username + ' (ID: ' + userId + ')';
|
||||
|
||||
// 폼 action 동적 설정: admin.reset_password 라우트 기대
|
||||
// 예: /admin/users/123/reset_password
|
||||
changePasswordForm.action = "{{ url_for('admin.reset_password', user_id=0) }}".replace('/0/', '/' + userId + '/');
|
||||
// 폼 내부 비밀번호 필드 초기화
|
||||
newPasswordInput.value = '';
|
||||
confirmPasswordInput.value = '';
|
||||
confirmPasswordInput.classList.remove('is-invalid');
|
||||
pwMismatch.style.display = 'none';
|
||||
});
|
||||
|
||||
// 폼 제출 전 클라이언트에서 비밀번호 일치 검사
|
||||
changePasswordForm.addEventListener('submit', function (e) {
|
||||
const a = newPasswordInput.value || '';
|
||||
const b = confirmPasswordInput.value || '';
|
||||
if (a.length < 8) {
|
||||
newPasswordInput.focus();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (a !== b) {
|
||||
e.preventDefault();
|
||||
confirmPasswordInput.classList.add('is-invalid');
|
||||
pwMismatch.style.display = 'block';
|
||||
confirmPasswordInput.focus();
|
||||
return;
|
||||
}
|
||||
// 제출 허용 (서버측에서도 반드시 검증)
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
{{ super() }}
|
||||
<script src="{{ url_for('static', filename='js/admin.js') }}"></script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
245
backend/templates/admin_logs.html
Normal file
245
backend/templates/admin_logs.html
Normal file
@@ -0,0 +1,245 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}시스템 로그 - Dell Server Info{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* 전체 레이아웃 */
|
||||
.editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 600px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 툴바 (헤더) */
|
||||
.editor-toolbar {
|
||||
background-color: #252526;
|
||||
border-bottom: 1px solid #333;
|
||||
padding: 0.5rem 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* 에디터 본문 */
|
||||
#monaco-editor-root {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 로딩 인디케이터 */
|
||||
.editor-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: #d4d4d4;
|
||||
font-size: 1.1rem;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h2 class="fw-bold mb-1">
|
||||
<i class="bi bi-terminal text-dark me-2"></i>시스템 로그
|
||||
</h2>
|
||||
<p class="text-muted mb-0 small">최근 생성된 1000줄의 시스템 로그를 실시간으로 확인합니다.</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('admin.admin_panel') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>돌아가기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-container">
|
||||
<!-- Toolbar -->
|
||||
<div class="editor-toolbar">
|
||||
<div class="d-flex gap-2 align-items-center flex-wrap">
|
||||
<div class="input-group input-group-sm" style="width: 250px;">
|
||||
<span class="input-group-text bg-dark border-secondary text-light"><i
|
||||
class="bi bi-search"></i></span>
|
||||
<input type="text" id="logSearch" class="form-control bg-dark border-secondary text-light"
|
||||
placeholder="검색어 입력...">
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<input type="checkbox" class="btn-check" id="checkInfo" checked autocomplete="off">
|
||||
<label class="btn btn-outline-secondary text-light" for="checkInfo">INFO</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="checkWarn" checked autocomplete="off">
|
||||
<label class="btn btn-outline-warning" for="checkWarn">WARN</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="checkError" checked autocomplete="off">
|
||||
<label class="btn btn-outline-danger" for="checkError">ERROR</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-light" id="btnScrollBottom">
|
||||
<i class="bi bi-arrow-down-circle me-1"></i>맨 아래로
|
||||
</button>
|
||||
<a href="{{ url_for('admin.view_logs') }}" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i>새로고침
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor Area -->
|
||||
<div id="monaco-editor-root">
|
||||
<div class="editor-loading">
|
||||
<div class="spinner-border text-light me-3" role="status"></div>
|
||||
<div>로그 뷰어를 불러오는 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<!-- Monaco Editor Loader -->
|
||||
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
||||
|
||||
<script>
|
||||
// 서버에서 전달된 로그 데이터 (Python list -> JS array)
|
||||
// tojson safe 필터 사용
|
||||
const allLogs = {{ logs | tojson | safe }};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (typeof require === 'undefined') {
|
||||
document.querySelector('.editor-loading').innerHTML =
|
||||
'<div class="text-danger"><i class="bi bi-exclamation-triangle me-2"></i>Monaco Editor를 로드할 수 없습니다.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
|
||||
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
var container = document.getElementById('monaco-editor-root');
|
||||
container.innerHTML = ''; // 로딩 제거
|
||||
|
||||
// 1. 커스텀 로그 언어 정의 (간단한 하이라이팅)
|
||||
monaco.languages.register({ id: 'simpleLog' });
|
||||
monaco.languages.setMonarchTokensProvider('simpleLog', {
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/\[INFO\]|INFO:/, 'info-token'],
|
||||
[/\[WARNING\]|\[WARN\]|WARNING:|WARN:/, 'warn-token'],
|
||||
[/\[ERROR\]|ERROR:|Traceback/, 'error-token'],
|
||||
[/\[DEBUG\]|DEBUG:/, 'debug-token'],
|
||||
[/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}/, 'date-token'],
|
||||
[/".*?"/, 'string']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 테마 정의
|
||||
monaco.editor.defineTheme('logTheme', {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: 'info-token', foreground: '4ec9b0' },
|
||||
{ token: 'warn-token', foreground: 'cca700', fontStyle: 'bold' },
|
||||
{ token: 'error-token', foreground: 'f44747', fontStyle: 'bold' },
|
||||
{ token: 'debug-token', foreground: '808080' },
|
||||
{ token: 'date-token', foreground: '569cd6' },
|
||||
],
|
||||
colors: {
|
||||
'editor.background': '#1e1e1e'
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 에디터 생성
|
||||
var editor = monaco.editor.create(container, {
|
||||
value: allLogs.join('\n'),
|
||||
language: 'simpleLog',
|
||||
theme: 'logTheme',
|
||||
readOnly: true,
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: true },
|
||||
fontSize: 13,
|
||||
lineHeight: 19, // 밀도 조절
|
||||
scrollBeyondLastLine: false,
|
||||
lineNumbers: 'on',
|
||||
wordWrap: 'on',
|
||||
renderLineHighlight: 'all',
|
||||
contextmenu: false,
|
||||
padding: { top: 10, bottom: 10 }
|
||||
});
|
||||
|
||||
// 4. 필터링 로직
|
||||
function updateLogs() {
|
||||
const query = document.getElementById('logSearch').value.toLowerCase();
|
||||
const showInfo = document.getElementById('checkInfo').checked;
|
||||
const showWarn = document.getElementById('checkWarn').checked;
|
||||
const showError = document.getElementById('checkError').checked;
|
||||
|
||||
const filtered = allLogs.filter(line => {
|
||||
const lower = line.toLowerCase();
|
||||
|
||||
// 레벨 체크 (매우 단순화)
|
||||
let levelMatch = false;
|
||||
|
||||
const isError = lower.includes('[error]') || lower.includes('error:') || lower.includes('traceback');
|
||||
const isWarn = lower.includes('[warning]') || lower.includes('[warn]') || lower.includes('warn:');
|
||||
const isInfo = lower.includes('[info]') || lower.includes('info:');
|
||||
|
||||
if (isError) {
|
||||
if (showError) levelMatch = true;
|
||||
} else if (isWarn) {
|
||||
if (showWarn) levelMatch = true;
|
||||
} else if (isInfo) {
|
||||
if (showInfo) levelMatch = true;
|
||||
} else {
|
||||
// 레벨 키워드가 없는 줄은 기본적으로 표시 (맥락 유지)
|
||||
levelMatch = true;
|
||||
}
|
||||
|
||||
if (!levelMatch) return false;
|
||||
|
||||
// 검색어 체크
|
||||
if (query && !lower.includes(query)) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// 현재 스크롤 위치 저장? 아니면 항상 아래로? -> 보통 필터링하면 아래로 가는게 편함
|
||||
const currentModel = editor.getModel();
|
||||
if (currentModel) {
|
||||
currentModel.setValue(filtered.join('\n'));
|
||||
}
|
||||
// editor.revealLine(editor.getModel().getLineCount());
|
||||
}
|
||||
|
||||
// 이벤트 연결
|
||||
document.getElementById('logSearch').addEventListener('keyup', updateLogs);
|
||||
document.getElementById('checkInfo').addEventListener('change', updateLogs);
|
||||
document.getElementById('checkWarn').addEventListener('change', updateLogs);
|
||||
document.getElementById('checkError').addEventListener('change', updateLogs);
|
||||
|
||||
// 맨 아래로 버튼
|
||||
document.getElementById('btnScrollBottom').addEventListener('click', function () {
|
||||
editor.revealLine(editor.getModel().getLineCount());
|
||||
});
|
||||
|
||||
// 초기 스크롤 (약간의 지연 후)
|
||||
setTimeout(() => {
|
||||
editor.revealLine(editor.getModel().getLineCount());
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
328
backend/templates/admin_settings.html
Normal file
328
backend/templates/admin_settings.html
Normal file
@@ -0,0 +1,328 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}시스템 설정 - Dell Server Info{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin_settings.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">
|
||||
<i class="bi bi-gear-fill text-primary me-2"></i>시스템 설정
|
||||
</h2>
|
||||
<a href="{{ url_for('admin.admin_panel') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>관리자 패널로 돌아가기
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 텔레그램 봇 설정 섹션 -->
|
||||
<div class="card border shadow-sm mb-4">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 fw-bold">
|
||||
<i class="bi bi-telegram text-info me-2"></i>텔레그램 봇 관리
|
||||
</h5>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addBotModal">
|
||||
<i class="bi bi-plus-lg me-1"></i>봇 추가
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info d-flex align-items-center" role="alert">
|
||||
<i class="bi bi-info-circle-fill me-2 flex-shrink-0 fs-5"></i>
|
||||
<div>
|
||||
등록된 모든 <strong>활성(Active)</strong> 봇에게 알림이 동시에 전송됩니다.<br>
|
||||
<small class="text-muted">알림 종류: 로그인, 회원가입, 로그아웃, 서버 작업 등</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if bots %}
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-4">
|
||||
{% for bot in bots %}
|
||||
<div class="col">
|
||||
<div class="card h-100 border-0 shadow-sm hover-shadow transition-all">
|
||||
<div
|
||||
class="card-header bg-white border-0 pt-3 pb-0 d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="avatar-circle bg-primary bg-opacity-10 text-primary me-2 rounded-circle d-flex align-items-center justify-content-center"
|
||||
style="width: 40px; height: 40px;">
|
||||
<i class="bi bi-robot fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="card-title fw-bold mb-0 text-truncate" style="max-width: 150px;"
|
||||
title="{{ bot.name }}">
|
||||
{{ bot.name }}
|
||||
</h5>
|
||||
<small class="text-muted" style="font-size: 0.75rem;">ID: {{ bot.id }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if bot.is_active %}
|
||||
<span
|
||||
class="badge bg-success-subtle text-success border border-success-subtle rounded-pill px-2">
|
||||
<i class="bi bi-check-circle-fill me-1"></i>Active
|
||||
</span>
|
||||
{% else %}
|
||||
<span
|
||||
class="badge bg-secondary-subtle text-secondary border border-secondary-subtle rounded-pill px-2">
|
||||
<i class="bi bi-dash-circle me-1"></i>Inactive
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text text-muted small mb-3 text-truncate-2" style="min-height: 2.5em;">
|
||||
{{ bot.description or "설명이 없습니다." }}
|
||||
</p>
|
||||
|
||||
<div class="bg-light rounded p-2 mb-2">
|
||||
<div class="d-flex align-items-center text-secondary small mb-1">
|
||||
<i class="bi bi-key me-2 text-primary"></i>
|
||||
<span class="fw-semibold me-1">Token:</span>
|
||||
<span class="font-monospace text-truncate">{{ bot.token[:10] }}...</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center text-secondary small">
|
||||
<i class="bi bi-chat-dots me-2 text-success"></i>
|
||||
<span class="fw-semibold me-1">Chat ID:</span>
|
||||
<span class="font-monospace">{{ bot.chat_id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{% set types = (bot.notification_types or "").split(",") %}
|
||||
{% if 'auth' in types %}
|
||||
<span
|
||||
class="badge bg-info-subtle text-info border border-info-subtle rounded-pill me-1"><i
|
||||
class="bi bi-shield-lock-fill me-1"></i>인증</span>
|
||||
{% endif %}
|
||||
{% if 'activity' in types %}
|
||||
<span
|
||||
class="badge bg-warning-subtle text-warning border border-warning-subtle rounded-pill me-1"><i
|
||||
class="bi bi-activity me-1"></i>활동</span>
|
||||
{% endif %}
|
||||
{% if 'system' in types %}
|
||||
<span
|
||||
class="badge bg-danger-subtle text-danger border border-danger-subtle rounded-pill me-1"><i
|
||||
class="bi bi-gear-fill me-1"></i>시스템</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-white border-0 pt-0 pb-3">
|
||||
<div class="btn-group w-100 shadow-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal"
|
||||
data-bs-target="#editBotModal{{ bot.id }}">
|
||||
<i class="bi bi-pencil me-1"></i>수정
|
||||
</button>
|
||||
|
||||
<button type="submit" form="testForm{{ bot.id }}"
|
||||
class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-send me-1"></i>테스트
|
||||
</button>
|
||||
|
||||
<button type="submit" form="deleteForm{{ bot.id }}"
|
||||
class="btn btn-outline-danger btn-sm" onclick="return confirm('정말 삭제하시겠습니까?');">
|
||||
<i class="bi bi-trash me-1"></i>삭제
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Forms -->
|
||||
<form id="testForm{{ bot.id }}" action="{{ url_for('admin.test_bot', bot_id=bot.id) }}"
|
||||
method="post" class="d-none">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
</form>
|
||||
<form id="deleteForm{{ bot.id }}" action="{{ url_for('admin.delete_bot', bot_id=bot.id) }}"
|
||||
method="post" class="d-none">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 수정 모달 -->
|
||||
<div class="modal fade" id="editBotModal{{ bot.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<form action="{{ url_for('admin.edit_bot', bot_id=bot.id) }}" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header bg-light">
|
||||
<h5 class="modal-title fw-bold">
|
||||
<i class="bi bi-pencil-square me-2"></i>봇 설정 수정
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">이름 (식별용)</label>
|
||||
<input type="text" class="form-control" name="name" value="{{ bot.name }}"
|
||||
required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Bot Token</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-key"></i></span>
|
||||
<input type="text" class="form-control font-monospace" name="token"
|
||||
value="{{ bot.token }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Chat ID</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-chat-dots"></i></span>
|
||||
<input type="text" class="form-control font-monospace" name="chat_id"
|
||||
value="{{ bot.chat_id }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">알림 유형</label>
|
||||
<div class="d-flex gap-3">
|
||||
{% set types = (bot.notification_types or "").split(",") %}
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="notify_types"
|
||||
value="auth" id="edit_auth_{{ bot.id }}" {{ 'checked' if 'auth' in
|
||||
types else '' }}>
|
||||
<label class="form-check-label" for="edit_auth_{{ bot.id }}">
|
||||
인증
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="notify_types"
|
||||
value="activity" id="edit_activity_{{ bot.id }}" {{ 'checked'
|
||||
if 'activity' in types else '' }}>
|
||||
<label class="form-check-label" for="edit_activity_{{ bot.id }}">
|
||||
활동
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="notify_types"
|
||||
value="system" id="edit_system_{{ bot.id }}" {{ 'checked'
|
||||
if 'system' in types else '' }}>
|
||||
<label class="form-check-label" for="edit_system_{{ bot.id }}">
|
||||
시스템
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted">선택한 알림 유형만 전송됩니다</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">설명</label>
|
||||
<textarea class="form-control" name="description"
|
||||
rows="2">{{ bot.description or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="is_active"
|
||||
id="activeCheck{{ bot.id }}" {{ 'checked' if bot.is_active else '' }}>
|
||||
<label class="form-check-label" for="activeCheck{{ bot.id }}">
|
||||
활성화
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
||||
<button type="submit" class="btn btn-primary px-4">저장</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<div class="mb-3">
|
||||
<div class="d-inline-flex align-items-center justify-content-center bg-light rounded-circle"
|
||||
style="width: 80px; height: 80px;">
|
||||
<i class="bi bi-robot fs-1 text-secondary"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="text-muted fw-normal">등록된 텔레그램 봇이 없습니다.</h5>
|
||||
<p class="text-muted small mb-4">우측 상단의 '봇 추가' 버튼을 눌러 알림을 설정하세요.</p>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addBotModal">
|
||||
<i class="bi bi-plus-lg me-1"></i>봇 추가
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 봇 추가 모달 -->
|
||||
<div class="modal fade" id="addBotModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<form action="{{ url_for('admin.add_bot') }}" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header bg-light">
|
||||
<h5 class="modal-title fw-bold">
|
||||
<i class="bi bi-plus-circle me-2"></i>새 텔레그램 봇 추가
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">이름 (식별용)</label>
|
||||
<input type="text" class="form-control" name="name" placeholder="예: 알림용 봇" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Bot Token</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-key"></i></span>
|
||||
<input type="text" class="form-control font-monospace" name="token"
|
||||
placeholder="123456:ABC..." required>
|
||||
</div>
|
||||
<div class="form-text">BotFather에게 받은 API Token</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Chat ID</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-chat-dots"></i></span>
|
||||
<input type="text" class="form-control font-monospace" name="chat_id"
|
||||
placeholder="-100..." required>
|
||||
</div>
|
||||
<div class="form-text">메시지를 받을 채팅방 ID (그룹은 음수)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">알림 유형</label>
|
||||
<div class="d-flex gap-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="notify_types" value="auth"
|
||||
id="add_auth" checked>
|
||||
<label class="form-check-label" for="add_auth">
|
||||
인증
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="notify_types" value="activity"
|
||||
id="add_activity" checked>
|
||||
<label class="form-check-label" for="add_activity">
|
||||
활동
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="notify_types" value="system"
|
||||
id="add_system" checked>
|
||||
<label class="form-check-label" for="add_system">
|
||||
시스템
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted">선택한 알림 유형만 전송됩니다</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">설명</label>
|
||||
<textarea class="form-control" name="description" rows="2" placeholder="선택 사항"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
||||
<button type="submit" class="btn btn-primary px-4">추가</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,35 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="description" content="Dell Server 정보 및 MAC 주소 처리 시스템">
|
||||
|
||||
|
||||
<title>{% block title %}Dell Server Info, MAC 정보 처리{% endblock %}</title>
|
||||
|
||||
|
||||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
|
||||
|
||||
<!-- Bootstrap 5.3.3 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
||||
crossorigin="anonymous">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
integrity="sha384-tViUnnbYAV00FLIhhi3v/dWt3Jxw4gZQcNoSCxCIFNJVCx7/D55/wXsrNIRANwdD"
|
||||
crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
integrity="sha384-tViUnnbYAV00FLIhhi3v/dWt3Jxw4gZQcNoSCxCIFNJVCx7/D55/wXsrNIRANwdD" crossorigin="anonymous">
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Skip to main content (접근성) -->
|
||||
<a href="#main-content" class="visually-hidden-focusable">본문으로 건너뛰기</a>
|
||||
|
||||
{# 플래시 메시지 (좌측 상단 토스트 스타일) #}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 2000; margin-top: 60px;">
|
||||
{% for cat, msg in messages %}
|
||||
<div class="toast align-items-center text-white bg-{{ 'success' if cat == 'success' else 'danger' if cat == 'error' else 'primary' }} border-0 fade show"
|
||||
role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="3000">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body d-flex align-items-center">
|
||||
<i
|
||||
class="bi bi-{{ 'check-circle-fill' if cat == 'success' else 'exclamation-diamond-fill' }} me-2 fs-5"></i>
|
||||
<div>{{ msg }}</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||
<div class="container-fluid">
|
||||
@@ -37,12 +60,8 @@
|
||||
<i class="bi bi-server me-2"></i>
|
||||
Dell Server Info
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav"
|
||||
aria-expanded="false"
|
||||
aria-label="네비게이션 토글">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="네비게이션 토글">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
@@ -53,44 +72,44 @@
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('main.index') }}">
|
||||
<i class="bi bi-hdd-network me-1"></i>ServerInfo
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('xml.xml_management') }}">
|
||||
<i class="bi bi-file-code me-1"></i>XML Management
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('jobs.jobs_page') }}">
|
||||
<i class="bi bi-list-task me-1"></i>Job Monitor
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.is_admin %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.admin_panel') }}">
|
||||
<i class="bi bi-shield-lock me-1"></i>Admin
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.logout') }}">
|
||||
<i class="bi bi-box-arrow-right me-1"></i>Logout
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('main.index') }}">
|
||||
<i class="bi bi-hdd-network me-1"></i>ServerInfo
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('xml.xml_management') }}">
|
||||
<i class="bi bi-file-code me-1"></i>XML Management
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('jobs.jobs_page') }}">
|
||||
<i class="bi bi-list-task me-1"></i>Job Monitor
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.is_admin %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.admin_panel') }}">
|
||||
<i class="bi bi-shield-lock me-1"></i>Admin
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.logout') }}">
|
||||
<i class="bi bi-box-arrow-right me-1"></i>Logout
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.login') }}">
|
||||
<i class="bi bi-box-arrow-in-right me-1"></i>Login
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.register') }}">
|
||||
<i class="bi bi-person-plus me-1"></i>Register
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.login') }}">
|
||||
<i class="bi bi-box-arrow-in-right me-1"></i>Login
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.register') }}">
|
||||
<i class="bi bi-person-plus me-1"></i>Register
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -98,7 +117,12 @@
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="{% if request.endpoint in ['auth.login', 'auth.register', 'auth.reset_password'] %}container mt-5{% else %}container mt-4 container-card{% endif %}">
|
||||
<main id="main-content"
|
||||
class="{% if request.endpoint in ['auth.login', 'auth.register', 'auth.reset_password'] %}container mt-5{% else %}container mt-4 container-card{% endif %}">
|
||||
|
||||
{# 플래시 메시지 (컨텐츠 상단 표시) #}
|
||||
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
@@ -110,17 +134,36 @@
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap 5.3.3 JS Bundle (Popper.js 포함) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Socket.IO (필요한 경우만) -->
|
||||
{% if config.USE_SOCKETIO %}
|
||||
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"
|
||||
integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"
|
||||
integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4"
|
||||
crossorigin="anonymous"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
<!-- Auto-hide Toasts -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var toastElList = [].slice.call(document.querySelectorAll('.toast'));
|
||||
var toastList = toastElList.map(function (toastEl) {
|
||||
// 부트스트랩 토스트 인스턴스 생성 (autohide: true 기본값)
|
||||
var toast = new bootstrap.Toast(toastEl, { delay: 3000 });
|
||||
toast.show();
|
||||
|
||||
// 3초 후 자동으로 DOM에서 제거하고 싶다면 이벤트 리스너 추가 가능
|
||||
toastEl.addEventListener('hidden.bs.toast', function () {
|
||||
// toastEl.remove(); // 필요시 제거
|
||||
});
|
||||
return toast;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@@ -1,51 +1,170 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}XML 편집: {{ filename }} - Dell Server Info{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* 전체 레이아웃 */
|
||||
.editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 160px);
|
||||
/* 헤더/푸터 제외 높이 (조정 가능) */
|
||||
min-height: 600px;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 툴바 (헤더) */
|
||||
.editor-toolbar {
|
||||
background-color: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding: 0.75rem 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editor-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* 에디터 본문 */
|
||||
#monaco-editor-root {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 로딩 인디케이터 */
|
||||
.editor-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: #64748b;
|
||||
font-size: 1.2rem;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Edit XML File</title>
|
||||
<style>
|
||||
::-webkit-scrollbar { width: 12px; }
|
||||
::-webkit-scrollbar-track { background: #f1f1f1; }
|
||||
::-webkit-scrollbar-thumb { background-color: #888; border-radius: 6px; }
|
||||
::-webkit-scrollbar-thumb:hover { background-color: #555; }
|
||||
html { scrollbar-width: thin; scrollbar-color: #888 #f1f1f1; }
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #ccc;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
textarea:hover { background-color: #f0f0f0; }
|
||||
.xml-list-item {
|
||||
padding: 10px;
|
||||
background-color: #ffffff;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
.xml-list-item:hover { background-color: #e9ecef; cursor: pointer; }
|
||||
.btn { margin-top: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h3>Edit XML File: <strong>{{ filename }}</strong></h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="form-group">
|
||||
<label for="xmlContent">XML Content</label>
|
||||
<textarea id="xmlContent" name="content" class="form-control" rows="20">{{ content }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success mt-3">Save Changes</button>
|
||||
<a href="{{ url_for('xml.xml_management') }}" class="btn btn-secondary mt-3">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
<div class="container-fluid py-4 h-100">
|
||||
<!-- Breadcrumb / Navigation -->
|
||||
<div class="mb-3 d-flex align-items-center">
|
||||
<a href="{{ url_for('xml.xml_management') }}" class="text-decoration-none text-muted small fw-bold">
|
||||
<i class="bi bi-arrow-left me-1"></i>목록으로 돌아가기
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<form id="editorForm" method="post" style="height: 100%;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<!-- Monaco Editor의 내용은 submit 시 이 textarea에 동기화됨 -->
|
||||
<textarea name="content" id="hiddenContent" style="display:none;">{{ content }}</textarea>
|
||||
|
||||
<div class="editor-container">
|
||||
<!-- Toolbar -->
|
||||
<div class="editor-toolbar">
|
||||
<div class="editor-title">
|
||||
<i class="bi bi-filetype-xml text-primary fs-4"></i>
|
||||
<span>{{ filename }}</span>
|
||||
<span class="badge bg-light text-secondary border ms-2">XML</span>
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
<!-- 포맷팅 버튼 (Monaco 기능 호출) -->
|
||||
<button type="button" class="btn btn-white border text-dark btn-sm fw-bold" id="btnFormat">
|
||||
<i class="bi bi-magic me-1 text-info"></i> 자동 정렬
|
||||
</button>
|
||||
<!-- 저장 버튼 -->
|
||||
<button type="submit" class="btn btn-primary btn-sm fw-bold px-4">
|
||||
<i class="bi bi-save me-1"></i> 저장하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor Area -->
|
||||
<div id="monaco-editor-root">
|
||||
<div class="editor-loading">
|
||||
<div class="spinner-border text-primary me-3" role="status"></div>
|
||||
<div>에디터를 불러오는 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<!-- Monaco Editor Loader -->
|
||||
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (typeof require === 'undefined') {
|
||||
document.querySelector('.editor-loading').innerHTML =
|
||||
'<div class="text-danger"><i class="bi bi-exclamation-triangle me-2"></i>Monaco Editor를 로드할 수 없습니다. 인터넷 연결을 확인하거나 CDN 차단을 확인하세요.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
|
||||
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
// 초기 컨텐츠 가져오기
|
||||
var initialContent = document.getElementById('hiddenContent').value;
|
||||
var container = document.getElementById('monaco-editor-root');
|
||||
|
||||
// 기존 로딩 메시지 제거
|
||||
container.innerHTML = '';
|
||||
|
||||
// 에디터 생성
|
||||
var editor = monaco.editor.create(container, {
|
||||
value: initialContent,
|
||||
language: 'xml',
|
||||
theme: 'vs', // or 'vs-dark'
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: true },
|
||||
fontSize: 14,
|
||||
scrollBeyondLastLine: false,
|
||||
lineNumbers: 'on',
|
||||
formatOnPaste: true,
|
||||
formatOnType: true,
|
||||
wordWrap: 'on'
|
||||
});
|
||||
|
||||
// 1. 폼 제출 시 에디터 내용을 textarea에 동기화
|
||||
document.getElementById('editorForm').addEventListener('submit', function () {
|
||||
document.getElementById('hiddenContent').value = editor.getValue();
|
||||
});
|
||||
|
||||
// 2. 자동 정렬(Format) 버튼 기능 연결
|
||||
document.getElementById('btnFormat').addEventListener('click', function () {
|
||||
editor.getAction('editor.action.formatDocument').run();
|
||||
});
|
||||
|
||||
// 3. Ctrl+S 저장 단축키 지원
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
|
||||
document.getElementById('editorForm').requestSubmit();
|
||||
});
|
||||
|
||||
}, function (err) {
|
||||
// 로드 실패 시 에러 표시
|
||||
document.querySelector('.editor-loading').innerHTML =
|
||||
'<div class="text-danger"><i class="bi bi-exclamation-triangle me-2"></i>에디터 리소스 로드 실패: ' + err.message + '</div>';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -7,6 +8,7 @@
|
||||
<title>Dell iDRAC 멀티 서버 펌웨어 관리</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/idrac_style.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
@@ -84,43 +86,66 @@
|
||||
</div>
|
||||
|
||||
<!-- 펌웨어 버전 관리 탭 -->
|
||||
<div id="versions-tab" class="tab-content">
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>📦 펌웨어 최신 버전 관리</h2>
|
||||
<div class="header-actions">
|
||||
<button onclick="syncDellCatalog('PowerEdge R750')" class="btn btn-success">🔄 Dell에서 버전 가져오기</button>
|
||||
<button onclick="showAddVersionModal()" class="btn btn-primary">+ 버전 추가</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="versions-tab" class="tab-content">
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>📦 펌웨어 최신 버전 관리</h2>
|
||||
<div class="header-actions">
|
||||
<button onclick="syncDellCatalog('PowerEdge R750')" class="btn btn-success">🔄 Dell에서 버전
|
||||
가져오기</button>
|
||||
<button onclick="syncFromDRM()" class="btn btn-info">📁 DRM에서 가져오기</button>
|
||||
<button onclick="showAddVersionModal()" class="btn btn-primary">+ 버전 추가</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="server-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>컴포넌트</th>
|
||||
<th>최신 버전</th>
|
||||
<th>서버 모델</th>
|
||||
<th>릴리즈 날짜</th>
|
||||
<th>중요</th>
|
||||
<th width="150">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="version-list">
|
||||
<tr>
|
||||
<td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="table-container">
|
||||
<table class="server-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>컴포넌트</th>
|
||||
<th>최신 버전</th>
|
||||
<th>서버 모델</th>
|
||||
<th>릴리즈 날짜</th>
|
||||
<th>중요</th>
|
||||
<th width="150">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="version-list">
|
||||
<tr>
|
||||
<td colspan="6" class="empty-message">등록된 버전 정보가 없습니다</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 버전 비교 결과 탭 -->
|
||||
<div id="comparison-tab" class="tab-content">
|
||||
<section class="card">
|
||||
<h2>🔍 펌웨어 버전 비교 결과</h2>
|
||||
<div id="comparison-result"></div>
|
||||
<div class="card-header">
|
||||
<h2>🔍 펌웨어 버전 비교 결과</h2>
|
||||
<button onclick="compareSelectedFirmware()" class="btn btn-primary">🔄 다시 비교</button>
|
||||
</div>
|
||||
|
||||
<!-- 로딩 상태 -->
|
||||
<div id="comparison-loading" style="display: none;">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>버전 비교 중...</p>
|
||||
</div>
|
||||
|
||||
<!-- 비교 결과 -->
|
||||
<div id="comparison-result">
|
||||
<div class="info-text">
|
||||
<p><strong>📋 사용 방법:</strong></p>
|
||||
<ol style="margin-top: 10px; margin-left: 20px; line-height: 1.8;">
|
||||
<li>"서버 관리" 탭에서 비교할 서버를 선택하세요</li>
|
||||
<li>"버전 비교" 버튼을 클릭하세요</li>
|
||||
<li>현재 설치된 펌웨어 버전과 최신 버전을 자동으로 비교합니다</li>
|
||||
<li>업데이트가 필요한 컴포넌트를 확인할 수 있습니다</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -242,11 +267,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="card" id="result-section" style="display:none;">
|
||||
<div id="result-content"></div>
|
||||
</section>
|
||||
<section class="card" id="result-section" style="display:none;">
|
||||
<div id="result-content"></div>
|
||||
</section>
|
||||
|
||||
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/idrac_main.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@@ -3,20 +3,7 @@
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
|
||||
{# 플래시 메시지 #}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="position-fixed top-0 end-0 p-3" style="z-index: 1050">
|
||||
{% for cat, msg in messages %}
|
||||
<div class="alert alert-{{ cat }} alert-dismissible fade show shadow-lg" role="alert">
|
||||
<i class="bi bi-{{ 'check-circle' if cat == 'success' else 'exclamation-triangle' }} me-2"></i>
|
||||
{{ msg }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
|
||||
{# 헤더 섹션 #}
|
||||
<div class="row mb-4">
|
||||
@@ -34,51 +21,78 @@
|
||||
{# IP 처리 카드 #}
|
||||
<div class="col-lg-6">
|
||||
<div class="card border shadow-sm h-100">
|
||||
<div class="card-header bg-primary text-white border-0 py-2">
|
||||
<h6 class="mb-0 fw-semibold">
|
||||
<div class="card-header bg-light border-0 py-2">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-hdd-network me-2"></i>
|
||||
IP 처리
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form id="ipForm" method="post" action="{{ url_for('main.process_ips') }}">
|
||||
<div class="card-body p-4 h-100 d-flex flex-column">
|
||||
<form id="ipForm" method="post" action="{{ url_for('main.process_ips') }}" class="h-100 d-flex flex-column">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
{# 스크립트 선택 #}
|
||||
<div class="mb-3">
|
||||
<label for="script" class="form-label">스크립트 선택</label>
|
||||
<select id="script" name="script" class="form-select" required>
|
||||
<select id="script" name="script" class="form-select" required autocomplete="off">
|
||||
<option value="">스크립트를 선택하세요</option>
|
||||
{% for script in scripts %}
|
||||
{% if grouped_scripts %}
|
||||
{% for category, s_list in grouped_scripts.items() %}
|
||||
<optgroup label="{{ category }}">
|
||||
{% for script in s_list %}
|
||||
<option value="{{ script }}">{{ script }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# 만약 grouped_scripts가 없는 경우(하위 호환) #}
|
||||
{% for script in scripts %}
|
||||
<option value="{{ script }}">{{ script }}</option>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{# XML 파일 선택 (조건부) #}
|
||||
<div class="mb-3" id="xmlFileGroup" style="display:none;">
|
||||
<label for="xmlFile" class="form-label">XML 파일 선택</label>
|
||||
<select id="xmlFile" name="xmlFile" class="form-select">
|
||||
<option value="">XML 파일 선택</option>
|
||||
{% for xml_file in xml_files %}
|
||||
<option value="{{ xml_file }}">{{ xml_file }}</option>
|
||||
<option value="{{ xml_file }}">{{ xml_file }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{# IP 주소 입력 #}
|
||||
<div class="mb-3">
|
||||
<label for="ips" class="form-label">
|
||||
IP 주소 (각 줄에 하나)
|
||||
<span class="badge bg-secondary ms-2" id="ipLineCount">0 대설정</span>
|
||||
<div class="mb-3 flex-grow-1 d-flex flex-column">
|
||||
<label for="ips" class="form-label w-100 d-flex justify-content-between align-items-end mb-2">
|
||||
<span class="mb-1">
|
||||
IP 주소
|
||||
<span class="badge bg-secondary ms-1" id="ipLineCount">0</span>
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary px-2 py-1" id="btnClearIps"
|
||||
title="입력 내용 지우기" style="font-size: 0.75rem;">
|
||||
<i class="bi bi-trash me-1"></i>지우기
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary px-2 py-1" id="btnStartScan"
|
||||
title="10.10.0.1 ~ 255 자동 스캔" style="font-size: 0.75rem;">
|
||||
<i class="bi bi-search me-1"></i>IP 스캔
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<textarea id="ips" name="ips" rows="4" class="form-control font-monospace"
|
||||
placeholder="예: 192.168.1.1 192.168.1.2 192.168.1.3" required></textarea>
|
||||
<textarea id="ips" name="ips" class="form-control font-monospace flex-grow-1"
|
||||
placeholder="예: 192.168.1.1 192.168.1.2" required style="resize: none;"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
처리
|
||||
</button>
|
||||
<div class="mt-auto">
|
||||
<button type="submit"
|
||||
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move">
|
||||
<div class="rounded-circle bg-primary bg-opacity-10 text-primary p-1">
|
||||
<i class="bi bi-play-circle-fill fs-5"></i>
|
||||
</div>
|
||||
<span class="fw-medium text-dark" style="font-size: 0.8rem;">처리 시작</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,8 +101,8 @@
|
||||
{# 공유 작업 카드 #}
|
||||
<div class="col-lg-6">
|
||||
<div class="card border shadow-sm h-100">
|
||||
<div class="card-header bg-success text-white border-0 py-2">
|
||||
<h6 class="mb-0 fw-semibold">
|
||||
<div class="card-header bg-light border-0 py-2">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-share me-2"></i>
|
||||
공유 작업
|
||||
</h6>
|
||||
@@ -102,24 +116,38 @@
|
||||
서버 리스트 (덮어쓰기)
|
||||
<span class="badge bg-secondary ms-2" id="serverLineCount">0 대설정</span>
|
||||
</label>
|
||||
<textarea id="server_list_content" name="server_list_content" rows="8"
|
||||
class="form-control font-monospace" style="font-size: 0.95rem;"
|
||||
placeholder="서버 리스트를 입력하세요..."></textarea>
|
||||
<textarea id="server_list_content" name="server_list_content" rows="8" class="form-control font-monospace"
|
||||
style="font-size: 0.95rem;" placeholder="서버 리스트를 입력하세요..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}"
|
||||
class="btn btn-secondary">
|
||||
MAC to Excel
|
||||
</button>
|
||||
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}"
|
||||
class="btn btn-success">
|
||||
GUID to Excel
|
||||
</button>
|
||||
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}"
|
||||
class="btn btn-warning">
|
||||
GPU to Excel
|
||||
</button>
|
||||
<div class="row g-2">
|
||||
<div class="col-4">
|
||||
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}"
|
||||
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move h-100">
|
||||
<div class="rounded-circle bg-primary bg-opacity-10 text-primary p-1">
|
||||
<i class="bi bi-file-earmark-spreadsheet fs-5"></i>
|
||||
</div>
|
||||
<span class="fw-medium text-dark" style="font-size: 0.8rem;">MAC to Excel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}"
|
||||
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move h-100">
|
||||
<div class="rounded-circle bg-success bg-opacity-10 text-success p-1">
|
||||
<i class="bi bi-file-earmark-excel fs-5"></i>
|
||||
</div>
|
||||
<span class="fw-medium text-dark" style="font-size: 0.8rem;">GUID to Excel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}"
|
||||
class="btn btn-white bg-white border shadow-sm w-100 py-2 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move h-100">
|
||||
<div class="rounded-circle bg-danger bg-opacity-10 text-danger p-1">
|
||||
<i class="bi bi-gpu-card fs-5"></i>
|
||||
</div>
|
||||
<span class="fw-medium text-dark" style="font-size: 0.8rem;">GPU to Excel</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -137,8 +165,8 @@
|
||||
<span class="fw-semibold">처리 진행률</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 25px;">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success"
|
||||
role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success"
|
||||
role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
<span class="fw-semibold">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,104 +187,152 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4 file-tools">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xxl-5 g-3 align-items-end">
|
||||
<div class="d-flex flex-column gap-3">
|
||||
|
||||
<!-- ZIP 다운로드 -->
|
||||
<div class="col">
|
||||
<label class="form-label text-nowrap">ZIP 다운로드</label>
|
||||
<form method="post" action="{{ url_for('main.download_zip') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="zip_filename" placeholder="파일명" required>
|
||||
<button class="btn btn-primary" type="submit">다운로드</button>
|
||||
<!-- 상단: 입력형 도구 (다운로드/백업) -->
|
||||
<div class="row g-2">
|
||||
<!-- ZIP 다운로드 -->
|
||||
<div class="col-6">
|
||||
<div class="card h-100 border-primary-subtle bg-primary-subtle bg-opacity-10">
|
||||
<div class="card-body p-2 d-flex flex-column justify-content-center">
|
||||
<h6 class="card-title fw-bold text-primary mb-1 small" style="font-size: 0.75rem;">
|
||||
<i class="bi bi-file-earmark-zip me-1"></i>ZIP
|
||||
</h6>
|
||||
<form method="post" action="{{ url_for('main.download_zip') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control border-primary-subtle form-control-sm"
|
||||
name="zip_filename" placeholder="파일명" required
|
||||
style="font-size: 0.75rem; padding: 0.2rem 0.5rem;">
|
||||
<button class="btn btn-primary btn-sm px-2" type="submit">
|
||||
<i class="bi bi-download" style="font-size: 0.75rem;"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 파일 백업 -->
|
||||
<div class="col">
|
||||
<label class="form-label text-nowrap">파일 백업</label>
|
||||
<form method="post" action="{{ url_for('main.backup_files') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="backup_prefix" placeholder="PO로 시작">
|
||||
<button class="btn btn-success" type="submit">백업</button>
|
||||
<!-- 파일 백업 -->
|
||||
<div class="col-6">
|
||||
<div class="card h-100 border-success-subtle bg-success-subtle bg-opacity-10">
|
||||
<div class="card-body p-2 d-flex flex-column justify-content-center">
|
||||
<h6 class="card-title fw-bold text-success mb-1 small" style="font-size: 0.75rem;">
|
||||
<i class="bi bi-hdd-network me-1"></i>백업
|
||||
</h6>
|
||||
<form method="post" action="{{ url_for('main.backup_files') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control border-success-subtle form-control-sm"
|
||||
name="backup_prefix" placeholder="ex)PO-20251117-0015_20251223_판교_R6615(TY1A)"
|
||||
style="font-size: 0.75rem; padding: 0.2rem 0.5rem;">
|
||||
<button class="btn btn-success btn-sm px-2" type="submit">
|
||||
<i class="bi bi-save" style="font-size: 0.75rem;"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MAC 파일 이동 -->
|
||||
<div class="col">
|
||||
<label class="form-label text-nowrap">MAC 파일 이동</label>
|
||||
<form id="macMoveForm" method="post" action="{{ url_for('utils.move_mac_files') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn-warning w-100" type="submit">MAC Move</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- 하단: 원클릭 액션 (파일 정리) -->
|
||||
<div class="card bg-light border-0">
|
||||
<div class="card-body p-3">
|
||||
<small class="text-muted fw-bold text-uppercase mb-2 d-block">
|
||||
<i class="bi bi-folder-symlink me-1"></i>파일 정리 (Quick Move)
|
||||
</small>
|
||||
<div class="row g-2">
|
||||
<!-- MAC Move -->
|
||||
<div class="col-4">
|
||||
<form id="macMoveForm" method="post" action="{{ url_for('utils.move_mac_files') }}" class="h-100">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button
|
||||
class="btn btn-white bg-white border shadow-sm w-100 h-100 py-1 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move"
|
||||
type="submit">
|
||||
<div class="rounded-circle bg-primary bg-opacity-10 text-primary p-1">
|
||||
<i class="bi bi-cpu fs-6"></i>
|
||||
</div>
|
||||
<span class="fw-medium text-dark" style="font-size: 0.75rem;">MAC</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- GUID 파일 이동 -->
|
||||
<div class="col">
|
||||
<label class="form-label text-nowrap">GUID 파일 이동</label>
|
||||
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn-info w-100" type="submit">GUID Move</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- GUID Move -->
|
||||
<div class="col-4">
|
||||
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}" class="h-100">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button
|
||||
class="btn btn-white bg-white border shadow-sm w-100 h-100 py-1 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move"
|
||||
type="submit">
|
||||
<div class="rounded-circle bg-success bg-opacity-10 text-success p-1">
|
||||
<i class="bi bi-fingerprint fs-6"></i>
|
||||
</div>
|
||||
<span class="fw-medium text-dark" style="font-size: 0.75rem;">GUID</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- GPU 파일 이동 -->
|
||||
<div class="col">
|
||||
<label class="form-label text-nowrap">GPU 파일 이동</label>
|
||||
<form id="gpuMoveForm" method="post" action="{{ url_for('utils.move_gpu_files') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn-secondary w-100" type="submit">GPU Move</button>
|
||||
</form>
|
||||
<div class="col-4">
|
||||
<form id="gpuMoveForm" method="post" action="{{ url_for('utils.move_gpu_files') }}" class="h-100">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button
|
||||
class="btn btn-white bg-white border shadow-sm w-100 h-100 py-1 d-flex flex-column align-items-center justify-content-center gap-1 btn-quick-move"
|
||||
type="submit">
|
||||
<div class="rounded-circle bg-danger bg-opacity-10 text-danger p-1">
|
||||
<i class="bi bi-gpu-card fs-6"></i>
|
||||
</div>
|
||||
<span class="fw-medium text-dark" style="font-size: 0.75rem;">GPU</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 처리된 파일 목록 #}
|
||||
<div class="row mb-4 processed-list">
|
||||
<div class="col">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-light border-0 py-2 d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-files me-2"></i>
|
||||
처리된 파일 목록
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{% if files_to_display and files_to_display|length > 0 %}
|
||||
{# 처리된 파일 목록 #}
|
||||
<div class="row mb-4 processed-list">
|
||||
<div class="col">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-light border-0 py-2 d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-files me-2"></i>
|
||||
처리된 파일 목록
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{% if files_to_display and files_to_display|length > 0 %}
|
||||
<div class="row g-3">
|
||||
{% for file_info in files_to_display %}
|
||||
<div class="col-auto">
|
||||
<div class="file-card-compact border rounded p-2 text-center">
|
||||
<a href="{{ url_for('main.download_file', filename=file_info.file) }}"
|
||||
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
|
||||
download title="{{ file_info.name or file_info.file }}">
|
||||
{{ file_info.name or file_info.file }}
|
||||
</a>
|
||||
<div class="file-card-buttons d-flex gap-2 justify-content-center">
|
||||
<button type="button" class="btn btn-sm btn-outline btn-view-processed flex-fill"
|
||||
data-bs-toggle="modal" data-bs-target="#fileViewModal"
|
||||
data-folder="idrac_info"
|
||||
data-filename="{{ file_info.file }}">
|
||||
보기
|
||||
<div class="col-auto">
|
||||
<div class="file-card-compact border rounded p-2 text-center">
|
||||
<a href="{{ url_for('main.download_file', filename=file_info.file) }}"
|
||||
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2" download
|
||||
title="{{ file_info.name or file_info.file }}">
|
||||
{{ file_info.name or file_info.file }}
|
||||
</a>
|
||||
<div class="file-card-buttons d-flex gap-2 justify-content-center">
|
||||
<button type="button" class="btn btn-sm btn-outline btn-view-processed flex-fill"
|
||||
data-bs-toggle="modal" data-bs-target="#fileViewModal" data-folder="idrac_info"
|
||||
data-filename="{{ file_info.file }}">
|
||||
보기
|
||||
</button>
|
||||
<form action="{{ url_for('main.delete_file', filename=file_info.file) }}" method="post"
|
||||
class="d-inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline btn-delete-processed flex-fill"
|
||||
onclick="return confirm('삭제하시겠습니까?');">
|
||||
삭제
|
||||
</button>
|
||||
<form action="{{ url_for('main.delete_file', filename=file_info.file) }}"
|
||||
method="post" class="d-inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline btn-delete-processed flex-fill"
|
||||
onclick="return confirm('삭제하시겠습니까?');">
|
||||
삭제
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -267,54 +343,53 @@
|
||||
|
||||
<!-- 이전 페이지 -->
|
||||
{% if page > 1 %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ url_for('main.index', page=page-1) }}">
|
||||
<i class="bi bi-chevron-left"></i> 이전
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ url_for('main.index', page=page-1) }}">
|
||||
<i class="bi bi-chevron-left"></i> 이전
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-left"></i> 이전</span>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-left"></i> 이전</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- 페이지 번호 (최대 10개 표시) -->
|
||||
{% set start_page = ((page - 1) // 10) * 10 + 1 %}
|
||||
{% set end_page = [start_page + 9, total_pages]|min %}
|
||||
{% for p in range(start_page, end_page + 1) %}
|
||||
<li class="page-item {% if p == page %}active{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('main.index', page=p) }}">{{ p }}</a>
|
||||
</li>
|
||||
<li class="page-item {% if p == page %}active{% endif %}">
|
||||
<a class="page-link" href="{{ url_for('main.index', page=p) }}">{{ p }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
<!-- 다음 페이지 -->
|
||||
{% if page < total_pages %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ url_for('main.index', page=page+1) }}">
|
||||
다음 <i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
{% if page < total_pages %} <li class="page-item">
|
||||
<a class="page-link" href="{{ url_for('main.index', page=page+1) }}">
|
||||
다음 <i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">다음 <i class="bi bi-chevron-right"></i></span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
<!-- /페이지네이션 -->
|
||||
|
||||
{% else %}
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
|
||||
<p class="text-muted mb-0">표시할 파일이 없습니다.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 백업된 파일 목록 #}
|
||||
<div class="row backup-list">
|
||||
@@ -328,55 +403,52 @@
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{% if backup_files and backup_files|length > 0 %}
|
||||
<div class="list-group">
|
||||
{% for date, info in backup_files.items() %}
|
||||
<div class="list-group-item border rounded mb-2 p-0 overflow-hidden">
|
||||
<div class="d-flex justify-content-between align-items-center p-3 bg-light">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-calendar3 text-primary me-2"></i>
|
||||
<strong>{{ date }}</strong>
|
||||
<span class="badge bg-primary ms-3">{{ info.count }} 파일</span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#collapse-{{ loop.index }}"
|
||||
aria-expanded="false">
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="collapse-{{ loop.index }}" class="collapse">
|
||||
<div class="p-3">
|
||||
<div class="row g-3">
|
||||
{% for file in info.files %}
|
||||
<div class="col-auto">
|
||||
<div class="file-card-compact border rounded p-2 text-center">
|
||||
<a href="{{ url_for('main.download_backup_file', date=date, filename=file) }}"
|
||||
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
|
||||
download title="{{ file }}">
|
||||
{{ file.rsplit('.', 1)[0] }}
|
||||
</a>
|
||||
<div class="file-card-single-button">
|
||||
<button type="button" class="btn btn-sm btn-outline btn-view-backup w-100"
|
||||
data-bs-toggle="modal" data-bs-target="#fileViewModal"
|
||||
data-folder="backup"
|
||||
data-date="{{ date }}"
|
||||
data-filename="{{ file }}">
|
||||
보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="list-group">
|
||||
{% for date, info in backup_files.items() %}
|
||||
<div class="list-group-item border rounded mb-2 p-0 overflow-hidden">
|
||||
<div class="d-flex justify-content-between align-items-center p-3 bg-light">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-calendar3 text-primary me-2"></i>
|
||||
<strong>{{ date }}</strong>
|
||||
<span class="badge bg-primary ms-3">{{ info.count }} 파일</span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse-{{ loop.index }}" aria-expanded="false">
|
||||
<i class="bi bi-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="collapse-{{ loop.index }}" class="collapse">
|
||||
<div class="p-3">
|
||||
<div class="row g-3">
|
||||
{% for file in info.files %}
|
||||
<div class="col-auto">
|
||||
<div class="file-card-compact border rounded p-2 text-center">
|
||||
<a href="{{ url_for('main.download_backup_file', date=date, filename=file) }}"
|
||||
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2" download
|
||||
title="{{ file }}">
|
||||
{{ file.rsplit('.', 1)[0] }}
|
||||
</a>
|
||||
<div class="file-card-single-button">
|
||||
<button type="button" class="btn btn-sm btn-outline btn-view-backup w-100"
|
||||
data-bs-toggle="modal" data-bs-target="#fileViewModal" data-folder="backup"
|
||||
data-date="{{ date }}" data-filename="{{ file }}">
|
||||
보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
|
||||
<p class="text-muted mb-0">백업된 파일이 없습니다.</p>
|
||||
</div>
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
|
||||
<p class="text-muted mb-0">백업된 파일이 없습니다.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -397,8 +469,8 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
|
||||
</div>
|
||||
<div class="modal-body bg-light">
|
||||
<pre id="fileViewContent" class="mb-0 p-3 bg-white border rounded font-monospace"
|
||||
style="white-space:pre-wrap;word-break:break-word;max-height:70vh;">불러오는 중...</pre>
|
||||
<pre id="fileViewContent" class="mb-0 p-3 bg-white border rounded font-monospace"
|
||||
style="white-space:pre-wrap;word-break:break-word;max-height:70vh;">불러오는 중...</pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
@@ -408,279 +480,76 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
|
||||
<!-- Tom Select CSS (Bootstrap 5 theme) -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.bootstrap5.min.css" rel="stylesheet">
|
||||
<style>
|
||||
/* Tom Select 미세 조정 */
|
||||
.ts-wrapper.form-select {
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ts-control {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.ts-wrapper.focus .ts-control {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
/* Quick Move 버튼 호버 효과 */
|
||||
.btn-quick-move {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.btn-quick-move:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .1) !important;
|
||||
background-color: #f8f9fa !important;
|
||||
border-color: #dee2e6 !important;
|
||||
}
|
||||
|
||||
.btn-quick-move:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<style>
|
||||
/* ===== 공통 파일 카드 컴팩트 스타일 ===== */
|
||||
.file-card-compact {
|
||||
transition: all 0.2s ease;
|
||||
background: #fff;
|
||||
min-width: 120px;
|
||||
max-width: 200px;
|
||||
}
|
||||
{{ super() }}
|
||||
|
||||
.file-card-compact:hover {
|
||||
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.file-card-compact a {
|
||||
font-size: 0.9rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
/* ===== 목록별 버튼 분리 규칙 ===== */
|
||||
|
||||
/* 처리된 파일 목록 전용 컨테이너(보기/삭제 2열) */
|
||||
.processed-list .file-card-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
/* 보기(처리된) */
|
||||
.processed-list .btn-view-processed {
|
||||
border-color: #3b82f6;
|
||||
color: #1d4ed8;
|
||||
padding: .425rem .6rem;
|
||||
font-size: .8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.processed-list .btn-view-processed:hover {
|
||||
background: rgba(59,130,246,.08);
|
||||
}
|
||||
|
||||
/* 삭제(처리된) — 더 작게 */
|
||||
.processed-list .btn-delete-processed {
|
||||
border-color: #ef4444;
|
||||
color: #b91c1c;
|
||||
padding: .3rem .5rem;
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.processed-list .btn-delete-processed:hover {
|
||||
background: rgba(239,68,68,.08);
|
||||
}
|
||||
|
||||
/* 백업 파일 목록 전용 컨테이너(단일 버튼) */
|
||||
.backup-list .file-card-single-button {
|
||||
display: flex;
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
/* 보기(백업) — 강조 색상 */
|
||||
.backup-list .btn-view-backup {
|
||||
width: 100%;
|
||||
border-color: #10b981;
|
||||
color: #047857;
|
||||
padding: .45rem .75rem;
|
||||
font-size: .8125rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.backup-list .btn-view-backup:hover {
|
||||
background: rgba(16,185,129,.08);
|
||||
}
|
||||
|
||||
/* ===== 백업 파일 날짜 헤더 ===== */
|
||||
.list-group-item .bg-light {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
.list-group-item:hover .bg-light {
|
||||
background-color: #e9ecef !important;
|
||||
}
|
||||
|
||||
/* ===== 진행바 애니메이션 ===== */
|
||||
.progress {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-bar {
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
|
||||
/* ===== 반응형 텍스트 ===== */
|
||||
@media (max-width: 768px) {
|
||||
.card-body {
|
||||
padding: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 스크롤바 스타일링(모달) ===== */
|
||||
.modal-body pre::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
.modal-body pre::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
|
||||
.modal-body pre::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }
|
||||
.modal-body pre::-webkit-scrollbar-thumb:hover { background: #555; }
|
||||
</style>
|
||||
<!-- Tom Select JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 스크립트 선택 시 XML 드롭다운 토글
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const TARGET_SCRIPT = "02-set_config.py";
|
||||
const scriptSelect = document.getElementById('script');
|
||||
const xmlGroup = document.getElementById('xmlFileGroup');
|
||||
|
||||
function toggleXml() {
|
||||
if (!scriptSelect || !xmlGroup) return;
|
||||
if (scriptSelect.value === TARGET_SCRIPT) {
|
||||
xmlGroup.style.display = 'block';
|
||||
xmlGroup.classList.add('fade-in');
|
||||
} else {
|
||||
xmlGroup.style.display = 'none';
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Tom Select 초기화
|
||||
// 모바일 등 환경 고려, 검색 가능하게 설정
|
||||
if (document.getElementById('script')) {
|
||||
new TomSelect("#script", {
|
||||
create: false,
|
||||
sortField: {
|
||||
field: "text",
|
||||
direction: "asc"
|
||||
},
|
||||
placeholder: "스크립트를 검색하거나 선택하세요...",
|
||||
plugins: ['clear_button'],
|
||||
allowEmptyOption: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (scriptSelect) {
|
||||
toggleXml();
|
||||
scriptSelect.addEventListener('change', toggleXml);
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 파일 보기 모달
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const modalEl = document.getElementById('fileViewModal');
|
||||
const titleEl = document.getElementById('fileViewModalLabel');
|
||||
const contentEl = document.getElementById('fileViewContent');
|
||||
|
||||
if (modalEl) {
|
||||
modalEl.addEventListener('show.bs.modal', async (ev) => {
|
||||
const btn = ev.relatedTarget;
|
||||
const folder = btn?.getAttribute('data-folder') || '';
|
||||
const date = btn?.getAttribute('data-date') || '';
|
||||
const filename = btn?.getAttribute('data-filename') || '';
|
||||
|
||||
titleEl.innerHTML = `<i class="bi bi-file-text me-2"></i>${filename || '파일'}`;
|
||||
contentEl.textContent = '불러오는 중...';
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (folder) params.set('folder', folder);
|
||||
if (date) params.set('date', date);
|
||||
if (filename) params.set('filename', filename);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
|
||||
const data = await res.json();
|
||||
contentEl.textContent = data?.content ?? '(빈 파일)';
|
||||
} catch (e) {
|
||||
contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 진행바 업데이트
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
window.updateProgress = function (val) {
|
||||
const bar = document.getElementById('progressBar');
|
||||
if (!bar) return;
|
||||
const v = Math.max(0, Math.min(100, Number(val) || 0));
|
||||
bar.style.width = v + '%';
|
||||
bar.setAttribute('aria-valuenow', v);
|
||||
bar.innerHTML = `<span class="fw-semibold">${v}%</span>`;
|
||||
};
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// CSRF 토큰
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 공통 POST 함수
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
async function postFormAndHandle(url) {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8',
|
||||
},
|
||||
});
|
||||
|
||||
const ct = (res.headers.get('content-type') || '').toLowerCase();
|
||||
|
||||
if (ct.includes('application/json')) {
|
||||
const data = await res.json();
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || ('HTTP ' + res.status));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
return { success: true, html: true };
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// MAC 파일 이동
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const macForm = document.getElementById('macMoveForm');
|
||||
if (macForm) {
|
||||
macForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = macForm.querySelector('button');
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
|
||||
try {
|
||||
await postFormAndHandle(macForm.action);
|
||||
location.reload();
|
||||
} catch (err) {
|
||||
alert('MAC 이동 중 오류: ' + (err?.message || err));
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalHtml;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// GUID 파일 이동
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const guidForm = document.getElementById('guidMoveForm');
|
||||
if (guidForm) {
|
||||
guidForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = guidForm.querySelector('button');
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
|
||||
try {
|
||||
await postFormAndHandle(guidForm.action);
|
||||
location.reload();
|
||||
} catch (err) {
|
||||
alert('GUID 이동 중 오류: ' + (err?.message || err));
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalHtml;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 알림 자동 닫기
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.alert').forEach(alert => {
|
||||
const bsAlert = new bootstrap.Alert(alert);
|
||||
bsAlert.close();
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/index.js') }}?v={{ range(1, 100000) | random }}"></script>
|
||||
|
||||
<!-- 외부 script.js 파일 (IP 폼 처리 로직 포함) -->
|
||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||
{% endblock %}
|
||||
<script src="{{ url_for('static', filename='script.js') }}?v={{ range(1, 100000) | random }}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}iDRAC Job Queue 모니터링 (Redfish){% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/jobs.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<!-- CSRF Token for JavaScript -->
|
||||
<script>
|
||||
const csrfToken = "{{ csrf_token() }}";
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/jobs.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-3">
|
||||
<!-- 헤더 -->
|
||||
@@ -40,8 +52,8 @@
|
||||
<small class="text-muted d-block mb-2">
|
||||
한 줄에 하나씩 입력 (쉼표/세미콜론/공백 구분 가능, # 주석 지원)
|
||||
</small>
|
||||
<textarea id="ipInput" class="form-control font-monospace" rows="4"
|
||||
placeholder="10.10.0.11 10.10.0.12 # 주석 가능"></textarea>
|
||||
<textarea id="ipInput" class="form-control font-monospace" rows="4"
|
||||
placeholder="10.10.0.11 10.10.0.12 # 주석 가능"></textarea>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">총 <strong id="ip-count">0</strong>개 IP</small>
|
||||
</div>
|
||||
@@ -60,13 +72,13 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-auto">
|
||||
<button id="btn-refresh" class="btn btn-primary btn-sm" disabled>
|
||||
<i class="bi bi-arrow-clockwise"></i> 지금 새로고침
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-auto">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch">
|
||||
@@ -75,7 +87,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-auto">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="showCompletedSwitch" checked>
|
||||
@@ -150,494 +162,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #6c757d;
|
||||
}
|
||||
.status-dot.active {
|
||||
background-color: #198754;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* ↓↓↓ 여기부터 추가 ↓↓↓ */
|
||||
|
||||
/* 테이블 텍스트 한 줄 처리 */
|
||||
#jobs-table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#jobs-table td {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* 열별 너비 고정 */
|
||||
#jobs-table td:nth-child(1) { max-width: 110px; } /* IP */
|
||||
#jobs-table td:nth-child(2) { max-width: 160px; font-size: 0.85rem; } /* JID */
|
||||
#jobs-table td:nth-child(3) { max-width: 200px; } /* 작업명 */
|
||||
#jobs-table td:nth-child(4) { max-width: 180px; } /* 상태 */
|
||||
#jobs-table td:nth-child(5) { max-width: 120px; } /* 진행률 */
|
||||
#jobs-table td:nth-child(6) { max-width: 300px; } /* 메시지 */
|
||||
#jobs-table td:nth-child(7) { max-width: 150px; } /* 시간 */
|
||||
|
||||
/* 마우스 올리면 전체 텍스트 보기 */
|
||||
#jobs-table td:hover {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* ↑↑↑ 여기까지 추가 ↑↑↑ */
|
||||
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #6c757d;
|
||||
}
|
||||
.status-dot.active {
|
||||
background-color: #198754;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const csrfToken = "{{ csrf_token() }}";
|
||||
|
||||
// ========== 전역 변수 ==========
|
||||
let CONFIG = {
|
||||
grace_minutes: 60,
|
||||
recency_hours: 24,
|
||||
poll_interval_ms: 10000
|
||||
};
|
||||
|
||||
let monitoringOn = false;
|
||||
let pollTimer = null;
|
||||
let lastRenderHash = "";
|
||||
|
||||
// ========== Elements ==========
|
||||
const $ = id => document.getElementById(id);
|
||||
const $body = $('jobs-body');
|
||||
const $last = $('last-updated');
|
||||
const $loading = $('loading-indicator');
|
||||
const $btn = $('btn-refresh');
|
||||
const $auto = $('autoRefreshSwitch');
|
||||
const $ipInput = $('ipInput');
|
||||
const $ipCount = $('ip-count');
|
||||
const $btnLoad = $('btn-load-file');
|
||||
const $btnApply = $('btn-apply');
|
||||
const $monSw = $('monitorSwitch');
|
||||
const $statusDot = $('status-dot');
|
||||
const $statusText = $('status-text');
|
||||
const $showCompleted = $('showCompletedSwitch');
|
||||
const $pollInterval = $('poll-interval');
|
||||
|
||||
// Stats
|
||||
const $statTotal = $('stat-total');
|
||||
const $statRunning = $('stat-running');
|
||||
const $statCompleted = $('stat-completed');
|
||||
const $statError = $('stat-error');
|
||||
|
||||
// ========== LocalStorage Keys ==========
|
||||
const LS_IPS = 'idrac_job_ips';
|
||||
const LS_MON = 'idrac_monitor_on';
|
||||
const LS_AUTO = 'idrac_monitor_auto';
|
||||
const LS_SHOW_COMPLETED = 'idrac_show_completed';
|
||||
|
||||
// ========== 유틸리티 ==========
|
||||
function parseIps(text) {
|
||||
if (!text) return [];
|
||||
const raw = text.replace(/[,;]+/g, '\n');
|
||||
const out = [], seen = new Set();
|
||||
raw.split('\n').forEach(line => {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
parts.forEach(p => {
|
||||
if (!p || p.startsWith('#')) return;
|
||||
if (!seen.has(p)) {
|
||||
seen.add(p);
|
||||
out.push(p);
|
||||
}
|
||||
});
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
function getIpsFromUI() { return parseIps($ipInput.value); }
|
||||
function setIpsToUI(ips) {
|
||||
$ipInput.value = (ips || []).join('\n');
|
||||
updateIpCount();
|
||||
}
|
||||
function updateIpCount() {
|
||||
const ips = getIpsFromUI();
|
||||
$ipCount.textContent = ips.length;
|
||||
}
|
||||
function saveIps() {
|
||||
localStorage.setItem(LS_IPS, JSON.stringify(getIpsFromUI()));
|
||||
updateIpCount();
|
||||
}
|
||||
function loadIps() {
|
||||
try {
|
||||
const v = JSON.parse(localStorage.getItem(LS_IPS) || "[]");
|
||||
return Array.isArray(v) ? v : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s).replace(/[&<>"']/g, m => ({
|
||||
'&': '&', '<': '<', '>': '>',
|
||||
'"': '"', "'": '''
|
||||
}[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 %}
|
||||
@@ -1,305 +1,525 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}XML 설정 관리 & 배포 - Dell Server Info{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<!-- Existing SCP CSS for legacy support or specific components -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/scp.css') }}">
|
||||
<!-- Overriding/New Styles for Modern Look -->
|
||||
<style>
|
||||
/* 드래그 앤 드롭 영역 스타일 */
|
||||
.drop-zone {
|
||||
border: 2px dashed #cbd5e1;
|
||||
border-radius: 12px;
|
||||
background-color: #f8fafc;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.drop-zone:hover,
|
||||
.drop-zone.dragover {
|
||||
border-color: #3b82f6;
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
.drop-zone-icon {
|
||||
font-size: 2.5rem;
|
||||
color: #64748b;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.drop-zone-text {
|
||||
font-weight: 500;
|
||||
color: #334155;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.drop-zone-hint {
|
||||
font-size: 0.875rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.drop-zone input[type="file"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 카드 그리드 스타일 (index.html과 유사) */
|
||||
.xml-file-card {
|
||||
background: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.xml-file-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.file-icon-wrapper {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
|
||||
color: #2563eb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin-bottom: 0.5rem;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
flex: 1;
|
||||
padding: 0.4rem;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>XML 파일 관리</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px 0;
|
||||
}
|
||||
<div class="container-fluid py-4">
|
||||
|
||||
.main-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.card-header-custom {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px 8px 0 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
padding: 8px 24px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
border: none;
|
||||
padding: 6px 16px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #dc3545;
|
||||
border: none;
|
||||
padding: 6px 16px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-file-label {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.custom-file-label::after {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border-radius: 0 3px 3px 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* 아이콘 + 뱃지 스타일 */
|
||||
.file-list {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.icon-badge-item {
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition: all 0.3s;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.icon-badge-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #007bff;
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.icon-badge-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-icon-small {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: #007bff;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-name-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-name-badge {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.badge-custom {
|
||||
background-color: #e7f3ff;
|
||||
color: #007bff;
|
||||
padding: 3px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 30px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
/* 스크롤바 스타일 */
|
||||
.file-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.file-list::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.file-list::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.file-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="main-title">XML 파일 관리</h1>
|
||||
<p class="subtitle">XML 파일을 업로드하고 관리할 수 있습니다</p>
|
||||
|
||||
<!-- XML 파일 업로드 폼 -->
|
||||
<div class="card">
|
||||
<div class="card-header-custom">
|
||||
<i class="fas fa-cloud-upload-alt mr-2"></i>파일 업로드
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="upload-section">
|
||||
<div class="form-group mb-2">
|
||||
<label for="xmlFile" class="form-label">XML 파일 선택</label>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="xmlFile" name="xmlFile" accept=".xml" onchange="updateFileName(this)">
|
||||
<label class="custom-file-label" for="xmlFile" id="fileLabel">파일을 선택하세요</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-upload mr-1"></i>업로드
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Header Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h2 class="fw-bold mb-1">
|
||||
<i class="bi bi-file-earmark-code text-primary me-2"></i>
|
||||
설정 파일 관리
|
||||
</h2>
|
||||
<p class="text-muted mb-0">서버 설정(XML) 파일을 업로드, 관리 및 배포합니다.</p>
|
||||
</div>
|
||||
|
||||
<!-- XML 파일 목록 -->
|
||||
<div class="card">
|
||||
<div class="card-header-custom">
|
||||
<i class="fas fa-list mr-2"></i>파일 목록
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if xml_files %}
|
||||
<div class="file-list">
|
||||
{% for xml_file in xml_files %}
|
||||
<div class="icon-badge-item">
|
||||
<div class="icon-badge-left">
|
||||
<div class="file-icon-small">
|
||||
<i class="fas fa-file-code"></i>
|
||||
</div>
|
||||
<div class="file-name-section">
|
||||
<span class="file-name-badge" title="{{ xml_file }}">{{ xml_file }}</span>
|
||||
<span class="badge-custom">XML</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<!-- 파일 편집 버튼 -->
|
||||
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}" class="btn btn-success btn-sm">
|
||||
<i class="fas fa-edit"></i> 편집
|
||||
</a>
|
||||
<!-- 파일 삭제 버튼 -->
|
||||
<form action="{{ url_for('xml.delete_xml', filename=xml_file) }}" method="POST" style="display:inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('정말 삭제하시겠습니까?')">
|
||||
<i class="fas fa-trash"></i> 삭제
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-message">
|
||||
<i class="fas fa-folder-open" style="font-size: 2rem; color: #ddd;"></i>
|
||||
<p class="mt-2 mb-0">파일이 없습니다.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-auto align-self-end">
|
||||
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#exportModal">
|
||||
<i class="bi bi-server me-2"></i>iDRAC에서 추출
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
function updateFileName(input) {
|
||||
const fileName = input.files[0]?.name || '파일을 선택하세요';
|
||||
document.getElementById('fileLabel').textContent = fileName;
|
||||
<div class="row g-4">
|
||||
<!-- Left: Upload Section (30% on large screens) -->
|
||||
<div class="col-lg-4 col-xl-3">
|
||||
<div class="card border shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
|
||||
<h6 class="fw-bold mb-0 text-dark">
|
||||
<i class="bi bi-cloud-upload me-2 text-primary"></i>파일 업로드
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data"
|
||||
id="uploadForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
|
||||
<div class="drop-zone" id="dropZone">
|
||||
<input type="file" name="xmlFile" id="xmlFile" accept=".xml"
|
||||
onchange="handleFileSelect(this)">
|
||||
<div class="drop-zone-icon">
|
||||
<i class="bi bi-file-earmark-arrow-up"></i>
|
||||
</div>
|
||||
<div class="drop-zone-text" id="dropZoneText">
|
||||
클릭하여 파일 선택<br>또는 파일을 여기로 드래그
|
||||
</div>
|
||||
<div class="drop-zone-hint">XML 파일만 지원됩니다.</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 mt-3 shadow-sm">
|
||||
<i class="bi bi-upload me-2"></i>업로드 시작
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-light mt-4 border" role="alert">
|
||||
<h6 class="alert-heading fs-6 fw-bold"><i class="bi bi-info-circle me-2"></i>도움말</h6>
|
||||
<p class="mb-0 fs-small text-muted" style="font-size: 0.85rem;">
|
||||
업로드된 XML 파일을 사용하여 여러 서버에 동일한 설정을 일괄 배포할 수 있습니다.
|
||||
'비교' 기능을 사용하여 버전 간 차이를 확인하세요.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: File List (70%) -->
|
||||
<div class="col-lg-8 col-xl-9">
|
||||
<div class="card border shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom py-3 d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
<h6 class="fw-bold mb-0 text-dark me-3">
|
||||
<i class="bi bi-list-check me-2 text-success"></i>파일 목록
|
||||
</h6>
|
||||
<span class="badge bg-light text-dark border">{{ xml_files|length }}개 파일</span>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-outline-secondary" id="compareBtn"
|
||||
data-url="{{ url_for('scp.diff_scp') }}" onclick="compareSelected()">
|
||||
<i class="bi bi-arrow-left-right me-1"></i>선택 비교
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body bg-light">
|
||||
{% if xml_files %}
|
||||
<!-- 카드 크기 조정: 한 줄에 4개(xxl), 3개(xl) 등으로 조금 더 키움 -->
|
||||
<div class="row row-cols-1 row-cols-lg-2 row-cols-xl-3 row-cols-xxl-4 g-3">
|
||||
{% for xml_file in xml_files %}
|
||||
<div class="col">
|
||||
<div class="xml-file-card position-relative p-3 h-100 d-flex flex-column">
|
||||
<div class="position-absolute top-0 end-0 p-2 me-1">
|
||||
<input type="checkbox" class="form-check-input file-selector border-secondary"
|
||||
value="{{ xml_file }}" style="cursor: pointer;">
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="file-icon-wrapper me-3 mb-0 shadow-sm"
|
||||
style="width: 42px; height: 42px; font-size: 1.4rem;">
|
||||
<i class="bi bi-filetype-xml"></i>
|
||||
</div>
|
||||
<div class="file-name text-truncate fw-bold mb-0 text-dark"
|
||||
style="max-width: 140px; font-size: 0.95rem;" title="{{ xml_file }}">
|
||||
{{ xml_file }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto pt-3 border-top">
|
||||
<div class="d-flex gap-2">
|
||||
<!-- 배포 버튼 -->
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-primary flex-fill d-flex align-items-center justify-content-center gap-1"
|
||||
onclick="openDeployModal('{{ xml_file }}')" title="배포">
|
||||
<i class="bi bi-send-fill"></i> <span class="small fw-bold">배포</span>
|
||||
</button>
|
||||
|
||||
<!-- 편집 버튼 -->
|
||||
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}"
|
||||
class="btn btn-sm btn-white border flex-fill d-flex align-items-center justify-content-center gap-1 text-dark bg-white"
|
||||
title="편집">
|
||||
<i class="bi bi-pencil-fill text-secondary"></i> <span
|
||||
class="small fw-bold">편집</span>
|
||||
</a>
|
||||
|
||||
<!-- 삭제 버튼 -->
|
||||
<form action="{{ url_for('xml.delete_xml', filename=xml_file) }}" method="POST"
|
||||
class="d-flex flex-fill m-0" style="min-width: 0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<button type="submit"
|
||||
class="btn btn-sm btn-white border w-100 d-flex align-items-center justify-content-center gap-1 text-danger bg-white"
|
||||
onclick="return confirm('정말 삭제하시겠습니까?')" title="삭제">
|
||||
<i class="bi bi-trash-fill"></i> <span class="small fw-bold">삭제</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5 my-5">
|
||||
<div class="mb-3 text-secondary" style="font-size: 3rem; opacity: 0.3;">
|
||||
<i class="bi bi-folder2-open"></i>
|
||||
</div>
|
||||
<h5 class="text-secondary fw-normal">등록된 파일이 없습니다.</h5>
|
||||
<p class="text-muted">좌측 패널에서 XML 파일을 업로드해주세요.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Export Modal (Include existing modal logic but restyled) -->
|
||||
<div class="modal fade" id="exportModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg">
|
||||
<form action="{{ url_for('scp.export_scp') }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title fs-6 fw-bold"><i class="bi bi-download me-2"></i>iDRAC 설정 내보내기</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="alert alert-info py-2 small mb-4">
|
||||
<i class="bi bi-info-circle-fill me-2"></i> CIFS 네트워크 공유 폴더가 필요합니다.
|
||||
</div>
|
||||
|
||||
<h6 class="text-primary fw-bold mb-3 small text-uppercase">대상 iDRAC</h6>
|
||||
<div class="form-floating mb-2">
|
||||
<input type="text" class="form-control" id="targetIp" name="target_ip" placeholder="IP"
|
||||
required>
|
||||
<label for="targetIp">iDRAC IP Address</label>
|
||||
</div>
|
||||
<div class="row g-2 mb-4">
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" id="targetUser" name="username"
|
||||
placeholder="User" required>
|
||||
<label for="targetUser">Username</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" id="targetPwd" name="password"
|
||||
placeholder="Pwd" required>
|
||||
<label for="targetPwd">Password</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-success fw-bold mb-3 small text-uppercase">저장소 (CIFS Share)</h6>
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-8">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="share_ip" placeholder="IP" required>
|
||||
<label>Share IP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="share_name" placeholder="Name" required>
|
||||
<label>Share Name</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-floating mb-2">
|
||||
<input type="text" class="form-control" name="filename" placeholder="Filename" required>
|
||||
<label>저장할 파일명 (예: backup.xml)</label>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="share_user" placeholder="User">
|
||||
<label>Share User</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" name="share_pwd" placeholder="Pwd">
|
||||
<label>Share Password</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">취소</button>
|
||||
<button type="submit" class="btn btn-primary px-4">내보내기 실행</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deploy Modal -->
|
||||
<div class="modal fade" id="deployModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg">
|
||||
<form action="{{ url_for('scp.import_scp') }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title fs-6 fw-bold"><i class="bi bi-send-fill me-2"></i>설정 배포 (Import)</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="alert alert-warning py-2 small mb-4">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i> 적용 후 서버가 재부팅될 수 있습니다.
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold small text-muted">배포 파일</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="bi bi-file-code"></i></span>
|
||||
<input type="text" class="form-control fw-bold text-primary" id="deployFilename"
|
||||
name="filename" readonly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-primary fw-bold mb-3 small text-uppercase">대상 iDRAC</h6>
|
||||
<div class="form-floating mb-2">
|
||||
<input type="text" class="form-control" name="target_ip" placeholder="IP" required>
|
||||
<label>iDRAC IP</label>
|
||||
</div>
|
||||
<div class="row g-2 mb-4">
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="username" placeholder="User" required>
|
||||
<label>Username</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" name="password" placeholder="Pwd" required>
|
||||
<label>Password</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-success fw-bold mb-3 small text-uppercase">소스 위치 (CIFS Share)</h6>
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-8">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="share_ip" placeholder="IP" required>
|
||||
<label>Share IP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="share_name" placeholder="Name" required>
|
||||
<label>Share Name</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-2 mb-4">
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="share_user" placeholder="User">
|
||||
<label>Share User</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" name="share_pwd" placeholder="Pwd">
|
||||
<label>Share Password</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-floating">
|
||||
<select class="form-select" name="import_mode" id="importMode">
|
||||
<option value="Replace">전체 교체 (Replace)</option>
|
||||
<option value="Append">변경분만 적용 (Append)</option>
|
||||
</select>
|
||||
<label for="importMode">적용 모드</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">취소</button>
|
||||
<button type="submit" class="btn btn-danger px-4">배포 시작</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/scp.js') }}"></script>
|
||||
<script>
|
||||
// 드래그 앤 드롭 파일 처리
|
||||
function handleFileSelect(input) {
|
||||
const fileName = input.files[0]?.name;
|
||||
const dropZoneText = document.getElementById('dropZoneText');
|
||||
if (fileName) {
|
||||
dropZoneText.innerHTML = `<span class="text-primary fw-bold">${fileName}</span><br><span class="text-muted small">파일이 선택되었습니다.</span>`;
|
||||
document.getElementById('dropZone').classList.add('border-primary', 'bg-light');
|
||||
} else {
|
||||
dropZoneText.innerHTML = '클릭하여 파일 선택<br>또는 파일을 여기로 드래그';
|
||||
document.getElementById('dropZone').classList.remove('border-primary', 'bg-light');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
}
|
||||
|
||||
// 드래그 효과
|
||||
const dropZone = document.getElementById('dropZone');
|
||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
|
||||
function preventDefaults(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, highlight, false);
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, unhighlight, false);
|
||||
});
|
||||
|
||||
function highlight(e) {
|
||||
dropZone.classList.add('dragover');
|
||||
}
|
||||
|
||||
function unhighlight(e) {
|
||||
dropZone.classList.remove('dragover');
|
||||
}
|
||||
|
||||
dropZone.addEventListener('drop', handleDrop, false);
|
||||
|
||||
function handleDrop(e) {
|
||||
const dt = e.dataTransfer;
|
||||
const files = dt.files;
|
||||
const input = document.getElementById('xmlFile');
|
||||
|
||||
if (files.length > 0) {
|
||||
input.files = files; // input에 파일 할당
|
||||
handleFileSelect(input);
|
||||
}
|
||||
}
|
||||
|
||||
// 툴팁 초기화 및 자동 닫기
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
```
|
||||
173
backend/templates/scp_diff.html
Normal file
173
backend/templates/scp_diff.html
Normal file
@@ -0,0 +1,173 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}설정 파일 비교: {{ file1 }} vs {{ file2 }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<!-- Monaco Diff Editor Styles -->
|
||||
<style>
|
||||
.diff-page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 140px);
|
||||
min-height: 600px;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.diff-toolbar {
|
||||
background-color: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding: 0.75rem 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.diff-files {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.file-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: #fff;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.diff-arrow {
|
||||
color: #94a3b8;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
#monaco-diff-root {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4 h-100">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="mb-3">
|
||||
<a href="{{ url_for('xml.xml_management') }}" class="text-decoration-none text-muted small fw-bold">
|
||||
<i class="bi bi-arrow-left me-1"></i>목록으로 돌아가기
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="diff-page-container">
|
||||
<!-- Toolbar -->
|
||||
<div class="diff-toolbar">
|
||||
<div class="diff-files">
|
||||
<div class="file-badge text-danger border-danger-subtle bg-danger-subtle">
|
||||
<i class="bi bi-file-earmark-minus"></i>
|
||||
<span>{{ file1 }}</span> <!-- Original -->
|
||||
</div>
|
||||
<i class="bi bi-arrow-right diff-arrow"></i>
|
||||
<div class="file-badge text-success border-success-subtle bg-success-subtle">
|
||||
<i class="bi bi-file-earmark-plus"></i>
|
||||
<span>{{ file2 }}</span> <!-- Modified -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-secondary" onclick="toggleInlineDiff()" id="viewToggleBtn">
|
||||
<i class="bi bi-layout-split me-1"></i>Inline View
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monaco Diff Editor -->
|
||||
<div id="monaco-diff-root"></div>
|
||||
</div>
|
||||
|
||||
<!-- Raw Content Hidden Inputs (Jinja2 will escape HTML entities automatically) -->
|
||||
<textarea id="hidden_content1" style="display:none;">{{ content1 }}</textarea>
|
||||
<textarea id="hidden_content2" style="display:none;">{{ content2 }}</textarea>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
||||
<script>
|
||||
let diffEditor = null;
|
||||
let isInline = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// 1. Check for loader failure
|
||||
if (typeof require === 'undefined') {
|
||||
document.getElementById('monaco-diff-root').innerHTML =
|
||||
'<div class="d-flex justify-content-center align-items-center h-100 text-danger">' +
|
||||
'<i class="bi bi-exclamation-triangle me-2"></i>Monaco Editor 리소스를 불러올 수 없습니다. 인터넷 연결을 확인하세요.' +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
|
||||
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
// 2. Read content from hidden textareas
|
||||
// Jinja2가 HTML escaping을 처리하므로, .value를 통해 원본 XML을 얻을 수 있습니다.
|
||||
const content1 = document.getElementById('hidden_content1').value;
|
||||
const content2 = document.getElementById('hidden_content2').value;
|
||||
|
||||
const originalModel = monaco.editor.createModel(content1, 'xml');
|
||||
const modifiedModel = monaco.editor.createModel(content2, 'xml');
|
||||
|
||||
const container = document.getElementById('monaco-diff-root');
|
||||
|
||||
// 3. Create Diff Editor
|
||||
diffEditor = monaco.editor.createDiffEditor(container, {
|
||||
theme: 'vs',
|
||||
originalEditable: false,
|
||||
readOnly: true,
|
||||
renderSideBySide: true, // Default: Split View
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: true },
|
||||
diffWordWrap: 'off'
|
||||
});
|
||||
|
||||
diffEditor.setModel({
|
||||
original: originalModel,
|
||||
modified: modifiedModel
|
||||
});
|
||||
|
||||
// 네비게이션 기능 추가
|
||||
diffEditor.getNavigator();
|
||||
|
||||
}, function (err) {
|
||||
document.getElementById('monaco-diff-root').innerHTML =
|
||||
'<div class="d-flex justify-content-center align-items-center h-100 text-danger">' +
|
||||
'<i class="bi bi-exclamation-triangle me-2"></i>에디터 로드 실패: ' + err.message + '</div>';
|
||||
});
|
||||
});
|
||||
|
||||
function toggleInlineDiff() {
|
||||
if (!diffEditor) return;
|
||||
|
||||
isInline = !isInline;
|
||||
diffEditor.updateOptions({
|
||||
renderSideBySide: !isInline
|
||||
});
|
||||
|
||||
const btn = document.getElementById('viewToggleBtn');
|
||||
if (isInline) {
|
||||
btn.innerHTML = '<i class="bi bi-layout-sidebar me-1"></i>Split View';
|
||||
} else {
|
||||
btn.innerHTML = '<i class="bi bi-layout-split me-1"></i>Inline View';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,21 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<body>
|
||||
<h1>Uploaded XML Files</h1>
|
||||
<ul>
|
||||
{% for file in files %}
|
||||
<li>
|
||||
{{ file }}
|
||||
<a href="{{ url_for('download_xml', filename=file) }}">Download</a>
|
||||
<a href="{{ url_for('edit_xml', filename=file) }}">Edit</a>
|
||||
<form action="{{ url_for('delete_xml', filename=file) }}" method="post" style="display:inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit">Delete</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{{ url_for('upload_xml') }}">Upload new file</a>
|
||||
</body>
|
||||
<h1>Uploaded XML Files</h1>
|
||||
<ul>
|
||||
{% for file in files %}
|
||||
<li>
|
||||
{{ file }}
|
||||
<a href="{{ url_for('download_xml', filename=file) }}">Download</a>
|
||||
<a href="{{ url_for('edit_xml', filename=file) }}">Edit</a>
|
||||
<form action="{{ url_for('delete_xml', filename=file) }}" method="post" style="display:inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<button type="submit">Delete</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{{ url_for('upload_xml') }}">Upload new file</a>
|
||||
{% endblock %}
|
||||
10
check_telegram.py
Normal file
10
check_telegram.py
Normal 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}")
|
||||
14
config.py
14
config.py
@@ -40,9 +40,22 @@ class Config:
|
||||
# ── 보안
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", "change-me") # 반드시 환경변수로 고정 권장
|
||||
|
||||
# ── Redfish
|
||||
REDFISH_TIMEOUT = int(os.environ.get("REDFISH_TIMEOUT", 15))
|
||||
REDFISH_VERIFY_SSL = os.environ.get("REDFISH_VERIFY_SSL", "false").lower() == "true"
|
||||
|
||||
# ── DB (환경변수 DATABASE_URL 있으면 그 값을 우선 사용)
|
||||
sqlite_path = (INSTANCE_DIR / "site.db").as_posix()
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", f"sqlite:///{sqlite_path}")
|
||||
|
||||
# DB 연결 안정성 옵션 (SQLite 락/쓰레드 문제 완화)
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
"pool_pre_ping": True,
|
||||
"pool_recycle": 280,
|
||||
}
|
||||
if SQLALCHEMY_DATABASE_URI.startswith("sqlite"):
|
||||
SQLALCHEMY_ENGINE_OPTIONS["connect_args"] = {"check_same_thread": False}
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
# ── Telegram (미설정 시 기능 비활성처럼 동작)
|
||||
@@ -74,6 +87,7 @@ class Config:
|
||||
|
||||
# ── 세션
|
||||
PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.getenv("SESSION_MINUTES", 30)))
|
||||
SESSION_PERMANENT = True # 브라우저 닫아도 세션 유지 (타임아웃까지)
|
||||
|
||||
# ── SocketIO
|
||||
# threading / eventlet / gevent 중 선택. 기본은 threading (Windows 안정)
|
||||
|
||||
684
data/logs/2025-11-24.log
Normal file
684
data/logs/2025-11-24.log
Normal 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[32mGET / HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:24:22,174 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:22] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:24:23,389 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:23] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:24:24,274 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:24:24] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:25:28,286 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:25:28] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:26:47,190 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:47] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:26:48,967 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:26:48] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:27:19,614 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:19] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:27:21,247 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:21] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:27:34,552 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:34] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:27:35,706 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:27:35] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:43:06,124 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:06] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:43:14,371 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:43:14] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:47:47,464 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:47] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:47:52,022 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:47:52] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:49:45,817 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:45] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:49:50,447 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:49:50] "[36mGET /static/script.js HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[32mGET /login HTTP/1.1[0m" 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] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:57:26,975 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:26] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:57:27,029 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:27] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:57:34,994 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:57:34] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:58:42,633 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:42] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:58:49,993 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:58:49] "[36mGET /static/script.js HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 16:59:42,429 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 16:59:42] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[32mGET /index HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:06:18,643 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:18] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:06:24,438 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:06:24] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:09:34,444 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:34] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:09:40,090 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:09:40] "[36mGET /static/script.js HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:17:17,035 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:17] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:17:17,095 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:17] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:17:37,035 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:37] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:17:50,054 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:50] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:17:50,077 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:17:50] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:18:04,663 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:04] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:18:09,512 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:09] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[33mGET /moniter HTTP/1.1[0m" 404 -
|
||||
2025-11-24 17:18:17,452 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:18:17] "[33mGET /favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:36:31,230 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:31] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 17:36:37,665 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 17:36:37] "[36mGET /static/script.js HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 18:05:51,446 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:51] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-24 18:05:52,833 [INFO] werkzeug: 127.0.0.1 - - [24/Nov/2025 18:05:52] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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
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
557
data/logs/2025-11-27.log
Normal 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
2025-11-27 18:54:04,239 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:04] "[32mGET / HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 18:54:16,753 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 18:54:16] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
2025-11-27 19:49:52,485 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:49:52] "[32mGET / HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 19:50:00,591 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:00] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 19:50:22,796 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:22] "[36mGET /static/css/scp.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 19:50:37,577 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:37] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 19:50:37,578 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:37] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 19:50:49,724 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:49] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 19:50:49,724 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 19:50:49] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
2025-11-27 20:07:56,391 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:56] "[33mGET /xml_management HTTP/1.1[0m" 404 -
|
||||
2025-11-27 20:07:56,422 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:56] "[33mGET /favicon.ico HTTP/1.1[0m" 404 -
|
||||
2025-11-27 20:07:57,096 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:57] "[33mGET /xml_management HTTP/1.1[0m" 404 -
|
||||
2025-11-27 20:07:58,679 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:58] "[33mGET /xml_management HTTP/1.1[0m" 404 -
|
||||
2025-11-27 20:07:59,693 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:07:59] "[33mGET /xml_management HTTP/1.1[0m" 404 -
|
||||
2025-11-27 20:08:03,545 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:08:03,594 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:03,608 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:03] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 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] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,565 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:08:05,576 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,580 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,584 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,591 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,752 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:08:05,765 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,768 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,775 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png&s=KPmjd5URLBkm9fZIHKLS HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:05,792 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:05] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:08:06,661 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:08:06] "[35m[1mGET / HTTP/1.1[0m" 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] "[35m[1mGET / HTTP/1.1[0m" 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] "[35m[1mGET / HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
2025-11-27 20:09:10,319 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:09:10,362 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:10,365 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:10] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 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] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,297 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:09:11,308 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,311 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,318 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,337 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,859 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:09:11,870 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,874 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,880 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:11,895 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:11] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:12,026 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:09:12,038 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:12,042 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:12,048 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:12,065 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:12] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:13,701 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:09:13,713 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:13,713 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:13,720 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:13] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:19,987 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:19] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:09:20,588 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "[35m[1mGET / HTTP/1.1[0m" 500 -
|
||||
2025-11-27 20:09:20,605 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "[36mGET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:20,607 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "[36mGET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:09:20,632 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:20] "[36mGET /?__debugger__=yes&cmd=resource&f=console.png&s=toWbFrLNStmsnxQRxd1N HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
2025-11-27 20:09:35,916 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:35] "[35m[1mGET / HTTP/1.1[0m" 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] "[35m[1mGET / HTTP/1.1[0m" 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] "[35m[1mGET / HTTP/1.1[0m" 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] "[35m[1mGET / HTTP/1.1[0m" 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] "[35m[1mGET / HTTP/1.1[0m" 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] "[32mGET / HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 302 -
|
||||
2025-11-27 20:09:57,917 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:09:57] "[35m[1mGET /index HTTP/1.1[0m" 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] "[32mGET /login?next=/ HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
2025-11-27 20:10:21,639 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:10:21] "[35m[1mGET / HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:11:56,671 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:56] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:11:56,672 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:11:56] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:12:14,244 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:12:14] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:17:16,187 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:16] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:17:16,646 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:16] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:17:21,718 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:21] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:17:29,322 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:17:29,900 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:17:29,901 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:17:29] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:20:30,952 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:30] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:20:35,651 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:35] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:20:35,651 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:35] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:20:39,052 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:20:39] "[36mGET /static/css/scp.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:34,164 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:34,164 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:34] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:35,154 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:35,622 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:35,622 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:35] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:38,395 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:38,395 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:21:38,738 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:21:38] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mGET /logout HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /register HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:25:53,444 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:25:53,913 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:25:53] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:28:43,542 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:28:43] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:29:46,051 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:46] "[36mGET /static/favicon.ico HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:29:51,426 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:29:51] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mGET /admin/delete/3 HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:30:40,157 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:30:40] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:30:40,158 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:30:40] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:48,192 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:48] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:48,192 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:48] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:49,295 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:49] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:50,172 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:50,172 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:50] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:51,210 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:51] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:51,211 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:51] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:52,148 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:52,149 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:52,575 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:52] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-27 20:33:54,059 [INFO] werkzeug: 127.0.0.1 - - [27/Nov/2025 20:33:54] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[32mGET /idrac HTTP/1.1[0m" 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] "[33mGET /favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
95
data/logs/2025-11-28.log
Normal file
95
data/logs/2025-11-28.log
Normal 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[32mGET / HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[32mPOST /login HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-28 15:21:58,946 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:21:58] "[36mGET /static/script.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-28 15:22:06,076 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:06] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-28 15:22:06,076 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:06] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-28 15:22:29,696 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:29] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-28 15:22:29,696 [INFO] werkzeug: 127.0.0.1 - - [28/Nov/2025 15:22:29] "[36mGET /static/js/scp.js HTTP/1.1[0m" 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: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* 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: [33mPress CTRL+C to quit[0m
|
||||
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] "[32mGET / HTTP/1.1[0m" 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] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
756
data/logs/2025-11-29.log
Normal file
756
data/logs/2025-11-29.log
Normal file
@@ -0,0 +1,756 @@
|
||||
2025-11-29 08:14:35,726 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
|
||||
2025-11-29 08:14:35,756 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 08:14:35,756 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 08:14:35,783 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
|
||||
2025-11-29 08:14:35,803 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 08:14:35,803 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 08:14:35,803 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 08:14:35,803 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 08:14:35,856 [INFO] werkzeug: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:5000
|
||||
* Running on http://192.168.0.122:5000
|
||||
2025-11-29 08:14:35,856 [INFO] werkzeug: [33mPress CTRL+C to quit[0m
|
||||
2025-11-29 08:14:36,951 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:14:37,184 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:14:37,185 [INFO] telegram.ext.Application: Application started
|
||||
2025-11-29 08:14:38,056 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "[32mGET / HTTP/1.1[0m" 302 -
|
||||
2025-11-29 08:14:38,088 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "GET /login?next=/ HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:38,346 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "GET /static/style.css HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:38,694 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:38] "GET /static/favicon.ico HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:43,883 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 08:14:43,883 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 08:14:43,954 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-29 08:14:43,954 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-29 08:14:43,955 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-29 08:14:43,955 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-29 08:14:43,956 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:43] "[32mPOST /login HTTP/1.1[0m" 302 -
|
||||
2025-11-29 08:14:44,191 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:44,517 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:14:44,522 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /static/css/index.css HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:44,525 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /static/script.js HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:44,529 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:44] "GET /static/js/index.js HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:45,195 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:14:47,867 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:14:49,161 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "[32mGET /idrac HTTP/1.1[0m" 308 -
|
||||
2025-11-29 08:14:49,475 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "GET /idrac/ HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:49,797 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "GET /static/css/idrac_style.css HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:49,798 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:49] "GET /static/js/idrac_main.js HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:50,054 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /socket.io/?EIO=4&transport=polling&t=PhClt5V HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:50,136 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "POST /socket.io/?EIO=4&transport=polling&t=PhClt98&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:50,139 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /idrac/api/groups HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:50,140 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "[33mGET /favicon.ico HTTP/1.1[0m" 404 -
|
||||
2025-11-29 08:14:50,142 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /idrac/api/servers HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:50,367 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /socket.io/?EIO=4&transport=polling&t=PhClt99&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:50,372 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:50] "GET /socket.io/?EIO=4&transport=polling&t=PhCltE3&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:55,662 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:14:55] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 08:14:58,093 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:15:06,515 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:06] "POST /catalog/sync HTTP/1.1" 200 -
|
||||
2025-11-29 08:15:08,327 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:15:13,235 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:13] "POST /catalog/sync HTTP/1.1" 200 -
|
||||
2025-11-29 08:15:18,557 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:15:20,040 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:20] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 08:15:28,783 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:15:28,937 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:28] "POST /catalog/sync HTTP/1.1" 200 -
|
||||
2025-11-29 08:15:34,670 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:15:34] "POST /catalog/sync HTTP/1.1" 200 -
|
||||
2025-11-29 08:15:39,011 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:15:49,239 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:15:59,473 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:16:09,699 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:16:19,927 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:16:30,154 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:16:40,381 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:16:50,615 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:17:00,843 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:17:02,435 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:02] "GET /socket.io/?EIO=4&transport=websocket&sid=ybpAZIBf9CdxVyJIAAAA HTTP/1.1" 200 -
|
||||
2025-11-29 08:17:02,757 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:02] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-29 08:17:02,776 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:02] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:17:03,002 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:17:03,082 [INFO] app: LOGIN: already auth → /index
|
||||
2025-11-29 08:17:03,082 [INFO] app: LOGIN: already auth → /index
|
||||
2025-11-29 08:17:03,082 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "[32mGET /login?next=/ HTTP/1.1[0m" 302 -
|
||||
2025-11-29 08:17:03,083 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "[36mGET /static/js/index.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:17:03,191 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-29 08:17:03,390 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:17:03,515 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "[36mGET /static/js/index.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:17:03,516 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "GET /static/script.js HTTP/1.1" 200 -
|
||||
2025-11-29 08:17:03,517 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:17:03] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:17:11,069 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:17:21,296 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:17:31,524 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:17:41,752 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:17:51,985 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:18:02,214 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:18:12,440 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:18:22,665 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:18:32,897 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:18:43,122 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:18:53,349 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:19:03,575 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:19:13,803 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:19:24,030 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:19:34,258 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:19:44,487 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:19:54,717 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:19:58,781 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:58] "[32mGET /idrac HTTP/1.1[0m" 308 -
|
||||
2025-11-29 08:19:58,783 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:58] "GET /idrac/ HTTP/1.1" 200 -
|
||||
2025-11-29 08:19:59,032 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "[36mGET /static/css/idrac_style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:19:59,107 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "[36mGET /static/js/idrac_main.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 08:19:59,421 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /idrac/api/groups HTTP/1.1" 200 -
|
||||
2025-11-29 08:19:59,421 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /socket.io/?EIO=4&transport=polling&t=PhCn2c6 HTTP/1.1" 200 -
|
||||
2025-11-29 08:19:59,422 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /idrac/api/servers HTTP/1.1" 200 -
|
||||
2025-11-29 08:19:59,683 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "POST /socket.io/?EIO=4&transport=polling&t=PhCn2h1&sid=2pMufQvxsqmN2V1_AAAC HTTP/1.1" 200 -
|
||||
2025-11-29 08:19:59,728 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /socket.io/?EIO=4&transport=polling&t=PhCn2h2&sid=2pMufQvxsqmN2V1_AAAC HTTP/1.1" 200 -
|
||||
2025-11-29 08:19:59,731 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:19:59] "GET /socket.io/?EIO=4&transport=polling&t=PhCn2lp&sid=2pMufQvxsqmN2V1_AAAC HTTP/1.1" 200 -
|
||||
2025-11-29 08:20:00,769 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:20:00] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 08:20:04,945 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:20:15,177 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:20:25,403 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:20:35,632 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:20:45,864 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:20:56,090 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:21:06,318 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:21:16,543 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:21:26,770 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:21:36,999 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:21:47,226 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:21:57,452 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:22:07,684 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:22:17,910 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:22:28,138 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:22:38,367 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:22:48,595 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:22:58,821 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:23:09,047 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:23:19,273 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:23:29,500 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:23:39,735 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:23:49,961 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:24:00,192 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:24:10,417 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:24:20,646 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:24:30,873 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:24:41,103 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:24:51,330 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:25:01,559 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:25:11,790 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:25:22,018 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:25:32,246 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:25:42,473 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:25:52,705 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:26:02,931 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:26:13,187 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:26:23,415 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:26:33,648 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:26:43,874 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:26:54,100 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:27:04,325 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:27:14,551 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:27:24,777 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:27:35,003 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:27:45,231 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:27:55,457 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:28:05,685 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:28:15,911 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:28:26,138 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:28:36,363 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:28:46,594 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:28:56,820 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:29:07,047 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:29:17,276 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:29:27,503 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:29:37,731 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:29:47,957 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:29:58,191 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:30:02,765 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 08:30:02] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 08:30:08,423 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:30:18,651 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:30:28,878 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:30:39,106 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:30:49,332 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:30:59,559 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:31:09,787 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:31:20,014 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:31:30,240 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:31:40,465 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:31:51,174 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:32:01,407 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:32:11,642 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:32:21,877 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:32:32,111 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:32:42,345 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:32:52,577 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:33:02,812 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:33:13,045 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:33:23,280 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:33:33,516 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:33:43,749 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:33:53,983 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:34:04,216 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:34:14,449 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:34:24,682 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:34:34,916 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:34:45,150 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:34:55,385 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:35:05,621 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:35:15,853 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:35:26,087 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:35:36,321 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:35:46,554 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:35:56,788 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:36:07,023 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:36:17,255 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:36:27,488 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:36:37,721 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:36:47,957 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:36:58,191 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:37:08,430 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:37:18,665 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:37:28,899 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:37:39,149 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:37:49,392 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:37:59,628 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:38:09,861 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:38:20,093 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:38:30,329 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:38:40,563 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:38:50,800 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:39:01,033 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:39:11,270 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:39:21,504 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:39:31,738 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:39:41,972 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:39:52,206 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:40:02,440 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:40:12,673 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:40:22,908 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:40:33,144 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:40:43,379 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:40:53,613 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:41:03,847 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:41:14,080 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:41:24,344 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:41:34,578 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:41:44,811 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:41:55,043 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:42:05,277 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:42:15,512 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:42:25,750 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:42:35,985 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:42:46,218 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:42:56,453 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:43:06,688 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:43:16,922 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:43:27,155 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:43:37,387 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:43:47,621 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:43:57,855 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:44:08,089 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:44:18,322 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:44:28,558 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:44:38,791 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:44:49,028 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:44:59,263 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:45:09,502 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:45:19,736 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:45:29,970 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:45:40,205 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:45:50,438 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:46:00,670 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:46:10,903 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:46:21,137 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:46:31,370 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:46:41,623 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:46:51,857 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:47:02,090 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:47:12,331 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:47:22,565 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:47:32,798 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:47:43,033 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:47:53,269 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:48:03,502 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:48:13,738 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:48:23,972 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:48:34,207 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:48:44,441 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:48:55,138 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:49:05,370 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:49:15,600 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:49:25,831 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:49:36,063 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:49:46,293 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:49:56,524 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:50:06,756 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:50:16,990 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:50:27,219 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:50:37,450 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:50:47,679 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:50:57,912 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:51:08,143 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:51:18,374 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:51:28,606 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:51:38,837 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:51:49,067 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:51:59,297 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:52:09,529 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:52:19,759 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:52:29,991 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:52:40,221 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:52:50,451 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:53:00,680 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:53:10,913 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:53:21,144 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:53:31,376 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:53:41,606 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:53:51,836 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:54:02,066 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:54:12,297 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:54:22,528 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:54:32,759 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:54:42,991 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:54:53,222 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:55:03,452 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:55:13,683 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:55:23,914 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:55:34,145 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:55:44,377 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:55:54,607 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:56:04,839 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:56:15,070 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:56:25,301 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:56:35,532 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:56:45,763 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:56:55,995 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:57:06,224 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:57:16,456 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:57:26,708 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:57:36,938 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:57:47,169 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:57:57,400 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:58:07,631 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:58:17,861 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:58:28,093 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:58:38,324 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:58:48,555 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:58:58,785 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:59:09,016 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:59:19,246 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:59:29,478 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:59:39,709 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 08:59:49,939 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:00:00,169 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:00:10,400 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:00:20,630 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:00:30,863 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:00:41,094 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:00:51,324 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:01:01,554 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:01:11,785 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:01:22,016 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:01:32,246 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:01:42,477 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:01:52,708 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:02:02,938 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:02:13,169 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:02:23,400 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:02:33,653 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:02:43,885 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:02:54,115 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:03:04,348 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:03:14,583 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:03:24,813 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:03:35,043 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:03:45,273 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:03:55,505 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:04:05,736 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:04:15,969 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:04:26,200 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:04:36,429 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:04:46,659 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:04:56,899 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:05:07,130 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:05:17,362 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:05:27,593 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:05:37,824 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:05:48,055 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:05:58,746 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:06:08,975 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:06:19,203 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:06:29,433 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:06:39,662 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:06:49,891 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:07:00,120 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:07:10,348 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:07:20,577 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:07:30,807 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:07:41,036 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:07:51,266 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:08:01,495 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:08:11,725 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:08:21,954 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:08:32,184 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:08:42,414 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:08:52,644 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:09:02,873 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:09:13,102 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:09:23,339 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:09:33,567 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:09:43,796 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:09:54,025 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:10:04,255 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:10:14,485 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:10:24,714 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:10:34,948 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:10:45,179 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:10:55,409 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:11:05,638 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:11:15,867 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:11:26,097 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:11:36,327 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:11:46,557 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:11:56,788 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:12:07,017 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:12:17,247 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:12:27,477 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:12:37,706 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:12:47,935 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:12:58,165 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:13:08,394 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:13:18,624 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:13:28,853 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:13:39,082 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:13:49,315 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:13:59,543 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:14:09,772 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:14:20,002 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:14:30,230 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:14:40,460 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:14:50,689 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:15:00,918 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:15:11,147 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:15:21,376 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:15:31,605 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:15:41,834 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:15:52,063 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:16:02,294 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:16:12,523 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:16:22,753 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:16:32,982 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:16:43,212 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:16:53,442 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:17:03,671 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:17:13,901 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:17:24,129 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:17:34,359 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:17:44,587 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:17:54,818 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:18:05,047 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:18:15,276 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:18:25,505 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:18:35,735 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:18:45,964 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:18:56,193 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:19:06,422 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:19:16,652 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:19:26,880 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:19:37,109 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:19:47,339 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:19:57,568 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:20:07,798 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:20:18,028 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 09:20:21,965 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:21] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 09:20:22,683 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:22] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 09:20:24,123 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:24] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 09:20:24,715 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 09:20:24] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 09:20:28,257 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:00,388 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
|
||||
2025-11-29 11:25:00,411 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 11:25:00,411 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 11:25:00,511 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
|
||||
2025-11-29 11:25:00,524 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 11:25:00,525 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 11:25:00,524 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 11:25:00,525 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 11:25:00,608 [INFO] werkzeug: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:5000
|
||||
* Running on http://192.168.0.122:5000
|
||||
2025-11-29 11:25:00,609 [INFO] werkzeug: [33mPress CTRL+C to quit[0m
|
||||
2025-11-29 11:25:01,945 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:02,176 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:02,178 [INFO] telegram.ext.Application: Application started
|
||||
2025-11-29 11:25:05,399 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:05] "[32mGET / HTTP/1.1[0m" 302 -
|
||||
2025-11-29 11:25:05,453 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:05] "GET /login?next=/ HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:05,487 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:05] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:25:12,748 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "[35m[1mPOST /login HTTP/1.1[0m" 500 -
|
||||
2025-11-29 11:25:12,773 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:12,779 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:12,803 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=console.png&s=xoqKrRi6YNbxcjDr00Te HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:12,818 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:12] "GET /login?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:12,859 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:23,086 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:33,316 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:46,857 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
|
||||
2025-11-29 11:25:46,875 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 11:25:46,875 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 11:25:46,891 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
|
||||
2025-11-29 11:25:46,904 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 11:25:46,904 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 11:25:46,904 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 11:25:46,904 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 11:25:46,917 [INFO] werkzeug: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:5000
|
||||
* Running on http://192.168.0.122:5000
|
||||
2025-11-29 11:25:46,917 [INFO] werkzeug: [33mPress CTRL+C to quit[0m
|
||||
2025-11-29 11:25:47,784 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:47] "GET /login?next=/ HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:47,820 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:47] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:25:47,834 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:47] "GET /static/favicon.ico HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:47,981 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:48,215 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:25:48,217 [INFO] telegram.ext.Application: Application started
|
||||
2025-11-29 11:25:55,161 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:25:55,161 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:25:55,164 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
|
||||
2025-11-29 11:25:55,164 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:25:55,164 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:25:55,165 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:55] "POST /login HTTP/1.1" 200 -
|
||||
2025-11-29 11:25:55,178 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:25:55] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:25:58,922 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:00,658 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:00,658 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:00,660 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
|
||||
2025-11-29 11:26:00,660 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:00,660 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:00,661 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:00] "POST /login HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:00,680 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:00] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:09,154 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:14,556 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:14,556 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:14,557 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
|
||||
2025-11-29 11:26:14,557 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:14,557 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:14,558 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:14] "POST /login HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:14,571 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:14] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:14,592 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:14] "[36mGET /static/favicon.ico HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:16,303 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:16,303 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:16,305 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
|
||||
2025-11-29 11:26:16,305 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:16,305 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:16,306 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:16] "POST /login HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:16,318 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:16] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:16,338 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:16] "[36mGET /static/favicon.ico HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:19,385 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:26,537 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:26,537 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:26,538 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
|
||||
2025-11-29 11:26:26,539 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:26,539 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:26,539 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:26] "POST /login HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:26,556 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:26] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:29,618 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:30,145 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:30,145 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:30,147 [WARNING] root: password verify failed: argon2: no backends available -- recommend you install one (e.g. 'pip install argon2_cffi')
|
||||
2025-11-29 11:26:30,147 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:30,147 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:30,148 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:30] "POST /login HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:30,163 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:30] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:39,849 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:46,795 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
|
||||
2025-11-29 11:26:46,814 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 11:26:46,814 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 11:26:46,830 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
|
||||
2025-11-29 11:26:46,843 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 11:26:46,843 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 11:26:46,843 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 11:26:46,843 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 11:26:46,856 [INFO] werkzeug: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:5000
|
||||
* Running on http://192.168.0.122:5000
|
||||
2025-11-29 11:26:46,856 [INFO] werkzeug: [33mPress CTRL+C to quit[0m
|
||||
2025-11-29 11:26:47,893 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:48,119 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:48,121 [INFO] telegram.ext.Application: Application started
|
||||
2025-11-29 11:26:48,563 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:48,563 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:48,614 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:48,614 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=False
|
||||
2025-11-29 11:26:48,623 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:48] "POST /login HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:48,656 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:48] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:48,664 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:48] "[36mGET /static/favicon.ico HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:52,625 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:52,625 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 11:26:52,672 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-29 11:26:52,672 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-29 11:26:52,673 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-29 11:26:52,673 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-29 11:26:52,674 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "[32mPOST /login HTTP/1.1[0m" 302 -
|
||||
2025-11-29 11:26:52,700 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:52,716 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:52,728 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /static/css/index.css HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:52,731 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:52,734 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:52] "GET /static/js/index.js HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:54,110 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:58,026 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:58] "GET /admin HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:58,039 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:58] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:26:58,057 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:58] "GET /static/js/admin.js HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:58,801 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:26:59,260 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "[35m[1mGET /admin/settings HTTP/1.1[0m" 500 -
|
||||
2025-11-29 11:26:59,271 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:59,274 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:59,279 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=console.png&s=THSpWanxyHDEaEXGoJp8 HTTP/1.1" 200 -
|
||||
2025-11-29 11:26:59,302 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:26:59] "GET /admin/settings?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:09,027 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:27:19,254 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:27:29,482 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:27:34,883 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "[35m[1mGET /admin/settings HTTP/1.1[0m" 500 -
|
||||
2025-11-29 11:27:34,895 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "[36mGET /admin/settings?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:27:34,898 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "[36mGET /admin/settings?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:27:34,904 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:34] "[36mGET /admin/settings?__debugger__=yes&cmd=resource&f=console.png&s=THSpWanxyHDEaEXGoJp8 HTTP/1.1[0m" 304 -
|
||||
2025-11-29 11:27:39,464 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "[32mGET /idrac HTTP/1.1[0m" 308 -
|
||||
2025-11-29 11:27:39,485 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /idrac/ HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,505 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /static/css/idrac_style.css HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,524 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /static/js/idrac_main.js HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,552 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /socket.io/?EIO=4&transport=polling&t=PhDR_kS HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,555 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /idrac/api/servers HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,558 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /idrac/api/groups HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,559 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "[33mGET /favicon.ico HTTP/1.1[0m" 404 -
|
||||
2025-11-29 11:27:39,561 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "POST /socket.io/?EIO=4&transport=polling&t=PhDR_kc&sid=Rrkjr7nmXxZXXV5rAAAA HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,563 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:39] "GET /socket.io/?EIO=4&transport=polling&t=PhDR_kd&sid=Rrkjr7nmXxZXXV5rAAAA HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:39,709 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:27:40,780 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:40] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:43,165 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:43] "GET /idrac/api/firmware-versions HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:47,382 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 11:27:47] "GET /socket.io/?EIO=4&transport=websocket&sid=Rrkjr7nmXxZXXV5rAAAA HTTP/1.1" 200 -
|
||||
2025-11-29 11:27:49,936 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 11:28:00,164 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:05,980 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
|
||||
2025-11-29 16:19:06,006 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 16:19:06,006 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 16:19:06,078 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
|
||||
2025-11-29 16:19:06,096 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 16:19:06,096 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 16:19:06,096 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 16:19:06,096 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 16:19:06,145 [INFO] werkzeug: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:5000
|
||||
* Running on http://192.168.0.122:5000
|
||||
2025-11-29 16:19:06,146 [INFO] werkzeug: [33mPress CTRL+C to quit[0m
|
||||
2025-11-29 16:19:07,200 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:07,434 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:07,436 [INFO] telegram.ext.Application: Application started
|
||||
2025-11-29 16:19:12,329 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:12] "[32mGET / HTTP/1.1[0m" 302 -
|
||||
2025-11-29 16:19:12,343 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:12] "GET /login?next=/ HTTP/1.1" 200 -
|
||||
2025-11-29 16:19:12,375 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:12] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:17,479 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 16:19:17,479 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-29 16:19:17,547 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-29 16:19:17,547 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-29 16:19:17,548 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-29 16:19:17,548 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-29 16:19:17,549 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "[32mPOST /login HTTP/1.1[0m" 302 -
|
||||
2025-11-29 16:19:17,564 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-29 16:19:17,586 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:17,590 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:17,590 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "[36mGET /static/js/index.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:17,590 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:17] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:18,134 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:18,818 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:19,001 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:19] "GET /admin HTTP/1.1" 200 -
|
||||
2025-11-29 16:19:19,014 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:19] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:19,017 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:19] "[36mGET /static/js/admin.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:20,031 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "[35m[1mGET /admin/settings HTTP/1.1[0m" 500 -
|
||||
2025-11-29 16:19:20,055 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "[36mGET /admin/settings?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:20,070 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "[36mGET /admin/settings?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:19:20,088 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:19:20] "GET /admin/settings?__debugger__=yes&cmd=resource&f=console.png&s=nLGik2o7oqJf9jY3fsog HTTP/1.1" 200 -
|
||||
2025-11-29 16:19:28,365 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:38,596 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:48,832 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:19:59,063 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:20:09,295 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:20:19,528 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:20:29,759 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:20:39,991 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:23:51,770 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
|
||||
2025-11-29 16:23:51,793 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 16:23:51,793 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-29 16:23:51,817 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
|
||||
2025-11-29 16:23:51,831 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 16:23:51,831 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-29 16:23:51,831 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 16:23:51,831 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-29 16:23:51,847 [INFO] werkzeug: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:5000
|
||||
* Running on http://192.168.0.122:5000
|
||||
2025-11-29 16:23:51,847 [INFO] werkzeug: [33mPress CTRL+C to quit[0m
|
||||
2025-11-29 16:23:52,589 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "GET /admin/settings HTTP/1.1" 200 -
|
||||
2025-11-29 16:23:52,671 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "GET /static/css/admin_settings.css HTTP/1.1" 200 -
|
||||
2025-11-29 16:23:52,673 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:23:52,724 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:52] "[36mGET /static/favicon.ico HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:23:52,926 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:23:53,156 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:23:53,158 [INFO] telegram.ext.Application: Application started
|
||||
2025-11-29 16:23:59,415 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:23:59,418 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "[32mPOST /admin/settings/bot/test/1 HTTP/1.1[0m" 302 -
|
||||
2025-11-29 16:23:59,422 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "GET /admin/settings HTTP/1.1" 200 -
|
||||
2025-11-29 16:23:59,448 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:23:59,448 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:23:59] "[36mGET /static/css/admin_settings.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:03,847 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:24:14,079 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:24:14,607 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:14] "GET /admin HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:14,623 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:14] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:14,624 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:14] "[36mGET /static/js/admin.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:15,836 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:15] "GET /admin/settings HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:15,852 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:15] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:15,855 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:15] "[36mGET /static/css/admin_settings.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:17,060 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /admin HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:17,074 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:17,080 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "[36mGET /static/js/admin.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:17,684 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:17,703 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:17,705 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:17,708 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "[36mGET /static/js/index.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:17,709 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:17] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:18,247 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "GET /xml_management HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:18,262 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:18,266 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "GET /static/css/scp.css HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:18,267 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:18] "GET /static/js/scp.js HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:20,336 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:20] "GET /edit_xml/LinePlus_T1.xml HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:20,365 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:20] "GET /static/css/edit_xml.css HTTP/1.1" 200 -
|
||||
2025-11-29 16:24:20,368 [INFO] werkzeug: 127.0.0.1 - - [29/Nov/2025 16:24:20] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-29 16:24:24,311 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:24:34,542 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:24:44,774 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:24:55,005 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:25:05,234 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:25:15,464 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:25:25,695 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:25:35,928 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:25:46,157 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:25:56,389 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:26:06,619 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:26:16,849 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:26:27,079 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:26:37,310 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:26:47,540 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:26:57,770 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:27:08,002 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:27:18,232 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:27:28,463 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:27:38,693 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:27:48,923 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:27:59,154 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:28:09,384 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:28:19,614 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:28:29,845 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:28:40,076 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:28:50,307 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:29:00,537 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:29:10,768 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:29:20,998 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:29:31,228 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-29 16:29:41,458 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
96
data/logs/2025-11-30.log
Normal file
96
data/logs/2025-11-30.log
Normal file
@@ -0,0 +1,96 @@
|
||||
2025-11-30 07:56:33,462 [INFO] root: Logger initialized | level=INFO | file=D:\Code\iDRAC_Info\idrac_info\data\logs\app.log
|
||||
2025-11-30 07:56:33,483 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-30 07:56:33,483 [INFO] app: DB URI = sqlite:///D:/Code/iDRAC_Info/idrac_info/backend/instance/site.db
|
||||
2025-11-30 07:56:33,535 [INFO] backend.routes.jobs: Jobs routes registered at /jobs
|
||||
2025-11-30 07:56:33,548 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-30 07:56:33,548 [INFO] app: 🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)
|
||||
2025-11-30 07:56:33,549 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-30 07:56:33,549 [INFO] app: Starting polling for bot: admin_bot (ID: 1)
|
||||
2025-11-30 07:56:33,595 [INFO] werkzeug: [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:5000
|
||||
* Running on http://192.168.0.122:5000
|
||||
2025-11-30 07:56:33,595 [INFO] werkzeug: [33mPress CTRL+C to quit[0m
|
||||
2025-11-30 07:56:34,624 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getMe "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:56:34,851 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/deleteWebhook "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:56:34,853 [INFO] telegram.ext.Application: Application started
|
||||
2025-11-30 07:56:36,685 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:36] "[32mGET / HTTP/1.1[0m" 302 -
|
||||
2025-11-30 07:56:36,714 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:36] "GET /login?next=/ HTTP/1.1" 200 -
|
||||
2025-11-30 07:56:36,751 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:36] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:56:43,202 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-30 07:56:43,202 [INFO] app: LOGIN: form ok email=ganghee@zespro.co.kr
|
||||
2025-11-30 07:56:43,274 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-30 07:56:43,274 [INFO] app: LOGIN: found id=1 active=True approved=True pass_ok=True
|
||||
2025-11-30 07:56:43,275 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-30 07:56:43,275 [INFO] app: LOGIN: SUCCESS → redirect
|
||||
2025-11-30 07:56:43,276 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "[32mPOST /login HTTP/1.1[0m" 302 -
|
||||
2025-11-30 07:56:43,301 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-30 07:56:43,321 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:56:43,326 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /static/css/index.css HTTP/1.1" 200 -
|
||||
2025-11-30 07:56:43,334 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:56:43,339 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:56:43] "GET /static/js/index.js HTTP/1.1" 200 -
|
||||
2025-11-30 07:56:44,539 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/sendMessage "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:56:45,533 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:56:55,764 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:57:05,992 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:57:16,220 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:57:20,071 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:20] "GET /admin HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:20,087 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:20] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:20,091 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:20] "GET /static/js/admin.js HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:21,784 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /jobs HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:21,802 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:21,812 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /static/css/jobs.css HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:21,827 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /static/js/jobs.js HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:21,832 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /jobs/config HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:21,837 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
|
||||
2025-11-30 07:57:21,837 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:21] "GET /jobs/iplist HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:23,860 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:23] "GET /admin HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:23,873 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:23] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:23,877 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:23] "[36mGET /static/js/admin.js HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:26,447 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:57:27,008 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:27] "GET /admin/settings HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:27,021 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:27] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:27,032 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:27] "GET /static/css/admin_settings.css HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:35,500 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /jobs HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:35,514 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:35,517 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "[36mGET /static/css/jobs.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:35,518 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "[36mGET /static/js/jobs.js HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:35,537 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /jobs/config HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:35,542 [WARNING] backend.services.idrac_jobs: IP list file not found: data/server_list/idrac_ip_list.txt
|
||||
2025-11-30 07:57:35,542 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:35] "GET /jobs/iplist HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:36,597 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "GET /xml_management HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:36,610 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:36,615 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "GET /static/css/scp.css HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:36,615 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:36] "GET /static/js/scp.js HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:36,674 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:57:40,721 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:40] "GET /edit_xml/LinePlus_T1.xml HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:40,734 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:40] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:40,738 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:40] "GET /static/css/edit_xml.css HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:42,310 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "GET /xml_management HTTP/1.1" 200 -
|
||||
2025-11-30 07:57:42,322 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:42,326 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:42,327 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:57:42] "[36mGET /static/js/scp.js HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:57:46,902 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:57:57,128 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:58:07,122 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:07] "GET /scp/diff?file1=R6615.xml&file2=R6615_raid.xml HTTP/1.1" 200 -
|
||||
2025-11-30 07:58:07,138 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:07] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:07,140 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:07] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:07,356 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:58:17,592 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:58:19,128 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /xml_management HTTP/1.1" 200 -
|
||||
2025-11-30 07:58:19,142 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:19,146 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "[36mGET /static/css/scp.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:19,146 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "[36mGET /static/js/scp.js HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:19,647 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "GET /index HTTP/1.1" 200 -
|
||||
2025-11-30 07:58:19,661 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:19,667 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:19,667 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "[36mGET /static/js/index.js HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:19,668 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:19] "[36mGET /static/script.js HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:24,733 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:24] "GET /home/ HTTP/1.1" 200 -
|
||||
2025-11-30 07:58:24,746 [INFO] werkzeug: 127.0.0.1 - - [30/Nov/2025 07:58:24] "[36mGET /static/style.css HTTP/1.1[0m" 304 -
|
||||
2025-11-30 07:58:27,818 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:58:38,046 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:58:48,273 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:58:58,501 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:59:08,734 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
2025-11-30 07:59:18,960 [INFO] httpx: HTTP Request: POST https://api.telegram.org/bot6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo/getUpdates "HTTP/1.1 200 OK"
|
||||
3824
data/logs/2025-12-18.log
Normal file
3824
data/logs/2025-12-18.log
Normal file
File diff suppressed because it is too large
Load Diff
7623
data/logs/app.log
Normal file
7623
data/logs/app.log
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,14 @@ import subprocess
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# .env 파일 로드
|
||||
load_dotenv()
|
||||
@@ -99,9 +107,9 @@ def fetch_idrac_info(ip_address):
|
||||
f.write(f"01. RAID Settings - Raid ProductName : {get_value(hwinventory, 'ProductName = PERC')}\n")
|
||||
f.write(f"02. RAID Settings - Raid Types : No-Raid mode\n")
|
||||
|
||||
print(f"IP {ip_address} 에 대한 정보를 {output_file} 에 저장했습니다.")
|
||||
logging.info(f"IP {ip_address} 에 대한 정보를 {output_file} 에 저장했습니다.")
|
||||
except Exception as e:
|
||||
print(f"오류 발생: {e}")
|
||||
logging.error(f"오류 발생: {e}")
|
||||
|
||||
# 명령 결과에서 원하는 값 가져오기
|
||||
def get_value(output, key):
|
||||
@@ -117,7 +125,7 @@ start_time = time.time()
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) != 2:
|
||||
print(f"Usage: {sys.argv[0]} <ip_file>")
|
||||
logging.error(f"Usage: {sys.argv[0]} <ip_file>")
|
||||
sys.exit(1)
|
||||
|
||||
ip_file_path = validate_ip_file(sys.argv[1])
|
||||
@@ -138,5 +146,5 @@ elapsed_hours = int(elapsed_time // 3600)
|
||||
elapsed_minutes = int((elapsed_time % 3600) // 60)
|
||||
elapsed_seconds = int(elapsed_time % 60)
|
||||
|
||||
print("정보 수집 완료.")
|
||||
print(f"수집 완료 시간: {elapsed_hours} 시간, {elapsed_minutes} 분, {elapsed_seconds} 초.")
|
||||
logging.info("정보 수집 완료.")
|
||||
logging.info(f"수집 완료 시간: {elapsed_hours} 시간, {elapsed_minutes} 분, {elapsed_seconds} 초.")
|
||||
|
||||
@@ -16,7 +16,8 @@ RACADM = os.getenv("RACADM_PATH", "racadm") # PATH에 있으면 'racadm'
|
||||
# 로깅
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s"
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
def read_ip_list(ip_file: Path):
|
||||
@@ -66,7 +67,7 @@ def apply_xml(ip: str, xml_path: Path) -> tuple[bool, str]:
|
||||
# 대안:
|
||||
# cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "set", "-t", "xml", "-f", str(safe_path)]
|
||||
|
||||
logging.info("실행 명령(리스트) → %s", " ".join(cmd))
|
||||
logging.info(f"실행 명령(리스트) → {' '.join(cmd)}")
|
||||
try:
|
||||
p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=180)
|
||||
stdout = (p.stdout or "").strip()
|
||||
@@ -88,7 +89,7 @@ def apply_xml(ip: str, xml_path: Path) -> tuple[bool, str]:
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: python 02-set_config.py <ip_file> <xml_file>")
|
||||
logging.error("Usage: python 02-set_config.py <ip_file> <xml_file>")
|
||||
sys.exit(1)
|
||||
|
||||
ip_file = Path(sys.argv[1])
|
||||
|
||||
@@ -4,6 +4,14 @@ import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# 환경 변수 로드
|
||||
load_dotenv() # .env 파일에서 환경 변수 로드
|
||||
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
# IP 주소 파일 로드 함수
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
sys.exit(f"IP file {ip_file_path} does not exist.")
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# iDRAC 정보를 가져오는 함수 정의
|
||||
def fetch_idrac_info(idrac_ip):
|
||||
print(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
|
||||
logging.info(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "collect"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print(
|
||||
f"Successfully collected TSR report for {idrac_ip}"
|
||||
if result.returncode == 0
|
||||
else f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}"
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully collected TSR report for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# 메인 함수
|
||||
if __name__ == "__main__":
|
||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
||||
pool.map(fetch_idrac_info, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
print(f"설정 완료. 수집 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
logging.info(f"설정 완료. 수집 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -3,6 +3,14 @@ import subprocess
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# 환경 변수 로드
|
||||
load_dotenv() # .env 파일에서 환경 변수 로드
|
||||
@@ -16,13 +24,14 @@ OME_PASS = os.getenv("OME_PASS")
|
||||
# IP 주소 파일 로드 및 유효성 검사
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
sys.exit(f"IP file {ip_file_path} does not exist.")
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# iDRAC 정보를 가져오는 함수
|
||||
def fetch_idrac_info(idrac_ip):
|
||||
print(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
|
||||
logging.info(f"Collecting TSR report for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
@@ -32,13 +41,13 @@ def fetch_idrac_info(idrac_ip):
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print(
|
||||
f"Successfully collected TSR report for {idrac_ip}"
|
||||
if result.returncode == 0
|
||||
else f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}"
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully collected TSR report for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# 메인 함수
|
||||
if __name__ == "__main__":
|
||||
@@ -51,4 +60,4 @@ if __name__ == "__main__":
|
||||
with Pool() as pool:
|
||||
pool.map(fetch_idrac_info, ip_list)
|
||||
|
||||
print("설정 완료.")
|
||||
logging.info("설정 완료.")
|
||||
|
||||
@@ -4,6 +4,14 @@ import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv() # Load variables from .env file
|
||||
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
# Load IP addresses from file
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
sys.exit(f"IP file {ip_file_path} does not exist.")
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# Power on the server for given iDRAC IP
|
||||
def poweron_idrac_server(idrac_ip):
|
||||
print(f"Powering on server for iDRAC IP: {idrac_ip}")
|
||||
logging.info(f"Powering on server for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerup"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print(
|
||||
f"Successfully powered on server for {idrac_ip}"
|
||||
if result.returncode == 0
|
||||
else f"Failed to power on server for {idrac_ip}: {result.stderr.strip()}"
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully powered on server for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to power on server for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# Main function
|
||||
if __name__ == "__main__":
|
||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
||||
pool.map(poweron_idrac_server, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
print(f"Server Power On 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
logging.info(f"Server Power On 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -4,6 +4,14 @@ import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv() # Load variables from .env file
|
||||
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
# Load IP addresses from file
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
sys.exit(f"IP file {ip_file_path} does not exist.")
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# Power off the server for given iDRAC IP
|
||||
def poweroff_idrac_server(idrac_ip):
|
||||
print(f"Powering off server for iDRAC IP: {idrac_ip}")
|
||||
logging.info(f"Powering off server for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerdown"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print(
|
||||
f"Successfully powered off server for {idrac_ip}"
|
||||
if result.returncode == 0
|
||||
else f"Failed to power off server for {idrac_ip}: {result.stderr.strip()}"
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully powered off server for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to power off server for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# Main function
|
||||
if __name__ == "__main__":
|
||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
||||
pool.map(poweroff_idrac_server, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
print(f"Server Power Off 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
logging.info(f"Server Power Off 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -4,6 +4,14 @@ import time
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv() # Load variables from .env file
|
||||
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
||||
# Load IP addresses from file
|
||||
def load_ip_file(ip_file_path):
|
||||
if not os.path.isfile(ip_file_path):
|
||||
sys.exit(f"IP file {ip_file_path} does not exist.")
|
||||
logging.error(f"IP file {ip_file_path} does not exist.")
|
||||
sys.exit(1)
|
||||
with open(ip_file_path, "r") as file:
|
||||
return [line.strip() for line in file if line.strip()]
|
||||
|
||||
# Delete all jobs for given iDRAC IP
|
||||
def delete_all_jobs(idrac_ip):
|
||||
print(f"Deleting all jobs for iDRAC IP: {idrac_ip}")
|
||||
logging.info(f"Deleting all jobs for iDRAC IP: {idrac_ip}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "delete", "-i", "ALL"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
print(
|
||||
f"Successfully deleted all jobs for {idrac_ip}"
|
||||
if result.returncode == 0
|
||||
else f"Failed to delete jobs for {idrac_ip}: {result.stderr.strip()}"
|
||||
)
|
||||
if result.returncode == 0:
|
||||
logging.info(f"Successfully deleted all jobs for {idrac_ip}")
|
||||
else:
|
||||
logging.error(f"Failed to delete jobs for {idrac_ip}: {result.stderr.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
||||
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||
|
||||
# Main function
|
||||
if __name__ == "__main__":
|
||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
||||
pool.map(delete_all_jobs, ip_list)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
print(f"Job delete 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
logging.info(f"Job delete 완료. 완료 시간: {int(elapsed_time // 3600)} 시간, {int((elapsed_time % 3600) // 60)} 분, {int(elapsed_time % 60)} 초.")
|
||||
@@ -9,7 +9,11 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
load_dotenv()
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 사용자 이름 및 비밀번호 설정
|
||||
|
||||
206
data/scripts/AMD_Server_Info.py
Normal file
206
data/scripts/AMD_Server_Info.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [INFO] root: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# iDRAC 접속 설정
|
||||
IDRAC_USER = "root"
|
||||
IDRAC_PASS = "calvin"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 저장 위치 결정
|
||||
def resolve_output_dir() -> Path:
|
||||
here = Path(__file__).resolve().parent
|
||||
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
|
||||
base = here.parent
|
||||
elif here.name.lower() == "scripts":
|
||||
base = here.parent
|
||||
else:
|
||||
base = here.parent
|
||||
|
||||
out = base / "idrac_info"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
return out
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 유틸리티 함수
|
||||
def run_racadm(ip, *args):
|
||||
"""racadm 명령어를 실행하고 결과를 반환합니다."""
|
||||
cmd = ["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS] + list(args)
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
|
||||
return result.stdout
|
||||
except Exception as e:
|
||||
# 에러 발생 시 빈 문자열 반환 (스크립트 중단 방지)
|
||||
return ""
|
||||
|
||||
def extract_value(text, pattern, delimiter='='):
|
||||
"""텍스트에서 특정 키의 값을 추출하고 공백을 제거합니다."""
|
||||
for line in text.splitlines():
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
if delimiter in line:
|
||||
# '=' 기준으로 쪼갠 후 값만 가져와서 공백 제거
|
||||
return line.split(delimiter, 1)[1].strip()
|
||||
return "N/A"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 메인 수집 함수
|
||||
def fetch_idrac_info(ip, output_dir):
|
||||
logging.info(f">>> [{ip}] 데이터 수집 시작...")
|
||||
|
||||
# 1. 원본 데이터 덩어리 가져오기 (API 호출 최소화)
|
||||
hwinventory = run_racadm(ip, "hwinventory")
|
||||
getsysinfo = run_racadm(ip, "getsysinfo")
|
||||
sys_profile = run_racadm(ip, "get", "bios.SysProfileSettings")
|
||||
proc_settings = run_racadm(ip, "get", "bios.ProcSettings")
|
||||
mem_settings = run_racadm(ip, "get", "bios.MemSettings")
|
||||
storage_ctrl = run_racadm(ip, "get", "STORAGE.Controller.1")
|
||||
|
||||
# 서비스 태그 확인 (파일명 결정용)
|
||||
svc_tag = extract_value(getsysinfo, "SVC Tag")
|
||||
if svc_tag == "N/A":
|
||||
logging.error(f"!!! [{ip}] SVC Tag 추출 실패. 작업을 건너뜁니다.")
|
||||
return
|
||||
|
||||
output_file = output_dir / f"{svc_tag}.txt"
|
||||
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
# 헤더 작성
|
||||
f.write(f"Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: {svc_tag})\n\n")
|
||||
|
||||
# --- 1. Firmware Version 정보 ---
|
||||
f.write("-" * 42 + " Firmware Version 정보 " + "-" * 42 + "\n")
|
||||
f.write(f"1. SVC Tag : {svc_tag}\n")
|
||||
f.write(f"2. Bios Firmware : {extract_value(getsysinfo, 'System BIOS Version')}\n")
|
||||
f.write(f"3. iDRAC Firmware Version : {extract_value(getsysinfo, 'Firmware Version')}\n")
|
||||
|
||||
# NIC 펌웨어 (별도 호출 필요)
|
||||
nic1 = run_racadm(ip, "get", "NIC.FrmwImgMenu.1")
|
||||
nic5 = run_racadm(ip, "get", "NIC.FrmwImgMenu.5")
|
||||
f.write(f"4. NIC Integrated Firmware Version : {extract_value(nic1, '#FamilyVersion')}\n")
|
||||
f.write(f"5. OnBoard NIC Firmware Version : {extract_value(nic5, '#FamilyVersion')}\n")
|
||||
f.write(f"6. Raid Controller Firmware Version : {extract_value(hwinventory, 'ControllerFirmwareVersion')}\n\n")
|
||||
|
||||
# --- 2. Bios 설정 정보 ---
|
||||
f.write("-" * 45 + " Bios 설정 정보 " + "-" * 45 + "\n")
|
||||
boot_mode = run_racadm(ip, "get", "bios.BiosBootSettings")
|
||||
f.write(f"01. Bios Boot Mode : {extract_value(boot_mode, 'BootMode')}\n")
|
||||
f.write(f"02. System Profile Settings - System Profile : {extract_value(sys_profile, 'SysProfile=')}\n")
|
||||
f.write(f"03. System Profile Settings - CPU Power Management : {extract_value(sys_profile, 'ProcPwrPerf')}\n")
|
||||
f.write(f"04. System Profile Settings - Memory Frequency : {extract_value(sys_profile, 'MemFrequency')}\n")
|
||||
f.write(f"05. System Profile Settings - Turbo Boost : {extract_value(sys_profile, 'ProcTurboMode')}\n")
|
||||
f.write(f"06. System Profile Settings - PCI ASPM L1 Link Power Management : {extract_value(sys_profile, 'PcieAspmL1')}\n")
|
||||
f.write(f"07. System Profile Settings - C-States : {extract_value(sys_profile, 'ProcCStates')}\n")
|
||||
f.write(f"08. System Profile Settings - Determinism Slider : {extract_value(sys_profile, 'DeterminismSlider')}\n")
|
||||
f.write(f"08-2. System Profile Settings - Dynamic Link Width Management (DLWM) : {extract_value(sys_profile, 'DynamicLinkWidthManagement')}\n")
|
||||
|
||||
f.write(f"09. Processor Settings - Logical Processor : {extract_value(proc_settings, 'LogicalProc')}\n")
|
||||
f.write(f"10. Processor Settings - Virtualization Technology : {extract_value(proc_settings, 'ProcVirtualization')}\n")
|
||||
f.write(f"11. Processor Settings - NUMA Nodes Per Socket : {extract_value(proc_settings, 'NumaNodesPerSocket')}\n")
|
||||
f.write(f"12. Processor Settings - x2APIC Mode : {extract_value(proc_settings, 'ProcX2Apic')}\n")
|
||||
|
||||
f.write(f"13. Memory Settings - Dram Refresh Delay : {extract_value(mem_settings, 'DramRefreshDelay')}\n")
|
||||
f.write(f"14. Memory Settings - DIMM Self Healing (PPR) : {extract_value(mem_settings, 'PPROnUCE')}\n")
|
||||
f.write(f"15. Memory Settings - Correctable Error Logging : {extract_value(mem_settings, 'CECriticalSEL')}\n")
|
||||
|
||||
thermal = run_racadm(ip, "get", "System.ThermalSettings")
|
||||
f.write(f"16. System Settings - Thermal Profile Optimization : {extract_value(thermal, 'ThermalProfile')}\n")
|
||||
|
||||
integrated = run_racadm(ip, "get", "Bios.IntegratedDevices")
|
||||
f.write(f"17. Integrated Devices Settings - SR-IOV Global Enable : {extract_value(integrated, 'SriovGlobalEnable')}\n")
|
||||
|
||||
misc = run_racadm(ip, "get", "bios.MiscSettings")
|
||||
f.write(f"18. Miscellaneous Settings - F1/F2 Prompt on Error : {extract_value(misc, 'ErrPrompt')}\n\n")
|
||||
|
||||
# --- 3. iDRAC 설정 정보 ---
|
||||
f.write("-" * 45 + " iDRAC 설정 정보 " + "-" * 45 + "\n")
|
||||
f.write(f"01. iDRAC Settings - Timezone : {extract_value(run_racadm(ip, 'get', 'iDRAC.Time.Timezone'), 'Timezone')}\n")
|
||||
f.write(f"02. iDRAC Settings - IPMI LAN Selection : {extract_value(run_racadm(ip, 'get', 'iDRAC.CurrentNIC'), 'ActiveNIC')}\n")
|
||||
f.write(f"03. iDRAC Settings - IPMI IP(IPv4) : {extract_value(run_racadm(ip, 'get', 'iDRAC.CurrentIPv4'), 'DHCPEnable')}\n")
|
||||
f.write(f"04. iDRAC Settings - IPMI IP(IPv6) : {extract_value(run_racadm(ip, 'get', 'iDRAC.CurrentIPv6'), 'Enable=')}\n")
|
||||
f.write(f"05. iDRAC Settings - Redfish Support : {extract_value(run_racadm(ip, 'get', 'iDRAC.Redfish.Enable'), 'Enable=')}\n")
|
||||
f.write(f"06. iDRAC Settings - SSH Support : {extract_value(run_racadm(ip, 'get', 'iDRAC.SSH'), 'Enable=')}\n")
|
||||
|
||||
# AD 관련 설정 (AD Group 1, 2 포함)
|
||||
f.write(f"07. iDRAC Settings - AD User Domain Name : {extract_value(run_racadm(ip, 'get', 'iDRAC.USERDomain.1.Name'), 'Name')}\n")
|
||||
f.write(f"08. iDRAC Settings - SC Server Address : {extract_value(run_racadm(ip, 'get', 'iDRAC.ActiveDirectory.DomainController1'), 'DomainController1')}\n")
|
||||
f.write(f"09. iDRAC Settings - SE AD RoleGroup Name : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.1.Name'), 'Name')}\n")
|
||||
f.write(f"10. iDRAC Settings - SE AD RoleGroup Domain : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.1.Domain'), 'Domain')}\n")
|
||||
f.write(f"11. iDRAC Settings - SE AD RoleGroup Privilege : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.1.Privilege'), 'Privilege')}\n")
|
||||
f.write(f"12. iDRAC Settings - IDC AD RoleGroup Name : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.2.Name'), 'Name')}\n")
|
||||
f.write(f"13. iDRAC Settings - IDC AD RoleGroup Domain : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.2.Domain'), 'Domain')}\n")
|
||||
f.write(f"14. iDRAC Settings - IDC AD RoleGroup Privilege : {extract_value(run_racadm(ip, 'get', 'iDRAC.ADGroup.2.Privilege'), 'Privilege')}\n")
|
||||
|
||||
# Syslog 및 기타
|
||||
f.write(f"15. iDRAC Settings - Remote Log (syslog) : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.SysLogEnable'), 'SysLogEnable')}\n")
|
||||
f.write(f"16. iDRAC Settings - syslog server address 1 : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.Server1'), 'Server1')}\n")
|
||||
f.write(f"17. iDRAC Settings - syslog server address 2 : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.Server2'), 'Server2')}\n")
|
||||
f.write(f"18. iDRAC Settings - syslog server port : {extract_value(run_racadm(ip, 'get', 'iDRAC.SysLog.Port'), 'Port')}\n")
|
||||
f.write(f"19. iDRAC Settings - Remote KVM Nonsecure port : {extract_value(run_racadm(ip, 'get', 'iDRAC.VirtualConsole.Port'), 'Port')}\n\n")
|
||||
|
||||
# --- 4. Raid 설정 정보 (주석 해제 버전) ---
|
||||
f.write("-" * 45 + " Raid 설정 정보 " + "-" * 45 + "\n")
|
||||
f.write(f"01. RAID Settings - Raid ProductName : {extract_value(hwinventory, 'ProductName = PERC')}\n")
|
||||
f.write(f"02. RAID Settings - Raid Types : {extract_value(hwinventory, 'RAIDTypes')}\n")
|
||||
f.write(f"03. RAID Settings - StripeSize : {extract_value(hwinventory, 'StripeSize')}\n")
|
||||
f.write(f"04. RAID Settings - ReadCachePolicy : {extract_value(hwinventory, 'ReadCachePolicy')}\n")
|
||||
f.write(f"05. RAID Settings - WriteCachePolicy : {extract_value(hwinventory, 'WriteCachePolicy')}\n")
|
||||
f.write(f"06. RAID Settings - CheckConsistencyRate : {extract_value(storage_ctrl, 'CheckConsistencyRate')}\n")
|
||||
f.write(f"07. RAID Settings - PatrolReadMode : {extract_value(storage_ctrl, 'PatrolReadMode')}\n")
|
||||
f.write(f"08. RAID Settings - period : 168h\n")
|
||||
f.write(f"09. RAID Settings - Power Save : No\n")
|
||||
f.write(f"10. RAID Settings - JBODMODE : Controller does not support JBOD\n")
|
||||
f.write(f"11. RAID Settings - maxconcurrentpd : 240\n")
|
||||
|
||||
logging.info(f"✅ [{ip}] 저장 완료: {output_file.name}")
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 실행 흐름 제어
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
logging.error(f"사용법: python {sys.argv[0]} <ip_file>")
|
||||
sys.exit(1)
|
||||
|
||||
ip_file = Path(sys.argv[1])
|
||||
if not ip_file.exists():
|
||||
logging.error(f"오류: IP 파일 '{ip_file}'이 존재하지 않습니다.")
|
||||
sys.exit(1)
|
||||
|
||||
output_dir = resolve_output_dir()
|
||||
start_time = time.time()
|
||||
|
||||
# IP 목록 읽기
|
||||
with open(ip_file, "r") as f:
|
||||
ips = [line.strip() for line in f if line.strip()]
|
||||
|
||||
# 각 IP별로 수집 실행
|
||||
for ip in ips:
|
||||
try:
|
||||
fetch_idrac_info(ip, output_dir)
|
||||
except Exception as e:
|
||||
logging.error(f"❌ [{ip}] 처리 중 예외 발생: {e}")
|
||||
|
||||
# 소요 시간 계산
|
||||
end_time = time.time()
|
||||
elapsed = end_time - start_time
|
||||
hours, rem = divmod(elapsed, 3600)
|
||||
minutes, seconds = divmod(rem, 60)
|
||||
|
||||
logging.info("="*50)
|
||||
logging.info("정보 수집 완료.")
|
||||
logging.info(f"총 소요 시간: {int(hours)}시간 {int(minutes)}분 {int(seconds)}초")
|
||||
logging.info(f"저장 경로: {output_dir}")
|
||||
logging.info("="*50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user