feat(wbs-ux): inject 4 live operational presence features into SystemSettingsView (DB Ping, Batch Progress, Lock Guard, Telemetry Bar)
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) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 21s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 9s
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) / CI Workflow Lint (push) Failing after 9s
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) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 21s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 9s
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) / CI Workflow Lint (push) Failing after 9s
This commit is contained in:
@@ -27,6 +27,7 @@ interface SettingItem {
|
||||
status: 'ACTIVE' | 'WARNING' | 'BLOCKED';
|
||||
updated_at: string;
|
||||
note: string;
|
||||
lock_version: number; // 낙관적 락 버전
|
||||
attachments?: string[];
|
||||
audit_history?: AuditLog[];
|
||||
}
|
||||
@@ -40,12 +41,18 @@ const items = ref<SettingItem[]>([]);
|
||||
const selectedItem = ref<SettingItem | null>(null);
|
||||
const isEditMode = ref(false);
|
||||
|
||||
// 3. 페이지네이션 및 엑셀 드롭다운 상태
|
||||
// 3. 현장감 텔레메트리 & 배치 프로그레스 상태
|
||||
const pingMs = ref(4);
|
||||
const isBatchRunning = ref(false);
|
||||
const batchProgress = ref(0);
|
||||
const dbConnections = ref(4);
|
||||
|
||||
// 4. 페이지네이션 및 엑셀 드롭다운 상태
|
||||
const currentPage = ref(1);
|
||||
const showExportMenu = ref(false);
|
||||
const isGuideModalOpen = ref(false); // 12대 컴포넌트 명세 팝업 모달
|
||||
const isGuideModalOpen = ref(false);
|
||||
|
||||
// 4. 토스트 알림 시스템 상태
|
||||
// 5. 토스트 알림 시스템 상태
|
||||
const toastMessage = ref<{ text: string; type: 'success' | 'warning' | 'error' } | null>(null);
|
||||
const showToast = (text: string, type: 'success' | 'warning' | 'error' = 'success') => {
|
||||
toastMessage.value = { text, type };
|
||||
@@ -54,7 +61,7 @@ const showToast = (text: string, type: 'success' | 'warning' | 'error' = 'succes
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
// 5. 모달 대화상자 상태
|
||||
// 6. 모달 대화상자 상태
|
||||
const isModalOpen = ref(false);
|
||||
const modalItem = ref<SettingItem>({
|
||||
id: 0,
|
||||
@@ -63,10 +70,11 @@ const modalItem = ref<SettingItem>({
|
||||
setting_value: '',
|
||||
status: 'ACTIVE',
|
||||
updated_at: '',
|
||||
note: ''
|
||||
note: '',
|
||||
lock_version: 1
|
||||
});
|
||||
|
||||
// 6. 필터링된 항목 계산
|
||||
// 7. 필터링된 항목 계산
|
||||
const filteredItems = computed(() => {
|
||||
return items.value.filter(item => {
|
||||
const matchTab = activeTab.value === 'ALL' || item.category === activeTab.value;
|
||||
@@ -77,43 +85,44 @@ const filteredItems = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
// 7. AG Grid 컬럼 정의
|
||||
// 8. AG Grid 컬럼 정의
|
||||
const columnDefs = ref<ColDef[]>([
|
||||
{ field: 'id', headerName: 'ID', width: 70, sortable: true },
|
||||
{ field: 'setting_key', headerName: '설정 키 (Key)', width: 180, sortable: true, filter: true },
|
||||
{ field: 'category', headerName: '카테고리', width: 120 },
|
||||
{ field: 'setting_value', headerName: '설정 값 (Value)', width: 150 },
|
||||
{ field: 'id', headerName: 'ID', width: 60, sortable: true },
|
||||
{ field: 'setting_key', headerName: '설정 키 (Key)', width: 170, sortable: true, filter: true },
|
||||
{ field: 'category', headerName: '카테고리', width: 110 },
|
||||
{ field: 'setting_value', headerName: '설정 값 (Value)', width: 140 },
|
||||
{
|
||||
field: 'status',
|
||||
headerName: '상태 칩',
|
||||
width: 120,
|
||||
width: 110,
|
||||
cellRenderer: (params: any) => {
|
||||
const type = params.value === 'ACTIVE' ? 'PASS' : params.value === 'WARNING' ? 'WARNING' : 'BLOCKED';
|
||||
return `<span class="status-chip ${type.toLowerCase()}">${params.value}</span>`;
|
||||
}
|
||||
},
|
||||
{ field: 'updated_at', headerName: '수정일시', width: 140 }
|
||||
{ field: 'lock_version', headerName: 'Ver', width: 60 },
|
||||
{ field: 'updated_at', headerName: '수정일시', width: 130 }
|
||||
]);
|
||||
|
||||
const loadData = async () => {
|
||||
items.value = [
|
||||
{
|
||||
id: 1, setting_key: 'D2_CASH_TARGET', category: 'BUDGET', setting_value: '500,000,000', status: 'ACTIVE', updated_at: '2026-07-25 14:00', note: 'D+2 즉시방어 현금 목표', attachments: ['budget_spec_v1.pdf'],
|
||||
id: 1, setting_key: 'D2_CASH_TARGET', category: 'BUDGET', setting_value: '500,000,000', status: 'ACTIVE', updated_at: '2026-07-25 14:00', note: 'D+2 즉시방어 현금 목표', lock_version: 3, attachments: ['budget_spec_v1.pdf'],
|
||||
audit_history: [
|
||||
{ timestamp: '2026-07-25 14:00', user: 'admin_kjh', change: '설정값 4.8억 -> 5.0억 변경' },
|
||||
{ timestamp: '2026-07-20 09:00', user: 'system', change: '신규 등록' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2, setting_key: 'RSI_UPPER_LIMIT', category: 'FACTOR', setting_value: '70.0', status: 'ACTIVE', updated_at: '2026-07-24 18:30', note: '과매수 상한 임계값', attachments: [],
|
||||
id: 2, setting_key: 'RSI_UPPER_LIMIT', category: 'FACTOR', setting_value: '70.0', status: 'ACTIVE', updated_at: '2026-07-24 18:30', note: '과매수 상한 임계값', lock_version: 1, attachments: [],
|
||||
audit_history: [{ timestamp: '2026-07-24 18:30', user: 'quant_dev', change: '상한 75 -> 70 보정' }]
|
||||
},
|
||||
{
|
||||
id: 3, setting_key: 'ANTI_LATE_ENTRY', category: 'RISK', setting_value: 'ENABLED', status: 'WARNING', updated_at: '2026-07-22 10:15', note: '추격매수 방지 가드 레벨 2', attachments: ['gate_audit.log'],
|
||||
id: 3, setting_key: 'ANTI_LATE_ENTRY', category: 'RISK', setting_value: 'ENABLED', status: 'WARNING', updated_at: '2026-07-22 10:15', note: '추격매수 방지 가드 레벨 2', lock_version: 5, attachments: ['gate_audit.log'],
|
||||
audit_history: [{ timestamp: '2026-07-22 10:15', user: 'risk_officer', change: '경고 플래그 활성화' }]
|
||||
},
|
||||
{
|
||||
id: 4, setting_key: 'WATERFALL_SELL_PRIORITY', category: 'EXECUTION', setting_value: 'STRICT', status: 'BLOCKED', updated_at: '2026-07-20 09:00', note: '단일 sell priority waterfall 룰', attachments: [],
|
||||
id: 4, setting_key: 'WATERFALL_SELL_PRIORITY', category: 'EXECUTION', setting_value: 'STRICT', status: 'BLOCKED', updated_at: '2026-07-20 09:00', note: '단일 sell priority waterfall 룰', lock_version: 2, attachments: [],
|
||||
audit_history: []
|
||||
}
|
||||
];
|
||||
@@ -122,6 +131,21 @@ const loadData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const runFactorBatch = () => {
|
||||
if (isBatchRunning.value) return;
|
||||
isBatchRunning.value = true;
|
||||
batchProgress.value = 0;
|
||||
showToast('⚡ 팩터 재계산 비동기 배치 작업이 트리거되었습니다.', 'success');
|
||||
const timer = setInterval(() => {
|
||||
batchProgress.value += 25;
|
||||
if (batchProgress.value >= 100) {
|
||||
clearInterval(timer);
|
||||
isBatchRunning.value = false;
|
||||
showToast('✅ 팩터 재계산 배치가 성공적으로 완료되었습니다 (소요시간: 1.2s)', 'success');
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleRowSelect = (rows: any[]) => {
|
||||
if (rows.length > 0) {
|
||||
selectedItem.value = JSON.parse(JSON.stringify(rows[0]));
|
||||
@@ -137,7 +161,8 @@ const openCreateModal = () => {
|
||||
setting_value: '',
|
||||
status: 'ACTIVE',
|
||||
updated_at: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
||||
note: ''
|
||||
note: '',
|
||||
lock_version: 1
|
||||
};
|
||||
isModalOpen.value = true;
|
||||
};
|
||||
@@ -157,15 +182,16 @@ const saveDetailForm = () => {
|
||||
if (!selectedItem.value) return;
|
||||
const idx = items.value.findIndex(i => i.id === selectedItem.value?.id);
|
||||
if (idx !== -1) {
|
||||
selectedItem.value.lock_version += 1; // 락 버전 증가
|
||||
if (!selectedItem.value.audit_history) selectedItem.value.audit_history = [];
|
||||
selectedItem.value.audit_history.unshift({
|
||||
timestamp: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
||||
user: 'admin_kjh',
|
||||
change: '설정값 및 노티 정보 수정 커밋'
|
||||
change: `설정값 변경 (Ver -> v${selectedItem.value.lock_version})`
|
||||
});
|
||||
items.value[idx] = JSON.parse(JSON.stringify(selectedItem.value));
|
||||
isEditMode.value = false;
|
||||
showToast('PostgreSQL 원장 설정 변경사항이 저장되었습니다.', 'success');
|
||||
showToast(`PostgreSQL 원장 설정 변경사항이 저장되었습니다 (Lock Ver: v${selectedItem.value.lock_version}).`, 'success');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -201,22 +227,28 @@ onMounted(loadData);
|
||||
<!-- Top Action & Search Bar -->
|
||||
<div class="crud-toolbar">
|
||||
<div class="toolbar-title">
|
||||
<h3 class="title-text">⚙️ SCR-07: 올인원 12대 CRUD 마스터피스 매니저</h3>
|
||||
<span class="subtitle-text">Master-Detail, AG Grid, Toast, Tabs, Audit Timeline, Mode Switch, Modal, 더존 6대 표준 컴포넌트</span>
|
||||
<div class="title-with-chip">
|
||||
<h3 class="title-text">⚙️ SCR-07: 현장감 퀀트 CRUD 매니저</h3>
|
||||
<span class="db-ping-chip">🟢 PostgreSQL Live ({{ pingMs }}ms)</span>
|
||||
</div>
|
||||
<span class="subtitle-text">Master-Detail, AG Grid, Batch Progress, Lock Guard, Telemetry, Toast, 더존 컴포넌트</span>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
<button class="btn-guide-spec" @click="isGuideModalOpen = true">💡 12대 CRUD 명세 팝업</button>
|
||||
<button class="btn-batch-trigger" @click="runFactorBatch" :disabled="isBatchRunning">
|
||||
{{ isBatchRunning ? `⏳ 계산중 ${batchProgress}%` : '⚡ 팩터 배치 실행' }}
|
||||
</button>
|
||||
<button class="btn-guide-spec" @click="isGuideModalOpen = true">💡 16대 명세 팝업</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-create" @click="openCreateModal"><span class="badge-key">F4</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>엑셀 내보내기 ▼
|
||||
<span class="badge-key">F7</span>엑셀 ▼
|
||||
</button>
|
||||
<div v-if="showExportMenu" class="export-menu-popover">
|
||||
<button @click="triggerExport('OpenXML Excel (.xlsx)')">📊 Excel (.xlsx) 내보내기</button>
|
||||
@@ -226,6 +258,11 @@ onMounted(loadData);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Batch Execution Progress Bar -->
|
||||
<div v-if="isBatchRunning" class="batch-progress-bar-container">
|
||||
<div class="batch-progress-fill" :style="{ width: batchProgress + '%' }"></div>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter Tabs Bar -->
|
||||
<div class="category-tabs-bar">
|
||||
<button v-for="tab in ['ALL', 'BUDGET', 'FACTOR', 'RISK', 'EXECUTION']" :key="tab"
|
||||
@@ -274,10 +311,13 @@ onMounted(loadData);
|
||||
<span class="panel-tag tag-active">Detail Form</span>
|
||||
<h4 class="panel-title">✏️ 상세 데이터 [{{ selectedItem.setting_key }}]</h4>
|
||||
|
||||
<!-- Mode Switch Toggle -->
|
||||
<div class="mode-switch-wrapper">
|
||||
<button class="mode-btn" :class="{ active: !isEditMode }" @click="isEditMode = false">👁️ 읽기 모드</button>
|
||||
<button class="mode-btn" :class="{ active: isEditMode }" @click="isEditMode = true">✏️ 편집 모드</button>
|
||||
<!-- Optimistic Lock Version Badge & Mode Switch Toggle -->
|
||||
<div class="lock-mode-wrapper">
|
||||
<span class="lock-ver-badge">🔒 Ver: v{{ selectedItem.lock_version }}</span>
|
||||
<div class="mode-switch-wrapper">
|
||||
<button class="mode-btn" :class="{ active: !isEditMode }" @click="isEditMode = false">👁️ 읽기</button>
|
||||
<button class="mode-btn" :class="{ active: isEditMode }" @click="isEditMode = true">✏️ 편집</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -347,12 +387,21 @@ onMounted(loadData);
|
||||
</div>
|
||||
|
||||
<button v-if="isEditMode" class="btn-save-form" @click="saveDetailForm">
|
||||
💾 PostgreSQL 설정 변경사항 저장
|
||||
💾 PostgreSQL 설정 변경사항 저장 (Ver v{{ selectedItem.lock_version + 1 }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<!-- Modal Dialog (신규 등록 대화상자) -->
|
||||
<div class="modal-backdrop" v-if="isModalOpen">
|
||||
<div class="modal-dialog">
|
||||
@@ -381,27 +430,31 @@ onMounted(loadData);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 12대 CRUD 컴포넌트 명세 가이드 Modal -->
|
||||
<!-- 16대 CRUD & 현장감 컴포넌트 명세 가이드 Modal -->
|
||||
<div class="modal-backdrop" v-if="isGuideModalOpen">
|
||||
<div class="modal-dialog guide-modal">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">💡 이 화면에 이식된 12대 CRUD 컴포넌트 라인업</h4>
|
||||
<h4 class="modal-title">💡 이 화면의 현장감 넘치는 16대 CRUD 컴포넌트 스펙</h4>
|
||||
<button class="btn-close-modal" @click="isGuideModalOpen = false">✕</button>
|
||||
</div>
|
||||
<div class="modal-body guide-body">
|
||||
<ul class="guide-list">
|
||||
<li><strong>1. 검색 & 핫키 툴바</strong>: F3/F4/F5/F7 더존 표준 핫키 탑재</li>
|
||||
<li><strong>2. 카테고리 탭 바</strong>: ALL, BUDGET, FACTOR 등 1-Click 필터</li>
|
||||
<li><strong>3. 6:4 Master-Detail Split Pane</strong>: 8px 물리 구분선 뷰포트</li>
|
||||
<li><strong>4. AG Grid 데이터 테이블</strong>: 바인딩, 필터, 정렬, Status Chips</li>
|
||||
<li><strong>5. 페이지네이션 바</strong>: 건수 및 페이지 제어 바</li>
|
||||
<li><strong>6. 더존 6대 표준 입력 컴포넌트</strong>: Input, ComboBox, Radio 등</li>
|
||||
<li><strong>7. 읽기/편집 모드 토글</strong>: 👁️/✏️ 안전 모드 스위치</li>
|
||||
<li><strong>8. 파일 첨부 드롭존</strong>: Drag & drop 증빙 서류 칩</li>
|
||||
<li><strong>9. 감사 이력 타임라인</strong>: 수정자/일시 Audit Trail 스캔</li>
|
||||
<li><strong>10. 신규 등록 Modal</strong>: 팝업 대화상자 폼 세트</li>
|
||||
<li><strong>11. 플로팅 토스트 알림</strong>: 3초 자동소멸 성공/경고 알림</li>
|
||||
<li><strong>12. 내보내기 팝업</strong>: Excel (.xlsx) / CSV 선택 메뉴</li>
|
||||
<li><strong>1. Live PostgreSQL Ping 레이턴시 칩</strong>: 4ms 실시간 핑 상태</li>
|
||||
<li><strong>2. 팩터 재계산 비동기 배치 버튼 & 진행률 바</strong>: 0~100% 모의 트랜잭션</li>
|
||||
<li><strong>3. 낙관적 락 버전 인디케이터 (Lock Guard)</strong>: 동시성 충돌 방지 v1~v5</li>
|
||||
<li><strong>4. 실시간 텔레메트리 시스템 바</strong>: CPU/RAM/DB Pool 현장감 지표</li>
|
||||
<li><strong>5. 검색 & 핫키 툴바</strong>: F3/F4/F5/F7 더존 표준 핫키</li>
|
||||
<li><strong>6. 카테고리 탭 바</strong>: ALL/BUDGET/FACTOR/RISK 1-Click 필터</li>
|
||||
<li><strong>7. 6:4 Master-Detail Split Pane</strong>: 8px 물리 구분선 뷰포트</li>
|
||||
<li><strong>8. AG Grid 데이터 테이블</strong>: 정렬, 필터, 단일 행 선택, Status Chips</li>
|
||||
<li><strong>9. 페이지네이션 바</strong>: Page 1 of 1 건수 및 페이지 컨트롤러</li>
|
||||
<li><strong>10. 더존 6대 표준 입력 마스크</strong>: Input, ComboBox, Radio, TextArea 등</li>
|
||||
<li><strong>11. 읽기/편집 모드 토글</strong>: 👁️ 읽기 / ✏️ 편집 전환 스위치</li>
|
||||
<li><strong>12. 증빙 파일 첨부 드롭존</strong>: Drag & drop 파일 첨부 칩</li>
|
||||
<li><strong>13. 감사 이력 타임라인</strong>: 수정자/일시 Audit Trail 스캔</li>
|
||||
<li><strong>14. 신규 등록 Modal</strong>: 팝업 대화상자 폼 세트</li>
|
||||
<li><strong>15. 플로팅 토스트 알림</strong>: 3초 자동소멸 피드백</li>
|
||||
<li><strong>16. 내보내기 드롭다운 팝업</strong>: Excel (.xlsx) / CSV 내보내기</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -420,10 +473,46 @@ onMounted(loadData);
|
||||
background-color: #F1F5F9;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title-with-chip { display: flex; align-items: center; gap: 10px; }
|
||||
.db-ping-chip {
|
||||
background: #166534;
|
||||
color: #DCFCE7;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.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;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.batch-progress-fill {
|
||||
height: 100%;
|
||||
background: #2563EB;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-guide-spec {
|
||||
background: #8E44AD;
|
||||
color: white;
|
||||
@@ -435,8 +524,6 @@ onMounted(loadData);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-guide-spec:hover { background: #7D3C98; }
|
||||
|
||||
.guide-modal { width: 560px; }
|
||||
.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; }
|
||||
@@ -468,7 +555,7 @@ onMounted(loadData);
|
||||
.crud-toolbar {
|
||||
background-color: #34495E;
|
||||
color: #FFFFFF;
|
||||
padding: 10px 16px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -481,7 +568,7 @@ onMounted(loadData);
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
@@ -493,7 +580,7 @@ onMounted(loadData);
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
padding: 6px 12px;
|
||||
padding: 6px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
@@ -537,8 +624,6 @@ onMounted(loadData);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.export-menu-popover button:hover { background-color: #F1F5F9; color: #2563EB; }
|
||||
|
||||
.badge-key {
|
||||
background: rgba(255,255,255,0.2);
|
||||
padding: 1px 4px;
|
||||
@@ -550,7 +635,7 @@ onMounted(loadData);
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
background: white;
|
||||
padding: 6px 12px;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #CBD5E1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
@@ -611,28 +696,43 @@ onMounted(loadData);
|
||||
|
||||
.panel-header {
|
||||
background-color: #F8FAFC;
|
||||
padding: 10px 16px;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mode-switch-wrapper {
|
||||
.lock-mode-wrapper {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lock-ver-badge {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
color: #1E293B;
|
||||
background: #E2E8F0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mode-switch-wrapper {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: #CBD5E1;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 2px 8px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
color: #64748B;
|
||||
cursor: pointer;
|
||||
@@ -641,7 +741,6 @@ onMounted(loadData);
|
||||
.mode-btn.active {
|
||||
background: white;
|
||||
color: #1E293B;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.panel-tag {
|
||||
@@ -656,10 +755,10 @@ onMounted(loadData);
|
||||
.panel-tag.tag-active { background: #2563EB; color: white; }
|
||||
.panel-title { font-size: 0.85rem; font-weight: 700; color: #1E293B; margin: 0; }
|
||||
|
||||
.panel-body { flex: 1; overflow-y: auto; padding: 12px; }
|
||||
.panel-body { flex: 1; overflow-y: auto; padding: 10px; }
|
||||
|
||||
.panel-footer {
|
||||
padding: 8px 16px;
|
||||
padding: 6px 14px;
|
||||
background: #F8FAFC;
|
||||
border-top: 1px solid #E2E8F0;
|
||||
font-size: 0.75rem;
|
||||
@@ -676,14 +775,14 @@ onMounted(loadData);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.form-scroll-body { display: flex; flex-direction: column; gap: 10px; }
|
||||
.form-row { display: flex; flex-direction: column; gap: 4px; }
|
||||
.form-scroll-body { display: flex; flex-direction: column; gap: 8px; }
|
||||
.form-row { display: flex; flex-direction: column; gap: 2px; }
|
||||
|
||||
.file-dropzone-box {
|
||||
border: 1px dashed #CBD5E1;
|
||||
background: #F8FAFC;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: #64748B;
|
||||
@@ -701,18 +800,18 @@ onMounted(loadData);
|
||||
|
||||
.audit-timeline-section {
|
||||
border-top: 1px solid #E2E8F0;
|
||||
padding-top: 10px;
|
||||
margin-top: 6px;
|
||||
padding-top: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: #475569;
|
||||
margin: 0 0 6px 0;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.timeline-list { display: flex; flex-direction: column; gap: 4px; }
|
||||
.timeline-list { display: flex; flex-direction: column; gap: 2px; }
|
||||
.timeline-item {
|
||||
font-size: 0.7rem;
|
||||
color: #64748B;
|
||||
@@ -725,8 +824,8 @@ onMounted(loadData);
|
||||
.change-desc { color: #475569; }
|
||||
|
||||
.btn-save-form {
|
||||
margin-top: 12px;
|
||||
padding: 10px 0;
|
||||
margin-top: 8px;
|
||||
padding: 8px 0;
|
||||
background-color: #2563EB;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
@@ -735,6 +834,19 @@ 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;
|
||||
|
||||
Reference in New Issue
Block a user