ipwatch
This commit is contained in:
155
frontend/src/components/IPGrid.vue
Executable file
155
frontend/src/components/IPGrid.vue
Executable file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Grille d'IPs par sous-réseaux -->
|
||||
<div class="flex-1 overflow-auto p-4">
|
||||
<!-- Pour chaque sous-réseau -->
|
||||
<div v-for="subnet in groupedSubnets" :key="subnet.name" class="mb-6">
|
||||
<!-- En-tête de section -->
|
||||
<div class="mb-3 pb-2 border-b-2 border-monokai-cyan/30">
|
||||
<p class="text-xs text-monokai-comment">{{ subnet.name }}</p>
|
||||
<h3 v-if="subnet.start && subnet.end" class="text-lg font-bold text-monokai-cyan mt-0.5">
|
||||
{{ subnet.start }} à {{ subnet.end }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- Grille des IPs de ce sous-réseau -->
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<IPCell
|
||||
v-for="ip in subnet.ips"
|
||||
:key="ip.ip"
|
||||
:ip="ip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message si vide -->
|
||||
<div v-if="groupedSubnets.length === 0" class="text-center text-monokai-comment mt-10">
|
||||
<p>Aucune IP à afficher</p>
|
||||
<p class="text-sm mt-2">Ajustez les filtres ou lancez un scan</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Légende -->
|
||||
<div class="bg-monokai-bg border-t border-monokai-comment p-3">
|
||||
<div class="flex gap-6 text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded border-2 border-monokai-green bg-monokai-green/15"></div>
|
||||
<span class="text-monokai-text">En ligne (connue)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded border-2 border-monokai-cyan bg-monokai-cyan/15"></div>
|
||||
<span class="text-monokai-text">En ligne (inconnue)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded border-2 border-dashed border-monokai-pink bg-monokai-pink/10 opacity-50"></div>
|
||||
<span class="text-monokai-text">Hors ligne (connue)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded border-2 border-dashed border-monokai-purple bg-monokai-purple/10 opacity-50"></div>
|
||||
<span class="text-monokai-text">Hors ligne (inconnue)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded border-2 border-monokai-comment bg-monokai-comment/20"></div>
|
||||
<span class="text-monokai-text">Libre</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useIPStore } from '@/stores/ipStore'
|
||||
import IPCell from './IPCell.vue'
|
||||
|
||||
const ipStore = useIPStore()
|
||||
const { filteredIPs } = storeToRefs(ipStore)
|
||||
|
||||
// Subnets depuis la config
|
||||
const subnets = ref([])
|
||||
|
||||
// Charger les subnets depuis la config
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/ips/config/content')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
// Parser le YAML pour extraire les subnets
|
||||
const yamlContent = data.content
|
||||
const subnetMatches = yamlContent.match(/subnets:[\s\S]*?(?=\n\w+:|\n$)/)?.[0]
|
||||
|
||||
if (subnetMatches) {
|
||||
// Simple parsing des subnets (améliorer si nécessaire)
|
||||
const subnetLines = subnetMatches.split('\n')
|
||||
let currentSubnet = null
|
||||
|
||||
subnetLines.forEach(line => {
|
||||
if (line.includes('- name:')) {
|
||||
if (currentSubnet) subnets.value.push(currentSubnet)
|
||||
currentSubnet = { name: line.split('"')[1] }
|
||||
} else if (currentSubnet) {
|
||||
if (line.includes('start:')) currentSubnet.start = line.split('"')[1]
|
||||
if (line.includes('end:')) currentSubnet.end = line.split('"')[1]
|
||||
if (line.includes('cidr:')) currentSubnet.cidr = line.split('"')[1]
|
||||
}
|
||||
})
|
||||
if (currentSubnet) subnets.value.push(currentSubnet)
|
||||
console.log('=== SUBNETS LOADED V2 ===', Date.now(), subnets.value)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur chargement subnets:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Fonction pour vérifier si une IP appartient à un subnet
|
||||
function ipInSubnet(ip, start, end) {
|
||||
const ipNum = ipToNumber(ip)
|
||||
const startNum = ipToNumber(start)
|
||||
const endNum = ipToNumber(end)
|
||||
return ipNum >= startNum && ipNum <= endNum
|
||||
}
|
||||
|
||||
// Convertir une IP en nombre
|
||||
function ipToNumber(ip) {
|
||||
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet), 0) >>> 0
|
||||
}
|
||||
|
||||
// Grouper les IPs par subnet
|
||||
const groupedSubnets = computed(() => {
|
||||
const groups = []
|
||||
|
||||
// Pour chaque subnet défini
|
||||
subnets.value.forEach(subnet => {
|
||||
const subnetIPs = filteredIPs.value.filter(ip =>
|
||||
ipInSubnet(ip.ip, subnet.start, subnet.end)
|
||||
)
|
||||
|
||||
if (subnetIPs.length > 0) {
|
||||
groups.push({
|
||||
name: subnet.name,
|
||||
start: subnet.start,
|
||||
end: subnet.end,
|
||||
ips: subnetIPs.sort((a, b) => ipToNumber(a.ip) - ipToNumber(b.ip))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Ajouter section "Autres" pour les IPs hors subnets
|
||||
const otherIPs = filteredIPs.value.filter(ip => {
|
||||
return !subnets.value.some(subnet => ipInSubnet(ip.ip, subnet.start, subnet.end))
|
||||
})
|
||||
|
||||
if (otherIPs.length > 0) {
|
||||
groups.push({
|
||||
name: 'Autres',
|
||||
start: '',
|
||||
end: '',
|
||||
ips: otherIPs.sort((a, b) => ipToNumber(a.ip) - ipToNumber(b.ip))
|
||||
})
|
||||
}
|
||||
|
||||
return groups
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user