77 lines
2.8 KiB
Vue
77 lines
2.8 KiB
Vue
|
|
<template>
|
||
|
|
<div class="quick-ping">
|
||
|
|
<div class="ping-row">
|
||
|
|
<input
|
||
|
|
v-model="target"
|
||
|
|
placeholder="IP address to ping..."
|
||
|
|
@keyup.enter="runPing"
|
||
|
|
:disabled="pinging"
|
||
|
|
/>
|
||
|
|
<button class="btn-ping" @click="runPing" :disabled="!target || pinging">
|
||
|
|
{{ pinging ? 'Pinging...' : 'Ping' }}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<div v-if="result" class="ping-result" :class="result.reachable ? 'reachable' : 'unreachable'">
|
||
|
|
<div class="ping-summary">
|
||
|
|
<span class="ping-target">{{ result.target }}</span>
|
||
|
|
<span v-if="result.reachable" class="ping-status ok">Reachable</span>
|
||
|
|
<span v-else class="ping-status fail">Unreachable</span>
|
||
|
|
</div>
|
||
|
|
<div v-if="result.reachable && result.stats" class="ping-stats">
|
||
|
|
<span>{{ result.received }}/{{ result.sent }} replies</span>
|
||
|
|
<span>Min: {{ result.stats.min_ms }}ms</span>
|
||
|
|
<span>Avg: {{ result.stats.avg_ms }}ms</span>
|
||
|
|
<span>Max: {{ result.stats.max_ms }}ms</span>
|
||
|
|
<span>Loss: {{ result.loss_pct }}%</span>
|
||
|
|
</div>
|
||
|
|
<div v-if="result.error" class="ping-error">{{ result.error }}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { ref } from 'vue'
|
||
|
|
import { api } from '../api.js'
|
||
|
|
|
||
|
|
const target = ref('')
|
||
|
|
const pinging = ref(false)
|
||
|
|
const result = ref(null)
|
||
|
|
|
||
|
|
async function runPing() {
|
||
|
|
if (!target.value || pinging.value) return
|
||
|
|
pinging.value = true
|
||
|
|
result.value = null
|
||
|
|
try {
|
||
|
|
result.value = await api.ping(target.value, 5)
|
||
|
|
} catch (e) {
|
||
|
|
result.value = { target: target.value, reachable: false, error: e.message }
|
||
|
|
} finally {
|
||
|
|
pinging.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.quick-ping { margin-bottom: 16px; }
|
||
|
|
.ping-row { display: flex; gap: 6px; }
|
||
|
|
.ping-row input { flex: 1; }
|
||
|
|
.btn-ping {
|
||
|
|
padding: 6px 16px; font-weight: 600; font-size: 13px;
|
||
|
|
background: var(--accent); color: #fff; white-space: nowrap;
|
||
|
|
}
|
||
|
|
.btn-ping:disabled { opacity: 0.4; }
|
||
|
|
.ping-result {
|
||
|
|
margin-top: 8px; padding: 8px 12px;
|
||
|
|
border-radius: var(--radius); font-size: 13px;
|
||
|
|
}
|
||
|
|
.ping-result.reachable { background: rgba(72,187,120,0.1); border: 1px solid rgba(72,187,120,0.3); }
|
||
|
|
.ping-result.unreachable { background: rgba(252,129,129,0.1); border: 1px solid rgba(252,129,129,0.3); }
|
||
|
|
.ping-summary { display: flex; align-items: center; gap: 10px; }
|
||
|
|
.ping-target { font-weight: 600; font-family: monospace; }
|
||
|
|
.ping-status { font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 600; }
|
||
|
|
.ping-status.ok { background: rgba(72,187,120,0.2); color: var(--success); }
|
||
|
|
.ping-status.fail { background: rgba(252,129,129,0.2); color: var(--danger); }
|
||
|
|
.ping-stats { display: flex; gap: 12px; margin-top: 6px; font-size: 12px; color: var(--muted); font-family: monospace; }
|
||
|
|
.ping-error { margin-top: 4px; color: var(--danger); font-size: 12px; }
|
||
|
|
</style>
|