fix: Much more helpful reconnection banner
This commit is contained in:
parent
2f77000de4
commit
a5c102dbf1
|
|
@ -2,7 +2,7 @@ import { buttonResults } from '../resources/vue/stores/buttonResults.js'
|
||||||
import { rateLimits } from '../resources/vue/stores/rateLimits.js'
|
import { rateLimits } from '../resources/vue/stores/rateLimits.js'
|
||||||
import { connectionState } from '../resources/vue/stores/connectionState.js'
|
import { connectionState } from '../resources/vue/stores/connectionState.js'
|
||||||
|
|
||||||
const RECONNECT_DELAY_MS = 3000
|
const RECONNECT_DELAY_MS = 10000
|
||||||
|
|
||||||
export function initWebsocket () {
|
export function initWebsocket () {
|
||||||
window.addEventListener('EventOutputChunk', onOutputChunk)
|
window.addEventListener('EventOutputChunk', onOutputChunk)
|
||||||
|
|
@ -21,12 +21,17 @@ async function reconnectWebsocket () {
|
||||||
|
|
||||||
connectionState.reconnecting = true
|
connectionState.reconnecting = true
|
||||||
connectionState.connected = false
|
connectionState.connected = false
|
||||||
|
connectionState.disconnectedAt = Date.now()
|
||||||
|
connectionState.nextReconnectAt = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.websocketAvailable = true
|
window.websocketAvailable = true
|
||||||
for await (const e of window.client.eventStream()) {
|
const stream = window.client.eventStream()
|
||||||
connectionState.connected = true
|
connectionState.connected = true
|
||||||
connectionState.reconnecting = false
|
connectionState.reconnecting = false
|
||||||
|
connectionState.disconnectedAt = null
|
||||||
|
connectionState.nextReconnectAt = null
|
||||||
|
for await (const e of stream) {
|
||||||
handleEvent(e)
|
handleEvent(e)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -35,6 +40,8 @@ async function reconnectWebsocket () {
|
||||||
|
|
||||||
window.websocketAvailable = false
|
window.websocketAvailable = false
|
||||||
connectionState.connected = false
|
connectionState.connected = false
|
||||||
|
connectionState.disconnectedAt = connectionState.disconnectedAt ?? Date.now()
|
||||||
|
connectionState.nextReconnectAt = Date.now() + RECONNECT_DELAY_MS
|
||||||
console.log('Reconnecting websocket in ' + RECONNECT_DELAY_MS + 'ms...')
|
console.log('Reconnecting websocket in ' + RECONNECT_DELAY_MS + 'ms...')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reconnectWebsocket()
|
reconnectWebsocket()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #user-info>
|
<template #user-info>
|
||||||
|
<ConnectionBanner />
|
||||||
<div class="flex-row user-info" style="gap: .5em;">
|
<div class="flex-row user-info" style="gap: .5em;">
|
||||||
<span id="link-login" v-if="!isLoggedIn && showLoginLink"><router-link to="/login">{{ t('login-button') }}</router-link></span>
|
<span id="link-login" v-if="!isLoggedIn && showLoginLink"><router-link to="/login">{{ t('login-button') }}</router-link></span>
|
||||||
<router-link v-else to="/user" class="user-link" v-if="isLoggedIn">
|
<router-link v-else to="/user" class="user-link" v-if="isLoggedIn">
|
||||||
|
|
@ -49,8 +50,6 @@
|
||||||
<span v-if="availableThemes.length > 1">
|
<span v-if="availableThemes.length > 1">
|
||||||
<a href="#" @click.prevent="openThemeDialog">{{ currentThemeName }}</a>
|
<a href="#" @click.prevent="openThemeDialog">{{ currentThemeName }}</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span :title="connectionStatusTitle">{{ connectionStatusLabel }}</span>
|
|
||||||
</p>
|
</p>
|
||||||
<p v-if="showVersionNumber">
|
<p v-if="showVersionNumber">
|
||||||
<a id="available-version" href="http://olivetin.app" target="_blank" hidden>?</a>
|
<a id="available-version" href="http://olivetin.app" target="_blank" hidden>?</a>
|
||||||
|
|
@ -100,6 +99,7 @@ import { useRouter } from 'vue-router';
|
||||||
import Sidebar from 'picocrank/vue/components/Sidebar.vue';
|
import Sidebar from 'picocrank/vue/components/Sidebar.vue';
|
||||||
import Navigation from 'picocrank/vue/components/Navigation.vue';
|
import Navigation from 'picocrank/vue/components/Navigation.vue';
|
||||||
import Header from 'picocrank/vue/components/Header.vue';
|
import Header from 'picocrank/vue/components/Header.vue';
|
||||||
|
import ConnectionBanner from './components/ConnectionBanner.vue';
|
||||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||||
import { Menu01Icon } from '@hugeicons/core-free-icons'
|
import { Menu01Icon } from '@hugeicons/core-free-icons'
|
||||||
import { UserCircle02Icon } from '@hugeicons/core-free-icons'
|
import { UserCircle02Icon } from '@hugeicons/core-free-icons'
|
||||||
|
|
@ -107,8 +107,6 @@ import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||||
import logoUrl from '../../OliveTinLogo.png';
|
import logoUrl from '../../OliveTinLogo.png';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import combinedTranslations from '../../../lang/combined_output.json';
|
import combinedTranslations from '../../../lang/combined_output.json';
|
||||||
import { connectionState } from './stores/connectionState.js';
|
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -130,18 +128,6 @@ const showVersionNumber = ref(true)
|
||||||
const showLoginLink = ref(true)
|
const showLoginLink = ref(true)
|
||||||
const sectionNavigationStyle = ref('sidebar')
|
const sectionNavigationStyle = ref('sidebar')
|
||||||
|
|
||||||
const connectionStatusLabel = computed(() => {
|
|
||||||
if (connectionState.connected) {
|
|
||||||
return t('connected')
|
|
||||||
}
|
|
||||||
if (connectionState.reconnecting) {
|
|
||||||
return t('reconnecting')
|
|
||||||
}
|
|
||||||
return t('disconnected')
|
|
||||||
})
|
|
||||||
|
|
||||||
const connectionStatusTitle = computed(() => connectionStatusLabel.value)
|
|
||||||
|
|
||||||
const languageDialog = ref(null)
|
const languageDialog = ref(null)
|
||||||
const browserLanguages = ref([])
|
const browserLanguages = ref([])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<span id="connection-banner" v-if="!connectionState.connected" class="inline-notification critical user-info-connection" role="status">{{ bannerText }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { connectionState } from '../stores/connectionState.js'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
function formatShortRelative(ms) {
|
||||||
|
if (ms < 0) return '0s'
|
||||||
|
const secs = Math.floor(ms / 1000)
|
||||||
|
const mins = Math.floor(secs / 60)
|
||||||
|
const hours = Math.floor(mins / 60)
|
||||||
|
if (hours > 0) return `${hours}h`
|
||||||
|
if (mins > 0) return `${mins}m`
|
||||||
|
return `${secs}s`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatShortTime(ts) {
|
||||||
|
if (ts == null) return '--:--'
|
||||||
|
return new Date(ts).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = ref(Date.now())
|
||||||
|
let ticker = null
|
||||||
|
watch(() => connectionState.connected, (connected) => {
|
||||||
|
if (ticker) {
|
||||||
|
clearInterval(ticker)
|
||||||
|
ticker = null
|
||||||
|
}
|
||||||
|
if (!connected) {
|
||||||
|
now.value = Date.now()
|
||||||
|
ticker = setInterval(() => { now.value = Date.now() }, 1000)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (ticker) {
|
||||||
|
clearInterval(ticker)
|
||||||
|
ticker = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const bannerText = computed(() => {
|
||||||
|
const at = connectionState.disconnectedAt
|
||||||
|
const next = connectionState.nextReconnectAt
|
||||||
|
const n = now.value
|
||||||
|
const disconnectedSince = formatShortTime(at)
|
||||||
|
if (next != null && next > n) {
|
||||||
|
const reconnectIn = formatShortRelative(next - n)
|
||||||
|
return t('disconnected-banner', { disconnectedSince, reconnectIn })
|
||||||
|
}
|
||||||
|
return t('disconnected-banner-reconnecting', { disconnectedSince })
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#connection-banner.user-info-connection {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.inline-notification {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -2,5 +2,7 @@ import { reactive } from 'vue'
|
||||||
|
|
||||||
export const connectionState = reactive({
|
export const connectionState = reactive({
|
||||||
connected: false,
|
connected: false,
|
||||||
reconnecting: false
|
reconnecting: false,
|
||||||
|
disconnectedAt: null,
|
||||||
|
nextReconnectAt: null
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
|
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
|
||||||
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
|
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
|
||||||
"disconnected": "Getrennt",
|
"disconnected": "Getrennt",
|
||||||
|
"disconnected-banner": "Events-Websocket getrennt seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}.",
|
||||||
|
"disconnected-banner-reconnecting": "Events-Websocket getrennt seit {disconnectedSince}. Verbindungsversuch…",
|
||||||
"docs": "Dokumentation",
|
"docs": "Dokumentation",
|
||||||
"language-dialog.browser-languages": "Browser-Sprachen",
|
"language-dialog.browser-languages": "Browser-Sprachen",
|
||||||
"language-dialog.close": "Schließen",
|
"language-dialog.close": "Schließen",
|
||||||
|
|
@ -76,6 +78,8 @@
|
||||||
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
|
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
|
||||||
"diagnostics.where-to-find-help": "Where to find help",
|
"diagnostics.where-to-find-help": "Where to find help",
|
||||||
"disconnected": "Disconnected",
|
"disconnected": "Disconnected",
|
||||||
|
"disconnected-banner": "Events websocket disconnected since {disconnectedSince}. Trying reconnect in {reconnectIn}.",
|
||||||
|
"disconnected-banner-reconnecting": "Events websocket disconnected since {disconnectedSince}. Trying reconnect…",
|
||||||
"docs": "Documentation",
|
"docs": "Documentation",
|
||||||
"language-dialog.browser-languages": "Browser languages",
|
"language-dialog.browser-languages": "Browser languages",
|
||||||
"language-dialog.close": "Close",
|
"language-dialog.close": "Close",
|
||||||
|
|
@ -131,6 +135,8 @@
|
||||||
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
|
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
|
||||||
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
|
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
|
||||||
"disconnected": "Desconectado",
|
"disconnected": "Desconectado",
|
||||||
|
"disconnected-banner": "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión en {reconnectIn}.",
|
||||||
|
"disconnected-banner-reconnecting": "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión…",
|
||||||
"docs": "Documentación",
|
"docs": "Documentación",
|
||||||
"language-dialog.browser-languages": "Idiomas del navegador",
|
"language-dialog.browser-languages": "Idiomas del navegador",
|
||||||
"language-dialog.close": "Cerrar",
|
"language-dialog.close": "Cerrar",
|
||||||
|
|
@ -186,6 +192,8 @@
|
||||||
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
|
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
|
||||||
"diagnostics.where-to-find-help": "Dove trovare aiuto",
|
"diagnostics.where-to-find-help": "Dove trovare aiuto",
|
||||||
"disconnected": "Disconnesso",
|
"disconnected": "Disconnesso",
|
||||||
|
"disconnected-banner": "Websocket eventi disconnesso dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}.",
|
||||||
|
"disconnected-banner-reconnecting": "Websocket eventi disconnesso dalle {disconnectedSince}. Tentativo di connessione…",
|
||||||
"docs": "Documentazione",
|
"docs": "Documentazione",
|
||||||
"language-dialog.browser-languages": "Lingue del browser",
|
"language-dialog.browser-languages": "Lingue del browser",
|
||||||
"language-dialog.close": "Chiudi",
|
"language-dialog.close": "Chiudi",
|
||||||
|
|
@ -241,6 +249,8 @@
|
||||||
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
|
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
|
||||||
"diagnostics.where-to-find-help": "在哪里找到帮助",
|
"diagnostics.where-to-find-help": "在哪里找到帮助",
|
||||||
"disconnected": "已断开连接",
|
"disconnected": "已断开连接",
|
||||||
|
"disconnected-banner": "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。",
|
||||||
|
"disconnected-banner-reconnecting": "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…",
|
||||||
"docs": "文档",
|
"docs": "文档",
|
||||||
"language-dialog.browser-languages": "浏览器语言",
|
"language-dialog.browser-languages": "浏览器语言",
|
||||||
"language-dialog.close": "关闭",
|
"language-dialog.close": "关闭",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ translations:
|
||||||
connected: Verbunden
|
connected: Verbunden
|
||||||
disconnected: Getrennt
|
disconnected: Getrennt
|
||||||
reconnecting: Verbinde erneut…
|
reconnecting: Verbinde erneut…
|
||||||
|
disconnected-banner: "Events-Websocket getrennt seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}."
|
||||||
|
disconnected-banner-reconnecting: "Events-Websocket getrennt seit {disconnectedSince}. Verbindungsversuch…"
|
||||||
login-button: Login
|
login-button: Login
|
||||||
raise-issue: Ein Problem melden auf GitHub
|
raise-issue: Ein Problem melden auf GitHub
|
||||||
docs: Dokumentation
|
docs: Dokumentation
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ translations:
|
||||||
connected: Connected
|
connected: Connected
|
||||||
disconnected: Disconnected
|
disconnected: Disconnected
|
||||||
reconnecting: Reconnecting…
|
reconnecting: Reconnecting…
|
||||||
|
disconnected-banner: "Events websocket disconnected since {disconnectedSince}. Trying reconnect in {reconnectIn}."
|
||||||
|
disconnected-banner-reconnecting: "Events websocket disconnected since {disconnectedSince}. Trying reconnect…"
|
||||||
login-button: Login
|
login-button: Login
|
||||||
logs.title: Logs
|
logs.title: Logs
|
||||||
logs.page-description: This is a list of logs from actions that have been executed. You can filter the list by action title.
|
logs.page-description: This is a list of logs from actions that have been executed. You can filter the list by action title.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ translations:
|
||||||
connected: Conectado
|
connected: Conectado
|
||||||
disconnected: Desconectado
|
disconnected: Desconectado
|
||||||
reconnecting: Reconectando…
|
reconnecting: Reconectando…
|
||||||
|
disconnected-banner: "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión en {reconnectIn}."
|
||||||
|
disconnected-banner-reconnecting: "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión…"
|
||||||
login-button: Iniciar sesión
|
login-button: Iniciar sesión
|
||||||
raise-issue: Reportar un problema en GitHub
|
raise-issue: Reportar un problema en GitHub
|
||||||
docs: Documentación
|
docs: Documentación
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ translations:
|
||||||
connected: Connesso
|
connected: Connesso
|
||||||
disconnected: Disconnesso
|
disconnected: Disconnesso
|
||||||
reconnecting: Riconnessione…
|
reconnecting: Riconnessione…
|
||||||
|
disconnected-banner: "Websocket eventi disconnesso dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}."
|
||||||
|
disconnected-banner-reconnecting: "Websocket eventi disconnesso dalle {disconnectedSince}. Tentativo di connessione…"
|
||||||
login-button: Login
|
login-button: Login
|
||||||
raise-issue: Segnala un problema su GitHub
|
raise-issue: Segnala un problema su GitHub
|
||||||
logs.title: Registri
|
logs.title: Registri
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ translations:
|
||||||
connected: 已连接
|
connected: 已连接
|
||||||
disconnected: 已断开连接
|
disconnected: 已断开连接
|
||||||
reconnecting: 正在重新连接…
|
reconnecting: 正在重新连接…
|
||||||
|
disconnected-banner: "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。"
|
||||||
|
disconnected-banner-reconnecting: "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…"
|
||||||
login-button: 登录
|
login-button: 登录
|
||||||
raise-issue: 在 GitHub 上报告问题
|
raise-issue: 在 GitHub 上报告问题
|
||||||
docs: 文档
|
docs: 文档
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue