Techs reported cloudflare.com showing 300+ms on the diagnostic page while OS-level ICMP ping returned 5ms. The gap is entirely protocol overhead: - fetch() ≠ ICMP. Every call pays DNS + TCP + TLS + HTTP on top of the real RTT, which is easily 150–300ms cold on mobile LTE when the radio has to wake the RRC connection. - Bare cloudflare.com redirects 301 → www.cloudflare.com, forcing a second DNS + TCP + TLS handshake for every "ping" and doubling the measured latency. - TechDiagnosticPage.vue was also labeling the full 10MB download time as "Latence", so the number on the speed-test card was never a latency measurement at all. Fixes, applied to both surfaces (Ops /j/diagnostic + Field /diagnostic): - Swap cloudflare.com → 1.1.1.1/cdn-cgi/trace. 88-byte response, no redirect, no keepalive games — canonical "internet is up" endpoint. - Warm-up fetch before every measurement. First call absorbs DNS + TCP + TLS + LTE wake; second call reports steady-state RTT. This applies to checkHosts() (ops) and resolveHost() (field composable). - Split runSpeed() into separate ping + throughput measurements. Ping hits speed.cloudflare.com/cdn-cgi/trace (88 bytes on a warm connection); throughput hits /__down on the same origin so the TLS session is reused. Deployed to production; smoke-verified: - ops bundle TechDiagnosticPage.b925e02c.js contains '1.1.1.1/cdn-cgi/trace' - field bundle DiagnosticPage.38a45f65.js contains the same - zero bare 'cloudflare.com' hostname in either hosts array Files: - apps/ops/src/modules/tech/pages/TechDiagnosticPage.vue - apps/field/src/composables/useSpeedTest.js - apps/field/src/pages/DiagnosticPage.vue Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
127 lines
4.6 KiB
Vue
127 lines
4.6 KiB
Vue
<template>
|
|
<q-page padding>
|
|
<div class="text-h6 q-mb-md">Diagnostic réseau</div>
|
|
|
|
<!-- Speed test -->
|
|
<q-card class="q-mb-md">
|
|
<q-card-section>
|
|
<div class="text-subtitle1 q-mb-sm">Test de vitesse</div>
|
|
<div class="row q-gutter-md q-mb-sm">
|
|
<div class="col text-center">
|
|
<div class="text-h4 text-primary">{{ speedTest.downloadSpeed.value ?? '—' }}</div>
|
|
<div class="text-caption">Mbps (API)</div>
|
|
</div>
|
|
<div class="col text-center">
|
|
<div class="text-h4 text-secondary">{{ speedTest.latency.value ?? '—' }}</div>
|
|
<div class="text-caption">ms latence</div>
|
|
</div>
|
|
</div>
|
|
<q-btn color="primary" icon="speed" label="Lancer le test" :loading="speedTest.running.value"
|
|
@click="speedTest.runSpeedTest()" class="full-width" />
|
|
<div v-if="speedTest.error.value" class="text-negative text-caption q-mt-xs">{{ speedTest.error.value }}</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- HTTP Resolve -->
|
|
<q-card class="q-mb-md">
|
|
<q-card-section>
|
|
<div class="text-subtitle1 q-mb-sm">Résolution HTTP</div>
|
|
<q-input v-model="resolveHost" label="Hostname (ex: google.ca)" outlined dense class="q-mb-sm"
|
|
@keyup.enter="doResolve">
|
|
<template v-slot:append>
|
|
<q-btn flat dense icon="dns" @click="doResolve" />
|
|
</template>
|
|
</q-input>
|
|
|
|
<!-- Quick hosts -->
|
|
<div class="row q-gutter-xs q-mb-sm">
|
|
<q-chip v-for="h in quickHosts" :key="h" dense clickable @click="resolveHost = h; doResolve()">{{ h }}</q-chip>
|
|
</div>
|
|
|
|
<!-- Results -->
|
|
<div v-if="resolveResults.length > 0">
|
|
<q-list dense separator>
|
|
<q-item v-for="r in resolveResults" :key="r.host">
|
|
<q-item-section avatar>
|
|
<q-icon :name="r.status === 'ok' ? 'check_circle' : 'error'" :color="r.status === 'ok' ? 'positive' : 'negative'" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ r.host }}</q-item-label>
|
|
<q-item-label caption>
|
|
{{ r.status === 'ok' ? r.time + 'ms' : r.error }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Batch check -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="text-subtitle1 q-mb-sm">Check complet</div>
|
|
<q-btn color="secondary" icon="fact_check" label="Tester tous les services" :loading="batchRunning"
|
|
@click="runBatchCheck" class="full-width" />
|
|
<div v-if="batchResults.length > 0" class="q-mt-sm">
|
|
<q-list dense separator>
|
|
<q-item v-for="r in batchResults" :key="r.host">
|
|
<q-item-section avatar>
|
|
<q-icon :name="r.status === 'ok' ? 'check_circle' : 'error'" :color="r.status === 'ok' ? 'positive' : 'negative'" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ r.host }}</q-item-label>
|
|
<q-item-label caption>{{ r.status === 'ok' ? r.time + 'ms' : r.error }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-page>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
import { useSpeedTest } from 'src/composables/useSpeedTest'
|
|
|
|
const speedTest = useSpeedTest()
|
|
const resolveHost = ref('')
|
|
const resolveResults = ref([])
|
|
const batchRunning = ref(false)
|
|
const batchResults = ref([])
|
|
|
|
// 1.1.1.1/cdn-cgi/trace instead of bare cloudflare.com — the bare host
|
|
// redirects 301 → www.cloudflare.com, doubling the handshake cost. The
|
|
// trace endpoint is 88 bytes, no redirect, canonical "internet is up" probe.
|
|
const quickHosts = ['google.ca', 'erp.gigafibre.ca', '1.1.1.1/cdn-cgi/trace', '8.8.8.8']
|
|
const batchHosts = [
|
|
'erp.gigafibre.ca',
|
|
'dispatch.gigafibre.ca',
|
|
'auth.targo.ca',
|
|
'id.gigafibre.ca',
|
|
'oss.gigafibre.ca',
|
|
'n8n.gigafibre.ca',
|
|
'www.gigafibre.ca',
|
|
'google.ca',
|
|
]
|
|
|
|
async function doResolve () {
|
|
if (!resolveHost.value.trim()) return
|
|
await speedTest.resolveHost(resolveHost.value.trim())
|
|
if (speedTest.resolveResult.value) {
|
|
// Prepend to results, avoid duplicates
|
|
resolveResults.value = [
|
|
speedTest.resolveResult.value,
|
|
...resolveResults.value.filter(r => r.host !== speedTest.resolveResult.value.host),
|
|
]
|
|
}
|
|
}
|
|
|
|
async function runBatchCheck () {
|
|
batchRunning.value = true
|
|
batchResults.value = await speedTest.checkHosts(batchHosts)
|
|
batchRunning.value = false
|
|
}
|
|
</script>
|