refactor(wbs-ux): refactor SystemSettingsView into a modular SOLID architecture with CrudToolbar, LiveTelemetryFooter, and AuditTimeline sub-components
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 11s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 11s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 21s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 12s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped

This commit is contained in:
2026-07-25 20:37:31 +09:00
parent 9bd158e2b1
commit e1c597ad0a
4 changed files with 403 additions and 334 deletions
@@ -0,0 +1,52 @@
<!-- AuditTimeline.vue -->
<script setup lang="ts">
interface AuditLog {
timestamp: string;
user: string;
change: string;
}
defineProps<{
auditHistory?: AuditLog[];
}>();
</script>
<template>
<div class="audit-timeline-section" v-if="auditHistory && auditHistory.length > 0">
<h5 class="timeline-title">🕒 변경 감사 이력 타임라인 (Audit Trail)</h5>
<div class="timeline-list">
<div v-for="(log, idx) in auditHistory" :key="idx" class="timeline-item">
<span class="time-stamp">{{ log.timestamp }}</span>
<span class="user-id">[{{ log.user }}]</span>
<span class="change-desc">{{ log.change }}</span>
</div>
</div>
</div>
</template>
<style scoped>
.audit-timeline-section {
border-top: 1px solid #E2E8F0;
padding-top: 8px;
margin-top: 4px;
}
.timeline-title {
font-size: 0.75rem;
font-weight: 700;
color: #475569;
margin: 0 0 4px 0;
}
.timeline-list { display: flex; flex-direction: column; gap: 2px; }
.timeline-item {
font-size: 0.7rem;
color: #64748B;
display: flex;
gap: 6px;
}
.time-stamp { font-family: monospace; font-weight: 700; color: #2563EB; }
.user-id { font-weight: 700; color: #334155; }
.change-desc { color: #475569; }
</style>
@@ -0,0 +1,248 @@
<!-- CrudToolbar.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{
activeDomain: string;
searchKeyword: string;
isBatchRunning: boolean;
batchProgress: number;
pingMs: number;
}>();
const emit = defineEmits<{
(e: 'update:activeDomain', domain: 'ALL' | 'OMS' | 'WMS' | 'ERP'): void;
(e: 'update:searchKeyword', val: string): void;
(e: 'loadData'): void;
(e: 'openCreate'): void;
(e: 'cloneItem'): void;
(e: 'deleteItem'): void;
(e: 'runBatch'): void;
(e: 'openGuide'): void;
(e: 'openOmsModal'): void;
(e: 'openWmsModal'): void;
(e: 'openErpModal'): void;
(e: 'export', fmt: string): void;
}>();
const showExportMenu = ref(false);
const handleExport = (fmt: string) => {
showExportMenu.value = false;
emit('export', fmt);
};
</script>
<template>
<div class="crud-toolbar">
<div class="toolbar-title">
<div class="title-with-chip">
<h3 class="title-text"> SCR-07: OMS · WMS · ERP · AX 통합 어드민</h3>
<span class="db-ping-chip">🟢 Live ({{ pingMs }}ms)</span>
<span class="socket-chip">📡 SignalR Active</span>
</div>
<div class="domain-switcher-bar">
<button class="domain-btn" :class="{ active: activeDomain === 'ALL' }" @click="emit('update:activeDomain', 'ALL')">🌐 전체</button>
<button class="domain-btn" :class="{ active: activeDomain === 'OMS' }" @click="emit('update:activeDomain', 'OMS')">📈 OMS (주문)</button>
<button class="domain-btn" :class="{ active: activeDomain === 'WMS' }" @click="emit('update:activeDomain', 'WMS')">🏦 WMS (자산)</button>
<button class="domain-btn" :class="{ active: activeDomain === 'ERP' }" @click="emit('update:activeDomain', 'ERP')">📑 ERP (재무)</button>
<div class="quick-modal-triggers">
<button class="btn-quick-domain oms" @click="emit('openOmsModal')">+ OMS 주문전송</button>
<button class="btn-quick-domain wms" @click="emit('openWmsModal')">+ WMS 현금이체</button>
<button class="btn-quick-domain erp" @click="emit('openErpModal')">+ ERP 결재승인</button>
</div>
</div>
</div>
<div class="toolbar-actions">
<span class="dbup-version-tag">🛡 DbUp: v2026.07.25</span>
<button class="btn-batch-trigger" @click="emit('runBatch')" :disabled="isBatchRunning">
{{ isBatchRunning ? `⏳ 배치 ${batchProgress}%` : '⚡ 팩터 배치 실행' }}
</button>
<button class="btn-guide-spec" @click="emit('openGuide')">💡 24대 명세 팝업</button>
<div class="search-box">
<input
type="text"
class="search-input"
placeholder="설정 키/설명 검색..."
:value="searchKeyword"
@input="emit('update:searchKeyword', ($event.target as HTMLInputElement).value)"
/>
</div>
<button class="btn-action btn-search" @click="emit('loadData')"><span class="badge-key">F3</span>조회</button>
<button class="btn-action btn-create" @click="emit('openCreate')"><span class="badge-key">F4</span>신규</button>
<button class="btn-action btn-clone" @click="emit('cloneItem')"><span class="badge-key">F6</span>복제</button>
<button class="btn-action btn-delete" @click="emit('deleteItem')"><span class="badge-key">F5</span>삭제</button>
<div class="export-dropdown-wrapper">
<button class="btn-action btn-excel" @click="showExportMenu = !showExportMenu">
<span class="badge-key">F7</span>엑셀
</button>
<div v-if="showExportMenu" class="export-menu-popover">
<button @click="handleExport('OpenXML Excel (.xlsx)')">📊 Excel (.xlsx) 내보내기</button>
<button @click="handleExport('CSV (UTF-8)')">📄 CSV (UTF-8) 내보내기</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.crud-toolbar {
background-color: #34495E;
color: #FFFFFF;
padding: 8px 16px;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title-text { font-size: 0.95rem; font-weight: 700; margin: 0; }
.title-with-chip { display: flex; align-items: center; gap: 8px; }
.db-ping-chip {
background: #166534;
color: #DCFCE7;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.65rem;
font-weight: 700;
}
.socket-chip {
background: #1E40AF;
color: #DBEAFE;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.65rem;
font-weight: 700;
}
.domain-switcher-bar { display: flex; align-items: center; gap: 4px; margin-top: 4px; }
.domain-btn {
padding: 3px 8px;
font-size: 0.7rem;
font-weight: 700;
border: 1px solid #64748B;
background: rgba(255,255,255,0.1);
color: #CBD5E1;
border-radius: 4px;
cursor: pointer;
}
.domain-btn.active { background: #2563EB; color: white; border-color: #2563EB; }
.quick-modal-triggers { display: flex; gap: 4px; margin-left: 12px; }
.btn-quick-domain {
font-size: 0.65rem;
font-weight: 700;
padding: 2px 6px;
border: none;
border-radius: 3px;
color: white;
cursor: pointer;
}
.btn-quick-domain.oms { background: #2563EB; }
.btn-quick-domain.wms { background: #166534; }
.btn-quick-domain.erp { background: #6B46C1; }
.toolbar-actions { display: flex; align-items: center; gap: 6px; }
.dbup-version-tag {
font-size: 0.7rem;
font-weight: 700;
color: #CBD5E1;
background: rgba(255,255,255,0.1);
padding: 2px 6px;
border-radius: 4px;
}
.btn-batch-trigger {
background: #E67E22;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
cursor: pointer;
}
.btn-batch-trigger:disabled { background: #D35400; cursor: not-allowed; }
.btn-guide-spec {
background: #8E44AD;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
cursor: pointer;
}
.search-input {
padding: 6px 10px;
border-radius: 4px;
border: 1px solid #64748B;
font-size: 0.8rem;
outline: none;
}
.btn-action {
padding: 6px 10px;
border: none;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
color: white;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
}
.btn-search { background-color: #2563EB; }
.btn-create { background-color: #166534; }
.btn-clone { background-color: #8E44AD; }
.btn-delete { background-color: #DC2626; }
.btn-excel { background-color: #D97706; }
.export-dropdown-wrapper { position: relative; }
.export-menu-popover {
position: absolute;
top: 100%;
right: 0;
margin-top: 4px;
background: white;
border: 1px solid #CBD5E1;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
display: flex;
flex-direction: column;
z-index: 100;
width: 180px;
}
.export-menu-popover button {
padding: 8px 12px;
background: none;
border: none;
text-align: left;
font-size: 0.75rem;
font-weight: 700;
color: #334155;
cursor: pointer;
}
.badge-key {
background: rgba(255,255,255,0.2);
padding: 1px 4px;
border-radius: 3px;
font-size: 0.65rem;
}
</style>
@@ -0,0 +1,72 @@
<!-- LiveTelemetryFooter.vue -->
<script setup lang="ts">
defineProps<{
dbConnections: number;
}>();
</script>
<template>
<div class="footer-wrapper">
<div class="hotkey-helper-bar">
<span class="hotkey-title"> 사용자 핫키 가이드:</span>
<span class="hotkey-chip"><kbd>F3</kbd> 조회</span>
<span class="hotkey-chip"><kbd>F4</kbd> 신규</span>
<span class="hotkey-chip"><kbd>F5</kbd> 삭제</span>
<span class="hotkey-chip"><kbd>F6</kbd> 복제</span>
<span class="hotkey-chip"><kbd>F7</kbd> 엑셀</span>
<span class="hotkey-chip"><kbd>Ctrl+S</kbd> 저장</span>
<span class="hotkey-chip"><kbd>Esc</kbd> 팝업닫기</span>
</div>
<div class="live-telemetry-bar">
<span class="telemetry-item">🖥 CPU: <strong>12%</strong></span>
<span class="telemetry-item">💾 RAM: <strong>512MB / 4GB</strong></span>
<span class="telemetry-item">🔌 DB Pool: <strong>{{ dbConnections }}/20 Active</strong></span>
<span class="telemetry-item">📬 Outbox Queue: <strong>0 Pending</strong></span>
<span class="telemetry-item"> Hangfire Jobs: <strong>Healthy</strong></span>
</div>
</div>
</template>
<style scoped>
.footer-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
}
.hotkey-helper-bar {
background: #334155;
color: white;
padding: 4px 14px;
border-radius: 4px;
font-size: 0.7rem;
display: flex;
align-items: center;
gap: 12px;
}
.hotkey-title { font-weight: 700; color: #CBD5E1; }
.hotkey-chip kbd {
background: #475569;
padding: 1px 4px;
border-radius: 3px;
font-family: monospace;
font-size: 0.65rem;
color: #F1F5F9;
margin-right: 2px;
}
.live-telemetry-bar {
background: #1E293B;
color: #94A3B8;
padding: 4px 16px;
border-radius: 4px;
font-size: 0.7rem;
display: flex;
gap: 16px;
align-items: center;
}
.telemetry-item strong { color: #F1F5F9; }
</style>
+31 -334
View File
@@ -8,9 +8,10 @@ import QuantComboBox from '../components/QuantComboBox.vue'
import QuantCheckBox from '../components/QuantCheckBox.vue'
import QuantRadio from '../components/QuantRadio.vue'
import QuantTextArea from '../components/QuantTextArea.vue'
import QuantAutoComplete from '../components/QuantAutoComplete.vue'
import QuantStatusChip from '../components/QuantStatusChip.vue'
import QuantDataGrid from '../components/QuantDataGrid.vue'
import CrudToolbar from '../components/crud/CrudToolbar.vue'
import LiveTelemetryFooter from '../components/crud/LiveTelemetryFooter.vue'
import AuditTimeline from '../components/crud/AuditTimeline.vue'
import type { ColDef } from 'ag-grid-community'
interface AuditLog {
@@ -60,7 +61,7 @@ const setSplitRatio = (percent: number) => {
showToast(`스플릿 분할 비율이 ${percent}% : ${100 - percent}% 로 조정되었습니다.`, 'success');
};
const startSplitterDrag = (e: MouseEvent) => {
const startSplitterDrag = () => {
isDraggingSplitter.value = true;
document.addEventListener('mousemove', onSplitterMouseMove);
document.addEventListener('mouseup', stopSplitterDrag);
@@ -105,7 +106,6 @@ const liveLogs = ref<string[]>([
// 7. 페이지네이션 및 드롭다운 상태
const currentPage = ref(1);
const showExportMenu = ref(false);
const isGuideModalOpen = ref(false);
// 8. 토스트 알림 메시지
@@ -318,7 +318,6 @@ const deleteSelectedItem = () => {
};
const triggerExport = (fmt: string) => {
showExportMenu.value = false;
liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [EXPORT] Streaming ${fmt}`);
showToast(`${fmt} 포맷 내보내기를 시작합니다.`, 'success');
};
@@ -338,54 +337,24 @@ onMounted(loadData);
</div>
</transition>
<!-- Top Toolbar with Domain Context Switcher & Live Ping -->
<div class="crud-toolbar">
<div class="toolbar-title">
<div class="title-with-chip">
<h3 class="title-text"> SCR-07: OMS · WMS · ERP · AX 통합 어드민</h3>
<span class="db-ping-chip">🟢 Live ({{ pingMs }}ms)</span>
<span class="socket-chip">📡 SignalR Active</span>
</div>
<!-- OMS / WMS / ERP 3 도메인 스위처 & Quick Modals Trigger -->
<div class="domain-switcher-bar">
<button class="domain-btn" :class="{ active: activeDomain === 'ALL' }" @click="activeDomain = 'ALL'">🌐 전체</button>
<button class="domain-btn" :class="{ active: activeDomain === 'OMS' }" @click="activeDomain = 'OMS'">📈 OMS (주문)</button>
<button class="domain-btn" :class="{ active: activeDomain === 'WMS' }" @click="activeDomain = 'WMS'">🏦 WMS (자산)</button>
<button class="domain-btn" :class="{ active: activeDomain === 'ERP' }" @click="activeDomain = 'ERP'">📑 ERP (재무)</button>
<div class="quick-modal-triggers">
<button class="btn-quick-domain oms" @click="isOmsModalOpen = true">+ OMS 주문전송</button>
<button class="btn-quick-domain wms" @click="isWmsModalOpen = true">+ WMS 현금이체</button>
<button class="btn-quick-domain erp" @click="isErpModalOpen = true">+ ERP 결재승인</button>
</div>
</div>
</div>
<div class="toolbar-actions">
<span class="dbup-version-tag">🛡 DbUp: v2026.07.25</span>
<button class="btn-batch-trigger" @click="runFactorBatch" :disabled="isBatchRunning">
{{ isBatchRunning ? `⏳ 배치 ${batchProgress}%` : '⚡ 팩터 배치 실행' }}
</button>
<button class="btn-guide-spec" @click="isGuideModalOpen = true">💡 24대 명세 팝업</button>
<div class="search-box">
<input type="text" class="search-input" placeholder="설정 키/설명 검색..." v-model="searchKeyword" />
</div>
<button class="btn-action btn-search" @click="loadData"><span class="badge-key">F3</span>조회</button>
<button class="btn-action btn-create" @click="openCreateModal"><span class="badge-key">F4</span>신규</button>
<button class="btn-action btn-clone" @click="cloneSelectedItem"><span class="badge-key">F6</span>복제</button>
<button class="btn-action btn-delete" @click="deleteSelectedItem"><span class="badge-key">F5</span>삭제</button>
<div class="export-dropdown-wrapper">
<button class="btn-action btn-excel" @click="showExportMenu = !showExportMenu">
<span class="badge-key">F7</span>엑셀
</button>
<div v-if="showExportMenu" class="export-menu-popover">
<button @click="triggerExport('OpenXML Excel (.xlsx)')">📊 Excel (.xlsx) 내보내기</button>
<button @click="triggerExport('CSV (UTF-8)')">📄 CSV (UTF-8) 내보내기</button>
</div>
</div>
</div>
</div>
<!-- 분리된 모듈형 툴바 컴포넌트 (CrudToolbar) -->
<CrudToolbar
v-model:activeDomain="activeDomain"
v-model:searchKeyword="searchKeyword"
:isBatchRunning="isBatchRunning"
:batchProgress="batchProgress"
:pingMs="pingMs"
@loadData="loadData"
@openCreate="openCreateModal"
@cloneItem="cloneSelectedItem"
@deleteItem="deleteSelectedItem"
@runBatch="runFactorBatch"
@openGuide="isGuideModalOpen = true"
@openOmsModal="isOmsModalOpen = true"
@openWmsModal="isWmsModalOpen = true"
@openErpModal="isErpModalOpen = true"
@export="triggerExport"
/>
<!-- Batch Execution Progress Bar -->
<div v-if="isBatchRunning" class="batch-progress-bar-container">
@@ -467,7 +436,6 @@ onMounted(loadData);
</div>
<div class="panel-body form-scroll-body">
<!-- 도메인별 전용 모듈 UX/AX 렌더링 -->
<div class="domain-module-box" :class="selectedItem.domain.toLowerCase()">
<span class="module-title">📌 {{ selectedItem.domain }} 도메인 전용 컴포넌트</span>
<div v-if="selectedItem.domain === 'OMS'" class="module-content">
@@ -545,7 +513,6 @@ onMounted(loadData);
<QuantTextArea v-model="selectedItem.note" :disabled="!isEditMode" :rows="2" />
</div>
<!-- 실시간 로그 터미널 -->
<div class="live-log-terminal">
<div class="terminal-header">💻 실시간 퀀트 파이프라인 로그 터미널</div>
<div class="terminal-body">
@@ -555,17 +522,8 @@ onMounted(loadData);
</div>
</div>
<!-- Audit Timeline -->
<div class="audit-timeline-section" v-if="selectedItem.audit_history && selectedItem.audit_history.length > 0">
<h5 class="timeline-title">🕒 변경 감사 이력 타임라인 (Audit Trail)</h5>
<div class="timeline-list">
<div v-for="(log, idx) in selectedItem.audit_history" :key="idx" class="timeline-item">
<span class="time-stamp">{{ log.timestamp }}</span>
<span class="user-id">[{{ log.user }}]</span>
<span class="change-desc">{{ log.change }}</span>
</div>
</div>
</div>
<!-- 분리된 감사 이력 서브 컴포넌트 (AuditTimeline) -->
<AuditTimeline :auditHistory="selectedItem.audit_history" />
<button v-if="isEditMode" class="btn-save-form" @click="saveDetailForm">
💾 PostgreSQL 설정 변경사항 저장 (Ver v{{ selectedItem.lock_version + 1 }})
@@ -574,26 +532,8 @@ onMounted(loadData);
</div>
</div>
<!-- 사용자 편의성: 단축키 핫키 가이드 헬퍼 -->
<div class="hotkey-helper-bar">
<span class="hotkey-title"> 사용자 핫키 가이드:</span>
<span class="hotkey-chip"><kbd>F3</kbd> 조회</span>
<span class="hotkey-chip"><kbd>F4</kbd> 신규</span>
<span class="hotkey-chip"><kbd>F5</kbd> 삭제</span>
<span class="hotkey-chip"><kbd>F6</kbd> 복제</span>
<span class="hotkey-chip"><kbd>F7</kbd> 엑셀</span>
<span class="hotkey-chip"><kbd>Ctrl+S</kbd> 저장</span>
<span class="hotkey-chip"><kbd>Esc</kbd> 팝업닫기</span>
</div>
<!-- Live Telemetry Footer Bar -->
<div class="live-telemetry-bar">
<span class="telemetry-item">🖥 CPU: <strong>12%</strong></span>
<span class="telemetry-item">💾 RAM: <strong>512MB / 4GB</strong></span>
<span class="telemetry-item">🔌 DB Pool: <strong>{{ dbConnections }}/20 Active</strong></span>
<span class="telemetry-item">📬 Outbox Queue: <strong>0 Pending</strong></span>
<span class="telemetry-item"> Hangfire Jobs: <strong>Healthy</strong></span>
</div>
<!-- 분리된 모듈형 텔레메트리 푸터 컴포넌트 (LiveTelemetryFooter) -->
<LiveTelemetryFooter :dbConnections="dbConnections" />
<!-- OMS Quick Order Modal -->
<div class="modal-backdrop" v-if="isOmsModalOpen">
@@ -692,7 +632,7 @@ onMounted(loadData);
<div class="modal-backdrop" v-if="isGuideModalOpen">
<div class="modal-dialog guide-modal">
<div class="modal-header">
<h4 class="modal-title">💡 OMS / WMS / ERP / AX & 생산성 24 컴포넌트 스펙</h4>
<h4 class="modal-title">💡 OMS / WMS / ERP / AX & 모듈화 24 컴포넌트 스펙</h4>
<button class="btn-close-modal" @click="isGuideModalOpen = false"></button>
</div>
<div class="modal-body guide-body">
@@ -716,11 +656,11 @@ onMounted(loadData);
<li><strong>17. 비동기 배치 실행 & 진행률 </strong>: 0~100% 모의 트랜잭션 게이지</li>
<li><strong>18. 낙관적 버전 인디케이터</strong>: 동시성 충돌 방지 v1~v5</li>
<li><strong>19. 실시간 텔레메트리 </strong>: CPU/RAM/DB Pool 현장감 지표</li>
<li><strong>20. 검색 & 핫키 툴바</strong>: F3/F4/F5/F7 더존 표준 핫키</li>
<li><strong>20. 검색 & 핫키 툴바 (CrudToolbar 모듈)</strong>: F3/F4/F5/F7 더존 표준 핫키</li>
<li><strong>21. AG Grid 데이터 테이블</strong>: 정렬, 필터, 단일 선택, Status Chips</li>
<li><strong>22. 더존 6 표준 입력 마스크</strong>: Input, ComboBox, Radio, TextArea </li>
<li><strong>23. 읽기/편집 모드 토글</strong>: 👁 읽기 / 편집 전환 스위치</li>
<li><strong>24. 감사 이력 타임라인 & 모달 & 엑셀 팝업</strong>: Audit Trail, Modal, Excel/CSV</li>
<li><strong>24. 감사 이력 타임라인 (AuditTimeline 모듈) & 엑셀 팝업</strong>: Audit Trail, Excel/CSV</li>
</ul>
</div>
<div class="modal-footer">
@@ -744,30 +684,6 @@ onMounted(loadData);
user-select: none;
}
.hotkey-helper-bar {
background: #334155;
color: white;
padding: 4px 14px;
border-radius: 4px;
font-size: 0.7rem;
display: flex;
align-items: center;
gap: 12px;
}
.hotkey-title { font-weight: 700; color: #CBD5E1; }
.hotkey-chip kbd {
background: #475569;
padding: 1px 4px;
border-radius: 3px;
font-family: monospace;
font-size: 0.65rem;
color: #F1F5F9;
margin-right: 2px;
}
.btn-clone { background-color: #8E44AD !important; }
.draft-saved-chip {
font-size: 0.65rem;
font-weight: 700;
@@ -777,26 +693,6 @@ onMounted(loadData);
border-radius: 4px;
}
.quick-modal-triggers {
display: flex;
gap: 4px;
margin-left: 12px;
}
.btn-quick-domain {
font-size: 0.65rem;
font-weight: 700;
padding: 2px 6px;
border: none;
border-radius: 3px;
color: white;
cursor: pointer;
}
.btn-quick-domain.oms { background: #2563EB; }
.btn-quick-domain.wms { background: #166534; }
.btn-quick-domain.erp { background: #6B46C1; }
.category-tabs-bar {
display: flex;
justify-content: space-between;
@@ -835,30 +731,6 @@ onMounted(loadData);
border-color: #2563EB;
}
.domain-switcher-bar {
display: flex;
align-items: center;
gap: 4px;
margin-top: 4px;
}
.domain-btn {
padding: 3px 8px;
font-size: 0.7rem;
font-weight: 700;
border: 1px solid #64748B;
background: rgba(255,255,255,0.1);
color: #CBD5E1;
border-radius: 4px;
cursor: pointer;
}
.domain-btn.active {
background: #2563EB;
color: white;
border-color: #2563EB;
}
.ai-ax-banner {
background: #2D3748;
color: #F7FAFC;
@@ -940,47 +812,6 @@ onMounted(loadData);
background-color: #2563EB;
}
.title-with-chip { display: flex; align-items: center; gap: 8px; }
.db-ping-chip {
background: #166534;
color: #DCFCE7;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.65rem;
font-weight: 700;
}
.socket-chip {
background: #1E40AF;
color: #DBEAFE;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.65rem;
font-weight: 700;
}
.dbup-version-tag {
font-size: 0.7rem;
font-weight: 700;
color: #CBD5E1;
background: rgba(255,255,255,0.1);
padding: 2px 6px;
border-radius: 4px;
}
.btn-batch-trigger {
background: #E67E22;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
cursor: pointer;
}
.btn-batch-trigger:disabled { background: #D35400; cursor: not-allowed; }
.batch-progress-bar-container {
height: 4px;
background: #E2E8F0;
@@ -994,17 +825,6 @@ onMounted(loadData);
transition: width 0.3s ease;
}
.btn-guide-spec {
background: #8E44AD;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
cursor: pointer;
}
.guide-modal { width: 580px; }
.guide-body { max-height: 380px; overflow-y: auto; }
.guide-list { margin: 0; padding-left: 20px; font-size: 0.8rem; color: #334155; line-height: 1.8; }
@@ -1033,85 +853,6 @@ onMounted(loadData);
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
.crud-toolbar {
background-color: #34495E;
color: #FFFFFF;
padding: 8px 16px;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title-text { font-size: 0.95rem; font-weight: 700; margin: 0; }
.subtitle-text { font-size: 0.75rem; color: #CBD5E1; }
.toolbar-actions {
display: flex;
align-items: center;
gap: 6px;
}
.search-input {
padding: 6px 10px;
border-radius: 4px;
border: 1px solid #64748B;
font-size: 0.8rem;
outline: none;
}
.btn-action {
padding: 6px 10px;
border: none;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 700;
color: white;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
}
.btn-search { background-color: #2563EB; }
.btn-create { background-color: #166534; }
.btn-delete { background-color: #DC2626; }
.btn-excel { background-color: #D97706; }
.export-dropdown-wrapper { position: relative; }
.export-menu-popover {
position: absolute;
top: 100%;
right: 0;
margin-top: 4px;
background: white;
border: 1px solid #CBD5E1;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
display: flex;
flex-direction: column;
z-index: 100;
width: 180px;
}
.export-menu-popover button {
padding: 8px 12px;
background: none;
border: none;
text-align: left;
font-size: 0.75rem;
font-weight: 700;
color: #334155;
cursor: pointer;
}
.badge-key {
background: rgba(255,255,255,0.2);
padding: 1px 4px;
border-radius: 3px;
font-size: 0.65rem;
}
.tab-btn {
padding: 4px 12px;
border: none;
@@ -1123,10 +864,7 @@ onMounted(loadData);
cursor: pointer;
}
.tab-btn.active {
background-color: #2563EB;
color: white;
}
.tab-btn.active { background-color: #2563EB; color: white; }
.crud-split-section {
display: flex;
@@ -1199,10 +937,7 @@ onMounted(loadData);
cursor: pointer;
}
.mode-btn.active {
background: white;
color: #1E293B;
}
.mode-btn.active { background: white; color: #1E293B; }
.panel-tag {
font-size: 0.65rem;
@@ -1279,31 +1014,6 @@ onMounted(loadData);
.terminal-body { display: flex; flex-direction: column; gap: 2px; }
.log-line { word-break: break-all; }
.audit-timeline-section {
border-top: 1px solid #E2E8F0;
padding-top: 8px;
margin-top: 4px;
}
.timeline-title {
font-size: 0.75rem;
font-weight: 700;
color: #475569;
margin: 0 0 4px 0;
}
.timeline-list { display: flex; flex-direction: column; gap: 2px; }
.timeline-item {
font-size: 0.7rem;
color: #64748B;
display: flex;
gap: 6px;
}
.time-stamp { font-family: monospace; font-weight: 700; color: #2563EB; }
.user-id { font-weight: 700; color: #334155; }
.change-desc { color: #475569; }
.btn-save-form {
margin-top: 8px;
padding: 8px 0;
@@ -1315,19 +1025,6 @@ onMounted(loadData);
cursor: pointer;
}
.live-telemetry-bar {
background: #1E293B;
color: #94A3B8;
padding: 4px 16px;
border-radius: 4px;
font-size: 0.7rem;
display: flex;
gap: 16px;
align-items: center;
}
.telemetry-item strong { color: #F1F5F9; }
/* Modal Dialog Styles */
.modal-backdrop {
position: fixed;