ipwatch
This commit is contained in:
450
frontend/src/stores/ipStore.js
Executable file
450
frontend/src/stores/ipStore.js
Executable file
@@ -0,0 +1,450 @@
|
||||
/**
|
||||
* Store Pinia pour la gestion des IPs
|
||||
*/
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
export const useIPStore = defineStore('ip', () => {
|
||||
// État
|
||||
const ips = ref([])
|
||||
const selectedIP = ref(null)
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
const stats = ref({
|
||||
total: 0,
|
||||
online: 0,
|
||||
offline: 0,
|
||||
known: 0,
|
||||
unknown: 0
|
||||
})
|
||||
const searchQuery = ref('')
|
||||
const invertSearch = ref(false)
|
||||
const lastScanDate = ref(null)
|
||||
const scanProgress = ref({
|
||||
current: 0,
|
||||
total: 0,
|
||||
currentIP: null
|
||||
})
|
||||
const scanLogs = ref([])
|
||||
const isScanning = ref(false)
|
||||
const uiConfig = ref({
|
||||
cell_size: 30,
|
||||
architecture_title_font_size: 18
|
||||
})
|
||||
const configReloadTick = ref(0)
|
||||
|
||||
// WebSocket
|
||||
const ws = ref(null)
|
||||
const wsConnected = ref(false)
|
||||
|
||||
// Computed
|
||||
const filteredIPs = computed(() => {
|
||||
const { tokens, flags } = parseSearchQuery(searchQuery.value)
|
||||
|
||||
return ips.value.filter(ip => {
|
||||
if (flags.requireOnline && ip.last_status !== 'online') return false
|
||||
if (flags.requireOffline && ip.last_status !== 'offline') return false
|
||||
if (flags.requireFree && ip.last_status) return false
|
||||
if (flags.requireKnown && !ip.known) return false
|
||||
if (flags.requireUnknown && ip.known) return false
|
||||
if (flags.requireTracked && !ip.tracked) return false
|
||||
if (flags.requireVm && !ip.vm) return false
|
||||
if (flags.requireHardwareBench && !ip.hardware_bench) return false
|
||||
|
||||
let matches = true
|
||||
|
||||
if (tokens.length === 0) {
|
||||
matches = true
|
||||
} else {
|
||||
const haystack = normalizeText([
|
||||
ip.ip,
|
||||
ip.name,
|
||||
ip.hostname,
|
||||
ip.host,
|
||||
ip.location,
|
||||
ip.mac,
|
||||
ip.vendor,
|
||||
ip.link,
|
||||
ip.last_status,
|
||||
ip.tracked ? 'suivie' : '',
|
||||
ip.vm ? 'vm' : '',
|
||||
ip.hardware_bench ? 'hardware' : '',
|
||||
(ip.open_ports || []).join(' ')
|
||||
].filter(Boolean).join(' '))
|
||||
|
||||
// Match si au moins un mot est présent
|
||||
matches = tokens.some(token => haystack.includes(token))
|
||||
}
|
||||
|
||||
return invertSearch.value ? !matches : matches
|
||||
})
|
||||
})
|
||||
|
||||
// Actions
|
||||
async function fetchUIConfig() {
|
||||
try {
|
||||
const response = await axios.get('/api/config/ui')
|
||||
uiConfig.value = response.data
|
||||
// Appliquer la taille des cellules, de la police et de l'espacement via variables CSS
|
||||
document.documentElement.style.setProperty('--cell-size', `${response.data.cell_size}px`)
|
||||
document.documentElement.style.setProperty('--font-size', `${response.data.font_size}px`)
|
||||
document.documentElement.style.setProperty('--cell-gap', `${response.data.cell_gap}px`)
|
||||
document.documentElement.style.setProperty(
|
||||
'--arch-title-size',
|
||||
`${response.data.architecture_title_font_size}px`
|
||||
)
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement config UI:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadConfig() {
|
||||
try {
|
||||
const response = await axios.post('/api/config/reload')
|
||||
if (response.data.success) {
|
||||
// Appliquer la nouvelle config UI
|
||||
uiConfig.value = response.data.ui
|
||||
document.documentElement.style.setProperty('--cell-size', `${response.data.ui.cell_size}px`)
|
||||
document.documentElement.style.setProperty('--font-size', `${response.data.ui.font_size}px`)
|
||||
document.documentElement.style.setProperty('--cell-gap', `${response.data.ui.cell_gap}px`)
|
||||
document.documentElement.style.setProperty(
|
||||
'--arch-title-size',
|
||||
`${response.data.ui.architecture_title_font_size}px`
|
||||
)
|
||||
return response.data.message
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Erreur rechargement config:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function bumpConfigReload() {
|
||||
configReloadTick.value += 1
|
||||
}
|
||||
|
||||
async function fetchIPs() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await axios.get('/api/ips/')
|
||||
ips.value = response.data
|
||||
await fetchStats()
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
console.error('Erreur chargement IPs:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStats() {
|
||||
try {
|
||||
const response = await axios.get('/api/ips/stats/summary')
|
||||
stats.value = response.data
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement stats:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateIP(ipAddress, data) {
|
||||
try {
|
||||
const response = await axios.put(`/api/ips/${ipAddress}`, data)
|
||||
|
||||
// Mettre à jour dans le store
|
||||
const index = ips.value.findIndex(ip => ip.ip === ipAddress)
|
||||
if (index !== -1) {
|
||||
ips.value[index] = response.data
|
||||
}
|
||||
|
||||
if (selectedIP.value?.ip === ipAddress) {
|
||||
selectedIP.value = response.data
|
||||
}
|
||||
|
||||
return response.data
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteIP(ipAddress) {
|
||||
try {
|
||||
await axios.delete(`/api/ips/${ipAddress}`)
|
||||
|
||||
// Retirer du store
|
||||
const index = ips.value.findIndex(ip => ip.ip === ipAddress)
|
||||
if (index !== -1) {
|
||||
ips.value.splice(index, 1)
|
||||
}
|
||||
|
||||
if (selectedIP.value?.ip === ipAddress) {
|
||||
selectedIP.value = null
|
||||
}
|
||||
|
||||
await fetchStats()
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function getIPHistory(ipAddress, hours = 24) {
|
||||
try {
|
||||
const response = await axios.get(`/api/ips/${ipAddress}/history?hours=${hours}`)
|
||||
return response.data
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement historique:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function startScan() {
|
||||
try {
|
||||
await axios.post('/api/scan/start')
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function selectIP(ip) {
|
||||
selectedIP.value = ip
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
selectedIP.value = null
|
||||
}
|
||||
|
||||
// WebSocket
|
||||
function connectWebSocket() {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws`
|
||||
|
||||
ws.value = new WebSocket(wsUrl)
|
||||
|
||||
ws.value.onopen = () => {
|
||||
console.log('WebSocket connecté')
|
||||
wsConnected.value = true
|
||||
|
||||
// Heartbeat toutes les 30s
|
||||
setInterval(() => {
|
||||
if (ws.value?.readyState === WebSocket.OPEN) {
|
||||
ws.value.send('ping')
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
|
||||
ws.value.onmessage = (event) => {
|
||||
// Ignorer les messages ping/pong (texte brut)
|
||||
if (typeof event.data === 'string' && (event.data === 'ping' || event.data === 'pong')) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const message = JSON.parse(event.data)
|
||||
handleWebSocketMessage(message)
|
||||
} catch (err) {
|
||||
// Ignorer silencieusement les erreurs de parsing pour les messages non-JSON
|
||||
}
|
||||
}
|
||||
|
||||
ws.value.onerror = (error) => {
|
||||
// Erreur WebSocket - ne pas logger si c'est juste une déconnexion normale
|
||||
wsConnected.value = false
|
||||
}
|
||||
|
||||
ws.value.onclose = (event) => {
|
||||
wsConnected.value = false
|
||||
|
||||
// Ne logger que si c'est une fermeture anormale
|
||||
if (!event.wasClean) {
|
||||
console.log('WebSocket déconnecté - reconnexion dans 5s...')
|
||||
}
|
||||
|
||||
// Reconnexion après 5s
|
||||
setTimeout(() => {
|
||||
// Ne reconnecter que si on n'est pas déjà connecté
|
||||
if (!ws.value || ws.value.readyState === WebSocket.CLOSED) {
|
||||
connectWebSocket()
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
function handleWebSocketMessage(message) {
|
||||
// Logger uniquement les messages importants (pas scan_progress pour éviter le spam)
|
||||
if (message.type !== 'scan_progress') {
|
||||
console.log('WebSocket:', message.type)
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case 'scan_start':
|
||||
// Notification début de scan
|
||||
isScanning.value = true
|
||||
scanLogs.value = []
|
||||
scanProgress.value = {
|
||||
current: 0,
|
||||
total: message.total || 0,
|
||||
currentIP: null
|
||||
}
|
||||
break
|
||||
|
||||
case 'scan_progress':
|
||||
// Progression du scan
|
||||
if (message.current) scanProgress.value.current = message.current
|
||||
if (message.total) scanProgress.value.total = message.total
|
||||
if (message.ip) scanProgress.value.currentIP = message.ip
|
||||
break
|
||||
|
||||
case 'scan_complete':
|
||||
// Rafraîchir les données après scan
|
||||
isScanning.value = false
|
||||
lastScanDate.value = new Date()
|
||||
scanProgress.value = { current: 0, total: 0, currentIP: null }
|
||||
fetchIPs()
|
||||
if (message.stats) stats.value = message.stats
|
||||
break
|
||||
|
||||
case 'ip_update':
|
||||
// Mise à jour d'une IP
|
||||
const updatedIP = ips.value.find(ip => ip.ip === message.data.ip)
|
||||
if (updatedIP) {
|
||||
Object.assign(updatedIP, message.data)
|
||||
}
|
||||
break
|
||||
|
||||
case 'new_ip':
|
||||
// Nouvelle IP détectée
|
||||
fetchIPs() // Recharger pour être sûr
|
||||
break
|
||||
case 'scan_log':
|
||||
if (message.message) {
|
||||
scanLogs.value.push(message.message)
|
||||
if (scanLogs.value.length > 200) {
|
||||
scanLogs.value.splice(0, scanLogs.value.length - 200)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function disconnectWebSocket() {
|
||||
if (ws.value) {
|
||||
ws.value.close()
|
||||
ws.value = null
|
||||
wsConnected.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// État
|
||||
ips,
|
||||
selectedIP,
|
||||
loading,
|
||||
error,
|
||||
stats,
|
||||
searchQuery,
|
||||
invertSearch,
|
||||
wsConnected,
|
||||
lastScanDate,
|
||||
scanProgress,
|
||||
scanLogs,
|
||||
isScanning,
|
||||
uiConfig,
|
||||
configReloadTick,
|
||||
|
||||
// Computed
|
||||
filteredIPs,
|
||||
|
||||
// Actions
|
||||
fetchUIConfig,
|
||||
reloadConfig,
|
||||
bumpConfigReload,
|
||||
fetchIPs,
|
||||
fetchStats,
|
||||
updateIP,
|
||||
deleteIP,
|
||||
getIPHistory,
|
||||
startScan,
|
||||
selectIP,
|
||||
clearSelection,
|
||||
connectWebSocket,
|
||||
disconnectWebSocket
|
||||
}
|
||||
})
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value ?? '')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
}
|
||||
|
||||
function parseSearchQuery(query) {
|
||||
let normalized = normalizeText(query)
|
||||
|
||||
const flags = {
|
||||
requireOnline: false,
|
||||
requireOffline: false,
|
||||
requireKnown: false,
|
||||
requireUnknown: false,
|
||||
requireFree: false,
|
||||
requireTracked: false,
|
||||
requireVm: false,
|
||||
requireHardwareBench: false
|
||||
}
|
||||
|
||||
const onlinePattern = /\ben\s+ligne\b/g
|
||||
const offlinePattern = /\bhors\s+ligne\b/g
|
||||
|
||||
if (onlinePattern.test(normalized)) flags.requireOnline = true
|
||||
if (offlinePattern.test(normalized)) flags.requireOffline = true
|
||||
|
||||
normalized = normalized
|
||||
.replace(onlinePattern, ' ')
|
||||
.replace(offlinePattern, ' ')
|
||||
|
||||
let tokens = normalized.split(/\s+/).filter(Boolean)
|
||||
|
||||
tokens = tokens.filter(token => {
|
||||
if (token === 'connue') {
|
||||
flags.requireKnown = true
|
||||
return false
|
||||
}
|
||||
if (token === 'inconnue') {
|
||||
flags.requireUnknown = true
|
||||
return false
|
||||
}
|
||||
if (token === 'libre') {
|
||||
flags.requireFree = true
|
||||
return false
|
||||
}
|
||||
if (token === 'suivie' || token === 'suivi') {
|
||||
flags.requireTracked = true
|
||||
return false
|
||||
}
|
||||
if (token === 'vm') {
|
||||
flags.requireVm = true
|
||||
return false
|
||||
}
|
||||
if (token === 'hardware' || token === 'bench' || token === 'hardware_bench') {
|
||||
flags.requireHardwareBench = true
|
||||
return false
|
||||
}
|
||||
if (token === 'enligne' || token === 'en-ligne') {
|
||||
flags.requireOnline = true
|
||||
return false
|
||||
}
|
||||
if (token === 'horsligne' || token === 'hors-ligne') {
|
||||
flags.requireOffline = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return { tokens, flags }
|
||||
}
|
||||
Reference in New Issue
Block a user