Files
frontend/src/components/IpSecurity.vue
T
2026-06-14 13:22:27 +03:00

134 lines
4.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-if="ipData" class="bg-gray-50/80 dark:bg-gray-800/30 border-b border-gray-200 dark:border-gray-700 text-sm backdrop-blur-sm">
<div class="container mx-auto px-6 py-2 md:px-12">
<div class="flex items-center justify-between gap-3 flex-wrap">
<!-- IP и статус защиты -->
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400 text-xs">🌐 Ваш IP:</span>
<code class="font-mono text-xs md:text-sm font-medium bg-white dark:bg-gray-900 px-2 py-0.5 rounded border border-gray-200 dark:border-gray-700">
{{ ipData.ip }}
</code>
</div>
<span
class="inline-flex items-center gap-2 text-xs px-2 py-0.5 rounded-full font-medium"
:class="isProtected
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'"
>
<span class="relative flex h-1.5 w-1.5">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75"
:class="isProtected ? 'bg-green-500' : 'bg-amber-500'"></span>
<span class="relative inline-flex rounded-full h-1.5 w-1.5"
:class="isProtected ? 'bg-green-600' : 'bg-amber-600'"></span>
</span>
{{ isProtected ? 'Защищено' : 'Не защищено' }}
</span>
</div>
<!-- Локация с флагом -->
<div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
<img
v-if="flagUrl"
:src="flagUrl"
:alt="ipData.country_code"
class="w-4 h-3 object-cover"
@error="handleFlagError"
/>
<span v-if="ipData.city" class="hidden sm:inline">{{ ipData.city }}</span>
<span v-if="ipData.country" class="text-gray-600 dark:text-gray-300 font-medium">
{{ ipData.country }}
</span>
<span v-if="ipData.org" class="hidden md:inline text-gray-400">
{{ ipData.org.split(' ').slice(0, 2).join(' ') }}
</span>
</div>
</div>
</div>
</div>
<!-- Лоадер -->
<div v-else-if="loading" class="bg-gray-50/80 dark:bg-gray-800/30 border-b border-gray-200 dark:border-gray-700">
<div class="container mx-auto px-6 py-2 md:px-12">
<div class="flex items-center gap-3">
<div class="h-5 w-28 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
<div class="h-5 w-16 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
</div>
</div>
</div>
<!-- Ошибка -->
<div v-else-if="error" class="bg-amber-50 dark:bg-amber-900/10 border-b border-amber-200 dark:border-amber-800/50">
<div class="container mx-auto px-6 py-1.5 md:px-12">
<div class="flex items-center gap-2 text-xs text-amber-700 dark:text-amber-400">
<span>⚠️</span>
<span>Не удалось определить IP</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const ipData = ref(null)
const loading = ref(true)
const error = ref(false)
const isProtected = ref(false)
const flagError = ref(false)
// Определение VPN по org / asn
const vpnKeywords = ['vpn', 'vpn service', 'vpnserver', 'mullvad', 'nordvpn',
'expressvpn', 'protonvpn', 'wireguard', 'openvpn', 'surfshark',
'cyberghost', 'ivpn', 'vyprvpn', 'torguard', 'uvpn']
async function fetchIp() {
loading.value = true
error.value = false
flagError.value = false
try {
const response = await fetch('/api/ip')
if (!response.ok) throw new Error()
const data = await response.json()
ipData.value = {
ip: data.ip,
country_code: data.location?.country_code || null,
city: data.location?.city || null,
org: data.isp?.org || data.isp?.isp || null,
country: data.location?.country || null
}
// Определяем защищённость
const orgStr = (ipData.value.org || '').toLowerCase()
isProtected.value = vpnKeywords.some(keyword => orgStr.includes(keyword))
} catch (err) {
console.error('IP fetch error:', err)
error.value = true
ipData.value = null
} finally {
loading.value = false
}
}
// URL флага через flagcdn.com (более надёжный)
const flagUrl = computed(() => {
if (!ipData.value?.country_code || flagError.value) return null
const countryCode = ipData.value.country_code.toLowerCase()
// Используем flagcdn.com с автоматическим кэшированием
return `https://flagcdn.com/${countryCode}.svg`
})
function handleFlagError() {
flagError.value = true
}
onMounted(() => {
fetchIp()
})
</script>