Initial commit

This commit is contained in:
2025-10-05 17:37:51 +09:00
parent 5cbe9a2524
commit 3a7fabb830
219 changed files with 81295 additions and 0 deletions

22
.env Normal file
View File

@@ -0,0 +1,22 @@
# Flask
SECRET_KEY=your_secret_key
FLASK_HOST=0.0.0.0
FLASK_PORT=5000
FLASK_DEBUG=true
# Paths
APP_DATA_DIR=./data
# SocketIO (threading / eventlet / gevent)
SOCKETIO_ASYNC_MODE=threading
# Database (운영 시 외부 DB 권장)
# DATABASE_URL=postgresql+psycopg://user:pass@host/db
# Telegram (민감정보, 필수 시에만 설정)
TELEGRAM_BOT_TOKEN=6719918880:AAHC1on-KlzH0G3ylJP57p-q5qMyorFUGZo
TELEGRAM_CHAT_ID=298120612

Binary file not shown.

Binary file not shown.

105
app.py Normal file
View File

@@ -0,0 +1,105 @@
from __future__ import annotations
import os
import platform
from pathlib import Path
from flask import Flask
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_socketio import SocketIO
from flask_wtf import CSRFProtect
from config import Config
from backend.models.user import db, load_user
from backend.routes import register_routes
from backend.services.logger import setup_logging
from backend.services import watchdog_handler
# ─────────────────────────────────────────────────────────────
# 템플릿/정적 경로를 파일 위치 기준으로 안전하게 설정
# structure: <project_root>/backend/templates, <project_root>/backend/static
BASE_DIR = Path(__file__).resolve().parent
TEMPLATE_DIR = (BASE_DIR / "backend" / "templates").resolve()
STATIC_DIR = (BASE_DIR / "backend" / "static").resolve()
# Flask 애플리케이션 생성
app = Flask(__name__, template_folder=str(TEMPLATE_DIR), static_folder=str(STATIC_DIR))
app.config.from_object(Config)
# 로그 설정
setup_logging(app)
# ─────────────────────────────────────────────────────────────
# CSRF 보호 + 템플릿에서 {{ csrf_token() }} 사용 가능하게 주입
csrf = CSRFProtect()
csrf.init_app(app)
@app.context_processor
def inject_csrf():
try:
from flask_wtf.csrf import generate_csrf
return dict(csrf_token=generate_csrf)
except Exception:
# Flask-WTF 미설치/에러 시에도 앱이 뜨도록 방어
return dict(csrf_token=lambda: "")
# ─────────────────────────────────────────────────────────────
# SocketIO: Windows 기본 threading, Linux는 eventlet 설치 시 eventlet 사용
# 환경변수 SOCKETIO_ASYNC_MODE 로 강제 지정 가능 ("threading"/"eventlet"/"gevent"/"auto")
async_mode = (app.config.get("SOCKETIO_ASYNC_MODE") or "threading").lower()
if async_mode == "auto":
async_mode = "threading"
if async_mode == "eventlet":
# Windows에선 eventlet 비권장, Linux에서만 시도
if platform.system() != "Windows":
try:
import eventlet # type: ignore
eventlet.monkey_patch()
except Exception:
async_mode = "threading" # 폴백
else:
async_mode = "threading"
socketio = SocketIO(app, cors_allowed_origins="*", async_mode=async_mode)
# watchdog에서 socketio 사용
watchdog_handler.socketio = socketio
# ─────────────────────────────────────────────────────────────
# DB / 마이그레이션
app.logger.info("DB URI = %s", app.config.get("SQLALCHEMY_DATABASE_URI"))
db.init_app(app)
Migrate(app, db)
# (선택) 개발 편의용: 테이블 자동 부트스트랩
# 환경변수 AUTO_BOOTSTRAP_DB=true 일 때만 동작 (운영에서는 flask db upgrade 사용 권장)
if (os.getenv("AUTO_BOOTSTRAP_DB", "false").lower() == "true"):
from sqlalchemy import inspect
with app.app_context():
insp = inspect(db.engine)
if "user" not in insp.get_table_names():
db.create_all()
app.logger.info("DB bootstrap: created tables via create_all()")
# ─────────────────────────────────────────────────────────────
# Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "auth.login"
@login_manager.user_loader
def _load_user(user_id: str):
return load_user(user_id)
# 라우트 등록 (Blueprints 등)
register_routes(app, socketio)
# ─────────────────────────────────────────────────────────────
# 엔트리포인트
if __name__ == "__main__":
host = os.getenv("FLASK_HOST", "0.0.0.0")
port = int(os.getenv("FLASK_PORT", 5000))
debug = os.getenv("FLASK_DEBUG", "true").lower() == "true"
socketio.run(app, host=host, port=port, debug=debug)

Binary file not shown.

116
backend/forms/auth_forms.py Normal file
View File

@@ -0,0 +1,116 @@
# backend/forms/auth_forms.py (refactor)
from __future__ import annotations
import re
import unicodedata
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import (
DataRequired, Length, Email, EqualTo, ValidationError, Regexp
)
from backend.models.user import User
# ─────────────────────────────────────────────────────────────
# 공통 필터/유틸
def strip_filter(x: str | None) -> str | None:
return x.strip() if isinstance(x, str) else x
def lower_strip(x: str | None) -> str | None:
return x.strip().lower() if isinstance(x, str) else x
def nfc_korean(x: str | None) -> str | None:
if not isinstance(x, str):
return x
# 한글 이름 등 유니코드 정규화 (NFC)
return unicodedata.normalize("NFC", x.strip())
# 비밀번호 정책: 8~64자, 대문자/소문자/숫자/특수문자 각 1개 이상
password_policy_validators = [
Length(min=8, max=64, message="비밀번호는 8~64자여야 합니다."),
Regexp(r".*[A-Z].*", message="비밀번호에 대문자가 1자 이상 포함되어야 합니다."),
Regexp(r".*[a-z].*", message="비밀번호에 소문자가 1자 이상 포함되어야 합니다."),
Regexp(r".*\d.*", message="비밀번호에 숫자가 1자 이상 포함되어야 합니다."),
Regexp(r".*[^A-Za-z0-9].*", message="비밀번호에 특수문자가 1자 이상 포함되어야 합니다."),
]
# ─────────────────────────────────────────────────────────────
class RegistrationForm(FlaskForm):
username = StringField(
"이름",
filters=[nfc_korean],
validators=[
DataRequired(message="이름을 입력해주세요."),
Length(min=2, max=20, message="이름은 2~20자 사이여야 합니다."),
],
render_kw={
"placeholder": "이름 (한글만 허용)",
"autocomplete": "name",
"autocapitalize": "off",
"autocorrect": "off",
"spellcheck": "false",
},
)
email = StringField(
"이메일",
filters=[lower_strip],
validators=[
DataRequired(message="이메일을 입력해주세요."),
Email(message="유효한 이메일을 입력하세요."),
],
render_kw={
"placeholder": "예: user@example.com",
"autocomplete": "email",
"inputmode": "email",
},
)
password = PasswordField(
"비밀번호",
validators=[DataRequired(message="비밀번호를 입력해주세요."), *password_policy_validators],
render_kw={"placeholder": "비밀번호", "autocomplete": "new-password"},
)
confirm_password = PasswordField(
"비밀번호 확인",
validators=[
DataRequired(message="비밀번호 확인을 입력해주세요."),
EqualTo("password", message="비밀번호가 일치하지 않습니다."),
],
render_kw={"placeholder": "비밀번호 다시 입력", "autocomplete": "new-password"},
)
submit = SubmitField("회원가입")
def validate_username(self, field):
# 한글만 허용(2~20자) 기존 로직 유지
if not re.fullmatch(r"[가-힣]{2,20}", field.data or ""):
raise ValidationError("이름은 한글로만 2~20자 입력 가능합니다.")
# 중복 체크
user = User.query.filter_by(username=field.data).first()
if user:
raise ValidationError("이미 사용 중인 이름입니다.")
def validate_email(self, field):
# 이메일은 소문자 비교(필터로 이미 소문자화)
user = User.query.filter_by(email=field.data).first()
if user:
raise ValidationError("이미 등록된 이메일입니다.")
class LoginForm(FlaskForm):
email = StringField(
"이메일",
filters=[lower_strip],
validators=[DataRequired(message="이메일을 입력해주세요."), Email(message="유효한 이메일을 입력하세요.")],
render_kw={"placeholder": "이메일 주소", "autocomplete": "username", "inputmode": "email"},
)
password = PasswordField(
"비밀번호",
validators=[DataRequired(message="비밀번호를 입력해주세요.")],
render_kw={"placeholder": "비밀번호", "autocomplete": "current-password"},
)
remember = BooleanField("로그인 유지")
submit = SubmitField("로그인")

BIN
backend/instance/site.db Normal file

Binary file not shown.

Binary file not shown.

127
backend/models/user.py Normal file
View File

@@ -0,0 +1,127 @@
from __future__ import annotations
from typing import Optional
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from sqlalchemy import String, Boolean
from sqlalchemy.orm import Mapped, mapped_column
from werkzeug.security import check_password_hash as wz_check_password_hash
import logging
# passlib: Argon2id 기본, scrypt/pbkdf2는 검증만 (점진 마이그레이션)
from passlib.context import CryptContext
db = SQLAlchemy()
# Argon2id를 기본으로 사용하고, scrypt/pbkdf2_sha256은 검증만 허용(=deprecated)
# 로그인에 성공하면 자동으로 Argon2id로 재해시 저장합니다.
pwd_ctx = CryptContext(
schemes=["argon2", "scrypt", "pbkdf2_sha256"],
default="argon2",
deprecated=["scrypt", "pbkdf2_sha256"],
# Argon2id 파라미터 (기본도 충분하지만 서비스 보안수준에 맞춰 조정 가능)
# time_cost: 2~4, memory_cost: 64~256 MiB 권장 범위
argon2__type="ID",
argon2__time_cost=3,
argon2__memory_cost=102400, # 100 MiB
argon2__parallelism=8,
# PBKDF2 라운드 상향 (레거시 검증용)
pbkdf2_sha256__rounds=300_000,
)
class User(db.Model, UserMixin):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(80), unique=True, nullable=False)
email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False, index=True)
password: Mapped[str] = mapped_column(String(255), nullable=False)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# ── 유틸 메서드
def __repr__(self) -> str: # pragma: no cover
return f"<User id={self.id} email={self.email} active={self.is_active}>"
# 신규 저장/변경 시 항상 Argon2id로 해시
def set_password(self, password: str) -> None:
self.password = pwd_ctx.hash(password)
# 혼합 검증: scrypt/pbkdf2/argon2 모두 검증 가능
# 검증 성공 + 레거시 스킴이면 Argon2id로 즉시 재해시 & 커밋
def check_password(self, password: str) -> bool:
"""
- 우선 저장된 해시의 '형식'을 보고 검증기를 선택한다.
* 'scrypt:' 또는 'pbkdf2:'로 시작하면 → Werkzeug 검증
* 그 외 → passlib(CryptContext) 검증
- 검증에 성공했고 현재 해시가 Argon2가 아니거나(passlib가 needs_update True),
Werk­zeug 형식(scrypt/pbkdf2)이라면 즉시 Argon2id로 재해시 저장한다.
"""
raw = self.password or ""
ok = False
used_werkzeug = False
try:
# 1) Werkzeug 포맷 감지 (예: 'scrypt:32768:8:1$...', 'pbkdf2:sha256:260000$...')
if raw.startswith("scrypt:") or raw.startswith("pbkdf2:"):
used_werkzeug = True
ok = wz_check_password_hash(raw, password) # hashlib 기반 → 형식 그대로 검증
else:
# 2) passlib 포맷(argon2/$scrypt$/pbkdf2_sha256) 시도
ok = pwd_ctx.verify(password, raw)
except Exception as e:
logging.warning("password verify failed: %s", e)
ok = False
if not ok:
return False
# ── 여기까지 왔으면 검증 성공. 필요 시 Argon2id로 즉시 업그레이드.
try:
need_upgrade = False
if used_werkzeug:
# Werkzeug 형식(scrypt/pbkdf2)은 우리 정책상 모두 Argon2로 마이그레이션
need_upgrade = True
else:
# passlib가 판단하는 업그레이드 필요 여부(파라미터/알고리즘 기준)
need_upgrade = pwd_ctx.needs_update(raw)
if need_upgrade:
self.password = pwd_ctx.hash(password) # Argon2id 기본
db.session.add(self)
db.session.commit()
except Exception as e:
# 업그레이드 실패해도 로그인 자체는 성공시킨다(다음 로그인 때 재시도)
logging.warning("password rehash (argon2) failed: %s", e)
db.session.rollback()
return True
# Flask-Login 호환 (UserMixin 기본 get_id 사용 가능하지만 명시)
def get_id(self) -> str: # pragma: no cover
return str(self.id)
# ── 조회 헬퍼
@staticmethod
def find_by_email(email: Optional[str]) -> Optional["User"]:
q = (email or "").strip().lower()
if not q:
return None
return User.query.filter_by(email=q).first()
@staticmethod
def find_by_username(username: Optional[str]) -> Optional["User"]:
q = (username or "").strip()
if not q:
return None
return User.query.filter_by(username=q).first()
# Flask-Login user_loader (SQLAlchemy 2.0 방식)
def load_user(user_id: str) -> Optional[User]: # pragma: no cover
try:
return db.session.get(User, int(user_id))
except Exception:
return None

View File

@@ -0,0 +1,20 @@
from __future__ import annotations
from flask import Flask
from .home import register_home_routes
from .auth import register_auth_routes
from .admin import register_admin_routes
from .main import register_main_routes
from .xml import register_xml_routes
from .utilities import register_util_routes
from .file_view import register_file_view
def register_routes(app: Flask, socketio=None) -> None:
"""블루프린트 일괄 등록. socketio는 main 라우트에서만 사용."""
register_home_routes(app)
register_auth_routes(app)
register_admin_routes(app)
register_main_routes(app, socketio)
register_xml_routes(app)
register_util_routes(app)
register_file_view(app)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

126
backend/routes/admin.py Normal file
View File

@@ -0,0 +1,126 @@
# backend/routes/admin.py
from __future__ import annotations
import logging
from functools import wraps
from typing import Callable
from flask import (
Blueprint,
render_template,
redirect,
url_for,
flash,
abort,
request,
current_app,
)
from flask_login import login_required, current_user
from backend.models.user import User, db
admin_bp = Blueprint("admin", __name__)
# Blueprint 등록
def register_admin_routes(app):
app.register_blueprint(admin_bp)
# 관리자 권한 데코레이터
def admin_required(view_func: Callable):
@wraps(view_func)
def wrapper(*args, **kwargs):
if not current_user.is_authenticated:
return redirect(url_for("auth.login"))
if not getattr(current_user, "is_admin", False):
flash("관리자 권한이 필요합니다.", "danger")
return redirect(url_for("main.index"))
return view_func(*args, **kwargs)
return wrapper
# 관리자 대시보드
@admin_bp.route("/admin", methods=["GET"])
@login_required
@admin_required
def admin_panel():
users = db.session.query(User).order_by(User.id.asc()).all()
return render_template("admin.html", users=users)
# 사용자 승인
@admin_bp.route("/admin/approve/<int:user_id>", methods=["GET"])
@login_required
@admin_required
def approve_user(user_id: int):
user = db.session.get(User, user_id)
if not user:
abort(404)
user.is_active = True
db.session.commit()
flash("사용자가 승인되었습니다.", "success")
logging.info("✅ 승인된 사용자: %s (id=%s)", user.username, user.id)
return redirect(url_for("admin.admin_panel"))
# 사용자 삭제
@admin_bp.route("/admin/delete/<int:user_id>", methods=["GET"])
@login_required
@admin_required
def delete_user(user_id: int):
user = db.session.get(User, user_id)
if not user:
abort(404)
username = user.username
db.session.delete(user)
db.session.commit()
flash("사용자가 삭제되었습니다.", "success")
logging.info("🗑 삭제된 사용자: %s (id=%s)", username, user_id)
return redirect(url_for("admin.admin_panel"))
# ▼▼▼ 사용자 비밀번호 변경(관리자용) ▼▼▼
@admin_bp.route("/admin/users/<int:user_id>/reset_password", methods=["POST"])
@login_required
@admin_required
def reset_password(user_id: int):
"""
admin.html에서 각 사용자 행 아래 폼으로부터 POST:
- name="new_password"
- name="confirm_password"
CSRF는 템플릿에서 {{ csrf_token() }} 또는 {{ form.hidden_tag() }}로 포함되어야 합니다.
"""
new_pw = (request.form.get("new_password") or "").strip()
confirm = (request.form.get("confirm_password") or "").strip()
# 서버측 검증
if not new_pw or not confirm:
flash("비밀번호와 확인 값을 모두 입력하세요.", "warning")
return redirect(url_for("admin.admin_panel"))
if new_pw != confirm:
flash("비밀번호 확인이 일치하지 않습니다.", "warning")
return redirect(url_for("admin.admin_panel"))
if len(new_pw) < 8:
flash("비밀번호는 최소 8자 이상이어야 합니다.", "warning")
return redirect(url_for("admin.admin_panel"))
user = db.session.get(User, user_id)
if not user:
abort(404)
try:
# passlib(Argon2id) 기반 set_password 사용 (models.user에 구현됨)
user.set_password(new_pw)
db.session.commit()
flash(f"사용자(ID={user.id}) 비밀번호를 변경했습니다.", "success")
current_app.logger.info(
"ADMIN: reset password for user_id=%s by admin_id=%s",
user.id, current_user.id
)
except Exception as e:
db.session.rollback()
current_app.logger.exception("ADMIN: reset password failed: %s", e)
flash("비밀번호 변경 중 오류가 발생했습니다.", "danger")
return redirect(url_for("admin.admin_panel"))

180
backend/routes/auth.py Normal file
View File

@@ -0,0 +1,180 @@
# backend/routes/auth.py
from __future__ import annotations
import logging
import threading
from typing import Optional
from urllib.parse import urlparse, urljoin
from flask import (
Blueprint,
render_template,
redirect,
url_for,
flash,
request,
session,
current_app,
)
from flask_login import login_user, logout_user, current_user, login_required
from backend.forms.auth_forms import RegistrationForm, LoginForm
from backend.models.user import User, db
# ── (선택) Telegram: 미설정이면 조용히 패스
try:
from telegram import Bot
from telegram.constants import ParseMode
except Exception: # 라이브러리 미설치/미사용 환경
Bot = None
ParseMode = None
auth_bp = Blueprint("auth", __name__)
# ─────────────────────────────────────────────────────────────
# 유틸
# ─────────────────────────────────────────────────────────────
def _is_safe_url(target: str) -> bool:
"""로그인 후 next 파라미터의 안전성 확인(동일 호스트만 허용)."""
ref = urlparse(request.host_url)
test = urlparse(urljoin(request.host_url, target))
return (test.scheme in ("http", "https")) and (ref.netloc == test.netloc)
def _notify(text: str) -> None:
"""텔레그램 알림 (설정 없으면 바로 return)."""
token = (current_app.config.get("TELEGRAM_BOT_TOKEN") or "").strip()
chat_id = (current_app.config.get("TELEGRAM_CHAT_ID") or "").strip()
if not (token and chat_id and Bot and ParseMode):
return
def _send():
try:
bot = Bot(token=token)
bot.send_message(chat_id=chat_id, text=text, parse_mode=ParseMode.HTML)
except Exception as e:
current_app.logger.warning("Telegram send failed: %s", e)
threading.Thread(target=_send, daemon=True).start()
# ─────────────────────────────────────────────────────────────
# Blueprint 등록 훅
# ─────────────────────────────────────────────────────────────
def register_auth_routes(app):
"""app.py에서 register_routes(app, socketio) 호출 시 사용."""
app.register_blueprint(auth_bp)
@app.before_request
def _touch_session():
# 요청마다 세션 갱신(만료 슬라이딩) + 로그아웃 플래그 정리
session.modified = True
if current_user.is_authenticated and session.get("just_logged_out"):
session.pop("just_logged_out", None)
flash("세션이 만료되어 자동 로그아웃 되었습니다.", "info")
# ─────────────────────────────────────────────────────────────
# 회원가입
# ─────────────────────────────────────────────────────────────
@auth_bp.route("/register", methods=["GET", "POST"])
def register():
if current_user.is_authenticated:
current_app.logger.info("REGISTER: already auth → /index")
return redirect(url_for("main.index"))
form = RegistrationForm()
if form.validate_on_submit():
# 모델 내부에서 email/username 정규화됨(find_by_*)
if User.find_by_email(form.email.data):
flash("이미 등록된 이메일입니다.", "warning")
current_app.logger.info("REGISTER: dup email %s", form.email.data)
return render_template("register.html", form=form)
if User.find_by_username(form.username.data):
flash("이미 사용 중인 사용자명입니다.", "warning")
current_app.logger.info("REGISTER: dup username %s", form.username.data)
return render_template("register.html", form=form)
user = User(username=form.username.data, email=form.email.data, is_active=False)
user.set_password(form.password.data) # passlib: 기본 Argon2id
db.session.add(user)
db.session.commit()
_notify(
f"🆕 <b>신규 가입 요청</b>\n"
f"📛 사용자: <code>{user.username}</code>\n"
f"📧 이메일: <code>{user.email}</code>"
)
current_app.logger.info("REGISTER: created id=%s email=%s", user.id, user.email)
flash("회원가입이 완료되었습니다. 관리자의 승인을 기다려주세요.", "success")
return redirect(url_for("auth.login"))
else:
if request.method == "POST":
current_app.logger.info("REGISTER: form errors=%s", form.errors)
return render_template("register.html", form=form)
# ─────────────────────────────────────────────────────────────
# 로그인
# ─────────────────────────────────────────────────────────────
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
current_app.logger.info("LOGIN: already auth → /index")
return redirect(url_for("main.index"))
form = LoginForm()
if form.validate_on_submit():
current_app.logger.info("LOGIN: form ok email=%s", form.email.data)
user: Optional[User] = User.find_by_email(form.email.data)
if not user:
flash("이메일 또는 비밀번호가 올바르지 않습니다.", "danger")
current_app.logger.info("LOGIN: user not found")
return render_template("login.html", form=form)
pass_ok = user.check_password(form.password.data) # passlib verify(+자동 재해시)
current_app.logger.info(
"LOGIN: found id=%s active=%s pass_ok=%s",
user.id, user.is_active, pass_ok
)
if not pass_ok:
flash("이메일 또는 비밀번호가 올바르지 않습니다.", "danger")
return render_template("login.html", form=form)
if not user.is_active:
flash("계정이 아직 승인되지 않았습니다.", "warning")
return render_template("login.html", form=form)
# 성공
login_user(user, remember=form.remember.data)
session.permanent = True
_notify(f"🔐 <b>로그인 성공</b>\n👤 <code>{user.username}</code>")
current_app.logger.info("LOGIN: SUCCESS → redirect")
nxt = request.args.get("next")
if nxt and _is_safe_url(nxt):
return redirect(nxt)
return redirect(url_for("main.index"))
else:
if request.method == "POST":
current_app.logger.info("LOGIN: form errors=%s", form.errors)
return render_template("login.html", form=form)
# ─────────────────────────────────────────────────────────────
# 로그아웃
# ─────────────────────────────────────────────────────────────
@auth_bp.route("/logout", methods=["GET"])
@login_required
def logout():
if current_user.is_authenticated:
current_app.logger.info("LOGOUT: user=%s", current_user.username)
logout_user()
session["just_logged_out"] = True
return redirect(url_for("auth.login"))

View File

@@ -0,0 +1,95 @@
from __future__ import annotations
import logging
from pathlib import Path
from flask import Blueprint, request, jsonify, Response
from flask_login import login_required
from config import Config
import chardet
file_view_bp = Blueprint("file_view", __name__)
def register_file_view(app):
"""블루프린트 등록"""
app.register_blueprint(file_view_bp)
def _safe_within(base: Path, target: Path) -> bool:
"""
target 이 base 디렉터리 내부인지 검사 (경로 탈출 방지)
"""
try:
target.resolve().relative_to(base.resolve())
return True
except Exception:
return False
def _decode_bytes(raw: bytes) -> str:
"""
파일 바이트 → 문자열 디코딩 (감지 → utf-8 → cp949 순서로 시도)
"""
enc = (chardet.detect(raw).get("encoding") or "utf-8").strip().lower()
for cand in (enc, "utf-8", "cp949"):
try:
return raw.decode(cand)
except Exception:
continue
# 최후의 수단: 손실 허용 디코딩
return raw.decode("utf-8", errors="replace")
@file_view_bp.route("/view_file", methods=["GET"])
@login_required
def view_file():
"""
파일 내용을 읽어 반환.
- /view_file?folder=idrac_info&filename=abc.txt
- /view_file?folder=backup&date=<백업폴더명>&filename=abc.txt
- ?raw=1 을 붙이면 text/plain 으로 원문을 반환 (모달 표시용)
"""
folder = request.args.get("folder", "").strip()
date = request.args.get("date", "").strip()
filename = request.args.get("filename", "").strip()
want_raw = request.args.get("raw")
if not filename:
return jsonify({"error": "파일 이름이 없습니다."}), 400
# 파일명/폴더명은 유니코드 보존. 상위 경로만 제거하여 보안 유지
safe_name = Path(filename).name
if folder == "backup":
base = Path(Config.BACKUP_FOLDER)
safe_date = Path(date).name if date else ""
target = (base / safe_date / safe_name).resolve()
else:
base = Path(Config.IDRAC_INFO_FOLDER)
target = (base / safe_name).resolve()
logging.info(
"file_view: folder=%s date=%s filename=%s | base=%s | target=%s",
folder, date, filename, str(base), str(target)
)
if not _safe_within(base, target) or not target.is_file():
logging.warning("file_view: 파일 없음: %s", str(target))
return jsonify({"error": "파일을 찾을 수 없습니다."}), 404
try:
raw = target.read_bytes()
content = _decode_bytes(raw)
if want_raw:
# 텍스트 원문 반환 (모달에서 fetch().text()로 사용)
return Response(content, mimetype="text/plain; charset=utf-8")
# JSON으로 감싸서 반환 (기존 사용처 호환)
return jsonify({"content": content})
except Exception as e:
logging.error("file_view: 파일 읽기 실패: %s%s", str(target), e)
return jsonify({"error": "파일 열기 중 오류 발생"}), 500

13
backend/routes/home.py Normal file
View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from flask import Blueprint, render_template
home_bp = Blueprint("home", __name__, url_prefix="/home")
def register_home_routes(app):
app.register_blueprint(home_bp)
@home_bp.route("/", methods=["GET"])
def home():
return render_template("home.html")

208
backend/routes/main.py Normal file
View File

@@ -0,0 +1,208 @@
from __future__ import annotations
import os
import time
import shutil
import zipfile
import logging
from pathlib import Path
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, session, send_from_directory, send_file
from flask_login import login_required, current_user
from concurrent.futures import ThreadPoolExecutor
from watchdog.observers import Observer
from natsort import natsorted
from backend.services.ip_processor import (
save_ip_addresses,
process_ips_concurrently,
get_progress,
on_complete,
)
from backend.services.watchdog_handler import FileCreatedHandler
from config import Config
main_bp = Blueprint("main", __name__)
executor = ThreadPoolExecutor(max_workers=Config.MAX_WORKERS)
def register_main_routes(app, socketio):
app.register_blueprint(main_bp)
@app.context_processor
def inject_user():
return dict(current_user=current_user)
@app.before_request
def make_session_permanent():
session.permanent = True
if current_user.is_authenticated:
session.modified = True
@main_bp.route("/")
@main_bp.route("/index", methods=["GET"])
@login_required
def index():
script_dir = Path(Config.SCRIPT_FOLDER)
xml_dir = Path(Config.XML_FOLDER)
info_dir = Path(Config.IDRAC_INFO_FOLDER)
backup_dir = Path(Config.BACKUP_FOLDER)
scripts = [f.name for f in script_dir.glob("*") if f.is_file() and f.name != ".env"]
scripts = natsorted(scripts)
xml_files = [f.name for f in xml_dir.glob("*.xml")]
# 페이지네이션
page = int(request.args.get("page", 1))
info_files = [f.name for f in info_dir.glob("*") if f.is_file()]
info_files = natsorted(info_files)
start = (page - 1) * Config.FILES_PER_PAGE
end = start + Config.FILES_PER_PAGE
files_to_display = [{"name": Path(f).stem, "file": f} for f in info_files[start:end]]
total_pages = (len(info_files) + Config.FILES_PER_PAGE - 1) // Config.FILES_PER_PAGE
# 백업 폴더 목록 (디렉터리만)
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_page = int(request.args.get("backup_page", 1))
start_b = (backup_page - 1) * Config.BACKUP_FILES_PER_PAGE
end_b = start_b + Config.BACKUP_FILES_PER_PAGE
backup_slice = backup_dirs[start_b:end_b]
total_backup_pages = (len(backup_dirs) + Config.BACKUP_FILES_PER_PAGE - 1) // Config.BACKUP_FILES_PER_PAGE
backup_files = {}
for d in backup_slice:
files = [f.name for f in d.iterdir() if f.is_file()]
backup_files[d.name] = {"files": files, "count": len(files)}
return render_template(
"index.html",
files_to_display=files_to_display,
page=page,
total_pages=total_pages,
backup_files=backup_files,
total_backup_pages=total_backup_pages,
backup_page=backup_page,
scripts=scripts,
xml_files=xml_files,
)
@main_bp.route("/process_ips", methods=["POST"])
@login_required
def process_ips():
ips = request.form.get("ips")
selected_script = request.form.get("script")
selected_xml_file = request.form.get("xmlFile")
if not ips or not selected_script:
return jsonify({"error": "IP 주소와 스크립트를 모두 입력하세요."}), 400
xml_file_path = None
if selected_script == "02-set_config.py" and selected_xml_file:
xml_path = Path(Config.XML_FOLDER) / selected_xml_file
if not xml_path.exists():
return jsonify({"error": "선택한 XML 파일이 존재하지 않습니다."}), 400
xml_file_path = str(xml_path)
job_id = str(time.time())
session["job_id"] = job_id
ip_files = save_ip_addresses(ips, Config.UPLOAD_FOLDER)
total_files = len(ip_files)
handler = FileCreatedHandler(job_id, total_files)
observer = Observer()
observer.schedule(handler, Config.IDRAC_INFO_FOLDER, recursive=False)
observer.start()
future = executor.submit(
process_ips_concurrently, ip_files, job_id, observer, selected_script, xml_file_path
)
future.add_done_callback(lambda x: on_complete(job_id))
logging.info(f"[AJAX] 작업 시작: {job_id}, script: {selected_script}")
return jsonify({"job_id": job_id})
@main_bp.route("/progress_status/<job_id>")
@login_required
def progress_status(job_id: str):
return jsonify({"progress": get_progress(job_id)})
@main_bp.route("/backup", methods=["POST"])
@login_required
def backup_files():
prefix = request.form.get("backup_prefix", "")
if not prefix.startswith("PO"):
flash("Backup 이름은 PO로 시작해야 합니다.")
return redirect(url_for("main.index"))
folder_name = f"{prefix}_{time.strftime('%Y%m%d')}"
backup_path = Path(Config.BACKUP_FOLDER) / folder_name
backup_path.mkdir(parents=True, exist_ok=True)
info_dir = Path(Config.IDRAC_INFO_FOLDER)
for file in info_dir.iterdir():
if file.is_file():
shutil.move(str(file), str(backup_path / file.name))
flash("백업 완료되었습니다.")
logging.info(f"백업 완료: {folder_name}")
return redirect(url_for("main.index"))
@main_bp.route("/download/<filename>")
@login_required
def download_file(filename: str):
# send_from_directory는 내부적으로 안전 검사를 수행
return send_from_directory(Config.IDRAC_INFO_FOLDER, filename, as_attachment=True)
@main_bp.route("/delete/<filename>", methods=["POST"])
@login_required
def delete_file(filename: str):
file_path = Path(Config.IDRAC_INFO_FOLDER) / filename
if file_path.exists():
try:
file_path.unlink()
flash(f"{filename} 삭제됨.")
logging.info(f"파일 삭제됨: {filename}")
except Exception as e:
logging.error(f"파일 삭제 오류: {e}")
flash("파일 삭제 중 오류가 발생했습니다.", "danger")
else:
flash("파일이 존재하지 않습니다.")
return redirect(url_for("main.index"))
@main_bp.route("/download_zip", methods=["POST"])
@login_required
def download_zip():
zip_filename = request.form.get("zip_filename", "export")
zip_path = Path(Config.TEMP_ZIP_FOLDER) / f"{zip_filename}.zip"
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
for file in Path(Config.IDRAC_INFO_FOLDER).glob("*"):
if file.is_file():
zipf.write(file, arcname=file.name)
try:
response = send_file(str(zip_path), as_attachment=True)
return response
finally:
# 응답 후 임시 ZIP 삭제
try:
if zip_path.exists():
zip_path.unlink()
except Exception as e:
logging.warning(f"임시 ZIP 삭제 실패: {e}")
@main_bp.route("/download_backup/<date>/<filename>")
@login_required
def download_backup_file(date: str, filename: str):
backup_path = Path(Config.BACKUP_FOLDER) / date
return send_from_directory(str(backup_path), filename, as_attachment=True)

195
backend/routes/utilities.py Normal file
View File

@@ -0,0 +1,195 @@
from __future__ import annotations
import os
import sys
import shutil
import subprocess
import logging
from pathlib import Path
from flask import Blueprint, request, redirect, url_for, flash, jsonify, send_file
from flask_login import login_required
from config import Config
utils_bp = Blueprint("utils", __name__)
def register_util_routes(app):
app.register_blueprint(utils_bp)
@utils_bp.route("/move_mac_files", methods=["POST"])
@login_required
def move_mac_files():
src = Path(Config.IDRAC_INFO_FOLDER)
dst = Path(Config.MAC_FOLDER)
dst.mkdir(parents=True, exist_ok=True)
moved = 0
errors = []
try:
for file in src.iterdir():
if not file.is_file():
continue
try:
# 파일이 존재하는지 확인
if not file.exists():
errors.append(f"{file.name}: 파일이 존재하지 않음")
continue
# 대상 파일이 이미 존재하는 경우 건너뛰기
target = dst / file.name
if target.exists():
logging.warning(f"⚠️ 파일이 이미 존재하여 건너뜀: {file.name}")
continue
shutil.move(str(file), str(target))
moved += 1
except Exception as e:
error_msg = f"{file.name}: {str(e)}"
errors.append(error_msg)
logging.error(f"❌ 파일 이동 실패: {error_msg}")
# 결과 로깅
if moved > 0:
logging.info(f"✅ MAC 파일 이동 완료 ({moved}개)")
if errors:
logging.warning(f"⚠️ 일부 파일 이동 실패: {errors}")
# 하나라도 성공하면 success: true 반환
return jsonify({
"success": True,
"moved": moved,
"errors": errors if errors else None
})
except Exception as e:
logging.error(f"❌ MAC 이동 중 치명적 오류: {e}")
return jsonify({"success": False, "error": str(e)})
@utils_bp.route("/move_guid_files", methods=["POST"])
@login_required
def move_guid_files():
src = Path(Config.IDRAC_INFO_FOLDER)
dst = Path(Config.GUID_FOLDER)
dst.mkdir(parents=True, exist_ok=True)
moved = 0
errors = []
try:
for file in src.iterdir():
if not file.is_file():
continue
try:
# 파일이 존재하는지 확인
if not file.exists():
errors.append(f"{file.name}: 파일이 존재하지 않음")
continue
# 대상 파일이 이미 존재하는 경우 건너뛰기
target = dst / file.name
if target.exists():
logging.warning(f"⚠️ 파일이 이미 존재하여 건너뜀: {file.name}")
continue
shutil.move(str(file), str(target))
moved += 1
except Exception as e:
error_msg = f"{file.name}: {str(e)}"
errors.append(error_msg)
logging.error(f"❌ 파일 이동 실패: {error_msg}")
# 결과 메시지
if moved > 0:
flash(f"GUID 파일이 성공적으로 이동되었습니다. ({moved}개)", "success")
logging.info(f"✅ GUID 파일 이동 완료 ({moved}개)")
if errors:
logging.warning(f"⚠️ 일부 파일 이동 실패: {errors}")
flash(f"일부 파일 이동 실패: {len(errors)}", "warning")
except Exception as e:
logging.error(f"❌ GUID 이동 오류: {e}")
flash(f"오류 발생: {e}", "danger")
return redirect(url_for("main.index"))
@utils_bp.route("/update_server_list", methods=["POST"])
@login_required
def update_server_list():
content = request.form.get("server_list_content")
if not content:
flash("내용을 입력하세요.", "warning")
return redirect(url_for("main.index"))
path = Path(Config.SERVER_LIST_FOLDER) / "server_list.txt"
try:
path.write_text(content, encoding="utf-8")
result = subprocess.run(
[sys.executable, str(Path(Config.SERVER_LIST_FOLDER) / "excel.py")],
capture_output=True,
text=True,
check=True,
cwd=str(Path(Config.SERVER_LIST_FOLDER)),
timeout=300,
)
logging.info(f"서버 리스트 스크립트 실행 결과: {result.stdout}")
flash("서버 리스트가 업데이트되었습니다.", "success")
except subprocess.CalledProcessError as e:
logging.error(f"서버 리스트 스크립트 오류: {e.stderr}")
flash(f"스크립트 실행 실패: {e.stderr}", "danger")
except Exception as e:
logging.error(f"서버 리스트 처리 오류: {e}")
flash(f"서버 리스트 처리 중 오류 발생: {e}", "danger")
return redirect(url_for("main.index"))
@utils_bp.route("/update_guid_list", methods=["POST"])
@login_required
def update_guid_list():
content = request.form.get("server_list_content")
if not content:
flash("내용을 입력하세요.", "warning")
return redirect(url_for("main.index"))
path = Path(Config.SERVER_LIST_FOLDER) / "guid_list.txt"
try:
path.write_text(content, encoding="utf-8")
result = subprocess.run(
[sys.executable, str(Path(Config.SERVER_LIST_FOLDER) / "GUIDtxtT0Execl.py")],
capture_output=True,
text=True,
check=True,
cwd=str(Path(Config.SERVER_LIST_FOLDER)),
timeout=300,
)
logging.info(f"GUID 리스트 스크립트 실행 결과: {result.stdout}")
flash("GUID 리스트가 업데이트되었습니다.", "success")
except subprocess.CalledProcessError as e:
logging.error(f"GUID 리스트 스크립트 오류: {e.stderr}")
flash(f"스크립트 실행 실패: {e.stderr}", "danger")
except Exception as e:
logging.error(f"GUID 리스트 처리 오류: {e}")
flash(f"GUID 리스트 처리 중 오류 발생: {e}", "danger")
return redirect(url_for("main.index"))
@utils_bp.route("/download_excel")
@login_required
def download_excel():
path = Path(Config.SERVER_LIST_FOLDER) / "mac_info.xlsx"
if not path.is_file():
flash("엑셀 파일을 찾을 수 없습니다.", "danger")
return redirect(url_for("main.index"))
logging.info(f"엑셀 파일 다운로드: {path}")
return send_file(str(path), as_attachment=True, download_name="mac_info.xlsx")

105
backend/routes/xml.py Normal file
View File

@@ -0,0 +1,105 @@
from __future__ import annotations
import logging
from pathlib import Path
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required
from werkzeug.utils import secure_filename
from config import Config
xml_bp = Blueprint("xml", __name__)
def register_xml_routes(app):
app.register_blueprint(xml_bp)
def allowed_file(filename: str) -> bool:
return "." in filename and filename.rsplit(".", 1)[1].lower() in Config.ALLOWED_EXTENSIONS
@xml_bp.route("/xml_management")
@login_required
def xml_management():
xml_dir = Path(Config.XML_FOLDER)
try:
files = [f.name for f in xml_dir.iterdir() if f.is_file()]
except FileNotFoundError:
files = []
flash("XML 폴더가 존재하지 않습니다.", "danger")
return render_template("manage_xml.html", xml_files=files)
@xml_bp.route("/upload_xml", methods=["POST"])
@login_required
def upload_xml():
file = request.files.get("xmlFile")
if not file or not file.filename:
flash("업로드할 파일을 선택하세요.", "warning")
return redirect(url_for("xml.xml_management"))
if allowed_file(file.filename):
filename = secure_filename(file.filename)
save_path = Path(Config.XML_FOLDER) / filename
try:
save_path.parent.mkdir(parents=True, exist_ok=True)
file.save(str(save_path))
# 텍스트 파일이므로 0644 권장
try:
save_path.chmod(0o644)
except Exception:
pass # Windows 등에서 무시
logging.info(f"XML 업로드됨: {filename}")
flash("파일이 성공적으로 업로드되었습니다.", "success")
except Exception as e:
logging.error(f"파일 업로드 오류: {e}")
flash("파일 저장 중 오류가 발생했습니다.", "danger")
else:
flash("XML 확장자만 업로드할 수 있습니다.", "warning")
return redirect(url_for("xml.xml_management"))
@xml_bp.route("/delete_xml/<filename>", methods=["POST"])
@login_required
def delete_xml(filename: str):
path = Path(Config.XML_FOLDER) / secure_filename(filename)
if path.exists():
try:
path.unlink()
flash(f"{filename} 파일이 삭제되었습니다.", "success")
logging.info(f"XML 삭제됨: {filename}")
except Exception as e:
logging.error(f"XML 삭제 오류: {e}")
flash("파일 삭제 중 오류 발생", "danger")
else:
flash("해당 파일이 존재하지 않습니다.", "warning")
return redirect(url_for("xml.xml_management"))
@xml_bp.route("/edit_xml/<filename>", methods=["GET", "POST"])
@login_required
def edit_xml(filename: str):
path = Path(Config.XML_FOLDER) / secure_filename(filename)
if not path.exists():
flash("파일을 찾을 수 없습니다.", "danger")
return redirect(url_for("xml.xml_management"))
if request.method == "POST":
new_content = request.form.get("content", "")
try:
path.write_text(new_content, encoding="utf-8")
logging.info(f"XML 수정됨: {filename}")
flash("파일이 성공적으로 수정되었습니다.", "success")
return redirect(url_for("xml.xml_management"))
except Exception as e:
logging.error(f"XML 저장 실패: {e}")
flash("파일 저장 중 오류가 발생했습니다.", "danger")
try:
content = path.read_text(encoding="utf-8")
except Exception as e:
logging.error(f"XML 열기 실패: {e}")
flash("파일 열기 중 오류가 발생했습니다.", "danger")
content = ""
return render_template("edit_xml.html", filename=filename, content=content)

Binary file not shown.

View File

@@ -0,0 +1,152 @@
from __future__ import annotations
from pathlib import Path
import os
import sys
import uuid
import logging
import subprocess
import platform
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
from watchdog.observers import Observer
from backend.services.watchdog_handler import FileCreatedHandler
from config import Config
# Job ID별 진행률 (스레드 안전)
_progress: dict[str, int] = {}
_progress_lock = Lock()
def _set_progress(job_id: str, value: int) -> None:
with _progress_lock:
_progress[job_id] = max(0, min(100, int(value)))
def get_progress(job_id: str) -> int:
with _progress_lock:
return int(_progress.get(job_id, 0))
def on_complete(job_id: str) -> None:
_set_progress(job_id, 100)
# ─────────────────────────────────────────────────────────────
# IP 목록 저장
# ─────────────────────────────────────────────────────────────
def save_ip_addresses(ips: str, folder: str | os.PathLike[str]) -> list[tuple[str, str]]:
out_dir = Path(folder)
out_dir.mkdir(parents=True, exist_ok=True)
ip_files: list[tuple[str, str]] = []
for i, raw in enumerate((ips or "").splitlines()):
ip = raw.strip()
if not ip:
continue
file_path = out_dir / f"ip_{i}.txt"
file_path.write_text(ip + "\n", encoding="utf-8")
ip_files.append((ip, str(file_path)))
return ip_files
# ─────────────────────────────────────────────────────────────
# 개별 IP 처리
# ─────────────────────────────────────────────────────────────
def _build_command(script: str, ip_file: str, xml_file: str | None) -> list[str]:
script_path = Path(Config.SCRIPT_FOLDER) / script
if not script_path.exists():
raise FileNotFoundError(f"스크립트를 찾을 수 없습니다: {script_path}")
if script_path.suffix == ".sh":
# Windows에서 .sh 실행은 bash 필요 (Git Bash/WSL 등). 없으면 예외 처리.
if platform.system() == "Windows":
bash = shutil.which("bash") # type: ignore[name-defined]
if not bash:
raise RuntimeError("Windows에서 .sh 스크립트를 실행하려면 bash가 필요합니다.")
cmd = [bash, str(script_path), ip_file]
else:
cmd = [str(script_path), ip_file]
elif script_path.suffix == ".py":
cmd = [sys.executable, str(script_path), ip_file]
else:
raise ValueError(f"지원되지 않는 스크립트 형식: {script_path.suffix}")
if xml_file:
cmd.append(xml_file)
return cmd
def process_ip(ip_file: str, script: str, xml_file: str | None = None) -> None:
ip = Path(ip_file).read_text(encoding="utf-8").strip()
cmd = _build_command(script, ip_file, xml_file)
logging.info("🔧 실행 명령: %s", " ".join(cmd))
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True,
cwd=str(Path(Config.SCRIPT_FOLDER)),
timeout=int(os.getenv("SCRIPT_TIMEOUT", "1800")), # 30분 기본
)
logging.info("[%s] ✅ stdout:\n%s", ip, result.stdout)
if result.stderr:
logging.warning("[%s] ⚠ stderr:\n%s", ip, result.stderr)
except subprocess.CalledProcessError as e:
logging.error("[%s] ❌ 스크립트 실행 오류(code=%s): %s", ip, e.returncode, e.stderr or e)
except subprocess.TimeoutExpired:
logging.error("[%s] ⏰ 스크립트 실행 타임아웃", ip)
# ─────────────────────────────────────────────────────────────
# 병렬 처리 진입점
# ─────────────────────────────────────────────────────────────
def process_ips_concurrently(ip_files, job_id, observer: Observer, script: str, xml_file: str | None):
total = len(ip_files)
completed = 0
_set_progress(job_id, 0)
try:
with ThreadPoolExecutor(max_workers=Config.MAX_WORKERS) as pool:
futures = {pool.submit(process_ip, ip_path, script, xml_file): ip for ip, ip_path in ip_files}
for fut in as_completed(futures):
ip = futures[fut]
try:
fut.result()
except Exception as e:
logging.error("%s 처리 중 오류 발생: %s", ip, e)
finally:
completed += 1
if total:
_set_progress(job_id, int(completed * 100 / total))
finally:
try:
observer.stop()
observer.join(timeout=5)
except Exception:
pass
# ─────────────────────────────────────────────────────────────
# 외부에서 한 번에 처리(동기)
# ─────────────────────────────────────────────────────────────
def handle_ip_processing(ip_text: str, script: str, xml_file: str | None = None) -> str:
job_id = str(uuid.uuid4())
temp_dir = Path(Config.UPLOAD_FOLDER) / job_id
ip_files = save_ip_addresses(ip_text, temp_dir)
xml_path = str(Path(Config.XML_FOLDER) / xml_file) if xml_file else None
handler = FileCreatedHandler(job_id, len(ip_files))
observer = Observer()
observer.schedule(handler, Config.IDRAC_INFO_FOLDER, recursive=False)
observer.start()
process_ips_concurrently(ip_files, job_id, observer, script, xml_path)
return job_id

View File

@@ -0,0 +1,65 @@
from __future__ import annotations
from pathlib import Path
import logging
import os
from logging.handlers import TimedRotatingFileHandler
from typing import Optional
from config import Config
_DEF_LEVEL = os.getenv("APP_LOG_LEVEL", "INFO").upper()
_DEF_FMT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
def _ensure_log_dir() -> Path:
p = Path(Config.LOG_FOLDER)
p.mkdir(parents=True, exist_ok=True)
return p
def setup_logging(app: Optional[object] = None) -> logging.Logger:
"""앱 전역 로깅을 파일(일단위 회전) + 콘솔로 설정.
- 회전 파일명: YYYY-MM-DD.log
- 중복 핸들러 방지
- Windows/Linux 공통 동작
"""
log_dir = _ensure_log_dir()
log_path = log_dir / "app.log"
root = logging.getLogger()
root.setLevel(_DEF_LEVEL)
root.propagate = False
# 기존 핸들러 제거(중복 방지)
for h in root.handlers[:]:
root.removeHandler(h)
# 파일 로거
file_handler = TimedRotatingFileHandler(
filename=str(log_path), when="midnight", interval=1, backupCount=90, encoding="utf-8", utc=False
)
# 회전 파일명: 2025-09-30.log 형태로
def _namer(default_name: str) -> str:
# default_name: app.log.YYYY-MM-DD
base_dir = os.path.dirname(default_name)
date_str = default_name.rsplit(".", 1)[-1]
return os.path.join(base_dir, f"{date_str}.log")
file_handler.namer = _namer
file_handler.setFormatter(logging.Formatter(_DEF_FMT))
# 콘솔 로거
console = logging.StreamHandler()
console.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
root.addHandler(file_handler)
root.addHandler(console)
if app is not None:
# Flask 앱 로거에도 동일 핸들러 바인딩
app.logger.handlers = root.handlers
app.logger.setLevel(root.level)
root.info("Logger initialized | level=%s | file=%s", _DEF_LEVEL, log_path)
return root

View File

@@ -0,0 +1,50 @@
from __future__ import annotations
import logging
import os
from typing import Optional
from watchdog.events import FileSystemEventHandler
from flask_socketio import SocketIO
# 외부에서 주입되는 socketio 인스턴스 (app.py에서 설정)
socketio: Optional[SocketIO] = None
class FileCreatedHandler(FileSystemEventHandler):
"""파일 생성 감지를 처리하는 Watchdog 핸들러.
- temp_ip 등 임시 파일은 무시
- 감지 시 진행률/로그를 SocketIO로 실시간 브로드캐스트
"""
def __init__(self, job_id: str, total_files: int):
super().__init__()
self.job_id = job_id
self.total_files = max(int(total_files or 0), 0)
self.completed_files = 0
def _broadcast(self, event_name: str, data: dict) -> None:
if not socketio:
return
try:
socketio.emit(event_name, data, namespace="/")
except Exception as e:
logging.warning("[Watchdog] SocketIO 전송 실패: %s", e)
def _should_ignore(self, src_path: str) -> bool:
# 임시 업로드 디렉터리 하위 파일은 무시
return "temp_ip" in src_path.replace("\\", "/")
def on_created(self, event):
if event.is_directory:
return
if self._should_ignore(event.src_path):
return
self.completed_files = min(self.completed_files + 1, self.total_files or 0)
filename = os.path.basename(event.src_path)
msg = f"[Watchdog] 생성된 파일: {filename} ({self.completed_files}/{self.total_files})"
logging.info(msg)
self._broadcast("log_update", {"job_id": self.job_id, "log": msg})
if self.total_files:
progress = int((self.completed_files / self.total_files) * 100)
self._broadcast("progress", {"job_id": self.job_id, "progress": progress})

View File

@@ -0,0 +1,13 @@
# backend/socketio_events.py
import logging
from flask_socketio import SocketIO
def register_socketio_events(socketio: SocketIO):
@socketio.on('connect')
def on_connect():
logging.info("✅ 클라이언트가 연결되었습니다.")
socketio.emit('response', {'message': '✅ 서버에 연결되었습니다.'})
@socketio.on('disconnect')
def on_disconnect():
logging.info("⚠️ 클라이언트 연결이 해제되었습니다.")

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
backend/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

264
backend/static/script.js Normal file
View File

@@ -0,0 +1,264 @@
// script.js - 정리된 버전
document.addEventListener('DOMContentLoaded', () => {
// ─────────────────────────────────────────────────────────────
// CSRF 토큰
// ─────────────────────────────────────────────────────────────
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
// ─────────────────────────────────────────────────────────────
// 진행바 업데이트
// ─────────────────────────────────────────────────────────────
window.updateProgress = function(percent) {
const bar = document.getElementById('progressBar');
if (!bar) return;
const v = Math.max(0, Math.min(100, Number(percent) || 0));
bar.style.width = v + '%';
bar.setAttribute('aria-valuenow', v);
bar.innerHTML = `<span class="fw-semibold small">${v}%</span>`;
};
// ─────────────────────────────────────────────────────────────
// 줄 수 카운터
// ─────────────────────────────────────────────────────────────
function updateLineCount(textareaId, badgeId) {
const textarea = document.getElementById(textareaId);
const badge = document.getElementById(badgeId);
if (!textarea || !badge) return;
const updateCount = () => {
const text = textarea.value.trim();
if (text === '') {
badge.textContent = '0줄';
return;
}
const lines = text.split('\n').filter(line => line.trim().length > 0);
badge.textContent = `${lines.length}`;
};
updateCount();
textarea.addEventListener('input', updateCount);
textarea.addEventListener('change', updateCount);
textarea.addEventListener('keyup', updateCount);
textarea.addEventListener('paste', () => setTimeout(updateCount, 10));
}
updateLineCount('ips', 'ipLineCount');
updateLineCount('server_list_content', 'serverLineCount');
// ─────────────────────────────────────────────────────────────
// 스크립트 선택 시 XML 드롭다운 토글
// ─────────────────────────────────────────────────────────────
const TARGET_SCRIPT = "02-set_config.py";
const scriptSelect = document.getElementById('script');
const xmlGroup = document.getElementById('xmlFileGroup');
function toggleXml() {
if (!scriptSelect || !xmlGroup) return;
xmlGroup.style.display = (scriptSelect.value === TARGET_SCRIPT) ? 'block' : 'none';
}
if (scriptSelect) {
toggleXml();
scriptSelect.addEventListener('change', toggleXml);
}
// ─────────────────────────────────────────────────────────────
// 파일 보기 모달
// ─────────────────────────────────────────────────────────────
const modalEl = document.getElementById('fileViewModal');
const titleEl = document.getElementById('fileViewModalLabel');
const contentEl = document.getElementById('fileViewContent');
if (modalEl) {
modalEl.addEventListener('show.bs.modal', async (ev) => {
const btn = ev.relatedTarget;
const folder = btn?.getAttribute('data-folder') || '';
const date = btn?.getAttribute('data-date') || '';
const filename = btn?.getAttribute('data-filename') || '';
titleEl.innerHTML = `<i class="bi bi-file-text me-2"></i>${filename || '파일'}`;
contentEl.textContent = '불러오는 중...';
const params = new URLSearchParams();
if (folder) params.set('folder', folder);
if (date) params.set('date', date);
if (filename) params.set('filename', filename);
try {
const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' });
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
contentEl.textContent = data?.content ?? '(빈 파일)';
} catch (e) {
contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e);
}
});
}
// ─────────────────────────────────────────────────────────────
// 공통 POST 함수
// ─────────────────────────────────────────────────────────────
async function postFormAndHandle(url) {
const res = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'X-CSRFToken': csrfToken,
'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8',
},
});
const ct = (res.headers.get('content-type') || '').toLowerCase();
if (ct.includes('application/json')) {
const data = await res.json();
if (data.success === false) {
throw new Error(data.error || ('HTTP ' + res.status));
}
return data;
}
return { success: true, html: true };
}
// ─────────────────────────────────────────────────────────────
// MAC 파일 이동
// ─────────────────────────────────────────────────────────────
const macForm = document.getElementById('macMoveForm');
if (macForm) {
macForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = macForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(macForm.action);
location.reload();
} catch (err) {
alert('MAC 이동 중 오류: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// ─────────────────────────────────────────────────────────────
// GUID 파일 이동
// ─────────────────────────────────────────────────────────────
const guidForm = document.getElementById('guidMoveForm');
if (guidForm) {
guidForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = guidForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(guidForm.action);
location.reload();
} catch (err) {
alert('GUID 이동 중 오류: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// ─────────────────────────────────────────────────────────────
// IP 폼 제출 및 진행률 폴링
// ─────────────────────────────────────────────────────────────
const ipForm = document.getElementById("ipForm");
if (ipForm) {
ipForm.addEventListener("submit", async (ev) => {
ev.preventDefault();
const formData = new FormData(ipForm);
const btn = ipForm.querySelector('button[type="submit"]');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
const res = await fetch(ipForm.action, {
method: "POST",
body: formData
});
if (!res.ok) throw new Error("HTTP " + res.status);
const data = await res.json();
console.log("[DEBUG] process_ips 응답:", data);
if (data.job_id) {
pollProgress(data.job_id);
} else {
window.updateProgress(100);
setTimeout(() => location.reload(), 1000);
}
} catch (err) {
console.error("처리 중 오류:", err);
alert("처리 중 오류 발생: " + err.message);
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// ─────────────────────────────────────────────────────────────
// 진행률 폴링 함수
// ─────────────────────────────────────────────────────────────
function pollProgress(jobId) {
const interval = setInterval(async () => {
try {
const res = await fetch(`/progress_status/${jobId}`);
if (!res.ok) {
clearInterval(interval);
return;
}
const data = await res.json();
if (data.progress !== undefined) {
window.updateProgress(data.progress);
}
if (data.progress >= 100) {
clearInterval(interval);
window.updateProgress(100);
setTimeout(() => location.reload(), 1500);
}
} catch (err) {
console.error('진행률 확인 중 오류:', err);
clearInterval(interval);
}
}, 500);
}
// ─────────────────────────────────────────────────────────────
// 알림 자동 닫기 (5초 후)
// ─────────────────────────────────────────────────────────────
setTimeout(() => {
document.querySelectorAll('.alert').forEach(alert => {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
});
}, 5000);
});

534
backend/static/style.css Normal file
View File

@@ -0,0 +1,534 @@
/* ─────────────────────────────────────────────────────────────
기본 레이아웃
───────────────────────────────────────────────────────────── */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Malgun Gothic",
"Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
font-weight: 400;
background-color: #f8f9fa;
padding-top: 56px;
}
.container-card {
background-color: #ffffff;
padding: 20px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.08);
border-radius: 8px;
margin-bottom: 20px;
}
/* ─────────────────────────────────────────────────────────────
텍스트 및 제목 - 모두 일반 굵기
───────────────────────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
color: #343a40;
font-weight: 400;
}
.card-header h6 {
font-weight: 500;
}
/* ─────────────────────────────────────────────────────────────
폼 요소 - 모두 일반 굵기
───────────────────────────────────────────────────────────── */
.form-label {
color: #495057;
font-weight: 400;
}
.form-control, .form-select {
border-radius: 5px;
border: 1px solid #ced4da;
font-weight: 400;
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.form-control:focus, .form-select:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25);
}
/* ─────────────────────────────────────────────────────────────
버튼 - 일반 굵기
───────────────────────────────────────────────────────────── */
.btn {
border-radius: 5px;
font-weight: 400;
transition: all 0.2s ease-in-out;
}
.badge {
font-weight: 400;
}
/* ─────────────────────────────────────────────────────────────
네비게이션 바
───────────────────────────────────────────────────────────── */
.navbar {
background-color: #343a40 !important;
}
.navbar-brand {
font-weight: 700;
color: #ffffff !important;
}
.nav-link {
color: rgba(255, 255, 255, 0.75) !important;
font-weight: 400;
}
.nav-link:hover {
color: #ffffff !important;
}
/* ─────────────────────────────────────────────────────────────
카드 헤더 색상 (1번 이미지와 동일하게)
───────────────────────────────────────────────────────────── */
.card-header.bg-primary {
background-color: #007bff !important;
color: #ffffff !important;
}
.card-header.bg-success {
background-color: #28a745 !important;
color: #ffffff !important;
}
.card-header.bg-primary h6,
.card-header.bg-success h6 {
color: #ffffff !important;
}
.card-header.bg-primary i,
.card-header.bg-success i {
color: #ffffff !important;
}
/* 밝은 배경 헤더는 어두운 텍스트 */
.card-header.bg-light {
background-color: #f8f9fa !important;
color: #343a40 !important;
}
.card-header.bg-light h6 {
color: #343a40 !important;
}
.card-header.bg-light i {
color: #343a40 !important;
}
/* ─────────────────────────────────────────────────────────────
버튼 색상 (2번 이미지와 동일하게)
───────────────────────────────────────────────────────────── */
.btn-warning {
background-color: #ffc107 !important;
border-color: #ffc107 !important;
color: #000 !important;
}
.btn-warning:hover {
background-color: #e0a800 !important;
border-color: #d39e00 !important;
color: #000 !important;
}
.btn-info {
background-color: #17a2b8 !important;
border-color: #17a2b8 !important;
color: #fff !important;
}
.btn-info:hover {
background-color: #138496 !important;
border-color: #117a8b !important;
color: #fff !important;
}
/* ─────────────────────────────────────────────────────────────
진행바
───────────────────────────────────────────────────────────── */
.progress {
border-radius: 10px;
overflow: hidden;
}
.progress-bar {
transition: width 0.6s ease;
}
/* ─────────────────────────────────────────────────────────────
파일 그리드 레이아웃 - 빈 공간 없이 채우기
───────────────────────────────────────────────────────────── */
.file-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 1rem;
}
/* ─────────────────────────────────────────────────────────────
파일 카드 (컴팩트)
───────────────────────────────────────────────────────────── */
.file-card-compact {
transition: all 0.2s ease;
background: #fff;
width: 100%;
max-width: 180px; /* 기본값 유지(카드가 너무 넓어지지 않도록) */
}
.file-card-compact:hover {
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.file-card-compact a {
font-size: 0.85rem;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
/* 파일 카드 내 모든 텍스트 일반 굵기 */
.file-card-compact,
.file-card-compact * {
font-weight: 400 !important;
}
/* ─────────────────────────────────────────────────────────────
(공통) 파일 카드 버튼 컨테이너 기본값 (기존 유지)
───────────────────────────────────────────────────────────── */
.file-card-buttons { /* 처리된 목록(2버튼) 기본 레이아웃 */
display: flex;
gap: 0.15rem;
}
.file-card-buttons > button,
.file-card-buttons > form {
width: calc(50% - 0.075rem);
}
.file-card-buttons form { margin: 0; padding: 0; }
.file-card-buttons .btn-sm {
padding: 0.1rem 0.2rem !important;
font-size: 0.65rem !important;
width: 100%;
text-align: center;
}
/* 1버튼(백업) 기본 레이아웃 */
.file-card-single-button {
display: flex;
justify-content: center;
}
.file-card-single-button .btn-sm {
padding: 0.15rem 0.3rem !important;
font-size: 0.7rem !important;
min-width: 50px;
text-align: center;
}
/* ─────────────────────────────────────────────────────────────
(공통) Outline 기본값 (기존 유지)
───────────────────────────────────────────────────────────── */
.file-card-compact .btn-outline-primary {
background-color: transparent !important;
color: #0d6efd !important;
border: 1px solid #0d6efd !important;
}
.file-card-compact .btn-outline-primary:hover {
background-color: #0d6efd !important;
color: #fff !important;
}
.file-card-compact .btn-outline-danger {
background-color: transparent !important;
color: #dc3545 !important;
border: 1px solid #dc3545 !important;
}
.file-card-compact .btn-outline-danger:hover {
background-color: #dc3545 !important;
color: #fff !important;
}
/* 기존 d-flex gap-2 레거시 대응 */
.file-card-compact .d-flex.gap-2 { display: flex; gap: 0.2rem; }
.file-card-compact .d-flex.gap-2 > * { flex: 1; }
.file-card-compact .d-flex.gap-2 form { display: contents; }
/* ─────────────────────────────────────────────────────────────
!!! 목록별 버튼 스타일 "분리" 규칙 (HTML에 클래스만 달아주면 적용)
- processed-list 블록의 보기/삭제
- backup-list 블록의 보기
───────────────────────────────────────────────────────────── */
/* 처리된 파일 목록(Processed) : 컨테이너 세부 튜닝 */
.processed-list .file-card-buttons {
display: grid;
grid-template-columns: 1fr 1fr; /* 보기/삭제 2열 격자 */
gap: 0.2rem;
}
/* 보기(처리된) — 전용 클래스 우선 */
.processed-list .btn-view-processed,
.processed-list .file-card-buttons .btn-outline-primary { /* (백워드 호환) */
border-color: #3b82f6 !important;
color: #1d4ed8 !important;
background: transparent !important;
padding: .35rem .55rem !important;
font-size: .8rem !important;
font-weight: 600 !important;
}
.processed-list .btn-view-processed:hover,
.processed-list .file-card-buttons .btn-outline-primary:hover {
background: rgba(59,130,246,.10) !important;
color: #1d4ed8 !important;
}
/* 삭제(처리된) — 전용 클래스 우선(더 작게) */
.processed-list .btn-delete-processed,
.processed-list .file-card-buttons .btn-outline-danger { /* (백워드 호환) */
border-color: #ef4444 !important;
color: #b91c1c !important;
background: transparent !important;
padding: .25rem .45rem !important; /* 더 작게 */
font-size: .72rem !important; /* 더 작게 */
font-weight: 600 !important;
}
.processed-list .btn-delete-processed:hover,
.processed-list .file-card-buttons .btn-outline-danger:hover {
background: rgba(239,68,68,.10) !important;
color: #b91c1c !important;
}
/* 백업 파일 목록(Backup) : 1버튼 컨테이너 */
.backup-list .file-card-single-button {
display: flex;
margin-top: .25rem;
}
/* 보기(백업) — 전용 클래스 우선(초록계열), 기존 .btn-outline-primary 사용 시에도 분리 적용 */
.backup-list .btn-view-backup,
.backup-list .file-card-single-button .btn-outline-primary { /* (백워드 호환) */
width: 100%;
border-color: #10b981 !important; /* emerald-500 */
color: #047857 !important; /* emerald-700 */
background: transparent !important;
padding: .42rem .7rem !important;
font-size: .8rem !important;
font-weight: 700 !important; /* 백업은 강조 */
}
.backup-list .btn-view-backup:hover,
.backup-list .file-card-single-button .btn-outline-primary:hover {
background: rgba(16,185,129,.12) !important;
color: #047857 !important;
}
/* ─────────────────────────────────────────────────────────────
[★ 보완] 버튼 크기 “완전 통일”(처리/백업 공통)
- 폰트/라인하이트/패딩을 변수화해서 두 목록 크기 동일
- 기존 개별 padding/font-size를 덮어써서 시각적 높이 통일
───────────────────────────────────────────────────────────── */
:root{
--btn-font: .80rem; /* 버튼 폰트 크기 통일 지점 */
--btn-line: 1.2; /* 버튼 라인하이트 통일 지점 */
--btn-py: .32rem; /* 수직 패딩 */
--btn-px: .60rem; /* 좌우 패딩 */
}
.processed-list .file-card-buttons .btn,
.backup-list .file-card-single-button .btn {
font-size: var(--btn-font) !important;
line-height: var(--btn-line) !important;
padding: var(--btn-py) var(--btn-px) !important;
min-height: calc(1em * var(--btn-line) + (var(--btn-py) * 2)) !important;
}
/* 이전 규칙보다 더 구체적으로 동일 규격을 한 번 더 보장 */
.processed-list .file-card-buttons .btn.btn-outline,
.backup-list .file-card-single-button .btn.btn-outline {
font-size: var(--btn-font) !important;
line-height: var(--btn-line) !important;
padding: var(--btn-py) var(--btn-px) !important;
}
/* ─────────────────────────────────────────────────────────────
[★ 보완] 카드 “자동 한줄 배치”
- 기존 Bootstrap .row.g-3를 Grid로 오버라이드(HTML 수정 無)
- 우측 여백 최소화, 화면 너비에 맞춰 자연스럽게 줄 수 변경
───────────────────────────────────────────────────────────── */
.processed-list .card-body > .row.g-3,
.backup-list .card-body .row.g-3 {
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: .75rem;
}
/* 그리드 기준으로 카드 폭이 잘 늘어나도록 제한 완화 */
.processed-list .file-card-compact,
.backup-list .file-card-compact {
max-width: none !important; /* 기존 180px 제한을 목록 구간에 한해 해제 */
min-width: 160px;
width: 100%;
}
/* ─────────────────────────────────────────────────────────────
반응형 파일 그리드 (기존 유지)
───────────────────────────────────────────────────────────── */
@media (max-width: 1400px) {
.file-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
}
@media (max-width: 1200px) {
.file-grid { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
}
@media (max-width: 992px) {
.file-grid { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
}
@media (max-width: 768px) {
.file-grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
}
@media (max-width: 576px) {
.file-grid { grid-template-columns: repeat(auto-fit, minmax(45%, 1fr)); }
}
/* ─────────────────────────────────────────────────────────────
백업 파일 리스트
───────────────────────────────────────────────────────────── */
.list-group-item .bg-light {
transition: background-color 0.2s ease;
}
.list-group-item:hover .bg-light {
background-color: #e9ecef !important;
}
/* ─────────────────────────────────────────────────────────────
모달 - 파일 내용 보기
───────────────────────────────────────────────────────────── */
#fileViewContent {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
padding: 1rem;
border-radius: 5px;
max-height: 60vh;
overflow-y: auto;
font-weight: 400;
}
.modal-body pre::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.modal-body pre::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.modal-body pre::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.modal-body pre::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* ─────────────────────────────────────────────────────────────
접근성 - Skip to content
───────────────────────────────────────────────────────────── */
.visually-hidden-focusable:not(:focus):not(:focus-within) {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
.visually-hidden-focusable:focus {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
padding: 1rem;
background: #000;
color: #fff;
}
/* ─────────────────────────────────────────────────────────────
전역 폰트 굵기 강제 (Bootstrap 오버라이드)
───────────────────────────────────────────────────────────── */
* { font-weight: inherit; }
strong, b { font-weight: 600; }
label, .form-label, .card-title, .list-group-item strong {
font-weight: 400 !important;
}
/* ─────────────────────────────────────────────────────────────
반응형
───────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
.card-body {
padding: 1.5rem !important;
}
body {
font-size: 0.95rem;
}
}
/* === [FIX] 처리된 목록 보기/삭제 버튼 크기 완전 동일화 === */
/* 1) 그리드 두 칸을 꽉 채우게 강제 */
.processed-list .file-card-buttons {
display: grid !important;
grid-template-columns: 1fr 1fr !important;
gap: .2rem !important;
align-items: stretch !important; /* 높이도 칸 높이에 맞춰 늘림 */
}
/* 2) 그리드 아이템(버튼/폼) 자체를 칸 너비로 확장 */
.processed-list .file-card-buttons > * {
width: 100% !important;
}
/* 3) 폼 안의 버튼도 100%로 확장 (폼이 그리드 아이템인 경우 대비) */
.processed-list .file-card-buttons > form { display: block !important; }
.processed-list .file-card-buttons > form > button {
display: block !important;
width: 100% !important;
}
/* 4) 예전 플렉스 기반 전역 규칙 덮어쓰기(폭 계산식 무력화) */
.processed-list .file-card-buttons > button,
.processed-list .file-card-buttons > form {
width: 100% !important;
}
/* 5) 폰트/라인하이트/패딩 통일(높이 동일) — 필요 시 수치만 조정 */
:root{
--btn-font: .80rem;
--btn-line: 1.2;
--btn-py: .32rem;
--btn-px: .60rem;
}
.processed-list .file-card-buttons .btn {
font-size: var(--btn-font) !important;
line-height: var(--btn-line) !important;
padding: var(--btn-py) var(--btn-px) !important;
min-height: calc(1em * var(--btn-line) + (var(--btn-py) * 2)) !important;
box-sizing: border-box;
}

View File

@@ -0,0 +1,169 @@
{# backend/templates/admin.html #}
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<div class="card">
<div class="card-body">
<h3 class="card-title">Admin Page</h3>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mt-2">
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert">
{{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="table-responsive">
<table class="table table-striped align-middle">
<thead>
<tr>
<th style="width:60px">ID</th>
<th>Username</th>
<th>Email</th>
<th style="width:80px">Active</th>
<th style="width:260px">Action</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>
{% if user.is_active %}
<span class="badge bg-success">Yes</span>
{% else %}
<span class="badge bg-secondary">No</span>
{% endif %}
</td>
<td>
{% if not user.is_active %}
<a href="{{ url_for('admin.approve_user', user_id=user.id) }}" class="btn btn-success btn-sm me-1">Approve</a>
{% endif %}
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}"
class="btn btn-danger btn-sm me-1"
onclick="return confirm('사용자 {{ user.username }} (ID={{ user.id }}) 를 삭제하시겠습니까?');">
Delete
</a>
<!-- Change Password 버튼: 모달 오픈 -->
<button type="button"
class="btn btn-primary btn-sm"
data-user-id="{{ user.id }}"
data-username="{{ user.username | e }}"
data-bs-toggle="modal"
data-bs-target="#changePasswordModal">
Change Password
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{# ========== Change Password Modal ========== #}
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-labelledby="changePasswordModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form id="changePasswordForm" method="post" action="">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="modal-header">
<h5 class="modal-title" id="changePasswordModalLabel">Change Password</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<small class="text-muted">User:</small>
<div id="modalUserInfo" class="fw-bold"></div>
</div>
<div class="mb-3">
<label for="newPasswordInput" class="form-label">New password</label>
<input id="newPasswordInput" name="new_password" type="password" class="form-control" required minlength="8" placeholder="Enter new password">
<div class="form-text">최소 8자 이상을 권장합니다.</div>
</div>
<div class="mb-3">
<label for="confirmPasswordInput" class="form-label">Confirm password</label>
<input id="confirmPasswordInput" name="confirm_password" type="password" class="form-control" required minlength="8" placeholder="Confirm new password">
<div id="pwMismatch" class="invalid-feedback">비밀번호가 일치하지 않습니다.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button id="modalSubmitBtn" type="submit" class="btn btn-primary">Change Password</button>
</div>
</form>
</div>
</div>
</div>
{# ========== 스크립트: 모달에 사용자 정보 채우기 + 클라이언트 확인 ========== #}
{% block scripts %}
{{ super() }}
<script>
(function () {
// Bootstrap 5을 사용한다고 가정. data-bs-* 이벤트로 처리.
const changePasswordModal = document.getElementById('changePasswordModal');
const modalUserInfo = document.getElementById('modalUserInfo');
const changePasswordForm = document.getElementById('changePasswordForm');
const newPasswordInput = document.getElementById('newPasswordInput');
const confirmPasswordInput = document.getElementById('confirmPasswordInput');
const pwMismatch = document.getElementById('pwMismatch');
if (!changePasswordModal) return;
changePasswordModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget; // 버튼 that triggered the modal
const userId = button.getAttribute('data-user-id');
const username = button.getAttribute('data-username') || ('ID ' + userId);
// 표시 텍스트 세팅
modalUserInfo.textContent = username + ' (ID: ' + userId + ')';
// 폼 action 동적 설정: admin.reset_password 라우트 기대
// 예: /admin/users/123/reset_password
changePasswordForm.action = "{{ url_for('admin.reset_password', user_id=0) }}".replace('/0/', '/' + userId + '/');
// 폼 내부 비밀번호 필드 초기화
newPasswordInput.value = '';
confirmPasswordInput.value = '';
confirmPasswordInput.classList.remove('is-invalid');
pwMismatch.style.display = 'none';
});
// 폼 제출 전 클라이언트에서 비밀번호 일치 검사
changePasswordForm.addEventListener('submit', function (e) {
const a = newPasswordInput.value || '';
const b = confirmPasswordInput.value || '';
if (a.length < 8) {
newPasswordInput.focus();
e.preventDefault();
return;
}
if (a !== b) {
e.preventDefault();
confirmPasswordInput.classList.add('is-invalid');
pwMismatch.style.display = 'block';
confirmPasswordInput.focus();
return;
}
// 제출 허용 (서버측에서도 반드시 검증)
});
})();
</script>
{% endblock %}
{% endblock %}

121
backend/templates/base.html Normal file
View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Dell Server 정보 및 MAC 주소 처리 시스템">
<title>{% block title %}Dell Server Info, MAC 정보 처리{% endblock %}</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- Bootstrap 5.3.3 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
integrity="sha384-tViUnnbYAV00FLIhhi3v/dWt3Jxw4gZQcNoSCxCIFNJVCx7/D55/wXsrNIRANwdD"
crossorigin="anonymous">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Skip to main content (접근성) -->
<a href="#main-content" class="visually-hidden-focusable">본문으로 건너뛰기</a>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('home.home') }}">
<i class="bi bi-server me-2"></i>
Dell Server Info
</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="네비게이션 토글">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('home.home') }}">
<i class="bi bi-house-door me-1"></i>Home
</a>
</li>
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">
<i class="bi bi-hdd-network me-1"></i>ServerInfo
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('xml.xml_management') }}">
<i class="bi bi-file-code me-1"></i>XML Management
</a>
</li>
{% if current_user.is_admin %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.admin_panel') }}">
<i class="bi bi-shield-lock me-1"></i>Admin
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">
<i class="bi bi-box-arrow-right me-1"></i>Logout
</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">
<i class="bi bi-box-arrow-in-right me-1"></i>Login
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">
<i class="bi bi-person-plus me-1"></i>Register
</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main id="main-content" class="{% if request.endpoint in ['auth.login', 'auth.register', 'auth.reset_password'] %}container mt-5{% else %}container mt-4 container-card{% endif %}">
{% block content %}{% endblock %}
</main>
<!-- Footer (선택사항) -->
<footer class="mt-5 py-3 bg-light text-center">
<div class="container">
<small class="text-muted">&copy; 2025 Dell Server Info. All rights reserved.</small>
</div>
</footer>
<!-- Bootstrap 5.3.3 JS Bundle (Popper.js 포함) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<!-- Socket.IO (필요한 경우만) -->
{% if config.USE_SOCKETIO %}
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"
integrity="sha384-Gr6Lu2Ajx28mzwyVR8CFkULdCU7kMlZ9UthllibdOSo6qAiN+yXNHqtgdTvFXMT4"
crossorigin="anonymous"></script>
{% endif %}
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,51 @@
{% extends "base.html" %}
{% block content %}
<head>
<meta charset="UTF-8">
<title>Edit XML File</title>
<style>
::-webkit-scrollbar { width: 12px; }
::-webkit-scrollbar-track { background: #f1f1f1; }
::-webkit-scrollbar-thumb { background-color: #888; border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background-color: #555; }
html { scrollbar-width: thin; scrollbar-color: #888 #f1f1f1; }
textarea {
width: 100%;
height: 600px;
padding: 10px;
font-size: 14px;
line-height: 1.5;
background-color: #f9f9f9;
border: 1px solid #ccc;
transition: background-color 0.3s ease;
}
textarea:hover { background-color: #f0f0f0; }
.xml-list-item {
padding: 10px;
background-color: #ffffff;
transition: background-color 0.3s ease;
}
.xml-list-item:hover { background-color: #e9ecef; cursor: pointer; }
.btn { margin-top: 20px; }
</style>
</head>
<body>
<div class="card shadow-lg">
<div class="card-header bg-primary text-white">
<h3>Edit XML File: <strong>{{ filename }}</strong></h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="xmlContent">XML Content</label>
<textarea id="xmlContent" name="content" class="form-control" rows="20">{{ content }}</textarea>
</div>
<button type="submit" class="btn btn-success mt-3">Save Changes</button>
<a href="{{ url_for('xml.xml_management') }}" class="btn btn-secondary mt-3">Cancel</a>
</form>
</div>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
<h2>Dell Server & NAVER Settings Info</h2>
<p>사이트 가입 및 로그인후 이용 가능</p>
{% endblock %}

View File

@@ -0,0 +1,676 @@
{% extends "base.html" %}
{% block content %}
<div class="container-fluid py-4">
{# 플래시 메시지 #}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="position-fixed top-0 end-0 p-3" style="z-index: 11">
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }} alert-dismissible fade show shadow-lg" role="alert">
<i class="bi bi-{{ 'check-circle' if cat == 'success' else 'exclamation-triangle' }} me-2"></i>
{{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{# 헤더 섹션 #}
<div class="row mb-4">
<div class="col">
<h2 class="fw-bold mb-1">
<i class="bi bi-server text-primary me-2"></i>
서버 관리 대시보드
</h2>
<p class="text-muted mb-0">IP 처리 및 파일 관리를 위한 통합 관리 도구</p>
</div>
</div>
{# 메인 작업 영역 #}
<div class="row g-4 mb-4">
{# IP 처리 카드 #}
<div class="col-lg-6">
<div class="card border shadow-sm h-100">
<div class="card-header bg-primary text-white border-0 py-2">
<h6 class="mb-0 fw-semibold">
<i class="bi bi-hdd-network me-2"></i>
IP 처리
</h6>
</div>
<div class="card-body p-4">
<form id="ipForm" method="post" action="{{ url_for('main.process_ips') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{# 스크립트 선택 #}
<div class="mb-3">
<label for="script" class="form-label">스크립트 선택</label>
<select id="script" name="script" class="form-select" required>
<option value="">스크립트를 선택하세요</option>
{% for script in scripts %}
<option value="{{ script }}">{{ script }}</option>
{% endfor %}
</select>
</div>
{# XML 파일 선택 (조건부) #}
<div class="mb-3" id="xmlFileGroup" style="display:none;">
<label for="xmlFile" class="form-label">XML 파일 선택</label>
<select id="xmlFile" name="xmlFile" class="form-select">
<option value="">XML 파일 선택</option>
{% for xml_file in xml_files %}
<option value="{{ xml_file }}">{{ xml_file }}</option>
{% endfor %}
</select>
</div>
{# IP 주소 입력 #}
<div class="mb-3">
<label for="ips" class="form-label">
IP 주소 (각 줄에 하나)
<span class="badge bg-secondary ms-2" id="ipLineCount">0줄</span>
</label>
<textarea id="ips" name="ips" rows="4" class="form-control font-monospace"
placeholder="예:&#10;192.168.1.1&#10;192.168.1.2&#10;192.168.1.3" required></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">
처리
</button>
</form>
</div>
</div>
</div>
{# 공유 작업 카드 #}
<div class="col-lg-6">
<div class="card border shadow-sm h-100">
<div class="card-header bg-success text-white border-0 py-2">
<h6 class="mb-0 fw-semibold">
<i class="bi bi-share me-2"></i>
공유 작업
</h6>
</div>
<div class="card-body p-4">
<form id="sharedForm" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label for="server_list_content" class="form-label">
서버 리스트 (덮어쓰기)
<span class="badge bg-secondary ms-2" id="serverLineCount">0줄</span>
</label>
<textarea id="server_list_content" name="server_list_content" rows="8"
class="form-control font-monospace" style="font-size: 0.95rem;"
placeholder="서버 리스트를 입력하세요..."></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" formaction="{{ url_for('utils.update_server_list') }}"
class="btn btn-outline-primary">
MAC to Excel
</button>
<button type="submit" formaction="{{ url_for('utils.update_guid_list') }}"
class="btn btn-success">
GUID to Excel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{# 진행바 #}
<div class="row mb-4">
<div class="col">
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-activity text-primary me-2"></i>
<span class="fw-semibold">처리 진행률</span>
</div>
<div class="progress" style="height: 25px;">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success"
role="progressbar" style="width:0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span class="fw-semibold">0%</span>
</div>
</div>
</div>
</div>
</div>
</div>
{# 파일 관리 도구 #}
<div class="row mb-4">
<div class="col">
<div class="card border shadow-sm">
<div class="card-header bg-light border-0 py-2">
<h6 class="mb-0">
<i class="bi bi-tools me-2"></i>
파일 관리 도구
</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
{# ZIP 다운로드 #}
<div class="col-md-6 col-xl-3">
<label class="form-label">ZIP 다운로드</label>
<form method="post" action="{{ url_for('main.download_zip') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="input-group">
<input type="text" class="form-control" name="zip_filename"
placeholder="파일명" required>
<button class="btn btn-primary" type="submit">
다운로드
</button>
</div>
</form>
</div>
{# 파일 백업 #}
<div class="col-md-6 col-xl-3">
<label class="form-label">파일 백업</label>
<form method="post" action="{{ url_for('main.backup_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="input-group">
<input type="text" class="form-control" name="backup_prefix"
placeholder="PO로 시작">
<button class="btn btn-success" type="submit">
백업
</button>
</div>
</form>
</div>
{# MAC 파일 이동 #}
<div class="col-md-6 col-xl-3">
<label class="form-label">MAC 파일 이동</label>
<form id="macMoveForm" method="post" action="{{ url_for('utils.move_mac_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-warning w-100" type="submit">
MAC Move
</button>
</form>
</div>
{# GUID 파일 이동 #}
<div class="col-md-6 col-xl-3">
<label class="form-label">GUID 파일 이동</label>
<form id="guidMoveForm" method="post" action="{{ url_for('utils.move_guid_files') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-info w-100" type="submit">
GUID Move
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{# 처리된 파일 목록 - 목록별 버튼 스타일 분리 (processed-list) #}
<div class="row mb-4 processed-list">
<div class="col">
<div class="card border shadow-sm">
<div class="card-header bg-light border-0 py-2">
<h6 class="mb-0">
<i class="bi bi-files me-2"></i>
처리된 파일 목록
</h6>
</div>
<div class="card-body p-4">
{% if files_to_display and files_to_display|length > 0 %}
<div class="row g-3">
{% for file_info in files_to_display %}
<div class="col-auto">
<div class="file-card-compact border rounded p-2 text-center">
<a href="{{ url_for('main.download_file', filename=file_info.file) }}"
class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
download title="{{ file_info.name or file_info.file }}">
{{ file_info.name or file_info.file }}
</a>
<div class="file-card-buttons">
<button type="button" class="btn btn-sm btn-outline btn-view-processed flex-fill"
data-bs-toggle="modal" data-bs-target="#fileViewModal"
data-folder="idrac_info"
data-filename="{{ file_info.file }}">
보기
</button>
<form action="{{ url_for('main.delete_file', filename=file_info.file) }}"
method="post" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline btn-delete-processed"
onclick="return confirm('삭제하시겠습니까?');">
삭제
</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">표시할 파일이 없습니다.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{# 백업된 파일 목록 - 목록별 버튼 스타일 분리 (backup-list) #}
<div class="row backup-list">
<div class="col">
<div class="card border shadow-sm">
<div class="card-header bg-light border-0 py-2">
<h6 class="mb-0">
<i class="bi bi-archive me-2"></i>
백업된 파일 목록
</h6>
</div>
<div class="card-body p-4">
{% if backup_files and backup_files|length > 0 %}
<div class="list-group">
{% for date, info in backup_files.items() %}
<div class="list-group-item border rounded mb-2 p-0 overflow-hidden">
<div class="d-flex justify-content-between align-items-center p-3 bg-light">
<div class="d-flex align-items-center">
<i class="bi bi-calendar3 text-primary me-2"></i>
<strong>{{ date }}</strong>
<span class="badge bg-primary ms-3">{{ info.count }} 파일</span>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button"
data-bs-toggle="collapse" data-bs-target="#collapse-{{ loop.index }}"
aria-expanded="false">
<i class="bi bi-chevron-down"></i>
</button>
</div>
<div id="collapse-{{ loop.index }}" class="collapse">
<div class="p-3">
<div class="row g-3">
{% for file in info.files %}
<div class="col-auto">
<div class="file-card-compact border rounded p-2 text-center">
<a href="{{ url_for('main.download_backup_file', date=date, filename=file) }}
" class="text-decoration-none text-dark fw-semibold d-block mb-2 text-nowrap px-2"
download title="{{ file }}">
{{ file.rsplit('.', 1)[0] }}
</a>
<div class="file-card-single-button">
<button type="button" class="btn btn-sm btn-outline btn-view-backup w-100"
data-bs-toggle="modal" data-bs-target="#fileViewModal"
data-folder="backup"
data-date="{{ date }}"
data-filename="{{ file }}">
보기
</button>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
<p class="text-muted mb-0">백업된 파일이 없습니다.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{# 파일 보기 모달 #}
<div class="modal fade" id="fileViewModal" tabindex="-1" aria-labelledby="fileViewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="fileViewModalLabel">
<i class="bi bi-file-text me-2"></i>
파일 보기
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<div class="modal-body bg-light">
<pre id="fileViewContent" class="mb-0 p-3 bg-white border rounded font-monospace"
style="white-space:pre-wrap;word-break:break-word;max-height:70vh;">불러오는 중...</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle me-1"></i>닫기
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<style>
/* ===== 공통 파일 카드 컴팩트 스타일 ===== */
.file-card-compact {
transition: all 0.2s ease;
background: #fff;
min-width: 120px;
max-width: 200px;
}
.file-card-compact:hover {
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.file-card-compact a {
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
/* ===== 목록별 버튼 분리 규칙 ===== */
/* 처리된 파일 목록 전용 컨테이너(보기/삭제 2열) */
.processed-list .file-card-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: .5rem;
}
/* 보기(처리된) */
.processed-list .btn-view-processed {
border-color: #3b82f6;
color: #1d4ed8;
padding: .425rem .6rem;
font-size: .8125rem;
font-weight: 600;
}
.processed-list .btn-view-processed:hover {
background: rgba(59,130,246,.08);
}
/* 삭제(처리된) — 더 작게 */
.processed-list .btn-delete-processed {
border-color: #ef4444;
color: #b91c1c;
padding: .3rem .5rem;
font-size: .75rem;
font-weight: 600;
}
.processed-list .btn-delete-processed:hover {
background: rgba(239,68,68,.08);
}
/* 백업 파일 목록 전용 컨테이너(단일 버튼) */
.backup-list .file-card-single-button {
display: flex;
margin-top: .25rem;
}
/* 보기(백업) — 강조 색상 */
.backup-list .btn-view-backup {
width: 100%;
border-color: #10b981;
color: #047857;
padding: .45rem .75rem;
font-size: .8125rem;
font-weight: 700;
}
.backup-list .btn-view-backup:hover {
background: rgba(16,185,129,.08);
}
/* ===== 백업 파일 날짜 헤더 ===== */
.list-group-item .bg-light {
transition: background-color 0.2s ease;
}
.list-group-item:hover .bg-light {
background-color: #e9ecef !important;
}
/* ===== 진행바 애니메이션 ===== */
.progress {
border-radius: 10px;
overflow: hidden;
}
.progress-bar {
transition: width 0.6s ease;
}
/* ===== 반응형 텍스트 ===== */
@media (max-width: 768px) {
.card-body {
padding: 1.5rem !important;
}
}
/* ===== 스크롤바 스타일링(모달) ===== */
.modal-body pre::-webkit-scrollbar { width: 8px; height: 8px; }
.modal-body pre::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
.modal-body pre::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }
.modal-body pre::-webkit-scrollbar-thumb:hover { background: #555; }
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 스크립트 선택 시 XML 드롭다운 토글
const TARGET_SCRIPT = "02-set_config.py";
const scriptSelect = document.getElementById('script');
const xmlGroup = document.getElementById('xmlFileGroup');
function toggleXml() {
if (!scriptSelect || !xmlGroup) return;
if (scriptSelect.value === TARGET_SCRIPT) {
xmlGroup.style.display = 'block';
xmlGroup.classList.add('fade-in');
} else {
xmlGroup.style.display = 'none';
}
}
if (scriptSelect) {
toggleXml();
scriptSelect.addEventListener('change', toggleXml);
}
// 파일 보기 모달
const modalEl = document.getElementById('fileViewModal');
const titleEl = document.getElementById('fileViewModalLabel');
const contentEl = document.getElementById('fileViewContent');
if (modalEl) {
modalEl.addEventListener('show.bs.modal', async (ev) => {
const btn = ev.relatedTarget;
const folder = btn?.getAttribute('data-folder') || '';
const date = btn?.getAttribute('data-date') || '';
const filename = btn?.getAttribute('data-filename') || '';
titleEl.innerHTML = `<i class="bi bi-file-text me-2"></i>${filename || '파일'}`;
contentEl.textContent = '불러오는 중...';
const params = new URLSearchParams();
if (folder) params.set('folder', folder);
if (date) params.set('date', date);
if (filename) params.set('filename', filename);
try {
const res = await fetch(`/view_file?${params.toString()}`, { cache: 'no-store' });
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
contentEl.textContent = data?.content ?? '(빈 파일)';
} catch (e) {
contentEl.textContent = '파일을 불러오지 못했습니다: ' + (e?.message || e);
}
});
}
// 진행바 업데이트
window.updateProgress = function (val) {
const bar = document.getElementById('progressBar');
if (!bar) return;
const v = Math.max(0, Math.min(100, Number(val) || 0));
bar.style.width = v + '%';
bar.setAttribute('aria-valuenow', v);
bar.innerHTML = `<span class="fw-semibold">${v}%</span>`;
};
// CSRF 토큰
const csrfToken = document.querySelector('input[name="csrf_token"]')?.value || '';
// 공통 POST 함수
async function postFormAndHandle(url) {
const res = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'X-CSRFToken': csrfToken,
'Accept': 'application/json, text/html;q=0.9,*/*;q=0.8',
},
});
const ct = (res.headers.get('content-type') || '').toLowerCase();
if (ct.includes('application/json')) {
const data = await res.json();
if (data.success === false) {
throw new Error(data.error || ('HTTP ' + res.status));
}
return data;
}
return { success: true, html: true };
}
// MAC 파일 이동
const macForm = document.getElementById('macMoveForm');
if (macForm) {
macForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = macForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(macForm.action);
location.reload();
} catch (err) {
alert('MAC 이동 중 오류: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// GUID 파일 이동
const guidForm = document.getElementById('guidMoveForm');
if (guidForm) {
guidForm.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = guidForm.querySelector('button');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
await postFormAndHandle(guidForm.action);
location.reload();
} catch (err) {
alert('GUID 이동 중 오류: ' + (err?.message || err));
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// IP 폼 제출
const ipForm = document.getElementById("ipForm");
if (ipForm) {
ipForm.addEventListener("submit", async (ev) => {
ev.preventDefault();
const formData = new FormData(ipForm);
const btn = ipForm.querySelector('button[type="submit"]');
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>처리 중...';
try {
const res = await fetch(ipForm.action, {
method: "POST",
body: formData
});
if (!res.ok) throw new Error("HTTP " + res.status);
const data = await res.json();
console.log("[DEBUG] process_ips 응답:", data);
if (data.job_id) {
pollProgress(data.job_id);
} else {
// job_id가 없으면 완료로 간주
window.updateProgress(100);
setTimeout(() => location.reload(), 1000);
}
} catch (err) {
console.error("처리 중 오류:", err);
alert("처리 중 오류 발생: " + err.message);
btn.disabled = false;
btn.innerHTML = originalHtml;
}
});
}
// 진행률 폴링 함수
function pollProgress(jobId) {
const interval = setInterval(async () => {
try {
const res = await fetch(`/progress_status/${jobId}`);
if (!res.ok) {
clearInterval(interval);
return;
}
const data = await res.json();
if (data.progress !== undefined) {
window.updateProgress(data.progress);
}
// 완료 시 (100%)
if (data.progress >= 100) {
clearInterval(interval);
window.updateProgress(100);
// 페이지 새로고침
setTimeout(() => location.reload(), 1500);
}
} catch (err) {
console.error('진행률 확인 중 오류:', err);
clearInterval(interval);
}
}, 500); // 0.5초마다 확인
}
// 알림 자동 닫기
setTimeout(() => {
document.querySelectorAll('.alert').forEach(alert => {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
});
}, 5000);
});
</script>
<!-- 외부 script.js 파일만 로드 -->
<script src="{{ url_for('static', filename='script.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-center mt-5">
<div class="card shadow-sm p-4" style="min-width: 400px; max-width: 500px; width: 100%;">
<h3 class="text-center mb-4">Login</h3>
<form method="POST" action="{{ url_for('auth.login') }}">
{{ form.hidden_tag() }}
<div class="form-group mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
</div>
<div class="form-group mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
</div>
<div class="form-group form-check mb-3">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-block">Login</button>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,305 @@
{% extends "base.html" %}
{% block content %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XML 파일 관리</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body {
background-color: #f5f5f5;
padding: 20px 0;
}
.main-title {
font-size: 1.8rem;
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
font-size: 0.95rem;
margin-bottom: 30px;
}
.card {
border: none;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
background: white;
}
.card-header-custom {
background-color: #007bff;
color: white;
padding: 12px 20px;
font-weight: 600;
border-radius: 8px 8px 0 0;
font-size: 1rem;
}
.card-body {
padding: 20px;
}
.form-label {
font-weight: 500;
color: #555;
margin-bottom: 8px;
font-size: 0.9rem;
}
.form-control {
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px 12px;
font-size: 0.9rem;
}
.btn-primary {
background-color: #007bff;
border: none;
padding: 8px 24px;
font-weight: 500;
border-radius: 4px;
font-size: 0.9rem;
}
.btn-success {
background-color: #28a745;
border: none;
padding: 6px 16px;
font-weight: 500;
border-radius: 4px;
font-size: 0.85rem;
}
.btn-danger {
background-color: #dc3545;
border: none;
padding: 6px 16px;
font-weight: 500;
border-radius: 4px;
font-size: 0.85rem;
}
.upload-section {
background-color: #f8f9fa;
padding: 15px;
border-radius: 4px;
}
.custom-file-label {
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.custom-file-label::after {
background-color: #007bff;
color: white;
border-radius: 0 3px 3px 0;
font-size: 0.85rem;
}
/* 아이콘 + 뱃지 스타일 */
.file-list {
max-height: 500px;
overflow-y: auto;
}
.icon-badge-item {
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 10px 15px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.3s;
background: white;
}
.icon-badge-item:hover {
background-color: #f8f9fa;
border-color: #007bff;
transform: translateX(3px);
}
.icon-badge-left {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.file-icon-small {
width: 36px;
height: 36px;
background: #007bff;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
flex-shrink: 0;
}
.file-name-section {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
}
.file-name-badge {
font-weight: 500;
color: #333;
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.badge-custom {
background-color: #e7f3ff;
color: #007bff;
padding: 3px 10px;
border-radius: 10px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
.action-buttons {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.empty-message {
text-align: center;
color: #999;
padding: 30px;
font-size: 0.9rem;
}
.container {
max-width: 1200px;
}
/* 스크롤바 스타일 */
.file-list::-webkit-scrollbar {
width: 6px;
}
.file-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
.file-list::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>
</head>
<body>
<div class="container">
<h1 class="main-title">XML 파일 관리</h1>
<p class="subtitle">XML 파일을 업로드하고 관리할 수 있습니다</p>
<!-- XML 파일 업로드 폼 -->
<div class="card">
<div class="card-header-custom">
<i class="fas fa-cloud-upload-alt mr-2"></i>파일 업로드
</div>
<div class="card-body">
<form action="{{ url_for('xml.upload_xml') }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="upload-section">
<div class="form-group mb-2">
<label for="xmlFile" class="form-label">XML 파일 선택</label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="xmlFile" name="xmlFile" accept=".xml" onchange="updateFileName(this)">
<label class="custom-file-label" for="xmlFile" id="fileLabel">파일을 선택하세요</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload mr-1"></i>업로드
</button>
</div>
</form>
</div>
</div>
<!-- XML 파일 목록 -->
<div class="card">
<div class="card-header-custom">
<i class="fas fa-list mr-2"></i>파일 목록
</div>
<div class="card-body">
{% if xml_files %}
<div class="file-list">
{% for xml_file in xml_files %}
<div class="icon-badge-item">
<div class="icon-badge-left">
<div class="file-icon-small">
<i class="fas fa-file-code"></i>
</div>
<div class="file-name-section">
<span class="file-name-badge" title="{{ xml_file }}">{{ xml_file }}</span>
<span class="badge-custom">XML</span>
</div>
</div>
<div class="action-buttons">
<!-- 파일 편집 버튼 -->
<a href="{{ url_for('xml.edit_xml', filename=xml_file) }}" class="btn btn-success btn-sm">
<i class="fas fa-edit"></i> 편집
</a>
<!-- 파일 삭제 버튼 -->
<form action="{{ url_for('xml.delete_xml', filename=xml_file) }}" method="POST" style="display:inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('정말 삭제하시겠습니까?')">
<i class="fas fa-trash"></i> 삭제
</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-message">
<i class="fas fa-folder-open" style="font-size: 2rem; color: #ddd;"></i>
<p class="mt-2 mb-0">파일이 없습니다.</p>
</div>
{% endif %}
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
function updateFileName(input) {
const fileName = input.files[0]?.name || '파일을 선택하세요';
document.getElementById('fileLabel').textContent = fileName;
}
</script>
</body>
</html>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-center mt-5">
<div class="card shadow-sm p-4" style="min-width: 400px; max-width: 500px; width: 100%;">
<h3 class="text-center mb-4">Register</h3>
<form method="POST" action="{{ url_for('auth.register') }}">
{{ form.hidden_tag() }}
<div class="form-group mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control") }}
</div>
<div class="form-group mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
</div>
<div class="form-group mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
</div>
<div class="form-group mb-4">
{{ form.confirm_password.label(class="form-label") }}
{{ form.confirm_password(class="form-control") }}
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-block">Register</button>
</div>
</form>
</div>
</div>
{% endblock %}

View File

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

86
config.py Normal file
View File

@@ -0,0 +1,86 @@
from __future__ import annotations
import os
from pathlib import Path
from datetime import timedelta
# ─────────────────────────────────────────────────────────────
# 경로 기본값
# ─────────────────────────────────────────────────────────────
BASE_DIR = Path(__file__).resolve().parent
# 앱 데이터 루트(로그/업로드/백업 등) → 환경변수 APP_DATA_DIR 우선, 없으면 ./data
DATA_DIR = Path(os.getenv("APP_DATA_DIR", BASE_DIR / "data")).resolve()
# backend/instance 는 항상 유지 (DB 등)
INSTANCE_DIR = BASE_DIR / "backend" / "instance"
INSTANCE_DIR.mkdir(parents=True, exist_ok=True)
# data/ 하위 보조 디렉토리
(DATA_DIR / "logs").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "uploads").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "temp_zips").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "xml").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "scripts").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "idrac_info").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "mac").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "guid_file").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "mac_backup").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "server_list").mkdir(parents=True, exist_ok=True)
(DATA_DIR / "temp_ip").mkdir(parents=True, exist_ok=True)
class Config:
"""
운영 시에는 환경변수(.env)로 민감정보를 설정하세요.
DB는 기본적으로 backend/instance/site.db 를 사용합니다.
"""
# ── 보안
SECRET_KEY = os.environ.get("SECRET_KEY", "change-me") # 반드시 환경변수로 고정 권장
# ── DB (환경변수 DATABASE_URL 있으면 그 값을 우선 사용)
sqlite_path = (INSTANCE_DIR / "site.db").as_posix()
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", f"sqlite:///{sqlite_path}")
SQLALCHEMY_TRACK_MODIFICATIONS = False
# ── Telegram (미설정 시 기능 비활성처럼 동작)
TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
# ── 앱 폴더 경로 (문자열)
UPLOAD_FOLDER = (DATA_DIR / "temp_ip").as_posix()
BACKUP_FOLDER = (DATA_DIR / "backup").as_posix()
TEMP_ZIP_FOLDER = (DATA_DIR / "temp_zips").as_posix()
XML_FOLDER = (DATA_DIR / "xml").as_posix()
SCRIPT_FOLDER = (DATA_DIR / "scripts").as_posix()
IDRAC_INFO_FOLDER = (DATA_DIR / "idrac_info").as_posix()
MAC_FOLDER = (DATA_DIR / "mac").as_posix()
GUID_FOLDER = (DATA_DIR / "guid_file").as_posix()
MAC_BACKUP_FOLDER = (DATA_DIR / "mac_backup").as_posix()
SERVER_LIST_FOLDER = (DATA_DIR / "server_list").as_posix()
LOG_FOLDER = (DATA_DIR / "logs").as_posix()
# ── 업로드/파일
ALLOWED_EXTENSIONS = {"xml"}
MAX_CONTENT_LENGTH = int(os.getenv("MAX_CONTENT_LENGTH", 10 * 1024 * 1024)) # 10MB
# ── 페이지네이션/병렬
FILES_PER_PAGE = int(os.getenv("FILES_PER_PAGE", 32))
BACKUP_FILES_PER_PAGE = int(os.getenv("BACKUP_FILES_PER_PAGE", 3))
MAX_WORKERS = int(os.getenv("MAX_WORKERS", 60))
# ── 세션
PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.getenv("SESSION_MINUTES", 30)))
# ── SocketIO
# threading / eventlet / gevent 중 선택. 기본은 threading (Windows 안정)
SOCKETIO_ASYNC_MODE = os.getenv("SOCKETIO_ASYNC_MODE", "threading")
# ─────────────────────────────────────────────────────────
# ✅ 개발 환경에서 쿠키 저장/리다이렉트 문제 방지용 설정
# 로컬(HTTP)에서도 세션/remember 토큰이 저장되도록 Secure 비활성
SESSION_COOKIE_SECURE = False
REMEMBER_COOKIE_SECURE = False
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_DOMAIN = None

Binary file not shown.

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 1PYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 1PYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 1XZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 1XZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 2NYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 2NYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 2XZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 2XZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 3LYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 3LYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 3MYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 3MYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 3PYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 3PYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 4XZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 4XZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 5MYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 5MYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 5NYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 5NYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 6XZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 6XZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 7MYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 7MYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 7XZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 7XZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 8WZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 8WZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: 9NYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : 9NYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: BNYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : BNYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: CXZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : CXZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: DLYCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : DLYCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: DXZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : DXZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,50 @@
Dell EMC Server Bios,iDRAC,R/C Setting (SVC Tag: FWZCZC4)
------------------------------------------Firware Version 정보------------------------------------------
1. SVC Tag : FWZCZC4
2. Bios Firmware : 2.7.5
3. iDRAC Firmware Version : 7.20.60.50
4. NIC Integrated Firmware Version : 24.0.5
5. OnBoard NIC Firmware Version :
6. Raid Controller Firmware Version : 8.11.2.0.18-26
---------------------------------------------Bios 설정 정보----------------------------------------------
01. Bios Boot Mode : Uefi
02. System Profile Settings - System Profile : BIOS.Setup.1-1#SysProfileSettings]
03. System Profile Settings - CPU Power Management : MaxPower
04. System Profile Settings - Memory Frequency : MaxPerf
05. System Profile Settings - Turbo Boost : Enabled
06. System Profile Settings - C1E : Disabled
07. System Profile Settings - C-States : Disabled
08. System Profile Settings - Monitor/Mwait : Disabled
09. Processor Settings - Logical Processor : Enabled
10. Processor Settings - Virtualization Technology : Disabled
11. Processor Settings - LLC Prefetch : Enabled
12. Processor Settings - x2APIC Mode : Disabled
13. Memory Settings - Node Interleaving : Disabled
14. Memory Settings - DIMM Self Healing (Post Package Repair) on Uncorrectable Memory Error : Enabled
15. Memory Settings - Correctable Error Logging : Disabled
16. System Settings - Thermal Profile Optimization : Minimum Power
17. Integrated Devices Settings - SR-IOV Global Enable : Enabled
18. Miscellaneous Settings - F1/F2 Prompt on Error : Disabled
---------------------------------------------iDRAC 설정 정보----------------------------------------------
01. iDRAC Settings - Timezone : Asia/Seoul
02. iDRAC Settings - IPMI LAN Selection : Dedicated
03. iDRAC Settings - IPMI IP(IPv4) : Enabled
04. iDRAC Settings - IPMI IP(IPv6) : Enabled
05. iDRAC Settings - Redfish Support : Enabled
06. iDRAC Settings - SSH Support : Enabled
07. iDRAC Settings - AD User Domain Name : nhncorp.nhncorp.local
08. iDRAC Settings - SC Server Address : ad.o.nfra.io
09. iDRAC Settings - SE AD RoleGroup Name : SE_Admin
10. iDRAC Settings - SE AD RoleGroup Dome인 : nhncorp.nhncorp.local
11. iDRAC Settings - SE AD RoleGroup Privilege : 0x1ff
12. iDRAC Settings - IDC AD RoleGroup Name : IDC_Admin
13. iDRAC Settings - IDC AD RoleGroup Domain : nhncorp.nhncorp.local
14. iDRAC Settings - IDC AD RoleGroup Privilege : 0x1f3
15. iDRAC Settings - Remote Log (syslog) : Enabled
16. iDRAC Settings - syslog server address 1 : syslog.o.nfra.io
17. iDRAC Settings - syslog server address 2 :
18. iDRAC Settings - syslog server port : 514
19. iDRAC Settings - Remote KVM Nonsecure port : 25513

View File

@@ -0,0 +1,12 @@
1XZCZC4
Slot.38: 3825:F303:00C4:15EC
Slot.39: 3825:F303:00C4:15F8
Slot.37: 3825:F303:00C4:15E8
Slot.36: 3825:F303:00C4:15E4
Slot.32: 3825:F303:00C4:1564
Slot.33: 3825:F303:00C4:1560
Slot.34: 3825:F303:00C4:0AF4
Slot.35: 3825:F303:00C4:1600
Slot.31: 3825:F303:00C4:0910
Slot.40: 3825:F303:00C4:1608
GUID: 0x3825F30300C415EC;0x3825F30300C415F8;0x3825F30300C415E8;0x3825F30300C415E4;0x3825F30300C41564;0x3825F30300C41560;0x3825F30300C40AF4;0x3825F30300C41600;0x3825F30300C40910;0x3825F30300C41608

View File

@@ -0,0 +1,12 @@
2NYCZC4
Slot.38: 3825:F303:00C4:042C
Slot.39: 3825:F303:00C4:04A8
Slot.37: 3825:F303:00C4:0420
Slot.36: 3825:F303:00C4:0418
Slot.32: 3825:F303:00C4:0508
Slot.33: 3825:F303:00C4:12B4
Slot.34: 3825:F303:00C4:12EC
Slot.35: 3825:F303:00C4:122C
Slot.31: 3825:F303:00C4:0484
Slot.40: 3825:F303:00C4:048C
GUID: 0x3825F30300C4042C;0x3825F30300C404A8;0x3825F30300C40420;0x3825F30300C40418;0x3825F30300C40508;0x3825F30300C412B4;0x3825F30300C412EC;0x3825F30300C4122C;0x3825F30300C40484;0x3825F30300C4048C

View File

@@ -0,0 +1,12 @@
2XZCZC4
Slot.38: 3825:F303:00C4:0AEC
Slot.39: 3825:F303:00C4:0AD8
Slot.37: 3825:F303:00C4:0AC8
Slot.36: 3825:F303:00C4:15F4
Slot.32: 3825:F303:00C4:0AD0
Slot.33: 3825:F303:00C4:0AE0
Slot.34: 3825:F303:00C4:0ADC
Slot.35: 3825:F303:00C4:1568
Slot.31: 3825:F303:00C4:0AE8
Slot.40: 3825:F303:00C4:0AD4
GUID: 0x3825F30300C40AEC;0x3825F30300C40AD8;0x3825F30300C40AC8;0x3825F30300C415F4;0x3825F30300C40AD0;0x3825F30300C40AE0;0x3825F30300C40ADC;0x3825F30300C41568;0x3825F30300C40AE8;0x3825F30300C40AD4

View File

@@ -0,0 +1,12 @@
3LYCZC4
Slot.38: 5000:E603:0068:F204
Slot.39: 5000:E603:0068:F464
Slot.37: 5000:E603:0068:F2B8
Slot.36: 5000:E603:0068:F2FC
Slot.32: 5000:E603:0068:F294
Slot.33: 5000:E603:0068:F504
Slot.34: 5000:E603:0068:F450
Slot.35: 5000:E603:0068:F2C4
Slot.31: 5000:E603:0068:F50C
Slot.40: 5000:E603:0068:F4FC
GUID: 0x5000E6030068F204;0x5000E6030068F464;0x5000E6030068F2B8;0x5000E6030068F2FC;0x5000E6030068F294;0x5000E6030068F504;0x5000E6030068F450;0x5000E6030068F2C4;0x5000E6030068F50C;0x5000E6030068F4FC

View File

@@ -0,0 +1,12 @@
3MYCZC4
Slot.38: 5000:E603:0068:F480
Slot.39: 5000:E603:0068:F254
Slot.37: 5000:E603:0068:F408
Slot.36: 5000:E603:0068:F33C
Slot.32: 5000:E603:0068:F40C
Slot.33: 5000:E603:0068:F4AC
Slot.34: 5000:E603:0068:F4C8
Slot.35: 5000:E603:0068:F410
Slot.31: 5000:E603:0068:F490
Slot.40: 5000:E603:0068:F3A0
GUID: 0x5000E6030068F480;0x5000E6030068F254;0x5000E6030068F408;0x5000E6030068F33C;0x5000E6030068F40C;0x5000E6030068F4AC;0x5000E6030068F4C8;0x5000E6030068F410;0x5000E6030068F490;0x5000E6030068F3A0

View File

@@ -0,0 +1,12 @@
3PYCZC4
Slot.38: 5000:E603:0068:F460
Slot.39: 5000:E603:0068:F44C
Slot.37: 5000:E603:0068:F380
Slot.36: 5000:E603:0068:F2BC
Slot.32: 5000:E603:0068:F4EC
Slot.33: 5000:E603:0068:F274
Slot.34: 5000:E603:0068:F4E4
Slot.35: 5000:E603:0068:F284
Slot.31: 5000:E603:0068:F3DC
Slot.40: 5000:E603:0068:F354
GUID: 0x5000E6030068F460;0x5000E6030068F44C;0x5000E6030068F380;0x5000E6030068F2BC;0x5000E6030068F4EC;0x5000E6030068F274;0x5000E6030068F4E4;0x5000E6030068F284;0x5000E6030068F3DC;0x5000E6030068F354

View File

@@ -0,0 +1,12 @@
4XZCZC4
Slot.38: 5000:E603:0068:F318
Slot.39: 5000:E603:0068:F458
Slot.37: 5000:E603:0068:F23C
Slot.36: 5000:E603:0068:F090
Slot.32: 5000:E603:0068:F448
Slot.33: 5000:E603:0068:F440
Slot.34: 5000:E603:0068:F310
Slot.35: 5000:E603:0068:F430
Slot.31: 5000:E603:0068:F3C8
Slot.40: 5000:E603:0068:F438
GUID: 0x5000E6030068F318;0x5000E6030068F458;0x5000E6030068F23C;0x5000E6030068F090;0x5000E6030068F448;0x5000E6030068F440;0x5000E6030068F310;0x5000E6030068F430;0x5000E6030068F3C8;0x5000E6030068F438

View File

@@ -0,0 +1,12 @@
5MYCZC4
Slot.38: 7C8C:0903:00E4:DE9E
Slot.39: 7C8C:0903:00E4:DEDE
Slot.37: 7C8C:0903:00E4:DE96
Slot.36: 7C8C:0903:00E4:DF42
Slot.32: 7C8C:0903:00E4:DE86
Slot.33: 7C8C:0903:00E4:DED2
Slot.34: 7C8C:0903:00E4:ED06
Slot.35: 7C8C:0903:00E4:DF3E
Slot.31: 7C8C:0903:00E4:DEEA
Slot.40: 7C8C:0903:00E4:DED6
GUID: 0x7C8C090300E4DE9E;0x7C8C090300E4DEDE;0x7C8C090300E4DE96;0x7C8C090300E4DF42;0x7C8C090300E4DE86;0x7C8C090300E4DED2;0x7C8C090300E4ED06;0x7C8C090300E4DF3E;0x7C8C090300E4DEEA;0x7C8C090300E4DED6

View File

@@ -0,0 +1,12 @@
5NYCZC4
Slot.38: 3825:F303:00C4:0230
Slot.39: 3825:F303:00C4:0FA4
Slot.37: 3825:F303:00C4:023C
Slot.36: 3825:F303:00C4:0EB4
Slot.32: 3825:F303:00C4:0FB0
Slot.33: 3825:F303:00C4:0244
Slot.34: 3825:F303:00C4:0FA0
Slot.35: 3825:F303:00C4:0F90
Slot.31: 3825:F303:00C4:0FA8
Slot.40: 3825:F303:00C4:0F78
GUID: 0x3825F30300C40230;0x3825F30300C40FA4;0x3825F30300C4023C;0x3825F30300C40EB4;0x3825F30300C40FB0;0x3825F30300C40244;0x3825F30300C40FA0;0x3825F30300C40F90;0x3825F30300C40FA8;0x3825F30300C40F78

View File

@@ -0,0 +1,12 @@
6XZCZC4
Slot.38: 3825:F303:00C4:0874
Slot.39: 3825:F303:00C4:035C
Slot.37: 3825:F303:00C4:1450
Slot.36: 3825:F303:00C4:08DC
Slot.32: 3825:F303:00C4:086C
Slot.33: 3825:F303:00C4:0884
Slot.34: 3825:F303:00C4:153C
Slot.35: 3825:F303:00C4:0688
Slot.31: 3825:F303:00C4:096C
Slot.40: 3825:F303:00C4:0870
GUID: 0x3825F30300C40874;0x3825F30300C4035C;0x3825F30300C41450;0x3825F30300C408DC;0x3825F30300C4086C;0x3825F30300C40884;0x3825F30300C4153C;0x3825F30300C40688;0x3825F30300C4096C;0x3825F30300C40870

View File

@@ -0,0 +1,12 @@
7MYCZC4
Slot.38: 7C8C:0903:00E4:DE62
Slot.39: 7C8C:0903:00E4:DE4A
Slot.37: 7C8C:0903:00E4:DE4E
Slot.36: 7C8C:0903:00E4:ECFA
Slot.32: 7C8C:0903:00E4:ECE2
Slot.33: 7C8C:0903:00E4:DE52
Slot.34: 7C8C:0903:00E4:DE76
Slot.35: 7C8C:0903:00E4:ECDE
Slot.31: 7C8C:0903:00E4:DE5A
Slot.40: 7C8C:0903:00E4:ED2E
GUID: 0x7C8C090300E4DE62;0x7C8C090300E4DE4A;0x7C8C090300E4DE4E;0x7C8C090300E4ECFA;0x7C8C090300E4ECE2;0x7C8C090300E4DE52;0x7C8C090300E4DE76;0x7C8C090300E4ECDE;0x7C8C090300E4DE5A;0x7C8C090300E4ED2E

View File

@@ -0,0 +1,12 @@
7XZCZC4
Slot.38: 5000:E603:0068:F498
Slot.39: 5000:E603:0068:F37C
Slot.37: 5000:E603:0068:F2B0
Slot.36: 5000:E603:0068:F418
Slot.32: 5000:E603:0068:F478
Slot.33: 5000:E603:0068:F488
Slot.34: 5000:E603:0068:F3F4
Slot.35: 5000:E603:0068:F474
Slot.31: 5000:E603:0068:F2A8
Slot.40: 5000:E603:0068:F2AC
GUID: 0x5000E6030068F498;0x5000E6030068F37C;0x5000E6030068F2B0;0x5000E6030068F418;0x5000E6030068F478;0x5000E6030068F488;0x5000E6030068F3F4;0x5000E6030068F474;0x5000E6030068F2A8;0x5000E6030068F2AC

View File

@@ -0,0 +1,12 @@
8WZCZC4
Slot.38: 7C8C:0903:00A5:06B0
Slot.39: 7C8C:0903:00A5:0874
Slot.37: 7C8C:0903:00A5:0894
Slot.36: 7C8C:0903:00A5:089C
Slot.32: 7C8C:0903:00A5:0884
Slot.33: 7C8C:0903:00A5:08A4
Slot.34: 7C8C:0903:00A4:E5C4
Slot.35: 7C8C:0903:00A5:0820
Slot.31: 7C8C:0903:00A5:08B8
Slot.40: 7C8C:0903:00A5:0880
GUID: 0x7C8C090300A506B0;0x7C8C090300A50874;0x7C8C090300A50894;0x7C8C090300A5089C;0x7C8C090300A50884;0x7C8C090300A508A4;0x7C8C090300A4E5C4;0x7C8C090300A50820;0x7C8C090300A508B8;0x7C8C090300A50880

View File

@@ -0,0 +1,12 @@
9NYCZC4
Slot.38: 5000:E603:0068:F3E0
Slot.39: 5000:E603:0068:F3B4
Slot.37: 5000:E603:0068:F3E4
Slot.36: 5000:E603:0068:F404
Slot.32: 5000:E603:0068:F358
Slot.33: 5000:E603:0068:F3E8
Slot.34: 5000:E603:0068:F3B8
Slot.35: 5000:E603:0068:F394
Slot.31: 5000:E603:0068:F370
Slot.40: 5000:E603:0068:F364
GUID: 0x5000E6030068F3E0;0x5000E6030068F3B4;0x5000E6030068F3E4;0x5000E6030068F404;0x5000E6030068F358;0x5000E6030068F3E8;0x5000E6030068F3B8;0x5000E6030068F394;0x5000E6030068F370;0x5000E6030068F364

View File

@@ -0,0 +1,12 @@
BNYCZC4
Slot.38: 5000:E603:0068:F248
Slot.39: 5000:E603:0068:F428
Slot.37: 5000:E603:0068:F260
Slot.36: 5000:E603:0068:F200
Slot.32: 5000:E603:0068:F288
Slot.33: 5000:E603:0068:F24C
Slot.34: 5000:E603:0068:F338
Slot.35: 5000:E603:0068:F43C
Slot.31: 5000:E603:0068:F250
Slot.40: 5000:E603:0068:F41C
GUID: 0x5000E6030068F248;0x5000E6030068F428;0x5000E6030068F260;0x5000E6030068F200;0x5000E6030068F288;0x5000E6030068F24C;0x5000E6030068F338;0x5000E6030068F43C;0x5000E6030068F250;0x5000E6030068F41C

View File

@@ -0,0 +1,12 @@
CXZCZC4
Slot.38: 3825:F303:0016:62FA
Slot.39: 3825:F303:0016:7D02
Slot.37: 3825:F303:0016:7E26
Slot.36: 3825:F303:0016:6AEE
Slot.32: 3825:F303:0016:7DAE
Slot.33: 3825:F303:0016:6BBA
Slot.34: 3825:F303:0016:7DC2
Slot.35: 3825:F303:0016:7DD2
Slot.31: 3825:F303:0016:6B7E
Slot.40: 3825:F303:0016:7DD6
GUID: 0x3825F303001662FA;0x3825F30300167D02;0x3825F30300167E26;0x3825F30300166AEE;0x3825F30300167DAE;0x3825F30300166BBA;0x3825F30300167DC2;0x3825F30300167DD2;0x3825F30300166B7E;0x3825F30300167DD6

View File

@@ -0,0 +1,12 @@
DLYCZC4
Slot.38: 5000:E603:0068:F48C
Slot.39: 5000:E603:0068:F3A8
Slot.37: 5000:E603:0068:F400
Slot.36: 5000:E603:0068:F414
Slot.32: 5000:E603:0068:F49C
Slot.33: 5000:E603:0068:F34C
Slot.34: 5000:E603:0068:F348
Slot.35: 5000:E603:0068:F484
Slot.31: 5000:E603:0068:F39C
Slot.40: 5000:E603:0068:F3AC
GUID: 0x5000E6030068F48C;0x5000E6030068F3A8;0x5000E6030068F400;0x5000E6030068F414;0x5000E6030068F49C;0x5000E6030068F34C;0x5000E6030068F348;0x5000E6030068F484;0x5000E6030068F39C;0x5000E6030068F3AC

View File

@@ -0,0 +1,12 @@
DXZCZC4
Slot.38: 3825:F303:00C4:0A6C
Slot.39: 3825:F303:00C4:166C
Slot.37: 3825:F303:00C4:0A3C
Slot.36: 3825:F303:00C4:0A48
Slot.32: 3825:F303:00C4:1664
Slot.33: 3825:F303:00C4:1628
Slot.34: 3825:F303:00C4:1634
Slot.35: 3825:F303:00C4:156C
Slot.31: 3825:F303:00C4:0A70
Slot.40: 3825:F303:00C4:0A68
GUID: 0x3825F30300C40A6C;0x3825F30300C4166C;0x3825F30300C40A3C;0x3825F30300C40A48;0x3825F30300C41664;0x3825F30300C41628;0x3825F30300C41634;0x3825F30300C4156C;0x3825F30300C40A70;0x3825F30300C40A68

View File

@@ -0,0 +1,12 @@
FWZCZC4
Slot.38: 7C8C:0903:00A4:E46C
Slot.39: 7C8C:0903:00A5:0470
Slot.37: 7C8C:0903:00A5:0430
Slot.36: 7C8C:0903:00A5:0438
Slot.32: 7C8C:0903:00A5:046C
Slot.33: 7C8C:0903:00A5:0478
Slot.34: 7C8C:0903:00A5:04F4
Slot.35: 7C8C:0903:00A5:04E0
Slot.31: 7C8C:0903:00A5:04FC
Slot.40: 7C8C:0903:00A5:042C
GUID: 0x7C8C090300A4E46C;0x7C8C090300A50470;0x7C8C090300A50430;0x7C8C090300A50438;0x7C8C090300A5046C;0x7C8C090300A50478;0x7C8C090300A504F4;0x7C8C090300A504E0;0x7C8C090300A504FC;0x7C8C090300A5042C

View File

@@ -0,0 +1,20 @@
1PYCZC4
30:3E:A7:3C:A7:08
30:3E:A7:3C:A7:09
30:3E:A7:3C:A7:0A
30:3E:A7:3C:A7:0B
C8:4B:D6:EE:B1:1C
C8:4B:D6:EE:B1:1D
50:00:E6:68:F3:68
50:00:E6:68:F3:60
50:00:E6:68:F3:50
50:00:E6:68:F3:98
50:00:E6:68:F3:6C
50:00:E6:68:F3:74
50:00:E6:68:F3:EC
50:00:E6:68:F3:C0
50:00:E6:68:F3:5C
50:00:E6:68:F3:78
a8:3c:a5:5c:db:97
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
1XZCZC4
30:3E:A7:38:C6:40
30:3E:A7:38:C6:41
30:3E:A7:38:C6:42
30:3E:A7:38:C6:43
B4:E9:B8:03:41:DA
B4:E9:B8:03:41:DB
38:25:F3:C4:15:EC
38:25:F3:C4:15:F8
38:25:F3:C4:15:E8
38:25:F3:C4:15:E4
38:25:F3:C4:15:64
38:25:F3:C4:15:60
38:25:F3:C4:0A:F4
38:25:F3:C4:16:00
38:25:F3:C4:09:10
38:25:F3:C4:16:08
a8:3c:a5:5d:53:98
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
2NYCZC4
30:3E:A7:38:DD:D0
30:3E:A7:38:DD:D1
30:3E:A7:38:DD:D2
30:3E:A7:38:DD:D3
C8:4B:D6:EE:B1:5E
C8:4B:D6:EE:B1:5F
38:25:F3:C4:04:2C
38:25:F3:C4:04:A8
38:25:F3:C4:04:20
38:25:F3:C4:04:18
38:25:F3:C4:05:08
38:25:F3:C4:12:B4
38:25:F3:C4:12:EC
38:25:F3:C4:12:2C
38:25:F3:C4:04:84
38:25:F3:C4:04:8C
a8:3c:a5:5c:d8:f7
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
2XZCZC4
30:3E:A7:38:C5:E0
30:3E:A7:38:C5:E1
30:3E:A7:38:C5:E2
30:3E:A7:38:C5:E3
B4:E9:B8:03:3D:4A
B4:E9:B8:03:3D:4B
38:25:F3:C4:0A:EC
38:25:F3:C4:0A:D8
38:25:F3:C4:0A:C8
38:25:F3:C4:15:F4
38:25:F3:C4:0A:D0
38:25:F3:C4:0A:E0
38:25:F3:C4:0A:DC
38:25:F3:C4:15:68
38:25:F3:C4:0A:E8
38:25:F3:C4:0A:D4
a8:3c:a5:5d:52:6c
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
3LYCZC4
30:3E:A7:3C:E0:50
30:3E:A7:3C:E0:51
30:3E:A7:3C:E0:52
30:3E:A7:3C:E0:53
C8:4B:D6:EE:B1:48
C8:4B:D6:EE:B1:49
50:00:E6:68:F2:04
50:00:E6:68:F4:64
50:00:E6:68:F2:B8
50:00:E6:68:F2:FC
50:00:E6:68:F2:94
50:00:E6:68:F5:04
50:00:E6:68:F4:50
50:00:E6:68:F2:C4
50:00:E6:68:F5:0C
50:00:E6:68:F4:FC
a8:3c:a5:5c:d9:27
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
3MYCZC4
30:3E:A7:3C:DC:78
30:3E:A7:3C:DC:79
30:3E:A7:3C:DC:7A
30:3E:A7:3C:DC:7B
C8:4B:D6:EE:B1:44
C8:4B:D6:EE:B1:45
50:00:E6:68:F4:80
50:00:E6:68:F2:54
50:00:E6:68:F4:08
50:00:E6:68:F3:3C
50:00:E6:68:F4:0C
50:00:E6:68:F4:AC
50:00:E6:68:F4:C8
50:00:E6:68:F4:10
50:00:E6:68:F4:90
50:00:E6:68:F3:A0
c8:4b:d6:f0:6b:4a
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
3PYCZC4
30:3E:A7:3C:E4:48
30:3E:A7:3C:E4:49
30:3E:A7:3C:E4:4A
30:3E:A7:3C:E4:4B
C8:4B:D6:EE:B1:2E
C8:4B:D6:EE:B1:2F
50:00:E6:68:F4:60
50:00:E6:68:F4:4C
50:00:E6:68:F3:80
50:00:E6:68:F2:BC
50:00:E6:68:F4:EC
50:00:E6:68:F2:74
50:00:E6:68:F4:E4
50:00:E6:68:F2:84
50:00:E6:68:F3:DC
50:00:E6:68:F3:54
a8:3c:a5:5d:52:f6
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
4XZCZC4
30:3E:A7:38:CE:F0
30:3E:A7:38:CE:F1
30:3E:A7:38:CE:F2
30:3E:A7:38:CE:F3
B4:E9:B8:03:45:08
B4:E9:B8:03:45:09
50:00:E6:68:F3:18
50:00:E6:68:F4:58
50:00:E6:68:F2:3C
50:00:E6:68:F0:90
50:00:E6:68:F4:48
50:00:E6:68:F4:40
50:00:E6:68:F3:10
50:00:E6:68:F4:30
50:00:E6:68:F3:C8
50:00:E6:68:F4:38
a8:3c:a5:5c:dc:03
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
5MYCZC4
30:3E:A7:38:C6:F8
30:3E:A7:38:C6:F9
30:3E:A7:38:C6:FA
30:3E:A7:38:C6:FB
B4:E9:B8:03:43:3E
B4:E9:B8:03:43:3F
7C:8C:09:E4:DE:9E
7C:8C:09:E4:DE:DE
7C:8C:09:E4:DE:96
7C:8C:09:E4:DF:42
7C:8C:09:E4:DE:86
7C:8C:09:E4:DE:D2
7C:8C:09:E4:ED:06
7C:8C:09:E4:DF:3E
7C:8C:09:E4:DE:EA
7C:8C:09:E4:DE:D6
c8:4b:d6:f0:6b:50
H
SOLIDIGM

View File

@@ -0,0 +1,20 @@
5NYCZC4
30:3E:A7:38:DC:F0
30:3E:A7:38:DC:F1
30:3E:A7:38:DC:F2
30:3E:A7:38:DC:F3
C8:4B:D6:EE:B1:5C
C8:4B:D6:EE:B1:5D
38:25:F3:C4:02:30
38:25:F3:C4:0F:A4
38:25:F3:C4:02:3C
38:25:F3:C4:0E:B4
38:25:F3:C4:0F:B0
38:25:F3:C4:02:44
38:25:F3:C4:0F:A0
38:25:F3:C4:0F:90
38:25:F3:C4:0F:A8
38:25:F3:C4:0F:78
a8:3c:a5:5c:d3:57
H
SOLIDIGM

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