merge: ux_setting 브랜치를 main에 병합 (HWModal UI 고도화 및 PC 탭 제한 사항 반영)

This commit is contained in:
2026-06-11 11:23:09 +09:00
9 changed files with 661 additions and 96 deletions

View File

@@ -151,27 +151,20 @@ class HwAssetModal extends BaseModal {
<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>
<!-- 동적 디스크 할당 영역 -->
<div class="form-section-title spec-only" style="margin-top: 24px; margin-bottom: 12px;">디스크(용량) 정보</div>
<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>
<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>
<!-- [SECTION 4] 통합 원격 접속 정보 -->
<div class="form-section-title net-only" style="margin-top: 24px; margin-bottom: 12px;">원격 접속 정보</div>
<!-- 통합 원격 접속 정보 영역 -->
<div class="form-section-title net-only" style="margin-top: 24px; margin-bottom: 12px;">네트워크 및 원격 접속 정보</div>
<div class="form-group net-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);">IP/MAC 및 접속 계정 정보</label>
@@ -180,7 +173,7 @@ class HwAssetModal extends BaseModal {
<div id="hw-remote-info-container" style="display: flex; flex-direction: column; gap: 12px;"></div>
</div>
<!-- [SECTION 6] 설치 위치 -->
<!-- [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>
@@ -196,7 +189,7 @@ class HwAssetModal extends BaseModal {
<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>
<!-- [SECTION 7] 구매 및 증빙 -->
<!-- [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>
@@ -384,7 +377,7 @@ class HwAssetModal extends BaseModal {
document.querySelectorAll('#hw-remote-info-container .remote-info-row').forEach(row => {
const type = (row.querySelector('.ri-type') as HTMLSelectElement).value;
const val1 = (row.querySelector('.ri-val1') as HTMLInputElement).value;
if (type === 'IP' && val1) {
const tool = (row.querySelector('.ri-tool') as HTMLSelectElement)?.value || '';
const id = (row.querySelector('.ri-id') as HTMLInputElement)?.value || '';
@@ -436,7 +429,7 @@ class HwAssetModal extends BaseModal {
private addRemoteInfoRow(info: any = { type: 'IP', name: '원격접속', val1: '', val2: '' }) {
const container = document.getElementById('hw-remote-info-container');
if (!container) return;
// Parse val2 (which contains JSON with id and pw if type is IP)
let parsedId = '';
let parsedPw = '';
@@ -469,7 +462,7 @@ class HwAssetModal extends BaseModal {
// Second Line: Tool & Credentials (Only for IP)
const line2 = document.createElement('div');
line2.className = 'ri-line ri-cred-line';
line2.style.display = info.type === 'IP' ? 'flex' : 'none';
line2.style.display = info.type === 'IP' ? 'flex' : 'none';
line2.innerHTML = `
<div class="ri-connector"></div>
<select class="ri-tool" ${!this.isEditMode ? 'disabled' : ''}>
@@ -503,9 +496,9 @@ class HwAssetModal extends BaseModal {
private toggleEditOnlyBtns(isEdit: boolean) {
['btn-add-volume', 'btn-add-remote-info'].forEach(id => {
const btn = document.getElementById(id);
if (btn) btn.style.display = isEdit ? 'inline-flex' : 'none';
if (btn) btn.style.display = isEdit ? 'inline-flex' : 'none';
});
document.querySelectorAll('.edit-only-btn').forEach(btn => {
document.querySelectorAll('.edit-only-btn').forEach(btn => {
(btn as HTMLElement).style.display = isEdit ? 'inline-flex' : 'none';
});
@@ -556,33 +549,32 @@ class HwAssetModal extends BaseModal {
try { vols = asset.volumes ? (typeof asset.volumes === 'string' ? JSON.parse(asset.volumes) : asset.volumes) : []; } catch(e) {}
vols.forEach((v: any) => this.addVolumeRow(v));
// 통합 원격 접속 정보 렌더링
// 통합 원격 접속 정보 렌더링 초기화 및 생성
const remoteInfoContainer = document.getElementById('hw-remote-info-container');
if (remoteInfoContainer) remoteInfoContainer.innerHTML = '';
let nets = [];
try { nets = asset.remotes ? (typeof asset.remotes === 'string' ? JSON.parse(asset.remotes) : asset.remotes) : []; } catch(e) {}
// Fallback: 서버에서 배열을 안 줬지만 기존 평탄화 데이터가 있는 경우
if (nets.length === 0 && (asset.ip_address || asset.mac_address || asset.remote_tool || asset.remote_id)) {
if (asset.ip_address) {
// 기존 REMOTE 정보가 있다면 IP 로우에 통합 시도
const tool = asset.remote_tool || '원격접속';
const creds = (asset.remote_id || asset.remote_pw) ? JSON.stringify({ id: asset.remote_id || '', pw: asset.remote_pw || '' }) : '';
nets.push({ type: 'IP', name: tool, val1: asset.ip_address, val2: creds });
}
if (asset.mac_address) {
nets.push({ type: 'MAC', name: 'MAC 주소', val1: asset.mac_address, val2: '' });
}
// IP가 없는데 원격 정보만 있는 경우 (특이 케이스)
if (!asset.ip_address && (asset.remote_tool || asset.remote_id)) {
const creds = JSON.stringify({ id: asset.remote_id || '', pw: asset.remote_pw || '' });
nets.push({ type: 'IP', name: asset.remote_tool || '기타', val1: '', val2: creds });
}
}
if (remoteInfoContainer) {
remoteInfoContainer.innerHTML = '';
let nets = [];
try {
nets = asset.remotes ? (typeof asset.remotes === 'string' ? JSON.parse(asset.remotes) : asset.remotes) : [];
} catch(e) {}
nets.forEach((n: any) => {
this.addRemoteInfoRow(n);
});
// Fallback: 서버에서 배열을 안 줬지만 기존 평탄화 데이터가 있는 경우
if (nets.length === 0 && (asset.ip_address || asset.mac_address || asset.remote_tool || asset.remote_id)) {
if (asset.ip_address) {
const tool = asset.remote_tool || '원격접속';
const creds = (asset.remote_id || asset.remote_pw) ? JSON.stringify({ id: asset.remote_id || '', pw: asset.remote_pw || '' }) : '';
nets.push({ type: 'IP', name: tool, val1: asset.ip_address, val2: creds });
}
if (asset.mac_address) {
nets.push({ type: 'MAC', name: 'MAC 주소', val1: asset.mac_address, val2: '' });
}
if (!asset.ip_address && (asset.remote_tool || asset.remote_id)) {
const creds = JSON.stringify({ id: asset.remote_id || '', pw: asset.remote_pw || '' });
nets.push({ type: 'IP', name: asset.remote_tool || '기타', val1: '', val2: creds });
}
}
nets.forEach((n: any) => this.addRemoteInfoRow(n));
}
setFieldValue('hw-monitoring', asset.monitoring || '비대상');
setFieldValue('hw-serial_num', asset.serial_num || '');
@@ -787,7 +779,7 @@ class HwAssetModal extends BaseModal {
}
// 화살표 기호(➔)를 사용하여 변경 사항 강조
const formattedDetails = l.details.replace(' -> ', ' <span class="history-arrow">➔</span> ');
const formattedDetails = (l.details || '').replace(' -> ', ' <span class="history-arrow">➔</span> ');
return `
<div class="history-item ${itemClass}">