diff --git a/src/frontend/src/views/SystemSettingsView.vue b/src/frontend/src/views/SystemSettingsView.vue index bb9354a3..42ccc327 100644 --- a/src/frontend/src/views/SystemSettingsView.vue +++ b/src/frontend/src/views/SystemSettingsView.vue @@ -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([]); const selectedItem = ref(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({ id: 0, @@ -63,10 +70,11 @@ const modalItem = ref({ 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([ - { 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 `${params.value}`; } }, - { 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);
-

⚙️ SCR-07: 올인원 12대 CRUD 마스터피스 매니저

- Master-Detail, AG Grid, Toast, Tabs, Audit Timeline, Mode Switch, Modal, 더존 6대 표준 컴포넌트 +
+

⚙️ SCR-07: 현장감 퀀트 CRUD 매니저

+ 🟢 PostgreSQL Live ({{ pingMs }}ms) +
+ Master-Detail, AG Grid, Batch Progress, Lock Guard, Telemetry, Toast, 더존 컴포넌트
- + + - +
@@ -226,6 +258,11 @@ onMounted(loadData);
+ +
+
+
+
- + +
+ 🔒 Ver: v{{ selectedItem.lock_version }} +
+ + +
@@ -347,12 +387,21 @@ onMounted(loadData);
+ +
+ 🖥️ CPU: 12% + 💾 RAM: 512MB / 4GB + 🔌 DB Pool: {{ dbConnections }}/20 Active + 📬 Outbox Queue: 0 Pending + ⚡ Hangfire Jobs: Healthy +
+ - +