2025-06-06 23:53:28 +02:00

488 lines
15 KiB
Vue

<template>
<UDashboardPanel id="data-centres">
<template #header>
<UDashboardNavbar title="Data Centres">
<template #leading>
<UDashboardSidebarCollapse />
</template>
<template #right>
<UButton to="/connect/clouds" size="sm">
Connecter Cloud
</UButton>
</template>
</UDashboardNavbar>
<UDashboardToolbar>
<template #left>
<UBadge variant="subtle" size="lg">
{{ dataCentres.length }} centres mondiaux
</UBadge>
</template>
<template #right>
<UBadge variant="subtle" class="text-xs">
{{ dataCentres.filter(dc => dc.status === 'active').length }} actifs
</UBadge>
<UBadge variant="subtle" class="text-xs">
99.9% uptime
</UBadge>
</template>
</UDashboardToolbar>
</template>
<template #body>
<div class="space-y-12">
<!-- Header Section -->
<div class="text-center mb-8">
<h1 class="text-3xl font-bold mb-4">
Réseau Global de Data Centres
</h1>
<p class="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
Connectez-vous à notre infrastructure mondiale avec plus de {{ dataCentres.length }} data centres dans {{ regions.length }} régions
</p>
</div>
<!-- Statistiques -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<UCard class="text-center">
<div class="p-6">
<div class="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<UIcon name="i-lucide-server" class="w-6 h-6 text-primary" />
</div>
<div class="text-2xl font-bold mb-1">
{{ dataCentres.length }}+
</div>
<div class="text-sm text-gray-600 dark:text-gray-400">
Data Centres
</div>
</div>
</UCard>
<UCard class="text-center">
<div class="p-6">
<div class="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<UIcon name="i-lucide-globe" class="w-6 h-6 text-primary" />
</div>
<div class="text-2xl font-bold mb-1">
{{ regions.length }}
</div>
<div class="text-sm text-gray-600 dark:text-gray-400">
Régions
</div>
</div>
</UCard>
<UCard class="text-center">
<div class="p-6">
<div class="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<UIcon name="i-lucide-shield-check" class="w-6 h-6 text-primary" />
</div>
<div class="text-2xl font-bold mb-1">
99.9%
</div>
<div class="text-sm text-gray-600 dark:text-gray-400">
Uptime SLA
</div>
</div>
</UCard>
<UCard class="text-center">
<div class="p-6">
<div class="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<UIcon name="i-lucide-leaf" class="w-6 h-6 text-primary" />
</div>
<div class="text-2xl font-bold mb-1">
100%
</div>
<div class="text-sm text-gray-600 dark:text-gray-400">
Énergie verte
</div>
</div>
</UCard>
</div>
<!-- Carte Interactive -->
<UCard>
<template #header>
<h2 class="text-xl font-semibold">
Carte Interactive des Data Centres
</h2>
</template>
<div class="space-y-4">
<!-- Filtres -->
<div>
<UTabs v-model="selectedRegion" :items="tabs" class="w-full" />
</div>
<!-- Carte Leaflet -->
<div id="map" class="h-[500px] w-full rounded-lg" />
</div>
</UCard>
<!-- Liste des Data Centres -->
<div>
<h2 class="text-2xl font-bold mb-8">
Nos Data Centres
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<UCard
v-for="dc in filteredDataCentres"
:key="dc.id"
class="hover:shadow-lg transition-shadow duration-300"
>
<template #header>
<div class="flex items-center justify-between">
<h3 class="font-semibold">
{{ dc.name }}
</h3>
<UBadge
:variant="dc.status === 'active' ? 'solid' : 'outline'"
size="xs"
>
{{ dc.status === 'active' ? 'Actif' : 'En construction' }}
</UBadge>
</div>
</template>
<div class="space-y-3">
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<UIcon name="i-lucide-map-pin" class="w-4 h-4" />
{{ dc.city }}, {{ dc.country }}
</div>
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<UIcon name="i-lucide-building" class="w-4 h-4" />
{{ dc.floors }} étages, {{ dc.racks }} racks
</div>
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<UIcon name="i-lucide-zap" class="w-4 h-4" />
{{ dc.power }} MW de puissance
</div>
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<UIcon name="i-lucide-users" class="w-4 h-4" />
{{ dc.carriers }}+ opérateurs
</div>
<div class="flex flex-wrap gap-1 pt-2">
<UBadge
v-for="certification in dc.certifications"
:key="certification"
variant="subtle"
size="xs"
>
{{ certification }}
</UBadge>
</div>
</div>
<template #footer>
<UButton variant="outline" block>
Demander un devis
</UButton>
</template>
</UCard>
</div>
</div>
<!-- Avantages -->
<div>
<h2 class="text-2xl font-bold mb-8 text-center">
Pourquoi choisir nos Data Centres ?
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="text-center">
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
<UIcon name="i-lucide-shield" size="24" class="text-primary" />
</div>
<h3 class="font-semibold mb-2">
Sécurité
</h3>
<p class="text-sm text-gray-600 dark:text-gray-300">
Sécurité physique 24/7, biométrie, vidéosurveillance
</p>
</div>
<div class="text-center">
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
<UIcon name="i-lucide-activity" size="24" class="text-primary" />
</div>
<h3 class="font-semibold mb-2">
Fiabilité
</h3>
<p class="text-sm text-gray-600 dark:text-gray-300">
99.9% de disponibilité, alimentation redondante
</p>
</div>
<div class="text-center">
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
<UIcon name="i-lucide-network" size="24" class="text-primary" />
</div>
<h3 class="font-semibold mb-2">
Connectivité
</h3>
<p class="text-sm text-gray-600 dark:text-gray-300">
Accès à 500+ opérateurs, peering direct
</p>
</div>
<div class="text-center">
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
<UIcon name="i-lucide-leaf" size="24" class="text-primary" />
</div>
<h3 class="font-semibold mb-2">
Éco-responsable
</h3>
<p class="text-sm text-gray-600 dark:text-gray-300">
100% énergie renouvelable, PUE < 1.3
</p>
</div>
</div>
</div>
</div>
</template>
</UDashboardPanel>
</template>
<script setup lang="ts">
// Imports côté client uniquement
let L: any = null
if (process.client) {
L = await import('leaflet')
await import('leaflet/dist/leaflet.css')
}
// Métadonnées de la page
useSeoMeta({
title: 'Data Centres - Wibx Tour Layer 2',
description: 'Découvrez notre réseau mondial de data centres sécurisés et connectés. Plus de 50 centres dans 25 régions avec 99.9% de disponibilité.'
})
// Données des data centres
const dataCentres = ref([
{
id: 1,
name: 'Paris-Aubervilliers',
city: 'Paris',
country: 'France',
region: 'Europe',
lat: 48.8566,
lng: 2.3522,
status: 'active',
floors: 4,
racks: 2000,
power: 12,
certifications: ['ISO 27001', 'SOC 2', 'Tier III'],
carriers: 150,
latency: 2
},
{
id: 2,
name: 'London-Docklands',
city: 'Londres',
country: 'Royaume-Uni',
region: 'Europe',
lat: 51.5074,
lng: -0.1278,
status: 'active',
floors: 6,
racks: 3000,
power: 18,
certifications: ['ISO 27001', 'SOC 2', 'Tier IV'],
carriers: 200,
latency: 1
},
{
id: 3,
name: 'Frankfurt-Main',
city: 'Francfort',
country: 'Allemagne',
region: 'Europe',
lat: 50.1109,
lng: 8.6821,
status: 'active',
floors: 5,
racks: 2500,
power: 15,
certifications: ['ISO 27001', 'SOC 2', 'Tier III'],
carriers: 180,
latency: 1.5
},
{
id: 4,
name: 'New York-Manhattan',
city: 'New York',
country: 'États-Unis',
region: 'Amérique du Nord',
lat: 40.7128,
lng: -74.0060,
status: 'active',
floors: 8,
racks: 4000,
power: 25,
certifications: ['ISO 27001', 'SOC 2', 'Tier IV'],
carriers: 300,
latency: 0.5
},
{
id: 5,
name: 'Tokyo-Shibuya',
city: 'Tokyo',
country: 'Japon',
region: 'Asie-Pacifique',
lat: 35.6762,
lng: 139.6503,
status: 'active',
floors: 10,
racks: 3500,
power: 22,
certifications: ['ISO 27001', 'SOC 2', 'Tier IV'],
carriers: 250,
latency: 0.8
}
])
// Filtres
const selectedRegion = ref('all')
const regions = computed(() => [...new Set(dataCentres.value.map(dc => dc.region))])
const tabs = computed(() => [
{ value: 'all', label: 'Toutes les régions' },
...regions.value.map(region => ({ value: region, label: region }))
])
const filteredDataCentres = computed(() => {
if (selectedRegion.value === 'all') {
return dataCentres.value
}
return dataCentres.value.filter(dc => dc.region === selectedRegion.value)
})
// Initialisation de la carte
let map: L.Map | null = null
let markers: L.Marker[] = []
let currentTileLayer: L.TileLayer | null = null
const colorMode = useColorMode()
const initMap = async () => {
if (!process.client || !L) return
// Charger Leaflet si pas encore fait
if (!L.map) {
const leafletModule = await import('leaflet')
L = leafletModule.default
await import('leaflet/dist/leaflet.css')
}
// Corriger les icônes Leaflet par défaut
delete (L.Icon.Default.prototype as any)._getIconUrl
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png',
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png'
})
// Initialiser la carte
map = L.map('map', {
center: [20, 0],
zoom: 2
})
// Ajouter les tuiles selon le mode
updateTileLayer()
// Ajouter les marqueurs
updateMarkers()
}
const updateTileLayer = () => {
if (!map || !L || !process.client) return
// Supprimer l'ancienne couche de tuiles
if (currentTileLayer) {
map.removeLayer(currentTileLayer)
}
// Ajouter la nouvelle couche selon le mode
if (colorMode.value === 'dark') {
// Mode sombre - utiliser CartoDB Dark Matter
currentTileLayer = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap © CartoDB',
subdomains: 'abcd',
maxZoom: 19
})
} else {
// Mode clair - utiliser OpenStreetMap
currentTileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19
})
}
currentTileLayer.addTo(map)
}
const updateMarkers = () => {
if (!map || !L || !process.client) return
// Supprimer les anciens marqueurs
markers.forEach(marker => map?.removeLayer(marker))
markers = []
// Ajouter les nouveaux marqueurs
filteredDataCentres.value.forEach(dc => {
const marker = L.marker([dc.lat, dc.lng])
.bindPopup(`
<div class="p-2">
<h3 class="font-bold text-sm">${dc.name}</h3>
<p class="text-xs text-gray-600">${dc.city}, ${dc.country}</p>
<p class="text-xs mt-1">
<span class="inline-block w-2 h-2 rounded-full ${dc.status === 'active' ? 'bg-green-500' : 'bg-orange-500'} mr-1"></span>
${dc.status === 'active' ? 'Actif' : 'En construction'}
</p>
<p class="text-xs mt-1">${dc.carriers}+ opérateurs</p>
</div>
`)
if (map) {
marker.addTo(map)
markers.push(marker)
}
})
}
// Mise à jour de la carte quand les filtres changent
watch(filteredDataCentres, updateMarkers, { deep: true })
// Mise à jour des tuiles quand le mode dark change
watch(() => colorMode.value, updateTileLayer)
// Initialisation côté client
onMounted(() => {
nextTick(() => {
initMap()
})
})
// Nettoyage
onUnmounted(() => {
if (map) {
map.remove()
map = null
}
})
</script>
<style>
/* Fix pour les icônes Leaflet */
.leaflet-default-icon-path {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbXNfN1+3bBZFvbOkg9vJLJsJU6vKKUHVQmCQNkzQyGpHJfVgZcwKjGhgqCGHYRiRPUHBDOtklmZmcpjPXJBL8/PdFqZBHxhASNQAyBfOaOWIhAMCCGHaB0hAKIRBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5AYhBSCAABYBBEAKARAGCYhBBbGGQBkKCCGg5A');
}
</style>