Compare commits
5 Commits
2e4fc20523
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d5d2b8d99 | ||
|
|
b37c43ab86 | ||
|
|
b18412ecb2 | ||
| 804204ab97 | |||
| 25cbb6b8f8 |
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-314.pyc
Normal file
BIN
__pycache__/telegram_bot_service.cpython-314.pyc
Normal file
Binary file not shown.
42
app.py
42
app.py
@@ -46,6 +46,13 @@ setup_logging(app)
|
|||||||
csrf = CSRFProtect()
|
csrf = CSRFProtect()
|
||||||
csrf.init_app(app)
|
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
|
@app.context_processor
|
||||||
def inject_csrf():
|
def inject_csrf():
|
||||||
@@ -121,38 +128,49 @@ register_routes(app, socketio)
|
|||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# 텔레그램 봇 폴링 서비스 (중복 실행 방지 포함)
|
# 텔레그램 봇 폴링 서비스 (중복 실행 방지 포함)
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
_bot_polling_started = False # 전역 플래그로 중복 방지
|
_bot_socket_lock = None
|
||||||
|
|
||||||
|
|
||||||
def start_telegram_bot_polling() -> None:
|
def start_telegram_bot_polling() -> None:
|
||||||
"""텔레그램 봇 폴링을 백그라운드 스레드로 시작 (한 번만 실행)"""
|
"""텔레그램 봇 폴링을 백그라운드 스레드로 시작 (TCP 소켓 락으로 중복 방지)"""
|
||||||
import threading
|
import threading
|
||||||
|
import socket
|
||||||
|
|
||||||
global _bot_polling_started
|
global _bot_socket_lock
|
||||||
|
|
||||||
if _bot_polling_started:
|
if _bot_socket_lock:
|
||||||
app.logger.warning("🤖 텔레그램 봇 폴링은 이미 시작됨 - 중복 요청 무시")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
_bot_polling_started = True
|
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():
|
def _runner():
|
||||||
try:
|
try:
|
||||||
# telegram_bot_service.run_polling(app) 호출
|
|
||||||
telegram_run_polling(app)
|
telegram_run_polling(app)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.error("텔레그램 봇 폴링 서비스 오류: %s", e)
|
app.logger.error("텔레그램 봇 폴링 서비스 오류: %s", e)
|
||||||
|
|
||||||
polling_thread = threading.Thread(target=_runner, daemon=True)
|
polling_thread = threading.Thread(target=_runner, daemon=True)
|
||||||
polling_thread.start()
|
polling_thread.start()
|
||||||
app.logger.info("🤖 텔레그램 봇 폴링 스레드 생성됨 (중복 방지 플래그 적용)")
|
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# 텔레그램 봇 폴링 자동 시작
|
# 텔레그램 봇 폴링 자동 시작
|
||||||
# Flask 앱이 초기화되면 자동으로 봇 폴링 시작
|
# Flask 앱이 초기화되면 자동으로 봇 폴링 시작
|
||||||
|
# 주의: Flask 리로더(Debug 모드) 사용 시 메인/워커 프로세스 중복 실행 방지
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
start_telegram_bot_polling()
|
# 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()
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
@@ -163,4 +181,8 @@ if __name__ == "__main__":
|
|||||||
port = int(os.getenv("FLASK_PORT", 5000))
|
port = int(os.getenv("FLASK_PORT", 5000))
|
||||||
debug = os.getenv("FLASK_DEBUG", "true").lower() == "true"
|
debug = os.getenv("FLASK_DEBUG", "true").lower() == "true"
|
||||||
|
|
||||||
|
# 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)
|
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-314.pyc
Normal file
BIN
backend/models/__pycache__/telegram_bot.cpython-314.pyc
Normal file
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.
@@ -113,14 +113,24 @@ class User(db.Model, UserMixin):
|
|||||||
q = (email or "").strip().lower()
|
q = (email or "").strip().lower()
|
||||||
if not q:
|
if not q:
|
||||||
return None
|
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
|
@staticmethod
|
||||||
def find_by_username(username: Optional[str]) -> Optional["User"]:
|
def find_by_username(username: Optional[str]) -> Optional["User"]:
|
||||||
q = (username or "").strip()
|
q = (username or "").strip()
|
||||||
if not q:
|
if not q:
|
||||||
return None
|
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 방식)
|
# Flask-Login user_loader (SQLAlchemy 2.0 방식)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from .auth import register_auth_routes
|
|||||||
from .admin import register_admin_routes
|
from .admin import register_admin_routes
|
||||||
from .main import register_main_routes
|
from .main import register_main_routes
|
||||||
from .xml import register_xml_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 .file_view import register_file_view
|
||||||
from .jobs import register_jobs_routes
|
from .jobs import register_jobs_routes
|
||||||
from .idrac_routes import register_idrac_routes
|
from .idrac_routes import register_idrac_routes
|
||||||
@@ -21,7 +21,7 @@ def register_routes(app: Flask, socketio=None) -> None:
|
|||||||
register_admin_routes(app)
|
register_admin_routes(app)
|
||||||
register_main_routes(app, socketio)
|
register_main_routes(app, socketio)
|
||||||
register_xml_routes(app)
|
register_xml_routes(app)
|
||||||
register_util_routes(app)
|
app.register_blueprint(utils_bp, url_prefix="/utils")
|
||||||
register_file_view(app)
|
register_file_view(app)
|
||||||
register_jobs_routes(app)
|
register_jobs_routes(app)
|
||||||
register_idrac_routes(app)
|
register_idrac_routes(app)
|
||||||
|
|||||||
BIN
backend/routes/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/__init__.cpython-314.pyc
Normal file
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.
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.
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.
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-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.
BIN
backend/routes/__pycache__/xml.cpython-314.pyc
Normal file
BIN
backend/routes/__pycache__/xml.cpython-314.pyc
Normal file
Binary file not shown.
@@ -237,3 +237,55 @@ def test_bot(bot_id):
|
|||||||
flash(f"테스트 실패: {e}", "danger")
|
flash(f"테스트 실패: {e}", "danger")
|
||||||
|
|
||||||
return redirect(url_for("admin.settings"))
|
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)
|
||||||
|
|||||||
@@ -270,8 +270,15 @@ def register():
|
|||||||
approval_token=approval_token
|
approval_token=approval_token
|
||||||
)
|
)
|
||||||
user.set_password(form.password.data)
|
user.set_password(form.password.data)
|
||||||
db.session.add(user)
|
|
||||||
db.session.commit()
|
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 = (
|
message = (
|
||||||
@@ -293,6 +300,13 @@ def register():
|
|||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
else:
|
else:
|
||||||
if request.method == "POST":
|
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)
|
current_app.logger.info("REGISTER: form errors=%s", form.errors)
|
||||||
|
|
||||||
return render_template("register.html", form=form)
|
return render_template("register.html", form=form)
|
||||||
|
|||||||
@@ -47,11 +47,43 @@ def index():
|
|||||||
info_dir = Path(Config.IDRAC_INFO_FOLDER)
|
info_dir = Path(Config.IDRAC_INFO_FOLDER)
|
||||||
backup_dir = Path(Config.BACKUP_FOLDER)
|
backup_dir = Path(Config.BACKUP_FOLDER)
|
||||||
|
|
||||||
scripts = [f.name for f in script_dir.glob("*") if f.is_file() and f.name != ".env"]
|
# 1. 스크립트 목록 조회 및 카테고리 분류
|
||||||
scripts = natsorted(scripts)
|
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")]
|
xml_files = [f.name for f in xml_dir.glob("*.xml")]
|
||||||
|
|
||||||
# 페이지네이션
|
# 3. 페이지네이션 및 파일 목록
|
||||||
page = int(request.args.get("page", 1))
|
page = int(request.args.get("page", 1))
|
||||||
info_files = [f.name for f in info_dir.glob("*") if f.is_file()]
|
info_files = [f.name for f in info_dir.glob("*") if f.is_file()]
|
||||||
info_files = natsorted(info_files)
|
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
|
total_pages = (len(info_files) + Config.FILES_PER_PAGE - 1) // Config.FILES_PER_PAGE
|
||||||
|
|
||||||
# ✅ 추가: 10개 단위로 표시될 페이지 범위 계산
|
|
||||||
start_page = ((page - 1) // 10) * 10 + 1
|
start_page = ((page - 1) // 10) * 10 + 1
|
||||||
end_page = min(start_page + 9, total_pages)
|
end_page = min(start_page + 9, total_pages)
|
||||||
|
|
||||||
# 백업 폴더 목록 (디렉터리만)
|
# 4. 백업 폴더 목록
|
||||||
backup_dirs = [d for d in backup_dir.iterdir() if d.is_dir()]
|
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)
|
backup_dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
|
|
||||||
@@ -91,7 +122,8 @@ def index():
|
|||||||
backup_files=backup_files,
|
backup_files=backup_files,
|
||||||
total_backup_pages=total_backup_pages,
|
total_backup_pages=total_backup_pages,
|
||||||
backup_page=backup_page,
|
backup_page=backup_page,
|
||||||
scripts=scripts,
|
scripts=all_scripts, # 기존 리스트 호환
|
||||||
|
grouped_scripts=grouped_scripts_sorted, # 카테고리별 분류
|
||||||
xml_files=xml_files,
|
xml_files=xml_files,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -175,13 +207,13 @@ def delete_file(filename: str):
|
|||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
try:
|
try:
|
||||||
file_path.unlink()
|
file_path.unlink()
|
||||||
flash(f"{filename} 삭제됨.")
|
flash(f"'{filename}' 파일이 삭제되었습니다.", "success")
|
||||||
logging.info(f"파일 삭제됨: {filename}")
|
logging.info(f"파일 삭제됨: {filename}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"파일 삭제 오류: {e}")
|
logging.error(f"파일 삭제 오류: {e}")
|
||||||
flash("파일 삭제 중 오류가 발생했습니다.", "danger")
|
flash("파일 삭제 중 오류가 발생했습니다.", "danger")
|
||||||
else:
|
else:
|
||||||
flash("파일이 존재하지 않습니다.")
|
flash("파일이 존재하지 않습니다.", "warning")
|
||||||
return redirect(url_for("main.index"))
|
return redirect(url_for("main.index"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,26 +32,48 @@ def diff_scp():
|
|||||||
return redirect(url_for("xml.xml_management"))
|
return redirect(url_for("xml.xml_management"))
|
||||||
|
|
||||||
# 파일 내용 읽기 (LF로 통일)
|
# 파일 내용 읽기 (LF로 통일)
|
||||||
content1 = file1_path.read_text(encoding="utf-8").replace("\r\n", "\n").splitlines()
|
# 파일 내용 읽기 (LF로 통일)
|
||||||
content2 = file2_path.read_text(encoding="utf-8").replace("\r\n", "\n").splitlines()
|
# 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")
|
||||||
|
|
||||||
# Diff 생성
|
logger.info(f"Reading file2: {file2_path}")
|
||||||
diff = difflib.unified_diff(
|
content2 = file2_path.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
|
||||||
content1, content2,
|
|
||||||
fromfile=file1_name,
|
|
||||||
tofile=file2_name,
|
|
||||||
lineterm=""
|
|
||||||
)
|
|
||||||
|
|
||||||
diff_content = "\n".join(diff)
|
logger.info(f"Content1 length: {len(content1)}, Content2 length: {len(content2)}")
|
||||||
|
|
||||||
return render_template("scp_diff.html", file1=file1_name, file2=file2_name, diff_content=diff_content)
|
return render_template("scp_diff.html",
|
||||||
|
file1=file1_name,
|
||||||
|
file2=file2_name,
|
||||||
|
content1=content1,
|
||||||
|
content2=content2)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Diff error: {e}")
|
logger.error(f"Diff error: {e}")
|
||||||
flash(f"비교 중 오류가 발생했습니다: {str(e)}", "danger")
|
flash(f"비교 중 오류가 발생했습니다: {str(e)}", "danger")
|
||||||
return redirect(url_for("xml.xml_management"))
|
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"])
|
@scp_bp.route("/scp/export", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def export_scp():
|
def export_scp():
|
||||||
|
|||||||
@@ -290,13 +290,83 @@ def update_gpu_list():
|
|||||||
|
|
||||||
return redirect(url_for("main.index"))
|
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}")
|
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.
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.
@@ -60,6 +60,19 @@ def setup_logging(app: Optional[object] = None) -> logging.Logger:
|
|||||||
# Flask 앱 로거에도 동일 핸들러 바인딩
|
# Flask 앱 로거에도 동일 핸들러 바인딩
|
||||||
app.logger.handlers = root.handlers
|
app.logger.handlers = root.handlers
|
||||||
app.logger.setLevel(root.level)
|
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)
|
root.info("Logger initialized | level=%s | file=%s", _DEF_LEVEL, log_path)
|
||||||
return root
|
return root
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// 스크립트 선택 시 XML 드롭다운 토글
|
// 스크립트 선택 시 XML 드롭다운 토글
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
@@ -77,6 +79,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
|
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 함수
|
// 공통 POST 함수
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
@@ -119,7 +152,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
await postFormAndHandle(macForm.action);
|
await postFormAndHandle(macForm.action);
|
||||||
location.reload();
|
location.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('MAC 이동 중 오류: ' + (err?.message || err));
|
alert('MAC 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err));
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.innerHTML = originalHtml;
|
btn.innerHTML = originalHtml;
|
||||||
}
|
}
|
||||||
@@ -142,7 +175,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
await postFormAndHandle(guidForm.action);
|
await postFormAndHandle(guidForm.action);
|
||||||
location.reload();
|
location.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('GUID 이동 중 오류: ' + (err?.message || err));
|
alert('GUID 파일 이동 중 오류가 발생했습니다: ' + (err?.message || err));
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.innerHTML = originalHtml;
|
btn.innerHTML = originalHtml;
|
||||||
}
|
}
|
||||||
@@ -160,4 +193,106 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}, 5000);
|
}, 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')); // 로컬 스토리지 업데이트 및 카운트 갱신 트리거
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
if (monitoringOn) await fetchJobs(false);
|
if (monitoringOn) await fetchJobs(false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('IP 목록 불러오기 실패: ' + e.message);
|
alert('IP 목록을 불러오는 중 오류가 발생했습니다: ' + e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$btnApply.addEventListener('click', () => {
|
$btnApply.addEventListener('click', () => {
|
||||||
|
|||||||
@@ -1,72 +1,151 @@
|
|||||||
{# backend/templates/admin.html #}
|
{# backend/templates/admin.html #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}관리자 패널 - Dell Server Info{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container py-4">
|
||||||
<div class="card">
|
<!-- Header -->
|
||||||
<div class="card-body">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div>
|
||||||
<h3 class="card-title mb-0">Admin Page</h3>
|
<h2 class="fw-bold mb-1">
|
||||||
<a href="{{ url_for('admin.settings') }}" class="btn btn-outline-primary">
|
<i class="bi bi-shield-lock text-primary me-2"></i>관리자 패널
|
||||||
<i class="bi bi-gear-fill me-1"></i>시스템 설정
|
</h2>
|
||||||
</a>
|
<p class="text-muted mb-0">사용자 관리 및 시스템 설정을 수행합니다.</p>
|
||||||
</div>
|
</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) %}
|
<!-- Dashboard Stats -->
|
||||||
{% if messages %}
|
<div class="row g-3 mb-4">
|
||||||
<div class="mt-2">
|
<div class="col-md-4">
|
||||||
{% for cat, msg in messages %}
|
<div class="card border-0 shadow-sm h-100 bg-primary bg-opacity-10">
|
||||||
<div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert">
|
<div class="card-body d-flex align-items-center">
|
||||||
{{ msg }}
|
<div class="rounded-circle bg-primary text-white p-3 me-3">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<i class="bi bi-people-fill fs-4"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="text-primary fw-bold mb-1">총 사용자</h6>
|
||||||
|
<h3 class="mb-0 fw-bold">{{ users|length }}</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
{% endwith %}
|
<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">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped align-middle">
|
<table class="table table-hover align-middle mb-0">
|
||||||
<thead>
|
<thead class="bg-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:60px">ID</th>
|
<th class="ps-4 py-3 text-secondary text-uppercase small fw-bold" style="width: 60px;">NO</th>
|
||||||
<th>Username</th>
|
<th class="py-3 text-secondary text-uppercase small fw-bold">이름</th>
|
||||||
<th>Email</th>
|
<th class="py-3 text-secondary text-uppercase small fw-bold">ID (Email)</th>
|
||||||
<th style="width:80px">Active</th>
|
<th class="py-3 text-secondary text-uppercase small fw-bold">상태</th>
|
||||||
<th style="width:260px">Action</th>
|
<th class="py-3 text-secondary text-uppercase small fw-bold text-end pe-4">관리</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user.id }}</td>
|
<td class="ps-4 fw-bold text-secondary">{{ loop.index }}</td>
|
||||||
<td>{{ user.username }}</td>
|
<td>
|
||||||
<td>{{ user.email }}</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>
|
<td>
|
||||||
{% if user.is_active %}
|
{% 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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="text-end pe-4">
|
||||||
{% if not user.is_active %}
|
<div class="d-flex justify-content-end gap-2">
|
||||||
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}"
|
{% if not user.is_active %}
|
||||||
class="btn btn-success btn-sm me-1">Approve</a>
|
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}"
|
||||||
{% endif %}
|
class="btn btn-sm btn-success text-white d-flex align-items-center gap-1" title="가입 승인">
|
||||||
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}" class="btn btn-danger btn-sm me-1"
|
<i class="bi bi-check-lg"></i>승인
|
||||||
onclick="return confirm('사용자 {{ user.username }} (ID={{ user.id }}) 를 삭제하시겠습니까?');">
|
</a>
|
||||||
Delete
|
{% endif %}
|
||||||
</a>
|
|
||||||
|
|
||||||
<!-- Change Password 버튼: 모달 오픈 -->
|
<button type="button" class="btn btn-sm btn-outline-secondary d-flex align-items-center gap-1"
|
||||||
<button type="button" class="btn btn-primary btn-sm" data-user-id="{{ user.id }}"
|
data-user-id="{{ user.id }}" data-username="{{ user.username | e }}" data-bs-toggle="modal"
|
||||||
data-username="{{ user.username | e }}" data-bs-toggle="modal" data-bs-target="#changePasswordModal">
|
data-bs-target="#changePasswordModal">
|
||||||
Change Password
|
<i class="bi bi-key"></i>비밀번호
|
||||||
</button>
|
</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if not users %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-5 text-muted">사용자가 없습니다.</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,41 +154,44 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# ========== Change Password Modal ========== #}
|
{# ========== Change Password Modal ========== #}
|
||||||
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-labelledby="changePasswordModalLabel"
|
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-hidden="true">
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content border-0 shadow">
|
||||||
<form id="changePasswordForm" method="post" action="">
|
<form id="changePasswordForm" method="post" action="">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<div class="modal-header">
|
<div class="modal-header bg-light">
|
||||||
<h5 class="modal-title" id="changePasswordModalLabel">Change Password</h5>
|
<h5 class="modal-title fw-bold">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<i class="bi bi-key-fill me-2"></i>비밀번호 변경
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body p-4">
|
||||||
<div class="mb-2">
|
<div class="alert alert-light border mb-4 d-flex align-items-center">
|
||||||
<small class="text-muted">User:</small>
|
<i class="bi bi-person-circle fs-4 me-3 text-secondary"></i>
|
||||||
<div id="modalUserInfo" class="fw-bold"></div>
|
<div>
|
||||||
|
<small class="text-muted d-block">대상 사용자</small>
|
||||||
|
<span id="modalUserInfo" class="fw-bold text-dark fs-5"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="newPasswordInput" class="form-label">New password</label>
|
<label for="newPasswordInput" class="form-label fw-semibold">새 비밀번호</label>
|
||||||
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8"
|
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8"
|
||||||
placeholder="Enter new password">
|
placeholder="최소 8자 이상">
|
||||||
<div class="form-text">최소 8자 이상을 권장합니다.</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="confirmPasswordInput" class="form-label">Confirm password</label>
|
<label for="confirmPasswordInput" class="form-label fw-semibold">비밀번호 확인</label>
|
||||||
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required
|
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required
|
||||||
minlength="8" placeholder="Confirm new password">
|
minlength="8" placeholder="비밀번호 재입력">
|
||||||
<div id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
|
<div id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer bg-light">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
||||||
<button id="modalSubmitBtn" type="submit" class="btn btn-primary">Change Password</button>
|
<button id="modalSubmitBtn" type="submit" class="btn btn-primary px-4">변경 저장</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
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 %}
|
||||||
@@ -240,81 +240,89 @@
|
|||||||
<h5 class="text-muted fw-normal">등록된 텔레그램 봇이 없습니다.</h5>
|
<h5 class="text-muted fw-normal">등록된 텔레그램 봇이 없습니다.</h5>
|
||||||
<p class="text-muted small mb-4">우측 상단의 '봇 추가' 버튼을 눌러 알림을 설정하세요.</p>
|
<p class="text-muted small mb-4">우측 상단의 '봇 추가' 버튼을 눌러 알림을 설정하세요.</p>
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addBotModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addBotModal">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<i class="bi bi-plus-lg me-1"></i>봇 추가
|
||||||
<div class="modal-content border-0 shadow">
|
</button>
|
||||||
<form action="{{ url_for('admin.add_bot') }}" method="post">
|
</div>
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
{% endif %}
|
||||||
<div class="modal-header bg-light">
|
</div>
|
||||||
<h5 class="modal-title fw-bold">
|
</div>
|
||||||
<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="modal fade" id="addBotModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="d-flex gap-3">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="form-check">
|
<div class="modal-content border-0 shadow">
|
||||||
<input class="form-check-input" type="checkbox" name="notify_types"
|
<form action="{{ url_for('admin.add_bot') }}" method="post">
|
||||||
value="auth" id="add_auth" checked>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<label class="form-check-label" for="add_auth">
|
<div class="modal-header bg-light">
|
||||||
인증
|
<h5 class="modal-title fw-bold">
|
||||||
</label>
|
<i class="bi bi-plus-circle me-2"></i>새 텔레그램 봇 추가
|
||||||
</div>
|
</h5>
|
||||||
<div class="form-check">
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
<input class="form-check-input" type="checkbox" name="notify_types"
|
</div>
|
||||||
value="activity" id="add_activity" checked>
|
<div class="modal-body">
|
||||||
<label class="form-check-label" for="add_activity">
|
<div class="mb-3">
|
||||||
활동
|
<label class="form-label fw-semibold">이름 (식별용)</label>
|
||||||
</label>
|
<input type="text" class="form-control" name="name" placeholder="예: 알림용 봇" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="mb-3">
|
||||||
<input class="form-check-input" type="checkbox" name="notify_types"
|
<label class="form-label fw-semibold">Bot Token</label>
|
||||||
value="system" id="add_system" checked>
|
<div class="input-group">
|
||||||
<label class="form-check-label" for="add_system">
|
<span class="input-group-text"><i class="bi bi-key"></i></span>
|
||||||
시스템
|
<input type="text" class="form-control font-monospace" name="token"
|
||||||
</label>
|
placeholder="123456:ABC..." required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-text">BotFather에게 받은 API Token</div>
|
||||||
<small class="text-muted">선택한 알림 유형만 전송됩니다</small>
|
</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">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold">설명</label>
|
<label class="form-label fw-semibold">알림 유형</label>
|
||||||
<textarea class="form-control" name="description" rows="2"
|
<div class="d-flex gap-3">
|
||||||
placeholder="선택 사항"></textarea>
|
<div class="form-check">
|
||||||
</div>
|
<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>
|
||||||
<div class="modal-footer bg-light">
|
<div class="form-check">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
<input class="form-check-input" type="checkbox" name="notify_types" value="activity"
|
||||||
<button type="submit" class="btn btn-primary px-4">추가</button>
|
id="add_activity" checked>
|
||||||
|
<label class="form-check-label" for="add_activity">
|
||||||
|
활동
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<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>
|
</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>
|
||||||
{% endblock %}
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -29,21 +29,30 @@
|
|||||||
<!-- Skip to main content (접근성) -->
|
<!-- Skip to main content (접근성) -->
|
||||||
<a href="#main-content" class="visually-hidden-focusable">본문으로 건너뛰기</a>
|
<a href="#main-content" class="visually-hidden-focusable">본문으로 건너뛰기</a>
|
||||||
|
|
||||||
{# 플래시 메시지 (전역) #}
|
{# 플래시 메시지 (좌측 상단 토스트 스타일) #}
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="position-fixed end-0 p-3" style="z-index: 2000; top: 70px;">
|
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 2000; margin-top: 60px;">
|
||||||
{% for cat, msg in messages %}
|
{% for cat, msg in messages %}
|
||||||
<div class="alert alert-{{ cat }} alert-dismissible fade show shadow-lg" role="alert">
|
<div class="toast align-items-center text-white bg-{{ 'success' if cat == 'success' else 'danger' if cat == 'error' else 'primary' }} border-0 fade show"
|
||||||
<i class="bi bi-{{ 'check-circle' if cat == 'success' else 'exclamation-triangle' }} me-2"></i>
|
role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="3000">
|
||||||
{{ msg }}
|
<div class="d-flex">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<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>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
@@ -110,6 +119,10 @@
|
|||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main id="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 %}">
|
class="{% if request.endpoint in ['auth.login', 'auth.register', 'auth.reset_password'] %}container mt-5{% else %}container mt-4 container-card{% endif %}">
|
||||||
|
|
||||||
|
{# 플래시 메시지 (컨텐츠 상단 표시) #}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -133,6 +146,24 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block scripts %}{% endblock %}
|
{% 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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,26 +1,170 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Edit XML File - Dell Server Info{% endblock %}
|
{% block title %}XML 편집: {{ filename }} - Dell Server Info{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/edit_xml.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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card shadow-lg">
|
<div class="container-fluid py-4 h-100">
|
||||||
<div class="card-header bg-primary text-white">
|
<!-- Breadcrumb / Navigation -->
|
||||||
<h3>Edit XML File: <strong>{{ filename }}</strong></h3>
|
<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>
|
</div>
|
||||||
<div class="card-body">
|
|
||||||
<form method="post">
|
<form id="editorForm" method="post" style="height: 100%;">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<div class="form-group">
|
<!-- Monaco Editor의 내용은 submit 시 이 textarea에 동기화됨 -->
|
||||||
<label for="xmlContent">XML Content</label>
|
<textarea name="content" id="hiddenContent" style="display:none;">{{ content }}</textarea>
|
||||||
<textarea id="xmlContent" name="content" class="form-control" rows="20">{{ 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>
|
</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>
|
<!-- Editor Area -->
|
||||||
</form>
|
<div id="monaco-editor-root">
|
||||||
</div>
|
<div class="editor-loading">
|
||||||
|
<div class="spinner-border text-primary me-3" role="status"></div>
|
||||||
|
<div>에디터를 불러오는 중...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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 %}
|
||||||
@@ -21,30 +21,39 @@
|
|||||||
{# IP 처리 카드 #}
|
{# IP 처리 카드 #}
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="card border shadow-sm h-100">
|
<div class="card border shadow-sm h-100">
|
||||||
<div class="card-header bg-primary text-white border-0 py-2">
|
<div class="card-header bg-light border-0 py-2">
|
||||||
<h6 class="mb-0 fw-semibold">
|
<h6 class="mb-0">
|
||||||
<i class="bi bi-hdd-network me-2"></i>
|
<i class="bi bi-hdd-network me-2"></i>
|
||||||
IP 처리
|
IP 처리
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4 h-100 d-flex flex-column">
|
||||||
<form id="ipForm" method="post" action="{{ url_for('main.process_ips') }}">
|
<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() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
|
||||||
{# 스크립트 선택 #}
|
{# 스크립트 선택 #}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="script" class="form-label">스크립트 선택</label>
|
<select id="script" name="script" class="form-select" required autocomplete="off">
|
||||||
<select id="script" name="script" class="form-select" required>
|
|
||||||
<option value="">스크립트를 선택하세요</option>
|
<option value="">스크립트를 선택하세요</option>
|
||||||
|
{% 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 %}
|
{% for script in scripts %}
|
||||||
<option value="{{ script }}">{{ script }}</option>
|
<option value="{{ script }}">{{ script }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# XML 파일 선택 (조건부) #}
|
{# XML 파일 선택 (조건부) #}
|
||||||
<div class="mb-3" id="xmlFileGroup" style="display:none;">
|
<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">
|
<select id="xmlFile" name="xmlFile" class="form-select">
|
||||||
<option value="">XML 파일 선택</option>
|
<option value="">XML 파일 선택</option>
|
||||||
{% for xml_file in xml_files %}
|
{% for xml_file in xml_files %}
|
||||||
@@ -54,18 +63,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# IP 주소 입력 #}
|
{# IP 주소 입력 #}
|
||||||
<div class="mb-3">
|
<div class="mb-3 flex-grow-1 d-flex flex-column">
|
||||||
<label for="ips" class="form-label">
|
<label for="ips" class="form-label w-100 d-flex justify-content-between align-items-end mb-2">
|
||||||
IP 주소 (각 줄에 하나)
|
<span class="mb-1">
|
||||||
<span class="badge bg-secondary ms-2" id="ipLineCount">0 대설정</span>
|
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>
|
</label>
|
||||||
<textarea id="ips" name="ips" rows="4" class="form-control font-monospace"
|
<textarea id="ips" name="ips" class="form-control font-monospace flex-grow-1"
|
||||||
placeholder="예: 192.168.1.1 192.168.1.2 192.168.1.3" required></textarea>
|
placeholder="예: 192.168.1.1 192.168.1.2" required style="resize: none;"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-100">
|
<div class="mt-auto">
|
||||||
처리
|
<button type="submit"
|
||||||
</button>
|
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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,8 +101,8 @@
|
|||||||
{# 공유 작업 카드 #}
|
{# 공유 작업 카드 #}
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="card border shadow-sm h-100">
|
<div class="card border shadow-sm h-100">
|
||||||
<div class="card-header bg-success text-white border-0 py-2">
|
<div class="card-header bg-light border-0 py-2">
|
||||||
<h6 class="mb-0 fw-semibold">
|
<h6 class="mb-0">
|
||||||
<i class="bi bi-share me-2"></i>
|
<i class="bi bi-share me-2"></i>
|
||||||
공유 작업
|
공유 작업
|
||||||
</h6>
|
</h6>
|
||||||
@@ -93,16 +120,34 @@
|
|||||||
style="font-size: 0.95rem;" placeholder="서버 리스트를 입력하세요..."></textarea>
|
style="font-size: 0.95rem;" placeholder="서버 리스트를 입력하세요..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<div class="row g-2">
|
||||||
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}" class="btn btn-secondary">
|
<div class="col-4">
|
||||||
MAC to Excel
|
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}"
|
||||||
</button>
|
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">
|
||||||
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}" class="btn btn-success">
|
<div class="rounded-circle bg-primary bg-opacity-10 text-primary p-1">
|
||||||
GUID to Excel
|
<i class="bi bi-file-earmark-spreadsheet fs-5"></i>
|
||||||
</button>
|
</div>
|
||||||
<button type="submit" formaction="{{ url_for('utils.update_gpu_list') }}" class="btn btn-warning">
|
<span class="fw-medium text-dark" style="font-size: 0.8rem;">MAC to Excel</span>
|
||||||
GPU to Excel
|
</button>
|
||||||
</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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,59 +187,108 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body p-4 file-tools">
|
<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">
|
<div class="row g-2">
|
||||||
<label class="form-label text-nowrap">ZIP 다운로드</label>
|
<!-- ZIP 다운로드 -->
|
||||||
<form method="post" action="{{ url_for('main.download_zip') }}">
|
<div class="col-6">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<div class="card h-100 border-primary-subtle bg-primary-subtle bg-opacity-10">
|
||||||
<div class="input-group">
|
<div class="card-body p-2 d-flex flex-column justify-content-center">
|
||||||
<input type="text" class="form-control" name="zip_filename" placeholder="파일명" required>
|
<h6 class="card-title fw-bold text-primary mb-1 small" style="font-size: 0.75rem;">
|
||||||
<button class="btn btn-primary" type="submit">다운로드</button>
|
<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>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 파일 백업 -->
|
<!-- 파일 백업 -->
|
||||||
<div class="col">
|
<div class="col-6">
|
||||||
<label class="form-label text-nowrap">파일 백업</label>
|
<div class="card h-100 border-success-subtle bg-success-subtle bg-opacity-10">
|
||||||
<form method="post" action="{{ url_for('main.backup_files') }}">
|
<div class="card-body p-2 d-flex flex-column justify-content-center">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<h6 class="card-title fw-bold text-success mb-1 small" style="font-size: 0.75rem;">
|
||||||
<div class="input-group">
|
<i class="bi bi-hdd-network me-1"></i>백업
|
||||||
<input type="text" class="form-control" name="backup_prefix" placeholder="PO로 시작">
|
</h6>
|
||||||
<button class="btn btn-success" type="submit">백업</button>
|
<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>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MAC 파일 이동 -->
|
<!-- 하단: 원클릭 액션 (파일 정리) -->
|
||||||
<div class="col">
|
<div class="card bg-light border-0">
|
||||||
<label class="form-label text-nowrap">MAC 파일 이동</label>
|
<div class="card-body p-3">
|
||||||
<form id="macMoveForm" method="post" action="{{ url_for('utils.move_mac_files') }}">
|
<small class="text-muted fw-bold text-uppercase mb-2 d-block">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<i class="bi bi-folder-symlink me-1"></i>파일 정리 (Quick Move)
|
||||||
<button class="btn btn-warning w-100" type="submit">MAC Move</button>
|
</small>
|
||||||
</form>
|
<div class="row g-2">
|
||||||
</div>
|
<!-- 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 파일 이동 -->
|
<!-- GUID Move -->
|
||||||
<div class="col">
|
<div class="col-4">
|
||||||
<label class="form-label text-nowrap">GUID 파일 이동</label>
|
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}" class="h-100">
|
||||||
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<button
|
||||||
<button class="btn btn-info w-100" type="submit">GUID Move</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"
|
||||||
</form>
|
type="submit">
|
||||||
</div>
|
<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-4">
|
||||||
<div class="col">
|
<form id="gpuMoveForm" method="post" action="{{ url_for('utils.move_gpu_files') }}" class="h-100">
|
||||||
<label class="form-label text-nowrap">GPU 파일 이동</label>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<form id="gpuMoveForm" method="post" action="{{ url_for('utils.move_gpu_files') }}">
|
<button
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
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"
|
||||||
<button class="btn btn-secondary w-100" type="submit">GPU Move</button>
|
type="submit">
|
||||||
</form>
|
<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>
|
</div>
|
||||||
@@ -386,17 +480,76 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/index.js') }}"></script>
|
<!-- 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', function () {
|
||||||
|
// Tom Select 초기화
|
||||||
|
// 모바일 등 환경 고려, 검색 가능하게 설정
|
||||||
|
if (document.getElementById('script')) {
|
||||||
|
new TomSelect("#script", {
|
||||||
|
create: false,
|
||||||
|
sortField: {
|
||||||
|
field: "text",
|
||||||
|
direction: "asc"
|
||||||
|
},
|
||||||
|
placeholder: "스크립트를 검색하거나 선택하세요...",
|
||||||
|
plugins: ['clear_button'],
|
||||||
|
allowEmptyOption: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='js/index.js') }}?v={{ range(1, 100000) | random }}"></script>
|
||||||
|
|
||||||
<!-- 외부 script.js 파일 (IP 폼 처리 로직 포함) -->
|
<!-- 외부 script.js 파일 (IP 폼 처리 로직 포함) -->
|
||||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
<script src="{{ url_for('static', filename='script.js') }}?v={{ range(1, 100000) | random }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,215 +1,454 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}XML 파일 관리 & 배포 - Dell Server Info{% endblock %}
|
{% block title %}XML 설정 관리 & 배포 - Dell Server Info{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
|
<!-- Existing SCP CSS for legacy support or specific components -->
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/scp.css') }}">
|
<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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="main-title">설정 파일 관리 (SCP)</h1>
|
<div class="container-fluid py-4">
|
||||||
<p class="subtitle">iDRAC 서버 설정(XML)을 내보내거나 가져오고, 버전을 비교할 수 있습니다.</p>
|
|
||||||
|
|
||||||
<div class="row">
|
<!-- Header Section -->
|
||||||
<!-- 왼쪽: 파일 업로드 및 내보내기 -->
|
<div class="row mb-4">
|
||||||
<div class="col-md-4">
|
<div class="col">
|
||||||
<div class="card">
|
<h2 class="fw-bold mb-1">
|
||||||
<div class="card-header-custom">
|
<i class="bi bi-file-earmark-code text-primary me-2"></i>
|
||||||
<span><i class="fas fa-cloud-upload-alt me-2"></i>파일 등록</span>
|
설정 파일 관리
|
||||||
</div>
|
</h2>
|
||||||
<div class="card-body">
|
<p class="text-muted mb-0">서버 설정(XML) 파일을 업로드, 관리 및 배포합니다.</p>
|
||||||
<!-- 1. PC에서 업로드 -->
|
</div>
|
||||||
<h6 class="mb-3"><i class="fas fa-laptop me-2"></i>PC에서 업로드</h6>
|
<div class="col-auto align-self-end">
|
||||||
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data" class="mb-4">
|
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#exportModal">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<i class="bi bi-server me-2"></i>iDRAC에서 추출
|
||||||
<div class="upload-section">
|
</button>
|
||||||
<div class="mb-2">
|
|
||||||
<div class="custom-file">
|
|
||||||
<input type="file" class="custom-file-input" id="xmlFile" name="xmlFile" accept=".xml"
|
|
||||||
onchange="updateFileName(this)">
|
|
||||||
<label class="custom-file-label" for="xmlFile" id="fileLabel">파일 선택</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary w-100">
|
|
||||||
<i class="fas fa-upload me-1"></i>업로드
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<!-- 2. iDRAC에서 내보내기 -->
|
|
||||||
<h6 class="mb-3"><i class="fas fa-server me-2"></i>iDRAC에서 추출 (Export)</h6>
|
|
||||||
<button type="button" class="btn btn-outline-primary w-100" data-bs-toggle="modal"
|
|
||||||
data-bs-target="#exportModal">
|
|
||||||
<i class="fas fa-download me-1"></i>설정 추출하기
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 오른쪽: 파일 목록 및 작업 -->
|
<div class="row g-4">
|
||||||
<div class="col-md-8">
|
<!-- Left: Upload Section (30% on large screens) -->
|
||||||
<div class="card">
|
<div class="col-lg-4 col-xl-3">
|
||||||
<div class="card-header-custom">
|
<div class="card border shadow-sm h-100">
|
||||||
<span><i class="fas fa-list me-2"></i>파일 목록</span>
|
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
|
||||||
<button class="btn btn-light btn-sm text-primary" id="compareBtn"
|
<h6 class="fw-bold mb-0 text-dark">
|
||||||
data-url="{{ url_for('scp.diff_scp') }}" onclick="compareSelected()">
|
<i class="bi bi-cloud-upload me-2 text-primary"></i>파일 업로드
|
||||||
<i class="fas fa-exchange-alt me-1"></i>선택 비교
|
</h6>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
<div class="card-body">
|
||||||
<div class="card-body">
|
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data"
|
||||||
{% if xml_files %}
|
id="uploadForm">
|
||||||
<div class="file-list">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
{% for xml_file in xml_files %}
|
|
||||||
<div class="icon-badge-item">
|
<div class="drop-zone" id="dropZone">
|
||||||
<div class="icon-badge-left">
|
<input type="file" name="xmlFile" id="xmlFile" accept=".xml"
|
||||||
<input type="checkbox" class="select-checkbox file-selector" value="{{ xml_file }}">
|
onchange="handleFileSelect(this)">
|
||||||
<div class="file-icon-small">
|
<div class="drop-zone-icon">
|
||||||
<i class="fas fa-file-code"></i>
|
<i class="bi bi-file-earmark-arrow-up"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-name-section">
|
<div class="drop-zone-text" id="dropZoneText">
|
||||||
<span class="file-name-badge" title="{{ xml_file }}">{{ xml_file }}</span>
|
클릭하여 파일 선택<br>또는 파일을 여기로 드래그
|
||||||
<span class="badge-custom">XML</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="drop-zone-hint">XML 파일만 지원됩니다.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
|
||||||
<!-- 배포 버튼 -->
|
<button type="submit" class="btn btn-primary w-100 mt-3 shadow-sm">
|
||||||
<button type="button" class="btn btn-info btn-sm text-white"
|
<i class="bi bi-upload me-2"></i>업로드 시작
|
||||||
onclick="openDeployModal('{{ xml_file }}')">
|
</button>
|
||||||
<i class="fas fa-plane-departure"></i> <span>배포</span>
|
</form>
|
||||||
</button>
|
|
||||||
<!-- 편집 버튼 -->
|
<div class="alert alert-light mt-4 border" role="alert">
|
||||||
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}" class="btn btn-success btn-sm">
|
<h6 class="alert-heading fs-6 fw-bold"><i class="bi bi-info-circle me-2"></i>도움말</h6>
|
||||||
<i class="fas fa-edit"></i> <span>편집</span>
|
<p class="mb-0 fs-small text-muted" style="font-size: 0.85rem;">
|
||||||
</a>
|
업로드된 XML 파일을 사용하여 여러 서버에 동일한 설정을 일괄 배포할 수 있습니다.
|
||||||
<!-- 삭제 버튼 -->
|
'비교' 기능을 사용하여 버전 간 차이를 확인하세요.
|
||||||
<form action="{{ url_for('xml.delete_xml', filename=xml_file) }}" method="POST"
|
</p>
|
||||||
style="display:inline;">
|
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
|
||||||
<button type="submit" class="btn btn-danger btn-sm"
|
|
||||||
onclick="return confirm('정말 삭제하시겠습니까?')">
|
|
||||||
<i class="fas fa-trash"></i> <span>삭제</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
</div>
|
||||||
<div class="empty-message">
|
</div>
|
||||||
<i class="fas fa-folder-open" style="font-size: 2rem; color: #ddd;"></i>
|
|
||||||
<p class="mt-2 mb-0">파일이 없습니다.</p>
|
<!-- 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>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Export Modal -->
|
<!-- Export Modal (Include existing modal logic but restyled) -->
|
||||||
<div class="modal fade" id="exportModal" tabindex="-1">
|
<div class="modal fade" id="exportModal" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content border-0 shadow-lg">
|
||||||
<form action="{{ url_for('scp.export_scp') }}" method="POST">
|
<form action="{{ url_for('scp.export_scp') }}" method="POST">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<div class="modal-header">
|
<div class="modal-header bg-primary text-white">
|
||||||
<h5 class="modal-title">iDRAC 설정 내보내기</h5>
|
<h5 class="modal-title fs-6 fw-bold"><i class="bi bi-download me-2"></i>iDRAC 설정 내보내기</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body p-4">
|
||||||
<div class="alert alert-info py-2" style="font-size: 0.9rem;">
|
<div class="alert alert-info py-2 small mb-4">
|
||||||
<i class="fas fa-info-circle me-1"></i> 네트워크 공유 폴더(CIFS)가 필요합니다.
|
<i class="bi bi-info-circle-fill me-2"></i> CIFS 네트워크 공유 폴더가 필요합니다.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6>대상 iDRAC</h6>
|
<h6 class="text-primary fw-bold mb-3 small text-uppercase">대상 iDRAC</h6>
|
||||||
<div class="mb-2"><input type="text" class="form-control" name="target_ip" placeholder="iDRAC IP"
|
<div class="form-floating mb-2">
|
||||||
required></div>
|
<input type="text" class="form-control" id="targetIp" name="target_ip" placeholder="IP"
|
||||||
<div class="row mb-3">
|
required>
|
||||||
<div class="col"><input type="text" class="form-control" name="username" placeholder="User"
|
<label for="targetIp">iDRAC IP Address</label>
|
||||||
required></div>
|
</div>
|
||||||
<div class="col"><input type="password" class="form-control" name="password"
|
<div class="row g-2 mb-4">
|
||||||
placeholder="Password" required></div>
|
<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>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<h6 class="text-success fw-bold mb-3 small text-uppercase">저장소 (CIFS Share)</h6>
|
||||||
<h6>네트워크 공유 (저장소)</h6>
|
<div class="row g-2 mb-2">
|
||||||
<div class="mb-2"><input type="text" class="form-control" name="share_ip"
|
<div class="col-8">
|
||||||
placeholder="Share Server IP" required></div>
|
<div class="form-floating">
|
||||||
<div class="mb-2"><input type="text" class="form-control" name="share_name"
|
<input type="text" class="form-control" name="share_ip" placeholder="IP" required>
|
||||||
placeholder="Share Name (e.g. public)" required></div>
|
<label>Share IP</label>
|
||||||
<div class="mb-2"><input type="text" class="form-control" name="filename"
|
</div>
|
||||||
placeholder="Save Filename (e.g. backup.xml)" required></div>
|
</div>
|
||||||
<div class="row">
|
<div class="col-4">
|
||||||
<div class="col"><input type="text" class="form-control" name="share_user"
|
<div class="form-floating">
|
||||||
placeholder="Share User"></div>
|
<input type="text" class="form-control" name="share_name" placeholder="Name" required>
|
||||||
<div class="col"><input type="password" class="form-control" name="share_pwd"
|
<label>Share Name</label>
|
||||||
placeholder="Share Password"></div>
|
</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>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer bg-light">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">취소</button>
|
||||||
<button type="submit" class="btn btn-primary">내보내기 시작</button>
|
<button type="submit" class="btn btn-primary px-4">내보내기 실행</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Deploy (Import) Modal -->
|
<!-- Deploy Modal -->
|
||||||
<div class="modal fade" id="deployModal" tabindex="-1">
|
<div class="modal fade" id="deployModal" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="modal-content">
|
<div class="modal-content border-0 shadow-lg">
|
||||||
<form action="{{ url_for('scp.import_scp') }}" method="POST">
|
<form action="{{ url_for('scp.import_scp') }}" method="POST">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<div class="modal-header">
|
<div class="modal-header bg-danger text-white">
|
||||||
<h5 class="modal-title">설정 배포 (Import)</h5>
|
<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" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body p-4">
|
||||||
<div class="alert alert-warning py-2" style="font-size: 0.9rem;">
|
<div class="alert alert-warning py-2 small mb-4">
|
||||||
<i class="fas fa-exclamation-triangle me-1"></i> 적용 후 서버가 재부팅될 수 있습니다.
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> 적용 후 서버가 재부팅될 수 있습니다.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label">배포할 파일</label>
|
<label class="form-label fw-bold small text-muted">배포 파일</label>
|
||||||
<input type="text" class="form-control" id="deployFilename" name="filename" readonly>
|
<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>
|
</div>
|
||||||
|
|
||||||
<h6>대상 iDRAC</h6>
|
<h6 class="text-primary fw-bold mb-3 small text-uppercase">대상 iDRAC</h6>
|
||||||
<div class="mb-2"><input type="text" class="form-control" name="target_ip" placeholder="iDRAC IP"
|
<div class="form-floating mb-2">
|
||||||
required></div>
|
<input type="text" class="form-control" name="target_ip" placeholder="IP" required>
|
||||||
<div class="row mb-3">
|
<label>iDRAC IP</label>
|
||||||
<div class="col"><input type="text" class="form-control" name="username" placeholder="User"
|
</div>
|
||||||
required></div>
|
<div class="row g-2 mb-4">
|
||||||
<div class="col"><input type="password" class="form-control" name="password"
|
<div class="col">
|
||||||
placeholder="Password" required></div>
|
<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>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<h6 class="text-success fw-bold mb-3 small text-uppercase">소스 위치 (CIFS Share)</h6>
|
||||||
<h6>네트워크 공유 (소스 위치)</h6>
|
<div class="row g-2 mb-2">
|
||||||
<div class="mb-2"><input type="text" class="form-control" name="share_ip"
|
<div class="col-8">
|
||||||
placeholder="Share Server IP" required></div>
|
<div class="form-floating">
|
||||||
<div class="mb-2"><input type="text" class="form-control" name="share_name" placeholder="Share Name"
|
<input type="text" class="form-control" name="share_ip" placeholder="IP" required>
|
||||||
required></div>
|
<label>Share IP</label>
|
||||||
<div class="row mb-3">
|
</div>
|
||||||
<div class="col"><input type="text" class="form-control" name="share_user"
|
</div>
|
||||||
placeholder="Share User"></div>
|
<div class="col-4">
|
||||||
<div class="col"><input type="password" class="form-control" name="share_pwd"
|
<div class="form-floating">
|
||||||
placeholder="Share Password"></div>
|
<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>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="form-floating">
|
||||||
<label class="form-label">적용 모드</label>
|
<select class="form-select" name="import_mode" id="importMode">
|
||||||
<select class="form-select" name="import_mode">
|
|
||||||
<option value="Replace">전체 교체 (Replace)</option>
|
<option value="Replace">전체 교체 (Replace)</option>
|
||||||
<option value="Append">변경분만 적용 (Append)</option>
|
<option value="Append">변경분만 적용 (Append)</option>
|
||||||
</select>
|
</select>
|
||||||
|
<label for="importMode">적용 모드</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer bg-light">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">취소</button>
|
||||||
<button type="submit" class="btn btn-danger">배포 시작</button>
|
<button type="submit" class="btn btn-danger px-4">배포 시작</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -220,4 +459,67 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='js/scp.js') }}"></script>
|
<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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 드래그 효과
|
||||||
|
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 %}
|
{% endblock %}
|
||||||
|
```
|
||||||
@@ -1,58 +1,173 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}설정 파일 비교 - Dell Server Info{% endblock %}
|
{% block title %}설정 파일 비교: {{ file1 }} vs {{ file2 }}{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/scp.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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container-fluid py-4 h-100">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<!-- Breadcrumb -->
|
||||||
<h2>설정 파일 비교</h2>
|
<div class="mb-3">
|
||||||
<a href="{{ url_for('xml.xml_management') }}" class="btn btn-secondary">
|
<a href="{{ url_for('xml.xml_management') }}" class="text-decoration-none text-muted small fw-bold">
|
||||||
<i class="fas fa-arrow-left me-1"></i> 목록으로
|
<i class="bi bi-arrow-left me-1"></i>목록으로 돌아가기
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="diff-page-container">
|
||||||
<div class="card-header bg-info text-white">
|
<!-- Toolbar -->
|
||||||
<i class="fas fa-exchange-alt me-2"></i>비교 대상
|
<div class="diff-toolbar">
|
||||||
</div>
|
<div class="diff-files">
|
||||||
<div class="card-body">
|
<div class="file-badge text-danger border-danger-subtle bg-danger-subtle">
|
||||||
<div class="row text-center">
|
<i class="bi bi-file-earmark-minus"></i>
|
||||||
<div class="col-md-5">
|
<span>{{ file1 }}</span> <!-- Original -->
|
||||||
<h5>{{ file1 }}</h5>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<i class="bi bi-arrow-right diff-arrow"></i>
|
||||||
<i class="fas fa-arrow-right text-muted"></i>
|
<div class="file-badge text-success border-success-subtle bg-success-subtle">
|
||||||
</div>
|
<i class="bi bi-file-earmark-plus"></i>
|
||||||
<div class="col-md-5">
|
<span>{{ file2 }}</span> <!-- Modified -->
|
||||||
<h5>{{ file2 }}</h5>
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
|
<!-- Monaco Diff Editor -->
|
||||||
|
<div id="monaco-diff-root"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<!-- Raw Content Hidden Inputs (Jinja2 will escape HTML entities automatically) -->
|
||||||
<div class="card-header">
|
<textarea id="hidden_content1" style="display:none;">{{ content1 }}</textarea>
|
||||||
<i class="fas fa-code me-2"></i>Diff 결과
|
<textarea id="hidden_content2" style="display:none;">{{ content2 }}</textarea>
|
||||||
</div>
|
|
||||||
<div class="card-body p-0">
|
|
||||||
<div class="diff-container">
|
|
||||||
{% for line in diff_content.splitlines() %}
|
|
||||||
{% if line.startswith('+++') or line.startswith('---') %}
|
|
||||||
<span class="diff-line diff-header">{{ line }}</span>
|
|
||||||
{% elif line.startswith('+') %}
|
|
||||||
<span class="diff-line diff-add">{{ line }}</span>
|
|
||||||
{% elif line.startswith('-') %}
|
|
||||||
<span class="diff-line diff-del">{{ line }}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="diff-line">{{ line }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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 %}
|
||||||
10
config.py
10
config.py
@@ -47,6 +47,15 @@ class Config:
|
|||||||
# ── DB (환경변수 DATABASE_URL 있으면 그 값을 우선 사용)
|
# ── DB (환경변수 DATABASE_URL 있으면 그 값을 우선 사용)
|
||||||
sqlite_path = (INSTANCE_DIR / "site.db").as_posix()
|
sqlite_path = (INSTANCE_DIR / "site.db").as_posix()
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", f"sqlite:///{sqlite_path}")
|
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", f"sqlite:///{sqlite_path}")
|
||||||
|
|
||||||
|
# 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
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
# ── Telegram (미설정 시 기능 비활성처럼 동작)
|
# ── Telegram (미설정 시 기능 비활성처럼 동작)
|
||||||
@@ -78,6 +87,7 @@ class Config:
|
|||||||
|
|
||||||
# ── 세션
|
# ── 세션
|
||||||
PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.getenv("SESSION_MINUTES", 30)))
|
PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.getenv("SESSION_MINUTES", 30)))
|
||||||
|
SESSION_PERMANENT = True # 브라우저 닫아도 세션 유지 (타임아웃까지)
|
||||||
|
|
||||||
# ── SocketIO
|
# ── SocketIO
|
||||||
# threading / eventlet / gevent 중 선택. 기본은 threading (Windows 안정)
|
# threading / eventlet / gevent 중 선택. 기본은 threading (Windows 안정)
|
||||||
|
|||||||
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
8084
data/logs/app.log
8084
data/logs/app.log
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,14 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
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 파일 로드
|
# .env 파일 로드
|
||||||
load_dotenv()
|
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"01. RAID Settings - Raid ProductName : {get_value(hwinventory, 'ProductName = PERC')}\n")
|
||||||
f.write(f"02. RAID Settings - Raid Types : No-Raid mode\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:
|
except Exception as e:
|
||||||
print(f"오류 발생: {e}")
|
logging.error(f"오류 발생: {e}")
|
||||||
|
|
||||||
# 명령 결과에서 원하는 값 가져오기
|
# 명령 결과에서 원하는 값 가져오기
|
||||||
def get_value(output, key):
|
def get_value(output, key):
|
||||||
@@ -117,7 +125,7 @@ start_time = time.time()
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
if len(sys.argv) != 2:
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
ip_file_path = validate_ip_file(sys.argv[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_minutes = int((elapsed_time % 3600) // 60)
|
||||||
elapsed_seconds = int(elapsed_time % 60)
|
elapsed_seconds = int(elapsed_time % 60)
|
||||||
|
|
||||||
print("정보 수집 완료.")
|
logging.info("정보 수집 완료.")
|
||||||
print(f"수집 완료 시간: {elapsed_hours} 시간, {elapsed_minutes} 분, {elapsed_seconds} 초.")
|
logging.info(f"수집 완료 시간: {elapsed_hours} 시간, {elapsed_minutes} 분, {elapsed_seconds} 초.")
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ RACADM = os.getenv("RACADM_PATH", "racadm") # PATH에 있으면 'racadm'
|
|||||||
# 로깅
|
# 로깅
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
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):
|
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)]
|
# 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:
|
try:
|
||||||
p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=180)
|
p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=180)
|
||||||
stdout = (p.stdout or "").strip()
|
stdout = (p.stdout or "").strip()
|
||||||
@@ -88,7 +89,7 @@ def apply_xml(ip: str, xml_path: Path) -> tuple[bool, str]:
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 3:
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
ip_file = Path(sys.argv[1])
|
ip_file = Path(sys.argv[1])
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import time
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import sys
|
import sys
|
||||||
from multiprocessing import Pool
|
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 파일에서 환경 변수 로드
|
load_dotenv() # .env 파일에서 환경 변수 로드
|
||||||
@@ -14,26 +22,27 @@ IDRAC_USER, IDRAC_PASS = os.getenv("IDRAC_USER"), os.getenv("IDRAC_PASS")
|
|||||||
# IP 주소 파일 로드 함수
|
# IP 주소 파일 로드 함수
|
||||||
def load_ip_file(ip_file_path):
|
def load_ip_file(ip_file_path):
|
||||||
if not os.path.isfile(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:
|
with open(ip_file_path, "r") as file:
|
||||||
return [line.strip() for line in file if line.strip()]
|
return [line.strip() for line in file if line.strip()]
|
||||||
|
|
||||||
# iDRAC 정보를 가져오는 함수 정의
|
# iDRAC 정보를 가져오는 함수 정의
|
||||||
def fetch_idrac_info(idrac_ip):
|
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:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "collect"],
|
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "techsupreport", "collect"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
print(
|
if result.returncode == 0:
|
||||||
f"Successfully collected TSR report for {idrac_ip}"
|
logging.info(f"Successfully collected TSR report for {idrac_ip}")
|
||||||
if result.returncode == 0
|
else:
|
||||||
else f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}"
|
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||||
|
|
||||||
# 메인 함수
|
# 메인 함수
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
|||||||
pool.map(fetch_idrac_info, ip_list)
|
pool.map(fetch_idrac_info, ip_list)
|
||||||
|
|
||||||
elapsed_time = time.time() - start_time
|
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
|
from dotenv import load_dotenv
|
||||||
import sys
|
import sys
|
||||||
from multiprocessing import Pool
|
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 파일에서 환경 변수 로드
|
load_dotenv() # .env 파일에서 환경 변수 로드
|
||||||
@@ -16,13 +24,14 @@ OME_PASS = os.getenv("OME_PASS")
|
|||||||
# IP 주소 파일 로드 및 유효성 검사
|
# IP 주소 파일 로드 및 유효성 검사
|
||||||
def load_ip_file(ip_file_path):
|
def load_ip_file(ip_file_path):
|
||||||
if not os.path.isfile(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:
|
with open(ip_file_path, "r") as file:
|
||||||
return [line.strip() for line in file if line.strip()]
|
return [line.strip() for line in file if line.strip()]
|
||||||
|
|
||||||
# iDRAC 정보를 가져오는 함수
|
# iDRAC 정보를 가져오는 함수
|
||||||
def fetch_idrac_info(idrac_ip):
|
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:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
@@ -32,13 +41,13 @@ def fetch_idrac_info(idrac_ip):
|
|||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
print(
|
if result.returncode == 0:
|
||||||
f"Successfully collected TSR report for {idrac_ip}"
|
logging.info(f"Successfully collected TSR report for {idrac_ip}")
|
||||||
if result.returncode == 0
|
else:
|
||||||
else f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}"
|
logging.error(f"Failed to collect TSR report for {idrac_ip}: {result.stderr.strip()}")
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||||
|
|
||||||
# 메인 함수
|
# 메인 함수
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -51,4 +60,4 @@ if __name__ == "__main__":
|
|||||||
with Pool() as pool:
|
with Pool() as pool:
|
||||||
pool.map(fetch_idrac_info, ip_list)
|
pool.map(fetch_idrac_info, ip_list)
|
||||||
|
|
||||||
print("설정 완료.")
|
logging.info("설정 완료.")
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import time
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import sys
|
import sys
|
||||||
from multiprocessing import Pool
|
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 environment variables
|
||||||
load_dotenv() # Load variables from .env file
|
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
|
# Load IP addresses from file
|
||||||
def load_ip_file(ip_file_path):
|
def load_ip_file(ip_file_path):
|
||||||
if not os.path.isfile(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:
|
with open(ip_file_path, "r") as file:
|
||||||
return [line.strip() for line in file if line.strip()]
|
return [line.strip() for line in file if line.strip()]
|
||||||
|
|
||||||
# Power on the server for given iDRAC IP
|
# Power on the server for given iDRAC IP
|
||||||
def poweron_idrac_server(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:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerup"],
|
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerup"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
print(
|
if result.returncode == 0:
|
||||||
f"Successfully powered on server for {idrac_ip}"
|
logging.info(f"Successfully powered on server for {idrac_ip}")
|
||||||
if result.returncode == 0
|
else:
|
||||||
else f"Failed to power on server for {idrac_ip}: {result.stderr.strip()}"
|
logging.error(f"Failed to power on server for {idrac_ip}: {result.stderr.strip()}")
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||||
|
|
||||||
# Main function
|
# Main function
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
|||||||
pool.map(poweron_idrac_server, ip_list)
|
pool.map(poweron_idrac_server, ip_list)
|
||||||
|
|
||||||
elapsed_time = time.time() - start_time
|
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
|
from dotenv import load_dotenv
|
||||||
import sys
|
import sys
|
||||||
from multiprocessing import Pool
|
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 environment variables
|
||||||
load_dotenv() # Load variables from .env file
|
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
|
# Load IP addresses from file
|
||||||
def load_ip_file(ip_file_path):
|
def load_ip_file(ip_file_path):
|
||||||
if not os.path.isfile(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:
|
with open(ip_file_path, "r") as file:
|
||||||
return [line.strip() for line in file if line.strip()]
|
return [line.strip() for line in file if line.strip()]
|
||||||
|
|
||||||
# Power off the server for given iDRAC IP
|
# Power off the server for given iDRAC IP
|
||||||
def poweroff_idrac_server(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:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerdown"],
|
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "serveraction", "powerdown"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
print(
|
if result.returncode == 0:
|
||||||
f"Successfully powered off server for {idrac_ip}"
|
logging.info(f"Successfully powered off server for {idrac_ip}")
|
||||||
if result.returncode == 0
|
else:
|
||||||
else f"Failed to power off server for {idrac_ip}: {result.stderr.strip()}"
|
logging.error(f"Failed to power off server for {idrac_ip}: {result.stderr.strip()}")
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||||
|
|
||||||
# Main function
|
# Main function
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
|||||||
pool.map(poweroff_idrac_server, ip_list)
|
pool.map(poweroff_idrac_server, ip_list)
|
||||||
|
|
||||||
elapsed_time = time.time() - start_time
|
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
|
from dotenv import load_dotenv
|
||||||
import sys
|
import sys
|
||||||
from multiprocessing import Pool
|
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 environment variables
|
||||||
load_dotenv() # Load variables from .env file
|
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
|
# Load IP addresses from file
|
||||||
def load_ip_file(ip_file_path):
|
def load_ip_file(ip_file_path):
|
||||||
if not os.path.isfile(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:
|
with open(ip_file_path, "r") as file:
|
||||||
return [line.strip() for line in file if line.strip()]
|
return [line.strip() for line in file if line.strip()]
|
||||||
|
|
||||||
# Delete all jobs for given iDRAC IP
|
# Delete all jobs for given iDRAC IP
|
||||||
def delete_all_jobs(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:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "delete", "-i", "ALL"],
|
["racadm", "-r", idrac_ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "jobqueue", "delete", "-i", "ALL"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
print(
|
if result.returncode == 0:
|
||||||
f"Successfully deleted all jobs for {idrac_ip}"
|
logging.info(f"Successfully deleted all jobs for {idrac_ip}")
|
||||||
if result.returncode == 0
|
else:
|
||||||
else f"Failed to delete jobs for {idrac_ip}: {result.stderr.strip()}"
|
logging.error(f"Failed to delete jobs for {idrac_ip}: {result.stderr.strip()}")
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Exception occurred for {idrac_ip}: {e}")
|
logging.error(f"Exception occurred for {idrac_ip}: {e}")
|
||||||
|
|
||||||
# Main function
|
# Main function
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -48,4 +57,4 @@ if __name__ == "__main__":
|
|||||||
pool.map(delete_all_jobs, ip_list)
|
pool.map(delete_all_jobs, ip_list)
|
||||||
|
|
||||||
elapsed_time = time.time() - start_time
|
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()
|
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__)
|
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()
|
||||||
@@ -3,9 +3,17 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
import logging
|
||||||
|
|
||||||
from dotenv import load_dotenv, find_dotenv
|
from dotenv import load_dotenv, find_dotenv
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# .env 자동 탐색 로드 (현재 파일 기준 상위 디렉터리까지 검색)
|
# .env 자동 탐색 로드 (현재 파일 기준 상위 디렉터리까지 검색)
|
||||||
load_dotenv(find_dotenv())
|
load_dotenv(find_dotenv())
|
||||||
@@ -86,7 +94,7 @@ def fetch_idrac_info(idrac_ip: str, output_dir: Path) -> None:
|
|||||||
svc_tag = svc_tag_match.group(1) if svc_tag_match else None
|
svc_tag = svc_tag_match.group(1) if svc_tag_match else None
|
||||||
|
|
||||||
if not svc_tag:
|
if not svc_tag:
|
||||||
print(f"Failed to retrieve SVC Tag for IP: {idrac_ip}")
|
logging.error(f"Failed to retrieve SVC Tag for IP: {idrac_ip}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 전체 하드웨어 인벤토리
|
# 전체 하드웨어 인벤토리
|
||||||
@@ -125,13 +133,13 @@ def fetch_idrac_info(idrac_ip: str, output_dir: Path) -> None:
|
|||||||
f.write(f"SERIALS: {';'.join(serials_only)}\n")
|
f.write(f"SERIALS: {';'.join(serials_only)}\n")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing IP {idrac_ip}: {e}")
|
logging.error(f"Error processing IP {idrac_ip}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def main(ip_file: str) -> None:
|
def main(ip_file: str) -> None:
|
||||||
ip_path = Path(ip_file)
|
ip_path = Path(ip_file)
|
||||||
if not ip_path.is_file():
|
if not ip_path.is_file():
|
||||||
print(f"IP file {ip_file} does not exist.")
|
logging.error(f"IP file {ip_file} does not exist.")
|
||||||
return
|
return
|
||||||
|
|
||||||
output_dir = resolve_output_dir()
|
output_dir = resolve_output_dir()
|
||||||
@@ -146,14 +154,14 @@ def main(ip_file: str) -> None:
|
|||||||
ip = future_to_ip[future]
|
ip = future_to_ip[future]
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
print(f"✅ Completed: {ip}")
|
logging.info(f"✅ Completed: {ip}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error processing {ip}: {e}")
|
logging.error(f"❌ Error processing {ip}: {e}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print("Usage: python GPU_Serial_v1.py <ip_file>")
|
logging.error("Usage: python GPU_Serial_v1.py <ip_file>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
main(sys.argv[1])
|
main(sys.argv[1])
|
||||||
|
|||||||
192
data/scripts/Intel_Server_info.py
Normal file
192
data/scripts/Intel_Server_info.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
사용자가 지정한 로직에 따라 저장 위치를 결정합니다.
|
||||||
|
스크립트 위치가 /data/scripts/ 일 경우 /data/idrac_info/ 에 저장합니다.
|
||||||
|
"""
|
||||||
|
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: str, command: str) -> str:
|
||||||
|
"""racadm 명령어를 실행하고 결과를 문자열로 반환합니다."""
|
||||||
|
full_cmd = f"racadm -r {ip} -u {IDRAC_USER} -p {IDRAC_PASS} {command}"
|
||||||
|
try:
|
||||||
|
# 쉘 명령 실행 (stderr도 포함하여 수집)
|
||||||
|
result = subprocess.check_output(full_cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True)
|
||||||
|
return result
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
# 통신 실패 시 빈 문자열 반환
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_val(text: str, pattern: str) -> str:
|
||||||
|
"""텍스트 내에서 특정 키워드를 찾아 해당 줄의 설정값(= 이후의 값)을 추출합니다."""
|
||||||
|
for line in text.splitlines():
|
||||||
|
if re.search(pattern, line, re.IGNORECASE):
|
||||||
|
parts = line.split('=')
|
||||||
|
if len(parts) >= 2:
|
||||||
|
return parts[1].strip()
|
||||||
|
return "N/A"
|
||||||
|
|
||||||
|
def fetch_idrac_info(ip: str, output_dir: Path):
|
||||||
|
logging.info(f">>> {ip} 정보 수집 중...")
|
||||||
|
|
||||||
|
# 1. 원시 데이터 벌크 수집 (네트워크 오버헤드 감소)
|
||||||
|
getsysinfo = run_racadm(ip, "getsysinfo")
|
||||||
|
hwinventory = run_racadm(ip, "hwinventory")
|
||||||
|
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 = get_val(getsysinfo, "SVC Tag")
|
||||||
|
if svc_tag == "N/A":
|
||||||
|
logging.error(f"[경고] {ip} 접속 실패 혹은 SVC Tag 확인 불가. 건너뜁니다.")
|
||||||
|
return
|
||||||
|
|
||||||
|
report_path = output_dir / f"{svc_tag}.txt"
|
||||||
|
|
||||||
|
with open(report_path, "w", encoding="utf-8") as f:
|
||||||
|
# 헤더 기록
|
||||||
|
f.write(f"Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: {svc_tag})\n\n")
|
||||||
|
|
||||||
|
# --- Section 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 : {get_val(getsysinfo, 'System BIOS Version')}\n")
|
||||||
|
f.write(f"3. iDRAC Firmware Version : {get_val(getsysinfo, 'Firmware Version')}\n")
|
||||||
|
|
||||||
|
nic1 = run_racadm(ip, "get NIC.FrmwImgMenu.1")
|
||||||
|
f.write(f"4. NIC Integrated Firmware Version : {get_val(nic1, '#FamilyVersion')}\n")
|
||||||
|
|
||||||
|
nic5 = run_racadm(ip, "get NIC.FrmwImgMenu.5")
|
||||||
|
f.write(f"5. OnBoard NIC Firmware Version : {get_val(nic5, '#FamilyVersion')}\n")
|
||||||
|
f.write(f"6. Raid Controller Firmware Version : {get_val(hwinventory, 'ControllerFirmwareVersion')}\n\n")
|
||||||
|
|
||||||
|
# --- Section 2: Bios 설정 정보 ---
|
||||||
|
f.write("-" * 45 + " Bios 설정 정보 " + "-" * 46 + "\n")
|
||||||
|
boot_settings = run_racadm(ip, "get bios.BiosBootSettings")
|
||||||
|
f.write(f"01. Bios Boot Mode : {get_val(boot_settings, 'BootMode')}\n")
|
||||||
|
f.write(f"02. System Profile : {get_val(sys_profile, 'SysProfile=')}\n")
|
||||||
|
f.write(f"03. CPU Power Management : {get_val(sys_profile, 'EnergyPerformanceBias')}\n")
|
||||||
|
f.write(f"04. Memory Frequency : {get_val(sys_profile, 'MemFrequency')}\n")
|
||||||
|
f.write(f"05. Turbo Boost : {get_val(sys_profile, 'ProcTurboMode')}\n")
|
||||||
|
f.write(f"06. C1E : {get_val(sys_profile, 'ProcC1E')}\n")
|
||||||
|
f.write(f"07. C-States : {get_val(sys_profile, 'ProcCStates')}\n")
|
||||||
|
f.write(f"08. Monitor/Mwait : {get_val(sys_profile, 'MonitorMwait')}\n")
|
||||||
|
f.write(f"09. Logical Processor : {get_val(proc_settings, 'LogicalProc')}\n")
|
||||||
|
f.write(f"10. Virtualization Technology : {get_val(proc_settings, 'ProcVirtualization')}\n")
|
||||||
|
f.write(f"11. LLC Prefetch : {get_val(proc_settings, 'LlcPrefetch')}\n")
|
||||||
|
f.write(f"12. x2APIC Mode : {get_val(proc_settings, 'ProcX2Apic')}\n")
|
||||||
|
f.write(f"13. Node Interleaving : {get_val(mem_settings, 'NodeInterleave')}\n")
|
||||||
|
f.write(f"14. DIMM Self Healing : {get_val(mem_settings, 'PPROnUCE')}\n")
|
||||||
|
f.write(f"15. Correctable Error Logging : {get_val(mem_settings, 'CECriticalSEL')}\n")
|
||||||
|
|
||||||
|
thermal = run_racadm(ip, "get System.ThermalSettings")
|
||||||
|
f.write(f"16. Thermal Profile Optimization : {get_val(thermal, 'ThermalProfile')}\n")
|
||||||
|
|
||||||
|
sriov = run_racadm(ip, "get Bios.IntegratedDevices")
|
||||||
|
f.write(f"17. SR-IOV Global Enable : {get_val(sriov, 'SriovGlobalEnable')}\n")
|
||||||
|
|
||||||
|
misc = run_racadm(ip, "get bios.MiscSettings")
|
||||||
|
f.write(f"18. F1/F2 Prompt on Error : {get_val(misc, 'ErrPrompt')}\n\n")
|
||||||
|
|
||||||
|
# --- Section 3: iDRAC 설정 정보 ---
|
||||||
|
f.write("-" * 45 + " iDRAC 설정 정보 " + "-" * 45 + "\n")
|
||||||
|
f.write(f"01. Timezone : {get_val(run_racadm(ip, 'get iDRAC.Time.Timezone'), 'Timezone')}\n")
|
||||||
|
f.write(f"02. IPMI LAN Selection : {get_val(run_racadm(ip, 'get iDRAC.CurrentNIC'), 'ActiveNIC')}\n")
|
||||||
|
f.write(f"03. IPMI IP(IPv4) DHCP : {get_val(run_racadm(ip, 'get iDRAC.CurrentIPv4'), 'DHCPEnable')}\n")
|
||||||
|
f.write(f"04. IPMI IP(IPv6) Enable : {get_val(run_racadm(ip, 'get iDRAC.CurrentIPv6'), 'Enable=')}\n")
|
||||||
|
f.write(f"05. Redfish Support : {get_val(run_racadm(ip, 'get iDRAC.Redfish.Enable'), 'Enable=')}\n")
|
||||||
|
f.write(f"06. SSH Support : {get_val(run_racadm(ip, 'get iDRAC.SSH'), 'Enable=')}\n")
|
||||||
|
f.write(f"07. AD User Domain Name : {get_val(run_racadm(ip, 'get iDRAC.USERDomain.1.Name'), 'Name')}\n")
|
||||||
|
f.write(f"08. SC Server Address : {get_val(run_racadm(ip, 'get iDRAC.ActiveDirectory.DomainController1'), 'DomainController1')}\n")
|
||||||
|
|
||||||
|
# Syslog 관련
|
||||||
|
f.write(f"15. Remote Log (syslog) : {get_val(run_racadm(ip, 'get iDRAC.SysLog.SysLogEnable'), 'SysLogEnable')}\n")
|
||||||
|
f.write(f"16. syslog server 1 : {get_val(run_racadm(ip, 'get iDRAC.SysLog.Server1'), 'Server1')}\n")
|
||||||
|
f.write(f"17. syslog server 2 : {get_val(run_racadm(ip, 'get iDRAC.SysLog.Server2'), 'Server2')}\n")
|
||||||
|
f.write(f"18. syslog server port : {get_val(run_racadm(ip, 'get iDRAC.SysLog.Port'), 'Port')}\n")
|
||||||
|
f.write(f"19. VirtualConsole Port : {get_val(run_racadm(ip, 'get iDRAC.VirtualConsole.Port'), 'Port')}\n\n")
|
||||||
|
|
||||||
|
# --- Section 4: Raid 설정 정보 ---
|
||||||
|
f.write("-" * 45 + " Raid 설정 정보 " + "-" * 46 + "\n")
|
||||||
|
f.write(f"01. Raid ProductName : {get_val(hwinventory, 'ProductName = BOSS')}, {get_val(hwinventory, 'ProductName = PERC')}\n")
|
||||||
|
f.write(f"02. RAID Types : {get_val(hwinventory, 'RAIDTypes')}\n")
|
||||||
|
f.write(f"03. StripeSize : {get_val(hwinventory, 'StripeSize')}\n")
|
||||||
|
f.write(f"04. ReadCachePolicy : {get_val(hwinventory, 'ReadCachePolicy')}\n")
|
||||||
|
f.write(f"05. WriteCachePolicy : {get_val(hwinventory, 'WriteCachePolicy')}\n")
|
||||||
|
f.write(f"06. CheckConsistencyMode : {get_val(storage_ctrl, 'CheckConsistencyMode')}\n")
|
||||||
|
f.write(f"07. PatrolReadRate : {get_val(storage_ctrl, 'PatrolReadRate')}\n")
|
||||||
|
f.write(f"08. period : 168h\n")
|
||||||
|
f.write(f"09. Power Save : No\n")
|
||||||
|
f.write(f"10. JBODMODE : Controller does not support JBOD\n")
|
||||||
|
f.write(f"11. maxconcurrentpd : 240\n")
|
||||||
|
|
||||||
|
logging.info(f" ㄴ 완료: {report_path.name}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import sys
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
logging.error("Usage: python script.py <ip_list_file>")
|
||||||
|
return
|
||||||
|
|
||||||
|
ip_file = sys.argv[1]
|
||||||
|
if not os.path.exists(ip_file):
|
||||||
|
logging.error(f"파일을 찾을 수 없습니다: {ip_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 저장 위치 결정
|
||||||
|
output_dir = resolve_output_dir()
|
||||||
|
logging.info(f"[*] 결과 저장 폴더: {output_dir}")
|
||||||
|
|
||||||
|
# 시간 측정 시작
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# IP 파일 읽기
|
||||||
|
with open(ip_file, "r") as f:
|
||||||
|
ips = [line.strip() for line in f if line.strip()]
|
||||||
|
|
||||||
|
# 순차적 정보 수집
|
||||||
|
for ip in ips:
|
||||||
|
fetch_idrac_info(ip, output_dir)
|
||||||
|
|
||||||
|
# 소요 시간 계산
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
hours = int(elapsed // 3600)
|
||||||
|
minutes = int((elapsed % 3600) // 60)
|
||||||
|
seconds = int(elapsed % 60)
|
||||||
|
|
||||||
|
logging.info("=" * 50)
|
||||||
|
logging.info(f"정보 수집 완료.")
|
||||||
|
logging.info(f"소요 시간: {hours}시간 {minutes}분 {seconds}초")
|
||||||
|
logging.info("=" * 50)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
174
data/scripts/JP_54EA_MAC_info.py
Normal file
174
data/scripts/JP_54EA_MAC_info.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# 저장 위치: data/scripts 아래에 있다면 → data/idrac_info 생성
|
||||||
|
def resolve_output_dir() -> Path:
|
||||||
|
here = Path(__file__).resolve().parent
|
||||||
|
base = here.parent if here.name.lower() == "scripts" else here
|
||||||
|
out = base / "idrac_info"
|
||||||
|
out.mkdir(parents=True, exist_ok=True)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# iDRAC 접속 계정
|
||||||
|
IDRAC_USER = "root"
|
||||||
|
IDRAC_PASS = "calvin"
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list[str]) -> str:
|
||||||
|
"""racadm 명령 실행 (stdout 수집)"""
|
||||||
|
try:
|
||||||
|
return subprocess.getoutput(" ".join(cmd))
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_single_value(pattern: str, text: str) -> Optional[str]:
|
||||||
|
"""단일 값 추출용"""
|
||||||
|
m = re.search(pattern, text, flags=re.IGNORECASE)
|
||||||
|
return m.group(1).strip() if m else None
|
||||||
|
|
||||||
|
|
||||||
|
def find_mac_by_fqdd(fqdd: str, text: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
swinventory 출력에서 FQDD = {fqdd} 라인을 찾고,
|
||||||
|
그 주변(±10줄) 내에서 MAC 주소를 추출.
|
||||||
|
"""
|
||||||
|
lines = text.splitlines()
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if f"FQDD = {fqdd}" in line:
|
||||||
|
for j in range(max(0, i - 10), min(i + 10, len(lines))):
|
||||||
|
m = re.search(r"([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})", lines[j])
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_vendors(hwinventory: str) -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
[InstanceID: DIMM...] 또는 [InstanceID: Disk.Bay...] 블록 내 Manufacturer 추출
|
||||||
|
"""
|
||||||
|
# Memory Vendors
|
||||||
|
mem_vendors = re.findall(
|
||||||
|
r"\[InstanceID:\s*DIMM[^\]]*\][^\[]*?Manufacturer\s*=\s*([^\n\r]+)",
|
||||||
|
hwinventory,
|
||||||
|
flags=re.IGNORECASE
|
||||||
|
)
|
||||||
|
mem_vendors = [v.strip() for v in mem_vendors if v.strip()]
|
||||||
|
memory = ", ".join(sorted(set(mem_vendors))) if mem_vendors else "Not Found"
|
||||||
|
|
||||||
|
# SSD Vendors
|
||||||
|
ssd_vendors = re.findall(
|
||||||
|
r"\[InstanceID:\s*Disk\.Bay[^\]]*\][^\[]*?Manufacturer\s*=\s*([^\n\r]+)",
|
||||||
|
hwinventory,
|
||||||
|
flags=re.IGNORECASE
|
||||||
|
)
|
||||||
|
ssd_vendors = [v.strip() for v in ssd_vendors if v.strip()]
|
||||||
|
ssd = ", ".join(sorted(set(ssd_vendors))) if ssd_vendors else "Not Found"
|
||||||
|
|
||||||
|
return memory, ssd
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
|
||||||
|
"""단일 서버 iDRAC 정보 수집"""
|
||||||
|
logging.info(f"[+] Collecting iDRAC info from {ip} ...")
|
||||||
|
|
||||||
|
getsysinfo = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "getsysinfo"])
|
||||||
|
hwinventory = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "hwinventory"])
|
||||||
|
swinventory = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "swinventory"])
|
||||||
|
|
||||||
|
# 서비스 태그
|
||||||
|
svc_tag = parse_single_value(r"SVC\s*Tag\s*=\s*(\S+)", getsysinfo)
|
||||||
|
if not svc_tag:
|
||||||
|
logging.error(f"[!] Failed to retrieve SVC Tag for IP: {ip}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# iDRAC MAC
|
||||||
|
idrac_mac = parse_single_value(r"MAC\s*Address\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
|
||||||
|
# NIC.Integrated / Onboard MAC
|
||||||
|
integrated_1 = parse_single_value(r"NIC\.Integrated\.1-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
integrated_2 = parse_single_value(r"NIC\.Integrated\.1-2-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
onboard_1 = parse_single_value(r"NIC\.Embedded\.1-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
onboard_2 = parse_single_value(r"NIC\.Embedded\.2-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
|
||||||
|
# NIC Slot별 MAC
|
||||||
|
NIC_Slot_2_1 = find_mac_by_fqdd("NIC.Slot.2-1-1", swinventory)
|
||||||
|
NIC_Slot_2_2 = find_mac_by_fqdd("NIC.Slot.2-2-1", swinventory)
|
||||||
|
NIC_Slot_3_1 = find_mac_by_fqdd("NIC.Slot.3-1-1", swinventory)
|
||||||
|
NIC_Slot_3_2 = find_mac_by_fqdd("NIC.Slot.3-2-1", swinventory)
|
||||||
|
|
||||||
|
# 메모리 / SSD 제조사
|
||||||
|
memory, ssd = extract_vendors(hwinventory)
|
||||||
|
|
||||||
|
# 결과 파일 저장
|
||||||
|
out_file = output_dir / f"{svc_tag}.txt"
|
||||||
|
with out_file.open("w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(f"{svc_tag}\n")
|
||||||
|
f.write(f"{integrated_1 or ''}\n")
|
||||||
|
f.write(f"{integrated_2 or ''}\n")
|
||||||
|
f.write(f"{onboard_1 or ''}\n")
|
||||||
|
f.write(f"{onboard_2 or ''}\n")
|
||||||
|
f.write(f"{NIC_Slot_2_1 or ''}\n")
|
||||||
|
f.write(f"{NIC_Slot_2_2 or ''}\n")
|
||||||
|
f.write(f"{idrac_mac or ''}\n")
|
||||||
|
f.write(f"{memory}\n")
|
||||||
|
f.write(f"{ssd}\n")
|
||||||
|
|
||||||
|
logging.info(f"[✔] {svc_tag} ({ip}) info saved.")
|
||||||
|
|
||||||
|
|
||||||
|
def main(ip_file: str) -> None:
|
||||||
|
ip_path = Path(ip_file)
|
||||||
|
if not ip_path.is_file():
|
||||||
|
logging.error(f"[!] IP file {ip_file} does not exist.")
|
||||||
|
return
|
||||||
|
|
||||||
|
output_dir = resolve_output_dir()
|
||||||
|
ips = [line.strip() for line in ip_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
||||||
|
if not ips:
|
||||||
|
logging.error("[!] No IP addresses found in the file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for ip in ips:
|
||||||
|
try:
|
||||||
|
fetch_idrac_info_one(ip, output_dir)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"[!] Error processing {ip}: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip_path.unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.info("\n정보 수집 완료.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys, time
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
logging.error("Usage: python script.py <ip_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
main(sys.argv[1])
|
||||||
|
end = time.time()
|
||||||
|
elapsed = int(end - start)
|
||||||
|
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
||||||
|
logging.info(f"수집 완료 시간: {h}시간 {m}분 {s}초")
|
||||||
234
data/scripts/MAC_info.py
Normal file
234
data/scripts/MAC_info.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# 출력 디렉토리 결정
|
||||||
|
def resolve_output_dir() -> Path:
|
||||||
|
here = Path(__file__).resolve().parent
|
||||||
|
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
|
||||||
|
base = here.parent
|
||||||
|
else:
|
||||||
|
base = here.parent
|
||||||
|
|
||||||
|
out = base / "idrac_info"
|
||||||
|
out.mkdir(parents=True, exist_ok=True)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# iDRAC 계정
|
||||||
|
IDRAC_USER = "root"
|
||||||
|
IDRAC_PASS = "calvin"
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list[str]) -> str:
|
||||||
|
"""racadm 명령 실행 (stdout만 반환)"""
|
||||||
|
try:
|
||||||
|
return subprocess.getoutput(" ".join(cmd))
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_single_value(pattern: str, text: str) -> Optional[str]:
|
||||||
|
m = re.search(pattern, text, flags=re.IGNORECASE)
|
||||||
|
return m.group(1).strip() if m else None
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# NIC MAC 수집 (가변 포트 대응)
|
||||||
|
def extract_nic_macs(text: str, nic_type: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
nic_type:
|
||||||
|
- 'Integrated'
|
||||||
|
- 'Embedded'
|
||||||
|
"""
|
||||||
|
pattern = re.compile(
|
||||||
|
rf"NIC\.{nic_type}\.\d+-\d+-\d+.*?([0-9A-Fa-f]{{2}}(?::[0-9A-Fa-f]{{2}}){{5}})",
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
return sorted(set(m.group(1) for m in pattern.finditer(text)))
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# hwinventory 블록 기반 Manufacturer 추출 (DIMM)
|
||||||
|
def extract_memory_vendors(hwinventory: str) -> List[str]:
|
||||||
|
vendors = set()
|
||||||
|
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
if "[InstanceID: DIMM." in block:
|
||||||
|
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
vendors.add(m.group(1).strip())
|
||||||
|
|
||||||
|
return sorted(vendors)
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# BOSS 디스크 Vendor 추출
|
||||||
|
def extract_boss_vendors(hwinventory: str) -> List[str]:
|
||||||
|
vendors = set()
|
||||||
|
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
if "BOSS." in block:
|
||||||
|
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
vendors.add(m.group(1).strip())
|
||||||
|
|
||||||
|
return sorted(vendors)
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# 일반 Disk Vendor (BOSS 제외)
|
||||||
|
DISK_PREFIXES = [
|
||||||
|
"Disk.Bay.",
|
||||||
|
"Disk.M.2.",
|
||||||
|
"Disk.Direct.",
|
||||||
|
"Disk.Slot."
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_disk_vendors_excluding_boss(hwinventory: str) -> List[str]:
|
||||||
|
vendors = set()
|
||||||
|
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
if "BOSS." in block:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for prefix in DISK_PREFIXES:
|
||||||
|
if f"[InstanceID: {prefix}" in block:
|
||||||
|
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
vendors.add(m.group(1).strip())
|
||||||
|
|
||||||
|
return sorted(vendors)
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
|
||||||
|
getsysinfo = run([
|
||||||
|
"racadm", "-r", ip,
|
||||||
|
"-u", IDRAC_USER,
|
||||||
|
"-p", IDRAC_PASS,
|
||||||
|
"getsysinfo"
|
||||||
|
])
|
||||||
|
|
||||||
|
hwinventory = run([
|
||||||
|
"racadm", "-r", ip,
|
||||||
|
"-u", IDRAC_USER,
|
||||||
|
"-p", IDRAC_PASS,
|
||||||
|
"hwinventory"
|
||||||
|
])
|
||||||
|
|
||||||
|
# ── Service Tag
|
||||||
|
svc_tag = parse_single_value(r"SVC\s*Tag\s*=\s*(\S+)", getsysinfo)
|
||||||
|
if not svc_tag:
|
||||||
|
logging.error(f"[ERROR] SVC Tag 수집 실패: {ip}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# ── iDRAC MAC
|
||||||
|
idrac_mac = parse_single_value(
|
||||||
|
r"MAC Address\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── NIC MAC
|
||||||
|
integrated_macs = extract_nic_macs(getsysinfo, "Integrated")
|
||||||
|
embedded_macs = extract_nic_macs(getsysinfo, "Embedded")
|
||||||
|
|
||||||
|
# ── Vendors
|
||||||
|
memory_vendors = extract_memory_vendors(hwinventory)
|
||||||
|
disk_vendors = extract_disk_vendors_excluding_boss(hwinventory)
|
||||||
|
boss_vendors = extract_boss_vendors(hwinventory)
|
||||||
|
|
||||||
|
# ── 결과 파일 저장
|
||||||
|
out_file = output_dir / f"{svc_tag}.txt"
|
||||||
|
with out_file.open("w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(f"{svc_tag}\n")
|
||||||
|
|
||||||
|
for mac in integrated_macs:
|
||||||
|
f.write(f"{mac}\n")
|
||||||
|
|
||||||
|
for mac in embedded_macs:
|
||||||
|
f.write(f"{mac}\n")
|
||||||
|
|
||||||
|
f.write(f"{idrac_mac or ''}\n")
|
||||||
|
|
||||||
|
# Memory Vendors
|
||||||
|
for v in memory_vendors:
|
||||||
|
f.write(f"{v}\n")
|
||||||
|
|
||||||
|
# Disk Vendors (일반)
|
||||||
|
for v in disk_vendors:
|
||||||
|
f.write(f"{v}\n")
|
||||||
|
|
||||||
|
# BOSS Vendors (있을 때만, 벤더명만)
|
||||||
|
for v in boss_vendors:
|
||||||
|
f.write(f"{v}\n")
|
||||||
|
|
||||||
|
logging.info(f"[OK] {svc_tag} 수집 완료")
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
def main(ip_file: str) -> None:
|
||||||
|
ip_path = Path(ip_file)
|
||||||
|
if not ip_path.is_file():
|
||||||
|
logging.error(f"[ERROR] IP 파일 없음: {ip_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
output_dir = resolve_output_dir()
|
||||||
|
|
||||||
|
ips = [
|
||||||
|
line.strip()
|
||||||
|
for line in ip_path.read_text(encoding="utf-8").splitlines()
|
||||||
|
if line.strip()
|
||||||
|
]
|
||||||
|
|
||||||
|
if not ips:
|
||||||
|
logging.error("[ERROR] IP 목록이 비어 있습니다.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for ip in ips:
|
||||||
|
try:
|
||||||
|
fetch_idrac_info_one(ip, output_dir)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"[ERROR] {ip} 처리 실패: {e}")
|
||||||
|
|
||||||
|
# Bash 스크립트와 동일하게 입력 IP 파일 삭제
|
||||||
|
try:
|
||||||
|
ip_path.unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.info("정보 수집 완료.")
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
logging.error("Usage: python script.py <ip_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
main(sys.argv[1])
|
||||||
|
elapsed = int(time.time() - start)
|
||||||
|
|
||||||
|
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
||||||
|
logging.info(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
||||||
@@ -3,6 +3,14 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
# .env 파일에서 사용자 이름 및 비밀번호 설정
|
# .env 파일에서 사용자 이름 및 비밀번호 설정
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -18,7 +26,7 @@ def fetch_idrac_info(idrac_ip, output_dir):
|
|||||||
svc_tag = svc_tag_match.group(1) if svc_tag_match else None
|
svc_tag = svc_tag_match.group(1) if svc_tag_match else None
|
||||||
|
|
||||||
if not svc_tag:
|
if not svc_tag:
|
||||||
print(f"Failed to retrieve SVC Tag for IP: {idrac_ip}")
|
logging.error(f"Failed to retrieve SVC Tag for IP: {idrac_ip}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# InfiniBand.VndrConfigPage 목록 가져오기
|
# InfiniBand.VndrConfigPage 목록 가져오기
|
||||||
@@ -59,12 +67,14 @@ def fetch_idrac_info(idrac_ip, output_dir):
|
|||||||
if hex_guid_list:
|
if hex_guid_list:
|
||||||
f.write(f"GUID: {';'.join(hex_guid_list)}\n")
|
f.write(f"GUID: {';'.join(hex_guid_list)}\n")
|
||||||
|
|
||||||
|
logging.info(f"✅ Completed: {idrac_ip}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing IP {idrac_ip}: {e}")
|
logging.error(f"Error processing IP {idrac_ip}: {e}")
|
||||||
|
|
||||||
def main(ip_file):
|
def main(ip_file):
|
||||||
if not os.path.isfile(ip_file):
|
if not os.path.isfile(ip_file):
|
||||||
print(f"IP file {ip_file} does not exist.")
|
logging.error(f"IP file {ip_file} does not exist.")
|
||||||
return
|
return
|
||||||
|
|
||||||
output_dir = "/app/idrac_info/idrac_info"
|
output_dir = "/app/idrac_info/idrac_info"
|
||||||
@@ -80,12 +90,12 @@ def main(ip_file):
|
|||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing task: {e}")
|
logging.error(f"Error processing task: {e}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print("Usage: python script.py <ip_file>")
|
logging.error("Usage: python script.py <ip_file>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
ip_file = sys.argv[1]
|
ip_file = sys.argv[1]
|
||||||
|
|||||||
@@ -3,9 +3,17 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
import logging
|
||||||
|
|
||||||
from dotenv import load_dotenv, find_dotenv
|
from dotenv import load_dotenv, find_dotenv
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# .env 자동 탐색 로드 (현재 파일 기준 상위 디렉터리까지 검색)
|
# .env 자동 탐색 로드 (현재 파일 기준 상위 디렉터리까지 검색)
|
||||||
load_dotenv(find_dotenv())
|
load_dotenv(find_dotenv())
|
||||||
@@ -47,7 +55,7 @@ def fetch_idrac_info(idrac_ip: str, output_dir: Path) -> None:
|
|||||||
svc_tag = svc_tag_match.group(1) if svc_tag_match else None
|
svc_tag = svc_tag_match.group(1) if svc_tag_match else None
|
||||||
|
|
||||||
if not svc_tag:
|
if not svc_tag:
|
||||||
print(f"Failed to retrieve SVC Tag for IP: {idrac_ip}")
|
logging.error(f"Failed to retrieve SVC Tag for IP: {idrac_ip}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# InfiniBand.VndrConfigPage 목록 가져오기
|
# InfiniBand.VndrConfigPage 목록 가져오기
|
||||||
@@ -110,13 +118,13 @@ def fetch_idrac_info(idrac_ip: str, output_dir: Path) -> None:
|
|||||||
f.write(f"GUID: {';'.join(hex_guid_list)}\n")
|
f.write(f"GUID: {';'.join(hex_guid_list)}\n")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing IP {idrac_ip}: {e}")
|
logging.error(f"Error processing IP {idrac_ip}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def main(ip_file: str) -> None:
|
def main(ip_file: str) -> None:
|
||||||
ip_path = Path(ip_file)
|
ip_path = Path(ip_file)
|
||||||
if not ip_path.is_file():
|
if not ip_path.is_file():
|
||||||
print(f"IP file {ip_file} does not exist.")
|
logging.error(f"IP file {ip_file} does not exist.")
|
||||||
return
|
return
|
||||||
|
|
||||||
output_dir = resolve_output_dir() # ← 여기서 OS 무관 저장 위치 확정 (data/idrac_info)
|
output_dir = resolve_output_dir() # ← 여기서 OS 무관 저장 위치 확정 (data/idrac_info)
|
||||||
@@ -133,15 +141,15 @@ def main(ip_file: str) -> None:
|
|||||||
ip = future_to_ip[future]
|
ip = future_to_ip[future]
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
print(f"✅ Completed: {ip}")
|
logging.info(f"Completed: {ip}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error processing {ip}: {e}")
|
logging.error(f"Error processing {ip}: {e}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print("Usage: python script.py <ip_file>")
|
logging.error("Usage: python script.py <ip_file>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
main(sys.argv[1])
|
main(sys.argv[1])
|
||||||
|
|||||||
166
data/scripts/SP_18EA_MAC_info.py
Normal file
166
data/scripts/SP_18EA_MAC_info.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# 저장 위치: data/scripts 아래에 있다면 → data/idrac_info 생성
|
||||||
|
def resolve_output_dir() -> Path:
|
||||||
|
here = Path(__file__).resolve().parent
|
||||||
|
base = here.parent if here.name.lower() == "scripts" else here
|
||||||
|
out = base / "idrac_info"
|
||||||
|
out.mkdir(parents=True, exist_ok=True)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# iDRAC 접속 계정
|
||||||
|
IDRAC_USER = "root"
|
||||||
|
IDRAC_PASS = "calvin"
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list[str]) -> str:
|
||||||
|
"""racadm 명령 실행 (stdout 수집)"""
|
||||||
|
try:
|
||||||
|
return subprocess.getoutput(" ".join(cmd))
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_single_value(pattern: str, text: str) -> Optional[str]:
|
||||||
|
"""단일 값 추출용"""
|
||||||
|
m = re.search(pattern, text, flags=re.IGNORECASE)
|
||||||
|
return m.group(1).strip() if m else None
|
||||||
|
|
||||||
|
|
||||||
|
def find_mac_by_fqdd(fqdd: str, text: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
swinventory 출력에서 FQDD = {fqdd} 라인을 찾고,
|
||||||
|
그 주변(±10줄) 내에서 MAC 주소를 추출.
|
||||||
|
"""
|
||||||
|
lines = text.splitlines()
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if f"FQDD = {fqdd}" in line:
|
||||||
|
for j in range(max(0, i - 10), min(i + 10, len(lines))):
|
||||||
|
m = re.search(r"([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})", lines[j])
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_vendors(hwinventory: str) -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
[InstanceID: DIMM...] 또는 [InstanceID: Disk.Bay...] 블록 내 Manufacturer 추출
|
||||||
|
"""
|
||||||
|
# Memory Vendors
|
||||||
|
mem_vendors = re.findall(
|
||||||
|
r"\[InstanceID:\s*DIMM[^\]]*\][^\[]*?Manufacturer\s*=\s*([^\n\r]+)",
|
||||||
|
hwinventory,
|
||||||
|
flags=re.IGNORECASE
|
||||||
|
)
|
||||||
|
mem_vendors = [v.strip() for v in mem_vendors if v.strip()]
|
||||||
|
memory = ", ".join(sorted(set(mem_vendors))) if mem_vendors else "Not Found"
|
||||||
|
|
||||||
|
# SSD Vendors
|
||||||
|
ssd_vendors = re.findall(
|
||||||
|
r"\[InstanceID:\s*Disk\.Bay[^\]]*\][^\[]*?Manufacturer\s*=\s*([^\n\r]+)",
|
||||||
|
hwinventory,
|
||||||
|
flags=re.IGNORECASE
|
||||||
|
)
|
||||||
|
ssd_vendors = [v.strip() for v in ssd_vendors if v.strip()]
|
||||||
|
ssd = ", ".join(sorted(set(ssd_vendors))) if ssd_vendors else "Not Found"
|
||||||
|
|
||||||
|
return memory, ssd
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
|
||||||
|
"""단일 서버 iDRAC 정보 수집"""
|
||||||
|
logging.info(f"[+] Collecting iDRAC info from {ip} ...")
|
||||||
|
|
||||||
|
getsysinfo = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "getsysinfo"])
|
||||||
|
hwinventory = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "hwinventory"])
|
||||||
|
swinventory = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "swinventory"])
|
||||||
|
|
||||||
|
# 서비스 태그
|
||||||
|
svc_tag = parse_single_value(r"SVC\s*Tag\s*=\s*(\S+)", getsysinfo)
|
||||||
|
if not svc_tag:
|
||||||
|
logging.error(f"[!] Failed to retrieve SVC Tag for IP: {ip}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# iDRAC MAC
|
||||||
|
idrac_mac = parse_single_value(r"MAC\s*Address\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
|
||||||
|
# NIC.Integrated / Onboard MAC
|
||||||
|
integrated_1 = parse_single_value(r"NIC\.Integrated\.1-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
integrated_2 = parse_single_value(r"NIC\.Integrated\.1-2-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
onboard_1 = parse_single_value(r"NIC\.Embedded\.1-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
onboard_2 = parse_single_value(r"NIC\.Embedded\.2-1-1\s+Ethernet\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
||||||
|
|
||||||
|
# 메모리 / SSD 제조사
|
||||||
|
memory, ssd = extract_vendors(hwinventory)
|
||||||
|
|
||||||
|
# 결과 파일 저장
|
||||||
|
out_file = output_dir / f"{svc_tag}.txt"
|
||||||
|
with out_file.open("w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(f"{svc_tag}\n")
|
||||||
|
f.write(f"{integrated_1 or ''}\n")
|
||||||
|
f.write(f"{integrated_2 or ''}\n")
|
||||||
|
f.write(f"{onboard_1 or ''}\n")
|
||||||
|
f.write(f"{onboard_2 or ''}\n")
|
||||||
|
f.write(f"{idrac_mac or ''}\n")
|
||||||
|
f.write(f"{memory}\n")
|
||||||
|
f.write(f"{ssd}\n")
|
||||||
|
|
||||||
|
logging.info(f"[✔] {svc_tag} ({ip}) info saved.")
|
||||||
|
|
||||||
|
|
||||||
|
def main(ip_file: str) -> None:
|
||||||
|
ip_path = Path(ip_file)
|
||||||
|
if not ip_path.is_file():
|
||||||
|
logging.error(f"[!] IP file {ip_file} does not exist.")
|
||||||
|
return
|
||||||
|
|
||||||
|
output_dir = resolve_output_dir()
|
||||||
|
ips = [line.strip() for line in ip_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
||||||
|
if not ips:
|
||||||
|
logging.error("[!] No IP addresses found in the file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for ip in ips:
|
||||||
|
try:
|
||||||
|
fetch_idrac_info_one(ip, output_dir)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"[!] Error processing {ip}: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip_path.unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.info("\n정보 수집 완료.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys, time
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
logging.error("Usage: python script.py <ip_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
main(sys.argv[1])
|
||||||
|
end = time.time()
|
||||||
|
elapsed = int(end - start)
|
||||||
|
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
||||||
|
logging.info(f"수집 완료 시간: {h}시간 {m}분 {s}초")
|
||||||
@@ -6,37 +6,43 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# 저장 위치: 이 파일이 data/scripts/ 아래 있다면 → data/idrac_info
|
# 저장 위치 결정
|
||||||
# 그 외 위치여도 이 파일의 상위 폴더에 idrac_info 생성
|
|
||||||
def resolve_output_dir() -> Path:
|
def resolve_output_dir() -> Path:
|
||||||
here = Path(__file__).resolve().parent # 예: .../data/scripts
|
here = Path(__file__).resolve().parent
|
||||||
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
|
if here.name.lower() == "scripts" and here.parent.name.lower() == "data":
|
||||||
base = here.parent # data
|
base = here.parent
|
||||||
elif here.name.lower() == "scripts":
|
elif here.name.lower() == "scripts":
|
||||||
base = here.parent
|
base = here.parent
|
||||||
else:
|
else:
|
||||||
base = here.parent
|
base = here.parent
|
||||||
|
|
||||||
out = base / "idrac_info"
|
out = base / "idrac_info"
|
||||||
out.mkdir(parents=True, exist_ok=True)
|
out.mkdir(parents=True, exist_ok=True)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# 사용자 이름 및 비밀번호 (Bash 스크립트와 동일하게 하드코딩)
|
# iDRAC 계정
|
||||||
IDRAC_USER = "root"
|
IDRAC_USER = "root"
|
||||||
IDRAC_PASS = "calvin"
|
IDRAC_PASS = "calvin"
|
||||||
|
|
||||||
|
|
||||||
def run(cmd: list[str]) -> str:
|
def run(cmd: list[str]) -> str:
|
||||||
"""racadm 호출을 간단히 실행 (stdout만 수집)"""
|
"""racadm 명령 실행 (stdout만 반환)"""
|
||||||
try:
|
try:
|
||||||
# join하여 getoutput로 호출 (Bash와 비슷한 동작)
|
|
||||||
return subprocess.getoutput(" ".join(cmd))
|
return subprocess.getoutput(" ".join(cmd))
|
||||||
except Exception as e:
|
except Exception:
|
||||||
return f"" # 실패 시 빈 문자열
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def parse_single_value(pattern: str, text: str) -> Optional[str]:
|
def parse_single_value(pattern: str, text: str) -> Optional[str]:
|
||||||
@@ -44,61 +50,81 @@ def parse_single_value(pattern: str, text: str) -> Optional[str]:
|
|||||||
return m.group(1).strip() if m else None
|
return m.group(1).strip() if m else None
|
||||||
|
|
||||||
|
|
||||||
def parse_mac_list_from_lines(text: str, fqdd_key: str) -> Optional[str]:
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# hwinventory 블록 기반 Manufacturer 추출 (핵심 수정 부분)
|
||||||
|
def extract_manufacturers(hwinventory: str, instance_prefix: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
특정 FQDD 라인을 찾아 MAC 주소를 반환하는 간단한 도우미.
|
instance_prefix:
|
||||||
(Bash의 grep/awk 파이프를 정규표현식으로 대체)
|
- 'DIMM.' → 메모리
|
||||||
|
- 'Disk.Bay.' → 디스크
|
||||||
"""
|
"""
|
||||||
# MAC 주소 패턴
|
vendors = []
|
||||||
mac_pat = r"([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})"
|
|
||||||
# 해당 FQDD 문자열이 포함된 줄과 가까운 곳에서 MAC을 찾는 간단한 방식
|
blocks = re.split(r"\n\s*\n", hwinventory)
|
||||||
# (원 스크립트는 awk로 이전 줄을 보는 등 상세하지만 여기선 단순화)
|
for block in blocks:
|
||||||
block_pat = rf"{re.escape(fqdd_key)}.*?{mac_pat}"
|
if f"[InstanceID: {instance_prefix}" in block:
|
||||||
m = re.search(block_pat, text, flags=re.IGNORECASE | re.DOTALL)
|
m = re.search(r"Manufacturer\s*=\s*(.+)", block, re.IGNORECASE)
|
||||||
if m:
|
if m:
|
||||||
return m.group(1)
|
vendors.append(m.group(1).strip())
|
||||||
# 라인 전체에서 MAC만 스캔하는 fallback
|
|
||||||
m2 = re.search(mac_pat, text, flags=re.IGNORECASE)
|
return vendors
|
||||||
return m2.group(1) if m2 else None
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
|
def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
|
||||||
# getsysinfo / hwinventory 호출
|
getsysinfo = run([
|
||||||
getsysinfo = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "getsysinfo"])
|
"racadm", "-r", ip,
|
||||||
hwinventory = run(["racadm", "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "hwinventory"])
|
"-u", IDRAC_USER,
|
||||||
|
"-p", IDRAC_PASS,
|
||||||
|
"getsysinfo"
|
||||||
|
])
|
||||||
|
|
||||||
# 서비스 태그
|
hwinventory = run([
|
||||||
|
"racadm", "-r", ip,
|
||||||
|
"-u", IDRAC_USER,
|
||||||
|
"-p", IDRAC_PASS,
|
||||||
|
"hwinventory"
|
||||||
|
])
|
||||||
|
|
||||||
|
# ── Service Tag
|
||||||
svc_tag = parse_single_value(r"SVC\s*Tag\s*=\s*(\S+)", getsysinfo)
|
svc_tag = parse_single_value(r"SVC\s*Tag\s*=\s*(\S+)", getsysinfo)
|
||||||
if not svc_tag:
|
if not svc_tag:
|
||||||
print(f"Failed to retrieve SVC Tag for IP: {ip}")
|
logging.error(f"[ERROR] SVC Tag 수집 실패: {ip}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# iDRAC MAC
|
# ── iDRAC MAC
|
||||||
idrac_mac = parse_single_value(r"MAC Address\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo)
|
idrac_mac = parse_single_value(
|
||||||
|
r"MAC Address\s*=\s*([0-9A-Fa-f:]{17})", getsysinfo
|
||||||
|
)
|
||||||
|
|
||||||
# NIC.Integrated MAC (1-1-1, 1-2-1)
|
# ── Integrated NIC
|
||||||
integrated_1 = parse_single_value(r"NIC\.Integrated\.1-1-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
integrated_1 = parse_single_value(
|
||||||
getsysinfo)
|
r"NIC\.Integrated\.1-1-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
||||||
integrated_2 = parse_single_value(r"NIC\.Integrated\.1-2-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
getsysinfo
|
||||||
getsysinfo)
|
)
|
||||||
|
integrated_2 = parse_single_value(
|
||||||
|
r"NIC\.Integrated\.1-2-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
||||||
|
getsysinfo
|
||||||
|
)
|
||||||
|
|
||||||
# Onboard MAC (Embedded 1-1-1, 2-1-1)
|
# ── Embedded NIC
|
||||||
onboard_1 = parse_single_value(r"NIC\.Embedded\.1-1-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
onboard_1 = parse_single_value(
|
||||||
getsysinfo)
|
r"NIC\.Embedded\.1-1-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
||||||
onboard_2 = parse_single_value(r"NIC\.Embedded\.2-1-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
getsysinfo
|
||||||
getsysinfo)
|
)
|
||||||
|
onboard_2 = parse_single_value(
|
||||||
|
r"NIC\.Embedded\.2-1-1.*?([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
|
||||||
|
getsysinfo
|
||||||
|
)
|
||||||
|
|
||||||
# 벤더(메모리/SSD) 첫 글자 모음 (원 스크립트는 uniq+sort+cut -c1)
|
# ─────────────────────────────────────────────
|
||||||
# 여기서는 Manufacturer= 값을 수집해 첫 글자만 취합 후 중복 제거.
|
# Memory / Disk Vendor (수정 완료)
|
||||||
mem_vendors = re.findall(r"DIMM.*?Manufacturer\s*=\s*(.+)", hwinventory, flags=re.IGNORECASE)
|
mem_vendors = sorted(set(extract_manufacturers(hwinventory, "DIMM.")))
|
||||||
mem_vendors = [v.strip() for v in mem_vendors if v.strip()]
|
ssd_vendors = sorted(set(extract_manufacturers(hwinventory, "Disk.Bay.")))
|
||||||
memory = "".join(sorted(set(v[0] for v in mem_vendors if v)))
|
|
||||||
|
|
||||||
ssd_vendors = re.findall(r"Disk\.Bay.*?Manufacturer\s*=\s*(.+)", hwinventory, flags=re.IGNORECASE)
|
memory = "\n".join(mem_vendors)
|
||||||
ssd_vendors = [v.strip() for v in ssd_vendors if v.strip()]
|
ssd = "\n".join(ssd_vendors)
|
||||||
ssd = "".join(sorted(set(v[0] for v in ssd_vendors if v)))
|
|
||||||
|
|
||||||
# 파일 저장
|
# ── 결과 파일 저장
|
||||||
out_file = output_dir / f"{svc_tag}.txt"
|
out_file = output_dir / f"{svc_tag}.txt"
|
||||||
with out_file.open("w", encoding="utf-8", newline="\n") as f:
|
with out_file.open("w", encoding="utf-8", newline="\n") as f:
|
||||||
f.write(f"{svc_tag}\n")
|
f.write(f"{svc_tag}\n")
|
||||||
@@ -110,47 +136,53 @@ def fetch_idrac_info_one(ip: str, output_dir: Path) -> None:
|
|||||||
f.write(f"{memory}\n")
|
f.write(f"{memory}\n")
|
||||||
f.write(f"{ssd}\n")
|
f.write(f"{ssd}\n")
|
||||||
|
|
||||||
|
logging.info(f"[OK] {svc_tag} 수집 완료")
|
||||||
|
|
||||||
|
|
||||||
def main(ip_file: str) -> None:
|
def main(ip_file: str) -> None:
|
||||||
ip_path = Path(ip_file)
|
ip_path = Path(ip_file)
|
||||||
if not ip_path.is_file():
|
if not ip_path.is_file():
|
||||||
print(f"IP file {ip_file} does not exist.")
|
logging.error(f"[ERROR] IP 파일 없음: {ip_file}")
|
||||||
return
|
return
|
||||||
|
|
||||||
output_dir = resolve_output_dir()
|
output_dir = resolve_output_dir()
|
||||||
|
|
||||||
# Bash 스크립트는 파일 전체를 cat 하여 '하나의 IP'로 사용했지만,
|
ips = [
|
||||||
# 여기서는 줄 단위로 모두 처리(한 줄만 있어도 동일하게 동작).
|
line.strip()
|
||||||
ips = [line.strip() for line in ip_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
for line in ip_path.read_text(encoding="utf-8").splitlines()
|
||||||
|
if line.strip()
|
||||||
|
]
|
||||||
|
|
||||||
if not ips:
|
if not ips:
|
||||||
print("No IP addresses found in the file.")
|
logging.error("[ERROR] IP 목록이 비어 있습니다.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 순차 처리 (필요하면 ThreadPoolExecutor로 병렬화 가능)
|
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
try:
|
try:
|
||||||
fetch_idrac_info_one(ip, output_dir)
|
fetch_idrac_info_one(ip, output_dir)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing {ip}: {e}")
|
logging.error(f"[ERROR] {ip} 처리 실패: {e}")
|
||||||
|
|
||||||
# 원본 Bash는 마지막에 입력 파일 삭제: rm -f $IP_FILE
|
# 원본 Bash 스크립트 동작 유지
|
||||||
try:
|
try:
|
||||||
ip_path.unlink(missing_ok=True)
|
ip_path.unlink(missing_ok=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
print("정보 수집 완료.")
|
logging.info("정보 수집 완료.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys, time
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print("Usage: python script.py <ip_file>")
|
logging.error("Usage: python script.py <ip_file>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
main(sys.argv[1])
|
main(sys.argv[1])
|
||||||
end = time.time()
|
elapsed = int(time.time() - start)
|
||||||
elapsed = int(end - start)
|
|
||||||
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
||||||
print(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
logging.info(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import re
|
|||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
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'
|
||||||
|
)
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# 저장 위치: 이 파일이 data/scripts/ 아래 있으면 → data/idrac_info
|
# 저장 위치: 이 파일이 data/scripts/ 아래 있으면 → data/idrac_info
|
||||||
@@ -168,7 +175,7 @@ def fetch_idrac_info(ip: str, output_dir: Path) -> None:
|
|||||||
r"Port")
|
r"Port")
|
||||||
|
|
||||||
if not svc_tag:
|
if not svc_tag:
|
||||||
print(f"Failed to retrieve SVC Tag for IP: {ip}")
|
logging.error(f"Failed to retrieve SVC Tag for IP: {ip}")
|
||||||
return
|
return
|
||||||
|
|
||||||
out_file = output_dir / f"{svc_tag}.txt"
|
out_file = output_dir / f"{svc_tag}.txt"
|
||||||
@@ -227,7 +234,7 @@ def fetch_idrac_info(ip: str, output_dir: Path) -> None:
|
|||||||
def main(ip_file: str) -> None:
|
def main(ip_file: str) -> None:
|
||||||
ip_path = Path(ip_file)
|
ip_path = Path(ip_file)
|
||||||
if not ip_path.is_file():
|
if not ip_path.is_file():
|
||||||
print(f"Usage: python script.py <ip_file>\nIP file {ip_file} does not exist.")
|
logging.error(f"Usage: python script.py <ip_file>\nIP file {ip_file} does not exist.")
|
||||||
return
|
return
|
||||||
|
|
||||||
output_dir = resolve_output_dir()
|
output_dir = resolve_output_dir()
|
||||||
@@ -237,7 +244,7 @@ def main(ip_file: str) -> None:
|
|||||||
lines = [ln.strip() for ln in ip_path.read_text(encoding="utf-8", errors="ignore").splitlines()]
|
lines = [ln.strip() for ln in ip_path.read_text(encoding="utf-8", errors="ignore").splitlines()]
|
||||||
ip = next((ln for ln in lines if ln), None)
|
ip = next((ln for ln in lines if ln), None)
|
||||||
if not ip:
|
if not ip:
|
||||||
print("No IP address found in the file.")
|
logging.error("No IP address found in the file.")
|
||||||
return
|
return
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
@@ -252,13 +259,13 @@ def main(ip_file: str) -> None:
|
|||||||
|
|
||||||
elapsed = int(end - start)
|
elapsed = int(end - start)
|
||||||
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60
|
||||||
print("정보 수집 완료.")
|
logging.info("정보 수집 완료.")
|
||||||
print(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
logging.info(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print("Usage: python script.py <ip_file>")
|
logging.error("Usage: python script.py <ip_file>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
main(sys.argv[1])
|
main(sys.argv[1])
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 사용자 이름 및 비밀번호 설정
|
|
||||||
IDRAC_USER="root"
|
|
||||||
IDRAC_PASS="calvin"
|
|
||||||
|
|
||||||
# IP 주소 파일 경로 인자 받기
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo "Usage: $0 <ip_file>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
IP_FILE=$1
|
|
||||||
|
|
||||||
if [ ! -f "$IP_FILE" ]; then
|
|
||||||
echo "IP file $IP_FILE does not exist."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 정보 저장 디렉터리 설정
|
|
||||||
OUTPUT_DIR="idrac_info"
|
|
||||||
mkdir -p $OUTPUT_DIR
|
|
||||||
|
|
||||||
# iDRAC 정보를 가져오는 함수 정의
|
|
||||||
fetch_idrac_info() {
|
|
||||||
local IDRAC_IP=$(cat $IP_FILE)
|
|
||||||
|
|
||||||
# 모든 hwinventory 저장
|
|
||||||
local hwinventory=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS hwinventory)
|
|
||||||
# 모든 샷시 정보 저장
|
|
||||||
local getsysinfo=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS getsysinfo)
|
|
||||||
# 모든 SysProfileSettings 저장
|
|
||||||
local SysProfileSettings=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get bios.SysProfileSettings)
|
|
||||||
# ProcessorSettings 저장
|
|
||||||
local ProcessorSettings=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get bios.ProcSettings)
|
|
||||||
# Memory Settings 저장
|
|
||||||
local MemorySettings=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get bios.MemSettings)
|
|
||||||
# Raid Settings 저장
|
|
||||||
local STORAGEController=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get STORAGE.Controller.1)
|
|
||||||
|
|
||||||
# 서비스 태그 가져오기
|
|
||||||
local SVC_TAG=$(echo "$getsysinfo" | grep -i "SVC Tag" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# Bios Firmware Version 확인
|
|
||||||
local Bios_firmware=$(echo "$getsysinfo" | grep -i "System BIOS Version" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Firmware Version 확인
|
|
||||||
local iDRAC_firmware=$(echo "$getsysinfo" | grep -i "Firmware Version" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# Intel NIC Firmware Version 확인
|
|
||||||
local Intel_NIC_firmware=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get NIC.FrmwImgMenu.1 | grep -i "#FamilyVersion" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# OnBoard NIC Firmware Version 확인
|
|
||||||
local Onboard_NIC_firmware=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get NIC.FrmwImgMenu.5 | grep -i "#FamilyVersion" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# R/C Firmware Version 확인
|
|
||||||
local Raid_firmware=$(echo "$hwinventory" | grep -i "ControllerFirmwareVersion" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# Bios 설정 Boot Mode 확인
|
|
||||||
local Bios_BootMode=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get bios.BiosBootSettings | grep -i "BootMode" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# Bios SysProfileSettings 설정 정보 확인
|
|
||||||
local SysProFileSettings_info1=$(echo "$SysProfileSettings" | grep -i "SysProfile=" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local SysProFileSettings_info2=$(echo "$SysProfileSettings" | grep -i "ProcPwrPerf" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local SysProFileSettings_info3=$(echo "$SysProfileSettings" | grep -i "MemFrequency" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local SysProFileSettings_info4=$(echo "$SysProfileSettings" | grep -i "ProcTurboMode" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local SysProFileSettings_info5=$(echo "$SysProfileSettings" | grep -i "PcieAspmL1" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local SysProFileSettings_info6=$(echo "$SysProfileSettings" | grep -i "ProcCStates" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local SysProFileSettings_info7=$(echo "$SysProfileSettings" | grep -i "DeterminismSlider" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local SysProFileSettings_info8=$(echo "$SysProfileSettings" | grep -i "DynamicLinkWidthManagement" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# Processor Settings - Logical Processor
|
|
||||||
local ProcessorSettings_info1=$(echo "$ProcessorSettings" | grep -i "LogicalProc" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local ProcessorSettings_info2=$(echo "$ProcessorSettings" | grep -i "ProcVirtualization" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local ProcessorSettings_info3=$(echo "$ProcessorSettings" | grep -i "NumaNodesPerSocket" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local ProcessorSettings_info4=$(echo "$ProcessorSettings" | grep -i "ProcX2Apic" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# Memory Settings - Node Interleaving
|
|
||||||
local MemorySettings_info1=$(echo "$MemorySettings" | grep -i "DramRefreshDelay" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local MemorySettings_info2=$(echo "$MemorySettings" | grep -i "PPROnUCE" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
local MemorySettings_info3=$(echo "$MemorySettings" | grep -i "CECriticalSEL" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# System Settings - Thermal Profile Optimization
|
|
||||||
local SystemSettings_info1=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get System.ThermalSettings | grep -i "ThermalProfile" | awk -F '=' '{print $2}')
|
|
||||||
# Integrated Devices Settings - SR-IOV Global Enable
|
|
||||||
local IntegratedDevicesSettings_info1=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get Bios.IntegratedDevices | grep -i "SriovGlobalEnable" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# Miscellaneous Settings - F1/F2 Prompt on Error
|
|
||||||
local IMiscellaneousSettings_info1=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get bios.MiscSettings | grep -i "ErrPrompt" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# iDRAC Settings - Timezone
|
|
||||||
local iDRAC_Settings_info1=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.Time.Timezone | grep -i "Timezone" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - IPMI LAN Selection
|
|
||||||
local iDRAC_Settings_info2=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.CurrentNIC | grep -i "ActiveNIC" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - IPMI IP(IPv4)
|
|
||||||
local iDRAC_Settings_info3=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.CurrentIPv4 | grep -i "DHCPEnable" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - IPMI IP(IPv6)
|
|
||||||
local iDRAC_Settings_info4=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.CurrentIPv6 | grep -i "Enable=" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - Redfish Support
|
|
||||||
local iDRAC_Settings_info5=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.Redfish.Enable | grep -i "Enable=" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - SSH Support
|
|
||||||
local iDRAC_Settings_info6=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.SSH | grep -i "Enable=" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - AD User Domain Name
|
|
||||||
local iDRAC_Settings_info7=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.USERDomain.1.Name | grep -i "Name" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - SC Server Address
|
|
||||||
local iDRAC_Settings_info8=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.ActiveDirectory.DomainController1 | grep -i "DomainController1" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - SE AD RoleGroup Name
|
|
||||||
local iDRAC_Settings_info9=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.ADGroup.1.Name | grep -i "Name" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - SE AD RoleGroup Dome인
|
|
||||||
local iDRAC_Settings_info10=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.ADGroup.1.Domain | grep -i "Domain" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - SE AD RoleGroup Privilege
|
|
||||||
local iDRAC_Settings_info11=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.ADGroup.1.Privilege | grep -i "Privilege" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - IDC AD RoleGroup name
|
|
||||||
local iDRAC_Settings_info12=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.ADGroup.2.Name | grep -i "Name" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - IDC AD RoleGroup Dome인
|
|
||||||
local iDRAC_Settings_info13=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.ADGroup.2.Domain | grep -i "Domain" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - IDC AD RoleGroup Privilege
|
|
||||||
local iDRAC_Settings_info14=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.ADGroup.2.Privilege | grep -i "Privilege" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - Remote Log (syslog)
|
|
||||||
local iDRAC_Settings_info15=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.SysLog.SysLogEnable | grep -i "SysLogEnable" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - syslog server address 1
|
|
||||||
local iDRAC_Settings_info16=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.SysLog.Server1 | grep -i "Server1" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - syslog server address 2
|
|
||||||
local iDRAC_Settings_info17=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.SysLog.Server2 | grep -i "Server2" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - syslog server port
|
|
||||||
local iDRAC_Settings_info18=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.SysLog.Port | grep -i "Port" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# iDRAC Settings - VirtualConsole Port
|
|
||||||
local iDRAC_Settings_info19=$(racadm -r $IDRAC_IP -u $IDRAC_USER -p $IDRAC_PASS get iDRAC.VirtualConsole.Port | grep -i "Port" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# RAID Settings - ProductName
|
|
||||||
local RAID_info0=$(echo "$hwinventory" | grep -i "ProductName = PERC" | awk -F '=' '{print $2}')
|
|
||||||
# RAID Settings - RAIDType
|
|
||||||
local RAID_info1=$(echo "$hwinventory" | grep -i "RAIDTypes" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# RAID Settings - StripeSize
|
|
||||||
local RAID_info2=$(echo "$hwinventory" | grep -i "StripeSize" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# RAID Settings - ReadCachePolicy
|
|
||||||
local RAID_info3=$(echo "$hwinventory" | grep -i "ReadCachePolicy" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# RAID Settings - WriteCachePolicy
|
|
||||||
local RAID_info4=$(echo "$hwinventory" | grep -i "WriteCachePolicy" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# RAID Settings - PatrolReadRate
|
|
||||||
local RAID_info5=$(echo "$STORAGEController" | grep -i "CheckConsistencyRate" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
# RAID Settings - PatrolReadRate
|
|
||||||
local RAID_info6=$(echo "$STORAGEController" | grep -i "PatrolReadMode" | awk -F '=' '{print $2}' | tr -d '[:space:]')
|
|
||||||
|
|
||||||
# 서비스 태그가 존재하는지 확인
|
|
||||||
if [ -z "$SVC_TAG" ]; then
|
|
||||||
echo "Failed to retrieve SVC Tag for IP: $IDRAC_IP"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local OUTPUT_FILE="$OUTPUT_DIR/$SVC_TAG.txt"
|
|
||||||
echo "Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: $SVC_TAG)" | tee -a "$OUTPUT_FILE"
|
|
||||||
echo -e "\n" >> "$OUTPUT_FILE"
|
|
||||||
echo "------------------------------------------Firware Version 정보------------------------------------------" >> "$OUTPUT_FILE"
|
|
||||||
# SVC Tag 확인
|
|
||||||
echo "1. SVC Tag : $SVC_TAG" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Bios Firmware Version 확인
|
|
||||||
echo "2. Bios Firmware : $Bios_firmware" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# iDRAC Firmware Version 확인
|
|
||||||
echo "3. iDRAC Firmware Version : $iDRAC_firmware" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Intel NIC Firmware Version 확인
|
|
||||||
echo "4. NIC Integrated Firmware Version : $Intel_NIC_firmware" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# OnBoard NIC Firmware Version 확인
|
|
||||||
echo "5. OnBoard NIC Firmware Version : $Onboard_NIC_firmware" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Raid Controller Firmware Version 확인
|
|
||||||
echo "6. Raid Controller Firmware Version : $Raid_firmware" >> "$OUTPUT_FILE"
|
|
||||||
echo -e "\n" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
echo "---------------------------------------------Bios 설정 정보----------------------------------------------" >> "$OUTPUT_FILE"
|
|
||||||
# bios Boot Mode 확인
|
|
||||||
echo "01. Bios Boot Mode : $Bios_BootMode" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - System Profile
|
|
||||||
echo "02. System Profile Settings - System Profile : $SysProFileSettings_info1" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - CPU Power Management
|
|
||||||
echo "03. System Profile Settings - CPU Power Management : $SysProFileSettings_info2" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - Memory Frequency
|
|
||||||
echo "04. System Profile Settings - Memory Frequency : $SysProFileSettings_info3" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - Turbo Boost
|
|
||||||
echo "05. System Profile Settings - Turbo Boost : $SysProFileSettings_info4" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - C1E
|
|
||||||
echo "06. System Profile Settings - PCI ASPM L1 Link Power Management : $SysProFileSettings_info5" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - C-States
|
|
||||||
echo "07. System Profile Settings - C-States : $SysProFileSettings_info6" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - Determinism Slider
|
|
||||||
echo "08. System Profile Settings - Determinism Slider : $SysProFileSettings_info7" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# SysProfileSettings - Dynamic Link Width Management (DLWM)
|
|
||||||
echo "08. System Profile Settings - Dynamic Link Width Management (DLWM) : $SysProFileSettings_info8" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Processor Settings - Logical Processor
|
|
||||||
echo "09. Processor Settings - Logical Processor : $ProcessorSettings_info1" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Processor Settings - Virtualization Technology
|
|
||||||
echo "10. Processor Settings - Virtualization Technology : $ProcessorSettings_info2" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Processor Settings - NUMA Nodes Per Socket
|
|
||||||
echo "11. Processor Settings - NUMA Nodes Per Socket : $ProcessorSettings_info3" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Processor Settings - x2APIC Mode
|
|
||||||
echo "12. Processor Settings - x2APIC Mode : $ProcessorSettings_info4" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Memory Settings - Dram Refresh Delayg
|
|
||||||
echo "13. Memory Settings - Dram Refresh Delay : $MemorySettings_info1" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error
|
|
||||||
echo "14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : $MemorySettings_info2" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Memory Settings - Correctable Error Logging
|
|
||||||
echo "15. Memory Settings - Correctable Error Logging : $MemorySettings_info3" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# System Settings - Thermal Profile Optimization
|
|
||||||
echo "16. System Settings - Thermal Profile Optimization : $SystemSettings_info1" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Integrated Devices Settings - SR-IOV Global Enable
|
|
||||||
echo "17. Integrated Devices Settings - SR-IOV Global Enable : $IntegratedDevicesSettings_info1" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# Miscellaneous Settings - F1/F2 Prompt on Error
|
|
||||||
echo "18. Miscellaneous Settings - F1/F2 Prompt on Error : $IMiscellaneousSettings_info1" >> "$OUTPUT_FILE"
|
|
||||||
echo -e "\n" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
echo "---------------------------------------------iDRAC 설정 정보----------------------------------------------" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - Timezone
|
|
||||||
echo "01. iDRAC Settings - Timezone : $iDRAC_Settings_info1" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - IPMI LAN Selection
|
|
||||||
echo "02. iDRAC Settings - IPMI LAN Selection : $iDRAC_Settings_info2" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - IPMI IP(IPv4)
|
|
||||||
echo "03. iDRAC Settings - IPMI IP(IPv4) : $iDRAC_Settings_info3" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - IPMI IP(IPv6)
|
|
||||||
echo "04. iDRAC Settings - IPMI IP(IPv6) : $iDRAC_Settings_info4" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - Redfish Support
|
|
||||||
echo "05. iDRAC Settings - Redfish Support : $iDRAC_Settings_info5" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SSH Support
|
|
||||||
echo "06. iDRAC Settings - SSH Support : $iDRAC_Settings_info6" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - AD User Domain Name
|
|
||||||
echo "07. iDRAC Settings - AD User Domain Name : $iDRAC_Settings_info7" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SC Server Address
|
|
||||||
echo "08. iDRAC Settings - SC Server Address : $iDRAC_Settings_info8" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SE AD RoleGroup Name
|
|
||||||
echo "09. iDRAC Settings - SE AD RoleGroup Name : $iDRAC_Settings_info9" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SE AD RoleGroup Dome인
|
|
||||||
echo "10. iDRAC Settings - SE AD RoleGroup Dome인 : $iDRAC_Settings_info10" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SE AD RoleGroup Privilege
|
|
||||||
echo "11. iDRAC Settings - SE AD RoleGroup Privilege : $iDRAC_Settings_info11" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SE IDC RoleGroup Name
|
|
||||||
echo "12. iDRAC Settings - IDC AD RoleGroup Name : $iDRAC_Settings_info12" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SE IDC RoleGroup Dome인
|
|
||||||
echo "13. iDRAC Settings - IDC AD RoleGroup Domain : $iDRAC_Settings_info13" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - SE IDC RoleGroup Dome인
|
|
||||||
echo "14. iDRAC Settings - IDC AD RoleGroup Privilege : $iDRAC_Settings_info14" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - Remote Log (syslog)
|
|
||||||
echo "15. iDRAC Settings - Remote Log (syslog) : $iDRAC_Settings_info15" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - Remote Log (syslog)
|
|
||||||
echo "16. iDRAC Settings - syslog server address 1 : $iDRAC_Settings_info16" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - Remote Log (syslog)
|
|
||||||
echo "17. iDRAC Settings - syslog server address 2 : $iDRAC_Settings_info17" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - syslog server port
|
|
||||||
echo "18. iDRAC Settings - syslog server port : $iDRAC_Settings_info18" >> "$OUTPUT_FILE"
|
|
||||||
# iDRAC Settings - Remote KVM Nonsecure port
|
|
||||||
echo "19. iDRAC Settings - Remote KVM Nonsecure port : $iDRAC_Settings_info19" >> "$OUTPUT_FILE"
|
|
||||||
echo -e "\n" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# echo "---------------------------------------------Raid 설정 정보----------------------------------------------" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - Raid Types
|
|
||||||
#echo "01. RAID Settings - Raid ProductName : $RAID_info0" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - Raid Types
|
|
||||||
#echo "02. RAID Settings - Raid Typest : $RAID_info1" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - StripeSize
|
|
||||||
#echo "03. RAID Settings - StripeSize : $RAID_info2" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - ReadCachePolicy
|
|
||||||
#echo "04. RAID Settings - ReadCachePolicy : $RAID_info3" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - ReadCachePolicy
|
|
||||||
#echo "05. RAID Settings - WriteCachePolicy : $RAID_info4" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - CheckConsistencyRate
|
|
||||||
#echo "06. RAID Settings - CheckConsistencyRate : $RAID_info5" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - PatrolReadMode
|
|
||||||
#echo "07. RAID Settings - PatrolReadMode : $RAID_info6" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - period
|
|
||||||
#echo "08. RAID Settings - period : 168h" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - Power Save
|
|
||||||
#echo "09. RAID Settings - Power Save : No" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - JBODMODE
|
|
||||||
#echo "10. RAID Settings - JBODMODE : Controller does not support JBOD" >> "$OUTPUT_FILE"
|
|
||||||
# RAID Settings - maxconcurrentpd
|
|
||||||
#echo "11. RAID Settings - maxconcurrentpd : 240" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
# 임시 파일 삭제
|
|
||||||
rm -f $IP_FILE
|
|
||||||
}
|
|
||||||
|
|
||||||
export -f fetch_idrac_info
|
|
||||||
export IDRAC_USER
|
|
||||||
export IDRAC_PASS
|
|
||||||
export OUTPUT_DIR
|
|
||||||
|
|
||||||
# 시작 시간 기록
|
|
||||||
START_TIME=$(date +%s)
|
|
||||||
|
|
||||||
# IP 목록 파일을 읽어 병렬로 작업 수행
|
|
||||||
fetch_idrac_info
|
|
||||||
|
|
||||||
# 종료 시간 기록
|
|
||||||
END_TIME=$(date +%s)
|
|
||||||
|
|
||||||
# 소요 시간 계산
|
|
||||||
ELAPSED_TIME=$(($END_TIME - $START_TIME))
|
|
||||||
ELAPSED_HOURS=$(($ELAPSED_TIME / 3600))
|
|
||||||
ELAPSED_MINUTES=$((($ELAPSED_TIME % 3600) / 60))
|
|
||||||
ELAPSED_SECONDS=$(($ELAPSED_TIME % 60))
|
|
||||||
|
|
||||||
echo "정보 수집 완료."
|
|
||||||
echo "수집 완료 시간: $ELAPSED_HOURS 시간, $ELAPSED_MINUTES 분, $ELAPSED_SECONDS 초."
|
|
||||||
@@ -10,6 +10,14 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional, Tuple, List
|
from typing import Dict, Optional, Tuple, List
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
# ===== 설정: 기본 계정/비밀번호 (환경변수로 덮어쓰기 가능) =====
|
# ===== 설정: 기본 계정/비밀번호 (환경변수로 덮어쓰기 가능) =====
|
||||||
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
||||||
@@ -190,17 +198,17 @@ def main():
|
|||||||
|
|
||||||
ip_file = Path(args.ip_file)
|
ip_file = Path(args.ip_file)
|
||||||
if not ip_file.exists():
|
if not ip_file.exists():
|
||||||
print(f"IP 파일이 존재하지 않습니다: {ip_file}", file=sys.stderr)
|
logging.error(f"IP 파일이 존재하지 않습니다: {ip_file}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
with ip_file.open("r", encoding="utf-8") as f:
|
with ip_file.open("r", encoding="utf-8") as f:
|
||||||
ips = [ln.strip() for ln in f if ln.strip()]
|
ips = [ln.strip() for ln in f if ln.strip()]
|
||||||
|
|
||||||
if not ips:
|
if not ips:
|
||||||
print("IP 목록이 비어 있습니다.", file=sys.stderr)
|
logging.error("IP 목록이 비어 있습니다.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"[시작] 총 {len(ips)}대, workers={args.workers}, IDRAC_USER={IDRAC_USER}")
|
logging.info(f"[시작] 총 {len(ips)}대, workers={args.workers}, IDRAC_USER={IDRAC_USER}")
|
||||||
|
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
ok = 0
|
ok = 0
|
||||||
@@ -214,20 +222,23 @@ def main():
|
|||||||
try:
|
try:
|
||||||
_ip, success, msg = fut.result()
|
_ip, success, msg = fut.result()
|
||||||
prefix = "[OK] " if success else "[FAIL] "
|
prefix = "[OK] " if success else "[FAIL] "
|
||||||
print(prefix + ip + " - " + msg)
|
if success:
|
||||||
|
logging.info(prefix + ip + " - " + msg)
|
||||||
|
else:
|
||||||
|
logging.error(prefix + ip + " - " + msg)
|
||||||
ok += int(success)
|
ok += int(success)
|
||||||
fail += int(not success)
|
fail += int(not success)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[EXC] {ip} - {e}", file=sys.stderr)
|
logging.error(f"[EXC] {ip} - {e}")
|
||||||
fail += 1
|
fail += 1
|
||||||
|
|
||||||
dt = int(time.time() - t0)
|
dt = int(time.time() - t0)
|
||||||
h, r = divmod(dt, 3600)
|
h, r = divmod(dt, 3600)
|
||||||
m, s = divmod(r, 60)
|
m, s = divmod(r, 60)
|
||||||
|
|
||||||
print("\n정보 수집 완료.")
|
logging.info("\n정보 수집 완료.")
|
||||||
print(f"성공 {ok}대 / 실패 {fail}대")
|
logging.info(f"성공 {ok}대 / 실패 {fail}대")
|
||||||
print(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
logging.info(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -10,6 +10,14 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional, Tuple, List
|
from typing import Dict, Optional, Tuple, List
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [INFO] root: %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
# ===== 설정: 기본 계정/비밀번호 (환경변수로 덮어쓰기 가능) =====
|
# ===== 설정: 기본 계정/비밀번호 (환경변수로 덮어쓰기 가능) =====
|
||||||
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
IDRAC_USER = os.getenv("IDRAC_USER", "root")
|
||||||
@@ -189,17 +197,17 @@ def main():
|
|||||||
|
|
||||||
ip_file = Path(args.ip_file)
|
ip_file = Path(args.ip_file)
|
||||||
if not ip_file.exists():
|
if not ip_file.exists():
|
||||||
print(f"IP 파일이 존재하지 않습니다: {ip_file}", file=sys.stderr)
|
logging.error(f"IP 파일이 존재하지 않습니다: {ip_file}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
with ip_file.open("r", encoding="utf-8") as f:
|
with ip_file.open("r", encoding="utf-8") as f:
|
||||||
ips = [ln.strip() for ln in f if ln.strip()]
|
ips = [ln.strip() for ln in f if ln.strip()]
|
||||||
|
|
||||||
if not ips:
|
if not ips:
|
||||||
print("IP 목록이 비어 있습니다.", file=sys.stderr)
|
logging.error("IP 목록이 비어 있습니다.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"[시작] 총 {len(ips)}대, workers={args.workers}, IDRAC_USER={IDRAC_USER}")
|
logging.info(f"[시작] 총 {len(ips)}대, workers={args.workers}, IDRAC_USER={IDRAC_USER}")
|
||||||
|
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
ok = 0
|
ok = 0
|
||||||
@@ -213,20 +221,23 @@ def main():
|
|||||||
try:
|
try:
|
||||||
_ip, success, msg = fut.result()
|
_ip, success, msg = fut.result()
|
||||||
prefix = "[OK] " if success else "[FAIL] "
|
prefix = "[OK] " if success else "[FAIL] "
|
||||||
print(prefix + ip + " - " + msg)
|
if success:
|
||||||
|
logging.info(prefix + ip + " - " + msg)
|
||||||
|
else:
|
||||||
|
logging.error(prefix + ip + " - " + msg)
|
||||||
ok += int(success)
|
ok += int(success)
|
||||||
fail += int(not success)
|
fail += int(not success)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[EXC] {ip} - {e}", file=sys.stderr)
|
logging.error(f"[EXC] {ip} - {e}")
|
||||||
fail += 1
|
fail += 1
|
||||||
|
|
||||||
dt = int(time.time() - t0)
|
dt = int(time.time() - t0)
|
||||||
h, r = divmod(dt, 3600)
|
h, r = divmod(dt, 3600)
|
||||||
m, s = divmod(r, 60)
|
m, s = divmod(r, 60)
|
||||||
|
|
||||||
print("\n정보 수집 완료.")
|
logging.info("\n정보 수집 완료.")
|
||||||
print(f"성공 {ok}대 / 실패 {fail}대")
|
logging.info(f"성공 {ok}대 / 실패 {fail}대")
|
||||||
print(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
logging.info(f"수집 완료 시간: {h} 시간, {m} 분, {s} 초.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -1 +1 @@
|
|||||||
10.10.0.1
|
10.10.0.2
|
||||||
|
|||||||
29
idrac-info.service
Normal file
29
idrac-info.service
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=iDRAC Info Web Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
# 실행할 사용자 (보안을 위해 root 대신 전용 계정 권장, 예: idrac)
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
|
||||||
|
# 프로젝트 루트 디렉토리 (서버 환경에 맞게 수정 필요)
|
||||||
|
WorkingDirectory=/data/app/idrac_info_new
|
||||||
|
|
||||||
|
# 가상환경의 python 실행 및 app.py 호출
|
||||||
|
# ExecStart=<python_path> <script_path>
|
||||||
|
ExecStart=/bin/bash -c '/data/app/idrac_info_new/venv/bin/python /data/app/idrac_info_new/app.py'
|
||||||
|
|
||||||
|
# 환경 변수 설정 (필요 시 수정)
|
||||||
|
Environment="FLASK_HOST=0.0.0.0"
|
||||||
|
Environment="FLASK_PORT=5000"
|
||||||
|
Environment="FLASK_DEBUG=false"
|
||||||
|
# Werkzeug 리로더 끄기 (프로덕션 모드) - Systemd에서는 이 설정이 오히려 오류(KeyError)를 유발하므로 제거
|
||||||
|
# Environment="WERKZEUG_RUN_MAIN=true"
|
||||||
|
|
||||||
|
# 프로세스 종료 시 자동 재시작
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -129,20 +129,36 @@ def run_polling(flask_app: Flask) -> None:
|
|||||||
if flask_app is None:
|
if flask_app is None:
|
||||||
raise ValueError("flask_app is required for run_polling")
|
raise ValueError("flask_app is required for run_polling")
|
||||||
|
|
||||||
|
bot_token: Optional[str] = None
|
||||||
|
bot_name: Optional[str] = None
|
||||||
|
bot_id: Optional[int] = None
|
||||||
|
|
||||||
# DB에서 활성 봇 조회
|
# DB에서 활성 봇 조회
|
||||||
with flask_app.app_context():
|
with flask_app.app_context():
|
||||||
bots = TelegramBot.query.filter_by(is_active=True).all()
|
bots = TelegramBot.query.filter_by(is_active=True).all()
|
||||||
|
|
||||||
if not bots:
|
if not bots:
|
||||||
logger.warning("No active bots found")
|
logger.warning("No active bots found for polling service.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 첫 번째 활성 봇만 사용 (여러 봇이 동시에 폴링하면 충돌 가능)
|
if len(bots) > 1:
|
||||||
|
logger.warning("Multiple active bots found. Only the first one (%s) will be used.", bots[0].name)
|
||||||
|
|
||||||
|
# 첫 번째 활성 봇 사용
|
||||||
bot = bots[0]
|
bot = bots[0]
|
||||||
flask_app.logger.info("Starting polling for bot: %s (ID: %s)", bot.name, bot.id)
|
# DB 세션 밖에서도 사용할 수 있도록 필요한 정보만 추출 (Detached Instance 에러 방지)
|
||||||
|
bot_token = bot.token
|
||||||
|
bot_name = bot.name
|
||||||
|
bot_id = bot.id
|
||||||
|
|
||||||
|
logger.info("Starting polling for bot: %s (ID: %s)", bot_name, bot_id)
|
||||||
|
|
||||||
|
if not bot_token:
|
||||||
|
logger.error("Bot token not found.")
|
||||||
|
return
|
||||||
|
|
||||||
# Application 생성
|
# Application 생성
|
||||||
application = Application.builder().token(bot.token).build()
|
application = Application.builder().token(bot_token).build()
|
||||||
|
|
||||||
# Flask app을 bot_data에 넣어서 핸들러에서 사용할 수 있게 함
|
# Flask app을 bot_data에 넣어서 핸들러에서 사용할 수 있게 함
|
||||||
application.bot_data["flask_app"] = flask_app
|
application.bot_data["flask_app"] = flask_app
|
||||||
@@ -152,6 +168,6 @@ def run_polling(flask_app: Flask) -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# v20 스타일: run_polling 은 동기 함수이고, 내부에서 이벤트 루프를 직접 관리함
|
# v20 스타일: run_polling 은 동기 함수이고, 내부에서 이벤트 루프를 직접 관리함
|
||||||
application.run_polling(drop_pending_updates=True)
|
application.run_polling(drop_pending_updates=True, stop_signals=[])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
flask_app.logger.exception("Error in bot polling: %s", e)
|
logger.exception("Error in bot polling: %s", e)
|
||||||
Reference in New Issue
Block a user