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 { connectionState } from '../resources/vue/stores/connectionState.js'
|
||||
|
||||
const RECONNECT_DELAY_MS = 3000
|
||||
const RECONNECT_DELAY_MS = 10000
|
||||
|
||||
export function initWebsocket () {
|
||||
window.addEventListener('EventOutputChunk', onOutputChunk)
|
||||
|
|
@ -21,12 +21,17 @@ async function reconnectWebsocket () {
|
|||
|
||||
connectionState.reconnecting = true
|
||||
connectionState.connected = false
|
||||
connectionState.disconnectedAt = Date.now()
|
||||
connectionState.nextReconnectAt = null
|
||||
|
||||
try {
|
||||
window.websocketAvailable = true
|
||||
for await (const e of window.client.eventStream()) {
|
||||
connectionState.connected = true
|
||||
connectionState.reconnecting = false
|
||||
const stream = window.client.eventStream()
|
||||
connectionState.connected = true
|
||||
connectionState.reconnecting = false
|
||||
connectionState.disconnectedAt = null
|
||||
connectionState.nextReconnectAt = null
|
||||
for await (const e of stream) {
|
||||
handleEvent(e)
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -35,6 +40,8 @@ async function reconnectWebsocket () {
|
|||
|
||||
window.websocketAvailable = 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...')
|
||||
setTimeout(() => {
|
||||
reconnectWebsocket()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
</template>
|
||||
|
||||
<template #user-info>
|
||||
<ConnectionBanner />
|
||||
<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>
|
||||
<router-link v-else to="/user" class="user-link" v-if="isLoggedIn">
|
||||
|
|
@ -49,8 +50,6 @@
|
|||
<span v-if="availableThemes.length > 1">
|
||||
<a href="#" @click.prevent="openThemeDialog">{{ currentThemeName }}</a>
|
||||
</span>
|
||||
|
||||
<span :title="connectionStatusTitle">{{ connectionStatusLabel }}</span>
|
||||
</p>
|
||||
<p v-if="showVersionNumber">
|
||||
<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 Navigation from 'picocrank/vue/components/Navigation.vue';
|
||||
import Header from 'picocrank/vue/components/Header.vue';
|
||||
import ConnectionBanner from './components/ConnectionBanner.vue';
|
||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||
import { Menu01Icon } 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 { useI18n } from 'vue-i18n';
|
||||
import combinedTranslations from '../../../lang/combined_output.json';
|
||||
import { connectionState } from './stores/connectionState.js';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
|
|
@ -130,18 +128,6 @@ const showVersionNumber = ref(true)
|
|||
const showLoginLink = ref(true)
|
||||
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 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({
|
||||
connected: false,
|
||||
reconnecting: false
|
||||
reconnecting: false,
|
||||
disconnectedAt: null,
|
||||
nextReconnectAt: null
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
|
||||
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
|
||||
"disconnected": "Getrennt",
|
||||
"disconnected-banner": "Events-Websocket getrennt seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}.",
|
||||
"disconnected-banner-reconnecting": "Events-Websocket getrennt seit {disconnectedSince}. Verbindungsversuch…",
|
||||
"docs": "Dokumentation",
|
||||
"language-dialog.browser-languages": "Browser-Sprachen",
|
||||
"language-dialog.close": "Schließen",
|
||||
|
|
@ -76,6 +78,8 @@
|
|||
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
|
||||
"diagnostics.where-to-find-help": "Where to find help",
|
||||
"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",
|
||||
"language-dialog.browser-languages": "Browser languages",
|
||||
"language-dialog.close": "Close",
|
||||
|
|
@ -131,6 +135,8 @@
|
|||
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
|
||||
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
|
||||
"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",
|
||||
"language-dialog.browser-languages": "Idiomas del navegador",
|
||||
"language-dialog.close": "Cerrar",
|
||||
|
|
@ -186,6 +192,8 @@
|
|||
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
|
||||
"diagnostics.where-to-find-help": "Dove trovare aiuto",
|
||||
"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",
|
||||
"language-dialog.browser-languages": "Lingue del browser",
|
||||
"language-dialog.close": "Chiudi",
|
||||
|
|
@ -241,6 +249,8 @@
|
|||
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
|
||||
"diagnostics.where-to-find-help": "在哪里找到帮助",
|
||||
"disconnected": "已断开连接",
|
||||
"disconnected-banner": "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。",
|
||||
"disconnected-banner-reconnecting": "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…",
|
||||
"docs": "文档",
|
||||
"language-dialog.browser-languages": "浏览器语言",
|
||||
"language-dialog.close": "关闭",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ translations:
|
|||
connected: Verbunden
|
||||
disconnected: Getrennt
|
||||
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
|
||||
raise-issue: Ein Problem melden auf GitHub
|
||||
docs: Dokumentation
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ translations:
|
|||
connected: Connected
|
||||
disconnected: Disconnected
|
||||
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
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ translations:
|
|||
connected: Conectado
|
||||
disconnected: Desconectado
|
||||
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
|
||||
raise-issue: Reportar un problema en GitHub
|
||||
docs: Documentación
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ translations:
|
|||
connected: Connesso
|
||||
disconnected: Disconnesso
|
||||
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
|
||||
raise-issue: Segnala un problema su GitHub
|
||||
logs.title: Registri
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ translations:
|
|||
connected: 已连接
|
||||
disconnected: 已断开连接
|
||||
reconnecting: 正在重新连接…
|
||||
disconnected-banner: "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。"
|
||||
disconnected-banner-reconnecting: "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…"
|
||||
login-button: 登录
|
||||
raise-issue: 在 GitHub 上报告问题
|
||||
docs: 文档
|
||||
|
|
|
|||
Loading…
Reference in New Issue