update
This commit is contained in:
@@ -1,79 +1,223 @@
|
||||
<template>
|
||||
<div class="settings">
|
||||
<h2>설정</h2>
|
||||
<p>애플리케이션 설정을 관리합니다.</p>
|
||||
|
||||
<div class="settings-form">
|
||||
<div class="form-group">
|
||||
<label>내보내기 경로</label>
|
||||
<input type="text" v-model="form.exportPath" placeholder="./exports">
|
||||
<Card>
|
||||
<template #header>
|
||||
<div class="card-header-content">
|
||||
<h3>설정</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="loading" class="loading">로딩중...</div>
|
||||
|
||||
<form v-else @submit.prevent="saveSettings" class="settings-form">
|
||||
<div class="setting-section">
|
||||
<h4>일반 설정</h4>
|
||||
|
||||
<FormInput
|
||||
v-model="settings['server.port']"
|
||||
label="서버 포트"
|
||||
type="number"
|
||||
hint="애플리케이션이 실행될 포트 번호 (기본: 8080)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="setting-section">
|
||||
<h4>내보내기 설정</h4>
|
||||
|
||||
<FormInput
|
||||
v-model="settings['export.path']"
|
||||
label="내보내기 경로"
|
||||
placeholder="예: C:\LogHunter\exports"
|
||||
hint="리포트 파일이 저장될 기본 경로"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="setting-section">
|
||||
<h4>데이터 관리</h4>
|
||||
|
||||
<FormInput
|
||||
v-model="settings['retention.days']"
|
||||
label="로그 보관 기간 (일)"
|
||||
type="number"
|
||||
hint="에러 로그 데이터 보관 기간 (0 = 무제한)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="setting-section">
|
||||
<h4>스캔 설정</h4>
|
||||
|
||||
<FormInput
|
||||
v-model="settings['scan.timeout']"
|
||||
label="스캔 타임아웃 (초)"
|
||||
type="number"
|
||||
hint="SFTP 연결 및 파일 다운로드 타임아웃"
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
v-model="settings['scan.maxFileSize']"
|
||||
label="최대 파일 크기 (MB)"
|
||||
type="number"
|
||||
hint="분석할 로그 파일의 최대 크기"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<Button @click="loadSettings" variant="secondary">초기화</Button>
|
||||
<Button type="submit" :loading="saving">저장</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
<!-- 앱 정보 -->
|
||||
<Card class="app-info">
|
||||
<template #header>
|
||||
<h3>애플리케이션 정보</h3>
|
||||
</template>
|
||||
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="label">버전</span>
|
||||
<span class="value">1.0.0</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">프레임워크</span>
|
||||
<span class="value">Spring Boot 3.2 + Vue 3</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">데이터베이스</span>
|
||||
<span class="value">SQLite (./data/loghunter.db)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>로그 보관 기간 (일)</label>
|
||||
<input type="number" v-model="form.retentionDays" min="1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>웹 서버 포트</label>
|
||||
<input type="number" v-model="form.port" min="1" max="65535">
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn-primary">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Card, Button, FormInput } from '@/components'
|
||||
import { settingApi } from '@/api'
|
||||
|
||||
const form = ref({
|
||||
exportPath: './exports',
|
||||
retentionDays: 30,
|
||||
port: 8080
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const settings = ref({})
|
||||
|
||||
// 기본값
|
||||
const defaultSettings = {
|
||||
'server.port': '8080',
|
||||
'export.path': './exports',
|
||||
'retention.days': '90',
|
||||
'scan.timeout': '30',
|
||||
'scan.maxFileSize': '100'
|
||||
}
|
||||
|
||||
const loadSettings = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await settingApi.getAllAsMap()
|
||||
settings.value = { ...defaultSettings, ...data }
|
||||
} catch (e) {
|
||||
console.error('Failed to load settings:', e)
|
||||
settings.value = { ...defaultSettings }
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const saveSettings = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
// 각 설정 저장
|
||||
for (const [key, value] of Object.entries(settings.value)) {
|
||||
await settingApi.save({ key, value: String(value) })
|
||||
}
|
||||
alert('설정이 저장되었습니다.')
|
||||
} catch (e) {
|
||||
console.error('Failed to save settings:', e)
|
||||
alert('설정 저장에 실패했습니다.')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings h2 { margin-bottom: 1rem; }
|
||||
.settings {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.card-header-content h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
max-width: 500px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
.setting-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
.setting-section:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.setting-section h4 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 16px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.app-info {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.app-info h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user