Files
system-monitor/frontend/components/DashboardControl.vue
2025-12-28 17:08:12 +09:00

213 lines
5.2 KiB
Vue

<template>
<div class="control-panel">
<div class="control-row">
<span class="control-label">조회 간격:</span>
<div class="interval-buttons">
<button
v-for="min in [1, 2, 3, 4, 5]"
:key="min"
:class="['interval-btn', { active: autoRefresh && interval === min }]"
@click="selectInterval(min)"
>
{{ min }}
</button>
</div>
<div class="control-divider"></div>
<button
:class="['auto-refresh-btn', { active: autoRefresh }]"
@click="toggleAutoRefresh"
>
{{ autoRefresh ? '⏸ 자동갱신 ON' : '▶ 자동갱신 OFF' }}
</button>
<div class="control-divider"></div>
<span class="control-label">특정시간:</span>
<input
type="datetime-local"
v-model="selectedDatetime"
class="datetime-input"
:disabled="autoRefresh || fetchState !== 'idle'"
:class="{ disabled: autoRefresh || fetchState !== 'idle' }"
/>
<button
class="refresh-btn"
:disabled="autoRefresh || fetchState !== 'idle'"
:class="{
disabled: autoRefresh,
loading: fetchState === 'loading',
success: fetchState === 'success'
}"
@click="doRefresh"
>
<span v-if="fetchState === 'loading'" class="loading-spinner"></span>
{{ buttonText }}
</button>
<div class="last-fetch-info">
마지막 조회: <span class="last-fetch-time">{{ relativeTime }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
interval: number
autoRefresh: boolean
lastFetchTime: Date | null
fetchState: 'idle' | 'loading' | 'success'
}>()
const emit = defineEmits<{
(e: 'update:interval', value: number): void
(e: 'update:autoRefresh', value: boolean): void
(e: 'fetchAt', datetime: string): void
(e: 'refresh'): void
}>()
// props destructure for template
const { interval, autoRefresh, fetchState } = toRefs(props)
const selectedDatetime = ref('')
const relativeTime = ref('-')
const buttonText = computed(() => {
switch (props.fetchState) {
case 'loading': return '조회 중...'
case 'success': return '✓ 완료'
default: return '조회'
}
})
function getCurrentDatetimeLocal(): string {
const now = new Date()
const y = now.getFullYear()
const m = String(now.getMonth() + 1).padStart(2, '0')
const d = String(now.getDate()).padStart(2, '0')
const h = String(now.getHours()).padStart(2, '0')
const min = String(now.getMinutes()).padStart(2, '0')
return `${y}-${m}-${d}T${h}:${min}`
}
function getRelativeTime(date: Date | null): string {
if (!date) return '-'
const now = new Date()
const diff = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diff < 5) return '방금 전'
if (diff < 60) return `${diff}초 전`
if (diff < 3600) return `${Math.floor(diff / 60)}분 전`
if (diff < 86400) return `${Math.floor(diff / 3600)}시간 전`
return `${Math.floor(diff / 86400)}일 전`
}
function updateRelativeTime() {
relativeTime.value = getRelativeTime(props.lastFetchTime)
}
let timeTimer: ReturnType<typeof window.setInterval> | null = null
onMounted(() => {
updateRelativeTime()
timeTimer = window.setInterval(updateRelativeTime, 1000)
})
onUnmounted(() => {
if (timeTimer) {
window.clearInterval(timeTimer)
}
})
watch(() => props.lastFetchTime, () => {
updateRelativeTime()
})
// 조회간격 선택 → 자동갱신 ON + 특정시간 초기화
function selectInterval(min: number) {
selectedDatetime.value = ''
emit('update:interval', min)
if (!props.autoRefresh) {
emit('update:autoRefresh', true)
}
emit('refresh')
}
function toggleAutoRefresh() {
const newValue = !props.autoRefresh
emit('update:autoRefresh', newValue)
if (newValue) {
// 자동갱신 ON 시 특정시간 초기화 + 즉시 조회
selectedDatetime.value = ''
emit('refresh')
} else {
// 자동갱신 OFF 시 현재시간을 기본값으로 설정
selectedDatetime.value = getCurrentDatetimeLocal()
}
}
function doRefresh() {
// 자동갱신 OFF일 때만 동작
if (props.autoRefresh || props.fetchState !== 'idle') return
// 특정시간 입력된 경우 → 해당 시간 조회
if (selectedDatetime.value) {
emit('fetchAt', selectedDatetime.value.replace('T', ' '))
} else {
// 특정시간 미입력 → 현재 시점 조회
emit('refresh')
}
}
</script>
<style scoped>
.datetime-input.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.refresh-btn.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.refresh-btn.loading {
cursor: wait;
background: var(--btn-primary-bg);
}
.refresh-btn.success {
background: var(--success-border);
border-color: var(--success-border);
}
.loading-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #fff;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-right: 6px;
vertical-align: middle;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.hint-text {
font-size: 12px;
color: var(--text-dim);
font-style: italic;
margin-left: 8px;
}
</style>