Initial commit
This commit is contained in:
22
.env
Normal file
22
.env
Normal 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
|
||||||
BIN
__pycache__/app.cpython-313.pyc
Normal file
BIN
__pycache__/app.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/config.cpython-313.pyc
Normal file
BIN
__pycache__/config.cpython-313.pyc
Normal file
Binary file not shown.
105
app.py
Normal file
105
app.py
Normal 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)
|
||||||
BIN
backend/forms/__pycache__/auth_forms.cpython-313.pyc
Normal file
BIN
backend/forms/__pycache__/auth_forms.cpython-313.pyc
Normal file
Binary file not shown.
116
backend/forms/auth_forms.py
Normal file
116
backend/forms/auth_forms.py
Normal 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
BIN
backend/instance/site.db
Normal file
Binary file not shown.
BIN
backend/models/__pycache__/user.cpython-313.pyc
Normal file
BIN
backend/models/__pycache__/user.cpython-313.pyc
Normal file
Binary file not shown.
127
backend/models/user.py
Normal file
127
backend/models/user.py
Normal 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),
|
||||||
|
Werkzeug 형식(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
|
||||||
20
backend/routes/__init__.py
Normal file
20
backend/routes/__init__.py
Normal 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)
|
||||||
BIN
backend/routes/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/admin.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/auth.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/file_view.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/file_view.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/home.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/home.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/main.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/utilities.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/utilities.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/routes/__pycache__/xml.cpython-313.pyc
Normal file
BIN
backend/routes/__pycache__/xml.cpython-313.pyc
Normal file
Binary file not shown.
126
backend/routes/admin.py
Normal file
126
backend/routes/admin.py
Normal 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
180
backend/routes/auth.py
Normal 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"))
|
||||||
95
backend/routes/file_view.py
Normal file
95
backend/routes/file_view.py
Normal 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
13
backend/routes/home.py
Normal 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
208
backend/routes/main.py
Normal 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
195
backend/routes/utilities.py
Normal 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
105
backend/routes/xml.py
Normal 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)
|
||||||
BIN
backend/services/__pycache__/ip_processor.cpython-313.pyc
Normal file
BIN
backend/services/__pycache__/ip_processor.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/services/__pycache__/logger.cpython-313.pyc
Normal file
BIN
backend/services/__pycache__/logger.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/services/__pycache__/watchdog_handler.cpython-313.pyc
Normal file
BIN
backend/services/__pycache__/watchdog_handler.cpython-313.pyc
Normal file
Binary file not shown.
152
backend/services/ip_processor.py
Normal file
152
backend/services/ip_processor.py
Normal 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
|
||||||
65
backend/services/logger.py
Normal file
65
backend/services/logger.py
Normal 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
|
||||||
50
backend/services/watchdog_handler.py
Normal file
50
backend/services/watchdog_handler.py
Normal 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})
|
||||||
13
backend/socketio_events.py
Normal file
13
backend/socketio_events.py
Normal 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("⚠️ 클라이언트 연결이 해제되었습니다.")
|
||||||
BIN
backend/static/android-chrome-192x192.png
Normal file
BIN
backend/static/android-chrome-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
backend/static/android-chrome-512x512.png
Normal file
BIN
backend/static/android-chrome-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
backend/static/apple-touch-icon.png
Normal file
BIN
backend/static/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
BIN
backend/static/favicon-16x16.png
Normal file
BIN
backend/static/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 579 B |
BIN
backend/static/favicon-32x32.png
Normal file
BIN
backend/static/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
backend/static/favicon.ico
Normal file
BIN
backend/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
264
backend/static/script.js
Normal file
264
backend/static/script.js
Normal 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
534
backend/static/style.css
Normal 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;
|
||||||
|
}
|
||||||
169
backend/templates/admin.html
Normal file
169
backend/templates/admin.html
Normal 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
121
backend/templates/base.html
Normal 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">© 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>
|
||||||
51
backend/templates/edit_xml.html
Normal file
51
backend/templates/edit_xml.html
Normal 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 %}
|
||||||
6
backend/templates/home.html
Normal file
6
backend/templates/home.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Dell Server & NAVER Settings Info</h2>
|
||||||
|
<p>사이트 가입 및 로그인후 이용 가능</p>
|
||||||
|
{% endblock %}
|
||||||
676
backend/templates/index.html
Normal file
676
backend/templates/index.html
Normal 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="예: 192.168.1.1 192.168.1.2 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 %}
|
||||||
26
backend/templates/login.html
Normal file
26
backend/templates/login.html
Normal 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 %}
|
||||||
305
backend/templates/manage_xml.html
Normal file
305
backend/templates/manage_xml.html
Normal 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 %}
|
||||||
30
backend/templates/register.html
Normal file
30
backend/templates/register.html
Normal 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 %}
|
||||||
21
backend/templates/xml_files.html
Normal file
21
backend/templates/xml_files.html
Normal 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
86
config.py
Normal 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
|
||||||
BIN
data/backend/instance/site.db
Normal file
BIN
data/backend/instance/site.db
Normal file
Binary file not shown.
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
12
data/guid_file/1XZCZC4.txt
Normal file
12
data/guid_file/1XZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/2NYCZC4.txt
Normal file
12
data/guid_file/2NYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/2XZCZC4.txt
Normal file
12
data/guid_file/2XZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/3LYCZC4.txt
Normal file
12
data/guid_file/3LYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/3MYCZC4.txt
Normal file
12
data/guid_file/3MYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/3PYCZC4.txt
Normal file
12
data/guid_file/3PYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/4XZCZC4.txt
Normal file
12
data/guid_file/4XZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/5MYCZC4.txt
Normal file
12
data/guid_file/5MYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/5NYCZC4.txt
Normal file
12
data/guid_file/5NYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/6XZCZC4.txt
Normal file
12
data/guid_file/6XZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/7MYCZC4.txt
Normal file
12
data/guid_file/7MYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/7XZCZC4.txt
Normal file
12
data/guid_file/7XZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/8WZCZC4.txt
Normal file
12
data/guid_file/8WZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/9NYCZC4.txt
Normal file
12
data/guid_file/9NYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/BNYCZC4.txt
Normal file
12
data/guid_file/BNYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/CXZCZC4.txt
Normal file
12
data/guid_file/CXZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/DLYCZC4.txt
Normal file
12
data/guid_file/DLYCZC4.txt
Normal 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
|
||||||
12
data/guid_file/DXZCZC4.txt
Normal file
12
data/guid_file/DXZCZC4.txt
Normal 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
|
||||||
12
data/guid_file/FWZCZC4.txt
Normal file
12
data/guid_file/FWZCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/1PYCZC4.txt
Normal file
20
data/idrac_info/1PYCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/1XZCZC4.txt
Normal file
20
data/idrac_info/1XZCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/2NYCZC4.txt
Normal file
20
data/idrac_info/2NYCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/2XZCZC4.txt
Normal file
20
data/idrac_info/2XZCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/3LYCZC4.txt
Normal file
20
data/idrac_info/3LYCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/3MYCZC4.txt
Normal file
20
data/idrac_info/3MYCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/3PYCZC4.txt
Normal file
20
data/idrac_info/3PYCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/4XZCZC4.txt
Normal file
20
data/idrac_info/4XZCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/5MYCZC4.txt
Normal file
20
data/idrac_info/5MYCZC4.txt
Normal 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
|
||||||
20
data/idrac_info/5NYCZC4.txt
Normal file
20
data/idrac_info/5NYCZC4.txt
Normal 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
Reference in New Issue
Block a user