|
|
|
|
@@ -19,161 +19,209 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected renderFrameHTML(): string {
|
|
|
|
|
// CSS 명세(modal.css)의 input 패딩(0.625rem)과 일치시켜 정렬을 완벽하게 잡는 스타일
|
|
|
|
|
const standardBtnStyle = 'height: auto !important; padding: 0.625rem 1.25rem; font-size: 0.875rem; line-height: 1.2; display: inline-flex; align-items: center; justify-content: center;';
|
|
|
|
|
const sharedStyle = 'height: 38px !important; box-sizing: border-box !important; font-size: 13px; margin: 0;';
|
|
|
|
|
const inputStyle = sharedStyle;
|
|
|
|
|
const btnStyle = `padding: 0 16px; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; white-space: nowrap; cursor: pointer; ${sharedStyle}`;
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
<div id="hw-asset-modal" class="modal-overlay hidden">
|
|
|
|
|
<div class="modal-content wide">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h2 id="hw-modal-title">${this.title}</h2>
|
|
|
|
|
<button id="btn-close-hw-modal" class="btn-icon" aria-label="닫기" style="font-size: 24px; color: white; background: none; border: none; cursor: pointer;">×</button>
|
|
|
|
|
<h2 id="hw-modal-title" style="margin: 0; font-size: 18px; font-weight: 800; color: white;">${this.title}</h2>
|
|
|
|
|
<button id="btn-close-hw-modal" class="btn-icon" aria-label="닫기" style="font-size: 28px; color: white; background: none; border: none; cursor: pointer; line-height: 1;">×</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
<div class="modal-body" style="padding: 24px; overflow-y: auto;">
|
|
|
|
|
<div class="modal-body-split">
|
|
|
|
|
<div class="modal-form-area">
|
|
|
|
|
<form id="hw-asset-form" class="grid-form">
|
|
|
|
|
<input type="hidden" id="hw-id" name="id" />
|
|
|
|
|
|
|
|
|
|
<div class="form-section-title" style="padding-top: 0;">기본 정보</div>
|
|
|
|
|
<!-- [SECTION 1] 기본 관리 정보 (필수 공통) -->
|
|
|
|
|
<div class="form-section-title" style="padding-top: 0; margin-bottom: 12px;">기본 관리 정보</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.ASSET_CODE.ui}</label>
|
|
|
|
|
<div class="input-with-btn">
|
|
|
|
|
<input type="text" id="hw-asset_code" name="asset_code" placeholder="자동 생성" readonly />
|
|
|
|
|
<button type="button" id="btn-gen-hw-code" class="btn btn-outline" style="${standardBtnStyle}">생성</button>
|
|
|
|
|
<div class="input-with-btn" style="display: flex; gap: 8px; align-items: stretch;">
|
|
|
|
|
<input type="text" id="hw-asset_code" name="asset_code" placeholder="자동 생성" readonly style="flex: 1; ${inputStyle}" />
|
|
|
|
|
<button type="button" id="btn-gen-hw-code" class="btn btn-outline" style="${btnStyle}">생성</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.PURCHASE_CORP.ui}</label>
|
|
|
|
|
<select id="hw-purchase_corp" name="purchase_corp">${generateOptionsHTML(CORP_LIST)}</select>
|
|
|
|
|
<select id="hw-purchase_corp" name="purchase_corp" style="${inputStyle}">${generateOptionsHTML(CORP_LIST)}</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.CATEGORY.ui}</label>
|
|
|
|
|
<select id="hw-category" name="category">
|
|
|
|
|
<select id="hw-category" name="category" style="${inputStyle}">
|
|
|
|
|
<option value="">선택</option>
|
|
|
|
|
${generateOptionsHTML(Object.keys(CATEGORY_TYPE_MAP), '', false)}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.ASSET_TYPE.ui}</label>
|
|
|
|
|
<select id="hw-asset_type" name="asset_type">
|
|
|
|
|
<select id="hw-asset_type" name="asset_type" style="${inputStyle}">
|
|
|
|
|
<option value="">구분을 먼저 선택하세요</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.HW_STATUS.ui}</label>
|
|
|
|
|
<select id="hw-hw_status" name="hw_status">${generateOptionsHTML(HW_STATUS_LIST)}</select>
|
|
|
|
|
<select id="hw-hw_status" name="hw_status" style="${inputStyle}">${generateOptionsHTML(HW_STATUS_LIST)}</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group server-only">
|
|
|
|
|
<div class="form-group infra-only monitoring-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.MONITORING.ui}</label>
|
|
|
|
|
<select id="hw-monitoring" name="monitoring">
|
|
|
|
|
<select id="hw-monitoring" name="monitoring" style="${inputStyle}">
|
|
|
|
|
<option value="비대상">비대상</option>
|
|
|
|
|
<option value="대상">대상</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="form-section-title user-tracking-field">담당 및 조직</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<!-- [SECTION 2] 조직 및 사용자 정보 -->
|
|
|
|
|
<div class="form-section-title org-user-section" style="margin-top: 24px; margin-bottom: 12px;">사용자 및 조직 정보</div>
|
|
|
|
|
<div class="form-group org-user-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.CURRENT_DEPT.ui}</label>
|
|
|
|
|
<select id="hw-current_dept" name="current_dept">${generateOptionsHTML(ORG_LIST)}</select>
|
|
|
|
|
<select id="hw-current_dept" name="current_dept" style="${inputStyle}">${generateOptionsHTML(ORG_LIST)}</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<div class="form-group org-user-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.MANAGER_MAIN.ui}</label>
|
|
|
|
|
<input type="text" id="hw-manager_primary" name="manager_primary" />
|
|
|
|
|
<input type="text" id="hw-manager_primary" name="manager_primary" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group user-tracking-field">
|
|
|
|
|
<div class="form-group personal-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.CURRENT_USER.ui}</label>
|
|
|
|
|
<input type="text" id="hw-user_current" name="user_current" />
|
|
|
|
|
<input type="text" id="hw-user_current" name="user_current" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group user-tracking-field">
|
|
|
|
|
<div class="form-group personal-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.USER_POSITION.ui}</label>
|
|
|
|
|
<input type="text" id="hw-user_position" name="user_position" />
|
|
|
|
|
<input type="text" id="hw-user_position" name="user_position" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.MANAGER_SUB.ui}</label>
|
|
|
|
|
<input type="text" id="hw-manager_secondary" name="manager_secondary" />
|
|
|
|
|
<input type="text" id="hw-manager_secondary" name="manager_secondary" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group user-tracking-field">
|
|
|
|
|
<div class="form-group personal-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.PREV_USER.ui}</label>
|
|
|
|
|
<input type="text" id="hw-previous_user" name="previous_user" />
|
|
|
|
|
<input type="text" id="hw-previous_user" name="previous_user" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="form-section-title">시스템 사양 및 접속 정보</div>
|
|
|
|
|
<!-- [SECTION 3] 하드웨어 사양 및 네트워크 -->
|
|
|
|
|
<div class="form-section-title hardware-section" style="margin-top: 24px; margin-bottom: 12px;">시스템 사양 및 네트워크</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.MODEL_NAME.ui}</label>
|
|
|
|
|
<input type="text" id="hw-model_name" name="model_name" />
|
|
|
|
|
<input type="text" id="hw-model_name" name="model_name" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.ASSET_MFR.ui}</label>
|
|
|
|
|
<input type="text" id="hw-asset_mfr" name="asset_mfr" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group sn-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.SERIAL_NUM.ui}</label>
|
|
|
|
|
<input type="text" id="hw-serial_num" name="serial_num" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group spec-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.OS.ui}</label>
|
|
|
|
|
<input type="text" id="hw-os" name="os" />
|
|
|
|
|
<input type="text" id="hw-os" name="os" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<div class="form-group spec-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.CPU.ui}</label>
|
|
|
|
|
<input type="text" id="hw-cpu" name="cpu" />
|
|
|
|
|
<input type="text" id="hw-cpu" name="cpu" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<div class="form-group spec-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.RAM.ui}</label>
|
|
|
|
|
<input type="text" id="hw-ram" name="ram" />
|
|
|
|
|
<input type="text" id="hw-ram" name="ram" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.IP_ADDR.ui}</label>
|
|
|
|
|
<input type="text" id="hw-ip_address" name="ip_address" />
|
|
|
|
|
<div class="form-group spec-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.GPU.ui}</label>
|
|
|
|
|
<input type="text" id="hw-gpu" name="gpu" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group server-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.IP_ADDR2.ui}</label>
|
|
|
|
|
<input type="text" id="hw-ip_address_2" name="ip_address_2" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.MAC_ADDR.ui}</label>
|
|
|
|
|
<input type="text" id="hw-mac_address" name="mac_address" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group server-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.REMOTE_TOOL.ui}</label>
|
|
|
|
|
<input type="text" id="hw-remote_tool" name="remote_tool" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group server-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.REMOTE_ID.ui}</label>
|
|
|
|
|
<input type="text" id="hw-remote_id" name="remote_id" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group server-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.REMOTE_PW.ui}</label>
|
|
|
|
|
<input type="text" id="hw-remote_pw" name="remote_pw" />
|
|
|
|
|
<div class="form-group spec-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.MAINBOARD.ui}</label>
|
|
|
|
|
<input type="text" id="hw-mainboard" name="mainboard" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="form-section-title infra-only">설치 위치</div>
|
|
|
|
|
<div class="form-group infra-only">
|
|
|
|
|
<label>건물/위치</label>
|
|
|
|
|
<select id="hw-bldg-select" name="location">${generateOptionsHTML(Object.keys(LOCATION_DATA))}</select>
|
|
|
|
|
<!-- 동적 디스크 할당 영역 (Plan B) -->
|
|
|
|
|
<div class="form-group spec-only full-width" style="grid-column: span 2;">
|
|
|
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
|
|
|
<label style="margin: 0; font-size: 11px; font-weight: 700; color: var(--text-muted);">저장장치 (디스크)</label>
|
|
|
|
|
<button type="button" id="btn-add-volume" class="btn btn-outline" style="height: 26px !important; padding: 0 10px; font-size: 11px; display: none;">+ 디스크 추가</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="hw-volume-container" style="display: flex; flex-direction: column; gap: 8px;"></div>
|
|
|
|
|
<input type="hidden" id="hw-volumes-data" name="volumes" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group infra-only">
|
|
|
|
|
|
|
|
|
|
<div class="form-group net-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.IP_ADDR.ui}</label>
|
|
|
|
|
<input type="text" id="hw-ip_address" name="ip_address" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group net-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.MAC_ADDR.ui}</label>
|
|
|
|
|
<input type="text" id="hw-mac_address" name="mac_address" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group monitor-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.MONITOR_INCH.ui}</label>
|
|
|
|
|
<input type="text" id="hw-monitor_inch" name="monitor_inch" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group parts-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.VOLUME.ui}</label>
|
|
|
|
|
<input type="text" id="hw-volume" name="volume" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group parts-only">
|
|
|
|
|
<label>${ASSET_SCHEMA.ASSET_COUNT.ui}</label>
|
|
|
|
|
<input type="text" id="hw-asset_count" name="asset_count" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- [SECTION 4] 원격 접속 정보 (서버 전용) -->
|
|
|
|
|
<div class="form-section-title remote-section" style="margin-top: 24px; margin-bottom: 12px;">원격 접속 정보</div>
|
|
|
|
|
<div class="form-group remote-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.IP_ADDR2.ui}</label>
|
|
|
|
|
<input type="text" id="hw-ip_address_2" name="ip_address_2" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group remote-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.REMOTE_TOOL.ui}</label>
|
|
|
|
|
<input type="text" id="hw-remote_tool" name="remote_tool" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group remote-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.REMOTE_ID.ui}</label>
|
|
|
|
|
<input type="text" id="hw-remote_id" name="remote_id" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group remote-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.REMOTE_PW.ui}</label>
|
|
|
|
|
<input type="text" id="hw-remote_pw" name="remote_pw" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- [SECTION 5] 설치 위치 (인프라/실물 장비 전용) -->
|
|
|
|
|
<div class="form-section-title location-section" style="margin-top: 24px; margin-bottom: 12px;">설치 위치</div>
|
|
|
|
|
<div class="form-group location-field">
|
|
|
|
|
<label>건물/위치</label>
|
|
|
|
|
<select id="hw-bldg-select" name="location" style="${inputStyle}">${generateOptionsHTML(Object.keys(LOCATION_DATA))}</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group location-field">
|
|
|
|
|
<label>${ASSET_SCHEMA.LOC_DETAIL.ui}</label>
|
|
|
|
|
<div class="input-with-btn">
|
|
|
|
|
<select id="hw-location_detail" name="location_detail" style="flex: 1;"><option value="">선택</option></select>
|
|
|
|
|
<button type="button" id="btn-reg-loc-map" class="btn btn-primary" style="${standardBtnStyle} display: none;">위치등록</button>
|
|
|
|
|
<button type="button" id="btn-view-loc-map" class="btn btn-primary btn-loc-action" style="${standardBtnStyle} display: none; pointer-events: auto !important;">위치보기</button>
|
|
|
|
|
<div class="input-with-btn" style="display: flex; gap: 8px; align-items: stretch;">
|
|
|
|
|
<select id="hw-location_detail" name="location_detail" style="flex: 1; ${inputStyle}"><option value="">선택</option></select>
|
|
|
|
|
<button type="button" id="btn-reg-loc-map" class="btn btn-primary" style="${btnStyle} display: none;">위치등록</button>
|
|
|
|
|
<button type="button" id="btn-view-loc-map" class="btn btn-primary btn-loc-action btn-loc-view" style="${btnStyle} display: none; pointer-events: auto !important; cursor: pointer !important;">위치보기</button>
|
|
|
|
|
</div>
|
|
|
|
|
<input type="hidden" id="hw-loc_x" name="loc_x" /><input type="hidden" id="hw-loc_y" name="loc_y" /><input type="hidden" id="hw-location_photo" name="location_photo" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="form-section-title">구매 및 증빙 정보</div>
|
|
|
|
|
<!-- [SECTION 6] 구매 및 증빙 (공통) -->
|
|
|
|
|
<div class="form-section-title" style="margin-top: 24px; margin-bottom: 12px;">구매 및 증빙 정보</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.PURCHASE_DATE.ui}</label>
|
|
|
|
|
<input type="text" id="hw-purchase_date" name="purchase_date" placeholder="YYYY-MM-DD" />
|
|
|
|
|
<input type="text" id="hw-purchase_date" name="purchase_date" placeholder="YYYY-MM-DD" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.PURCHASE_VENDOR.ui}</label>
|
|
|
|
|
<input type="text" id="hw-purchase_vendor" name="purchase_vendor" />
|
|
|
|
|
<input type="text" id="hw-purchase_vendor" name="purchase_vendor" style="${inputStyle}" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.PURCHASE_AMOUNT.ui}</label>
|
|
|
|
|
<input type="text" id="hw-purchase_amount" name="purchase_amount" placeholder="0" oninput="this.value = this.value.replace(/[^0-9]/g, '').replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')" />
|
|
|
|
|
<input type="text" id="hw-purchase_amount" name="purchase_amount" placeholder="0" style="${inputStyle}" oninput="this.value = this.value.replace(/[^0-9]/g, '').replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>${ASSET_SCHEMA.APPROVAL_DOC.ui} (첨부파일)</label>
|
|
|
|
|
<div class="file-upload-wrapper">
|
|
|
|
|
<input type="file" id="hw-approval_document_file" style="display:none;" />
|
|
|
|
|
<div class="input-with-btn">
|
|
|
|
|
<button type="button" id="btn-file-select" onclick="document.getElementById('hw-approval_document_file').click()" class="btn btn-outline btn-loc-action" style="${standardBtnStyle} flex: 1; justify-content: flex-start; pointer-events: auto !important;">
|
|
|
|
|
<div class="input-with-btn" style="display: flex; gap: 8px; align-items: stretch;">
|
|
|
|
|
<button type="button" id="btn-file-select" onclick="document.getElementById('hw-approval_document_file').click()" class="btn btn-outline btn-loc-action" style="${btnStyle} flex: 1; justify-content: flex-start; pointer-events: auto !important; cursor: pointer !important;">
|
|
|
|
|
<span id="hw-file-name-display">파일 선택...</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -183,26 +231,26 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group full-width">
|
|
|
|
|
<label>${ASSET_SCHEMA.MEMO.ui}</label>
|
|
|
|
|
<textarea id="hw-memo" name="memo" rows="3"></textarea>
|
|
|
|
|
<textarea id="hw-memo" name="memo" rows="3" style="width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; font-family: inherit; font-size: 13px; resize: none !important; box-sizing: border-box;"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="modal-history-area">
|
|
|
|
|
<div class="history-header">
|
|
|
|
|
<h3>자산 변동 이력</h3>
|
|
|
|
|
<button type="button" id="btn-add-hw-log" class="btn btn-outline btn-sm">이력 추가</button>
|
|
|
|
|
<div class="history-header" style="border-bottom: 1px solid var(--border-color); padding-bottom: 12px; margin-bottom: 16px;">
|
|
|
|
|
<h3 style="margin: 0; font-size: 14px; font-weight: 800;">자산 변동 이력</h3>
|
|
|
|
|
<button type="button" id="btn-add-hw-log" class="btn btn-outline btn-sm" style="height: 30px; font-size: 11px;">이력 추가</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="hw-history-list" class="history-timeline"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
<button id="btn-delete-hw-asset" class="btn btn-outline btn-danger">삭제</button>
|
|
|
|
|
<button id="btn-delete-hw-asset" class="btn btn-outline btn-danger" style="height: 42px;">삭제</button>
|
|
|
|
|
<div class="footer-actions">
|
|
|
|
|
<button id="btn-revert-hw-edit" class="btn btn-outline hidden">수정 취소</button>
|
|
|
|
|
<button id="btn-cancel-hw-modal" class="btn btn-outline">닫기</button>
|
|
|
|
|
<button id="btn-save-hw-asset" class="btn btn-primary">저장</button>
|
|
|
|
|
<button id="btn-revert-hw-edit" class="btn btn-outline hidden" style="height: 42px;">수정 취소</button>
|
|
|
|
|
<button id="btn-cancel-hw-modal" class="btn btn-outline" style="height: 42px;">닫기</button>
|
|
|
|
|
<button id="btn-save-hw-asset" class="btn btn-primary" style="height: 42px;">저장</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -280,8 +328,13 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
this.setEditLockMode('view');
|
|
|
|
|
if (this.currentAsset) this.fillFormData(this.currentAsset);
|
|
|
|
|
this.updateMapButtonVisibility();
|
|
|
|
|
this.toggleEditOnlyBtns(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 동적 볼륨 추가 기능 연결
|
|
|
|
|
const btnAddVolume = document.getElementById('btn-add-volume')!;
|
|
|
|
|
btnAddVolume.addEventListener('click', () => this.addVolumeRow());
|
|
|
|
|
|
|
|
|
|
const fileInput = document.getElementById('hw-approval_document_file') as HTMLInputElement;
|
|
|
|
|
const fileNameDisplay = document.getElementById('hw-file-name-display');
|
|
|
|
|
const fileLinkContainer = document.getElementById('hw-file-link-container');
|
|
|
|
|
@@ -317,12 +370,25 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
this.isEditMode = true;
|
|
|
|
|
this.updateMapButtonVisibility();
|
|
|
|
|
this.toggleFileUploadUI(true);
|
|
|
|
|
this.toggleEditOnlyBtns(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 동적 볼륨 데이터 수집 및 배열 생성
|
|
|
|
|
const vols: any[] = [];
|
|
|
|
|
document.querySelectorAll('#hw-volume-container .volume-row').forEach((row, idx) => {
|
|
|
|
|
const type = (row.querySelector('.vol-type') as HTMLSelectElement).value;
|
|
|
|
|
const cap = (row.querySelector('.vol-cap') as HTMLInputElement).value;
|
|
|
|
|
const unit = (row.querySelector('.vol-unit') as HTMLSelectElement).value;
|
|
|
|
|
if (cap) vols.push({ type, capacity: parseFloat(cap), unit, slot: idx + 1 });
|
|
|
|
|
});
|
|
|
|
|
setFieldValue('hw-volumes-data', JSON.stringify(vols));
|
|
|
|
|
|
|
|
|
|
const formData = new FormData(this.formEl!);
|
|
|
|
|
const updated = { ...this.currentAsset };
|
|
|
|
|
formData.forEach((value, key) => { if (key !== 'id') updated[key] = value; });
|
|
|
|
|
updated.location = getFieldValue('hw-bldg-select');
|
|
|
|
|
|
|
|
|
|
if (await saveAsset(this.getCategoryKey(updated), updated)) {
|
|
|
|
|
alert(UI_TEXT.MESSAGES.SAVE_SUCCESS);
|
|
|
|
|
onSave(); this.close(); closeModals();
|
|
|
|
|
@@ -330,6 +396,44 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private addVolumeRow(vol: any = { type: 'SSD', capacity: '', unit: 'GB' }) {
|
|
|
|
|
const container = document.getElementById('hw-volume-container');
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
const row = document.createElement('div');
|
|
|
|
|
row.style.display = 'flex';
|
|
|
|
|
row.style.gap = '8px';
|
|
|
|
|
row.style.alignItems = 'center';
|
|
|
|
|
row.className = 'volume-row';
|
|
|
|
|
|
|
|
|
|
const inputStyle = 'height: 38px !important; box-sizing: border-box !important; font-size: 13px; margin: 0; padding: 0 8px;';
|
|
|
|
|
|
|
|
|
|
row.innerHTML = `
|
|
|
|
|
<select class="vol-type" style="${inputStyle} width: 80px;" ${!this.isEditMode ? 'disabled' : ''}>
|
|
|
|
|
<option value="SSD" ${vol.type === 'SSD' ? 'selected' : ''}>SSD</option>
|
|
|
|
|
<option value="HDD" ${vol.type === 'HDD' ? 'selected' : ''}>HDD</option>
|
|
|
|
|
<option value="NVMe" ${vol.type === 'NVMe' ? 'selected' : ''}>NVMe</option>
|
|
|
|
|
</select>
|
|
|
|
|
<input type="number" class="vol-cap" value="${vol.capacity || ''}" placeholder="용량" style="${inputStyle} flex: 1;" ${!this.isEditMode ? 'readonly' : ''} />
|
|
|
|
|
<select class="vol-unit" style="${inputStyle} width: 70px;" ${!this.isEditMode ? 'disabled' : ''}>
|
|
|
|
|
<option value="GB" ${vol.unit === 'GB' ? 'selected' : ''}>GB</option>
|
|
|
|
|
<option value="TB" ${vol.unit === 'TB' ? 'selected' : ''}>TB</option>
|
|
|
|
|
</select>
|
|
|
|
|
<button type="button" class="btn btn-outline btn-remove-vol edit-only-btn" style="height: 38px !important; padding: 0 12px; color: #E11D48; border-color: #E11D48; display: ${this.isEditMode ? 'inline-flex' : 'none'};">×</button>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
row.querySelector('.btn-remove-vol')?.addEventListener('click', () => row.remove());
|
|
|
|
|
container.appendChild(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private toggleEditOnlyBtns(isEdit: boolean) {
|
|
|
|
|
const addBtn = document.getElementById('btn-add-volume');
|
|
|
|
|
if (addBtn) addBtn.style.display = isEdit ? 'inline-flex' : 'none';
|
|
|
|
|
document.querySelectorAll('.edit-only-btn').forEach(btn => {
|
|
|
|
|
(btn as HTMLElement).style.display = isEdit ? 'inline-flex' : 'none';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected fillFormData(asset: any): void {
|
|
|
|
|
setFieldValue('hw-id', asset.id);
|
|
|
|
|
setFieldValue('hw-asset_code', asset.asset_code || '');
|
|
|
|
|
@@ -347,12 +451,35 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
setFieldValue('hw-user_position', asset.user_position || '');
|
|
|
|
|
setFieldValue('hw-previous_user', asset.previous_user || '');
|
|
|
|
|
setFieldValue('hw-model_name', asset.model_name || '');
|
|
|
|
|
setFieldValue('hw-asset_mfr', asset.asset_mfr || '');
|
|
|
|
|
setFieldValue('hw-os', asset.os || '');
|
|
|
|
|
setFieldValue('hw-cpu', asset.cpu || '');
|
|
|
|
|
setFieldValue('hw-ram', asset.ram || '');
|
|
|
|
|
setFieldValue('hw-os', asset.os || '');
|
|
|
|
|
setFieldValue('hw-mac_address', asset.mac_address || '');
|
|
|
|
|
setFieldValue('hw-gpu', asset.gpu || '');
|
|
|
|
|
setFieldValue('hw-mainboard', asset.mainboard || '');
|
|
|
|
|
|
|
|
|
|
// 동적 볼륨 렌더링 초기화 및 생성
|
|
|
|
|
const volumeContainer = document.getElementById('hw-volume-container');
|
|
|
|
|
if (volumeContainer) {
|
|
|
|
|
volumeContainer.innerHTML = '';
|
|
|
|
|
let vols = [];
|
|
|
|
|
try {
|
|
|
|
|
vols = asset.volumes ? (typeof asset.volumes === 'string' ? JSON.parse(asset.volumes) : asset.volumes) : [];
|
|
|
|
|
} catch(e) {}
|
|
|
|
|
vols.forEach((v: any) => this.addVolumeRow(v));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setFieldValue('hw-ip_address', asset.ip_address || '');
|
|
|
|
|
setFieldValue('hw-ip_address_2', asset.ip_address_2 || '');
|
|
|
|
|
setFieldValue('hw-mac_address', asset.mac_address || '');
|
|
|
|
|
setFieldValue('hw-remote_tool', asset.remote_tool || '');
|
|
|
|
|
setFieldValue('hw-remote_id', asset.remote_id || '');
|
|
|
|
|
setFieldValue('hw-remote_pw', asset.remote_pw || '');
|
|
|
|
|
setFieldValue('hw-monitoring', asset.monitoring || '비대상');
|
|
|
|
|
setFieldValue('hw-serial_num', asset.serial_num || '');
|
|
|
|
|
setFieldValue('hw-monitor_inch', asset.monitor_inch || '');
|
|
|
|
|
setFieldValue('hw-volume', asset.volume || '');
|
|
|
|
|
setFieldValue('hw-asset_count', asset.asset_count || '');
|
|
|
|
|
setFieldValue('hw-purchase_date', asset.purchase_date || '');
|
|
|
|
|
setFieldValue('hw-purchase_vendor', asset.purchase_vendor || '');
|
|
|
|
|
setFieldValue('hw-purchase_amount', asset.purchase_amount || '');
|
|
|
|
|
@@ -379,6 +506,7 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
const genBtn = document.getElementById('btn-gen-hw-code');
|
|
|
|
|
if (genBtn) genBtn.style.display = (mode === 'add') ? 'inline-flex' : 'none';
|
|
|
|
|
this.toggleFileUploadUI(mode !== 'view');
|
|
|
|
|
this.toggleEditOnlyBtns(mode !== 'view');
|
|
|
|
|
this.updateMapButtonVisibility(asset);
|
|
|
|
|
this.applyRoleVisibility();
|
|
|
|
|
}
|
|
|
|
|
@@ -392,15 +520,41 @@ class HwAssetModal extends BaseModal {
|
|
|
|
|
const category = (document.getElementById('hw-category') as HTMLSelectElement)?.value || '';
|
|
|
|
|
const type = (document.getElementById('hw-asset_type') as HTMLSelectElement)?.value || '';
|
|
|
|
|
|
|
|
|
|
// 인프라 장비 (서버, 서버PC, 저장시스템 등)
|
|
|
|
|
const isInfra = ['서버', '저장매체', '네트워크', '공간정보장비'].includes(category) || type.includes('서버') || type.includes('저장시스템');
|
|
|
|
|
// 개인 장비 (PC, 노트북) - '서버PC'는 제외
|
|
|
|
|
const isPersonal = (['PC', '노트북'].includes(category) || type.includes('개인PC') || type.includes('노트북')) && !type.includes('서버PC');
|
|
|
|
|
// 인프라 장비 (서버, 저장매체, 네트워크, 보안장비, 공간정보장비, 서버PC)
|
|
|
|
|
const infraCategories = ['서버', '저장매체', '네트워크', '보안장비', '공간정보장비'];
|
|
|
|
|
const isInfra = infraCategories.includes(category) || type.includes('서버') || type.includes('저장시스템');
|
|
|
|
|
|
|
|
|
|
// JS에서 display: block을 강제하지 않고, 빈 문자열로 설정하여 CSS의 flex가 작동하게 함
|
|
|
|
|
document.querySelectorAll('.server-only').forEach(el => (el as HTMLElement).style.display = isInfra ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.infra-only').forEach(el => (el as HTMLElement).style.display = isInfra ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.user-tracking-field').forEach(el => (el as HTMLElement).style.display = isPersonal ? '' : 'none');
|
|
|
|
|
// 개인 장비 (PC, 노트북, 모바일, 태블릿) - '서버PC'는 제외
|
|
|
|
|
const personalCategories = ['PC', '노트북', '모바일', '태블릿'];
|
|
|
|
|
const isPersonal = (personalCategories.includes(category) || type.includes('개인PC') || type.includes('노트북')) && !type.includes('서버PC');
|
|
|
|
|
|
|
|
|
|
// 시스템 사양 (PC, 서버 등)
|
|
|
|
|
const specCategories = ['PC', '서버', '노트북', '스토리지', '워크스테이션'];
|
|
|
|
|
const hasSpec = specCategories.includes(category) || type.includes('서버PC');
|
|
|
|
|
|
|
|
|
|
// 네트워크 정보 (IP/MAC)
|
|
|
|
|
const noNetCategories = ['저장매체', '네트워크', '공간정보장비', 'PC부품', '사무가구'];
|
|
|
|
|
const showNet = (isInfra || isPersonal) && !noNetCategories.includes(category);
|
|
|
|
|
|
|
|
|
|
// 시리얼 번호
|
|
|
|
|
const hasSN = !['사무가구', 'PC부품'].includes(category);
|
|
|
|
|
|
|
|
|
|
// 수량/용량 전용 (부품)
|
|
|
|
|
const isParts = ['PC부품', '사무가구'].includes(category);
|
|
|
|
|
|
|
|
|
|
// 원격 접속 (서버 전용)
|
|
|
|
|
const showRemote = category === '서버' || type.includes('서버');
|
|
|
|
|
|
|
|
|
|
// JS에서 display: block 강제 대신 빈 문자열 할당하여 네이티브 CSS flex 활용
|
|
|
|
|
document.querySelectorAll('.remote-section, .remote-field, .monitoring-field').forEach(el => (el as HTMLElement).style.display = showRemote ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.net-only').forEach(el => (el as HTMLElement).style.display = showNet ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.spec-only').forEach(el => (el as HTMLElement).style.display = hasSpec ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.location-section, .location-field').forEach(el => (el as HTMLElement).style.display = (isInfra || category === '공간정보장비') ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.org-user-section, .org-user-field').forEach(el => (el as HTMLElement).style.display = (isPersonal || isParts || category === '업무지원장비') ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.personal-only').forEach(el => (el as HTMLElement).style.display = isPersonal ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.sn-only').forEach(el => (el as HTMLElement).style.display = hasSN ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.monitor-only').forEach(el => (el as HTMLElement).style.display = type.includes('모니터') ? '' : 'none');
|
|
|
|
|
document.querySelectorAll('.parts-only').forEach(el => (el as HTMLElement).style.display = isParts ? '' : 'none');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private updateMapButtonVisibility(asset?: any) {
|
|
|
|
|
|