From 58edb4f602930ecc790efe653a1eddb95aaf992b Mon Sep 17 00:00:00 2001 From: Amir Raminfar Date: Wed, 27 Sep 2023 16:00:44 -0700 Subject: [PATCH] feat: adds automatic redirect when a new container is found (#2396) * feat: implements a toast for alerting errors and other useful information * removes unused code * feat: adds automatic redirect when a new container is found * complete the flow with alerts and settings page * adds more langs and option for once * removes files --- assets/auto-imports.d.ts | 3 ++ assets/components.d.ts | 2 + .../LogViewer/DockerEventLogItem.vue | 30 +++++++++++--- assets/components/common/TimedButton.vue | 39 +++++++++++++++++++ assets/composables/settings.ts | 3 ++ assets/composables/toast.ts | 26 ++++++++++--- assets/layouts/default.vue | 7 +++- assets/pages/settings.vue | 5 +++ assets/stores/container.ts | 20 +++++++++- locales/de.yml | 21 ++++++++++ locales/en.yml | 33 +++++++++++----- locales/es.yml | 18 +++++++++ locales/pr.yml | 18 +++++++++ locales/ru.yml | 18 +++++++++ locales/zh.yml | 18 +++++++++ 15 files changed, 239 insertions(+), 22 deletions(-) create mode 100644 assets/components/common/TimedButton.vue diff --git a/assets/auto-imports.d.ts b/assets/auto-imports.d.ts index 997786d4..2e300ce3 100644 --- a/assets/auto-imports.d.ts +++ b/assets/auto-imports.d.ts @@ -18,6 +18,7 @@ declare global { const arrayEquals: typeof import('./utils/index')['arrayEquals'] const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] + const automaticRedirect: typeof import('./composables/settings')['automaticRedirect'] const collapseNav: typeof import('./composables/settings')['collapseNav'] const computed: typeof import('vue')['computed'] const computedAsync: typeof import('@vueuse/core')['computedAsync'] @@ -378,6 +379,7 @@ declare module 'vue' { readonly arrayEquals: UnwrapRef readonly asyncComputed: UnwrapRef readonly autoResetRef: UnwrapRef + readonly automaticRedirect: UnwrapRef readonly collapseNav: UnwrapRef readonly computed: UnwrapRef readonly computedAsync: UnwrapRef @@ -714,6 +716,7 @@ declare module '@vue/runtime-core' { readonly arrayEquals: UnwrapRef readonly asyncComputed: UnwrapRef readonly autoResetRef: UnwrapRef + readonly automaticRedirect: UnwrapRef readonly collapseNav: UnwrapRef readonly computed: UnwrapRef readonly computedAsync: UnwrapRef diff --git a/assets/components.d.ts b/assets/components.d.ts index 4dd821f8..e83dc2aa 100644 --- a/assets/components.d.ts +++ b/assets/components.d.ts @@ -14,6 +14,7 @@ declare module 'vue' { 'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default'] 'Carbon:star': typeof import('~icons/carbon/star')['default'] 'Carbon:starFilled': typeof import('~icons/carbon/star-filled')['default'] + 'Carbon:warning': typeof import('~icons/carbon/warning')['default'] 'Cil:checkCircle': typeof import('~icons/cil/check-circle')['default'] 'Cil:circle': typeof import('~icons/cil/circle')['default'] 'Cil:columns': typeof import('~icons/cil/columns')['default'] @@ -73,6 +74,7 @@ declare module 'vue' { StatMonitor: typeof import('./components/LogViewer/StatMonitor.vue')['default'] StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default'] Tag: typeof import('./components/common/Tag.vue')['default'] + TimedButton: typeof import('./components/common/TimedButton.vue')['default'] Toggle: typeof import('./components/common/Toggle.vue')['default'] ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default'] } diff --git a/assets/components/LogViewer/DockerEventLogItem.vue b/assets/components/LogViewer/DockerEventLogItem.vue index 9e7a0a76..bd5a1e83 100644 --- a/assets/components/LogViewer/DockerEventLogItem.vue +++ b/assets/components/LogViewer/DockerEventLogItem.vue @@ -6,12 +6,17 @@ v-if="nextContainer && logEntry.event === 'container-stopped'" > - - Another container instance with the same name was created . Do - you want to redirect to the new one? -
- +

{{ $t("alert.similar-container-found.title") }}

+ {{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }} +
+
+ Cancel + Redirect
@@ -20,6 +25,9 @@ diff --git a/assets/composables/settings.ts b/assets/composables/settings.ts index 781ffe57..f5c6f76c 100644 --- a/assets/composables/settings.ts +++ b/assets/composables/settings.ts @@ -13,6 +13,7 @@ export const DEFAULT_SETTINGS: { hourStyle: "auto" | "24" | "12"; softWrap: boolean; collapseNav: boolean; + automaticRedirect: boolean; } = { search: true, size: "medium", @@ -25,6 +26,7 @@ export const DEFAULT_SETTINGS: { hourStyle: "auto", softWrap: true, collapseNav: false, + automaticRedirect: true, }; export const settings = useStorage(DOZZLE_SETTINGS_KEY, DEFAULT_SETTINGS); @@ -42,4 +44,5 @@ export const { menuWidth, size, search, + automaticRedirect, } = toRefs(settings); diff --git a/assets/composables/toast.ts b/assets/composables/toast.ts index cd7fe832..1252f7e0 100644 --- a/assets/composables/toast.ts +++ b/assets/composables/toast.ts @@ -1,19 +1,35 @@ type Toast = { - id: number; + id: string; createdAt: Date; + title?: string; message: string; type: "success" | "error" | "warning" | "info"; }; +type ToastOptions = { + expire?: number; + once?: boolean; +}; + const toasts = ref([]); -const showToast = (message: string, type: Toast["type"]) => { +const showToast = ( + toast: Omit & { id?: string }, + { expire = -1, once = false }: ToastOptions = { expire: -1, once: false }, +) => { + if (once && toasts.value.some((t) => t.id === toast.id)) { + return; + } toasts.value.push({ - id: Date.now(), + id: Date.now().toString(), createdAt: new Date(), - message, - type, + ...toast, }); + if (expire > 0) { + setTimeout(() => { + removeToast(toasts.value[0].id); + }, expire); + } }; const removeToast = (id: Toast["id"]) => { diff --git a/assets/layouts/default.vue b/assets/layouts/default.vue index 55eaca9f..aa11b9d6 100644 --- a/assets/layouts/default.vue +++ b/assets/layouts/default.vue @@ -49,7 +49,12 @@ :key="toast.id" :class="{ 'alert-error': toast.type === 'error', 'alert-info': toast.type === 'info' }" > - {{ toast.message }} + + +
+

{{ toast.title }}

+ {{ toast.message }} +
diff --git a/assets/pages/settings.vue b/assets/pages/settings.vue index b3765253..b9d37271 100644 --- a/assets/pages/settings.vue +++ b/assets/pages/settings.vue @@ -80,6 +80,10 @@
{{ $t("settings.show-stopped-containers") }}
+ +
+ {{ $t("settings.automatic-redirect") }} +
@@ -95,6 +99,7 @@ import { showAllContainers, size, softWrap, + automaticRedirect, } from "@/composables/settings"; const { t } = useI18n(); diff --git a/assets/stores/container.ts b/assets/stores/container.ts index 3732ee5e..abdf5ea8 100644 --- a/assets/stores/container.ts +++ b/assets/stores/container.ts @@ -36,7 +36,15 @@ export const useContainerStore = defineStore("container", () => { ready.value = false; es = new EventSource(`${config.base}/api/events/stream`); es.addEventListener("error", (e) => { - showToast(t("error.events-stream"), "error"); + showToast( + { + id: "events-stream", + message: t("error.events-stream.message"), + title: t("error.events-stream.title"), + type: "error", + }, + { once: true }, + ); }); es.addEventListener("containers-changed", (e: Event) => @@ -75,7 +83,15 @@ export const useContainerStore = defineStore("container", () => { try { await until(ready).toBe(true, { timeout: 8000, throwOnTimeout: true }); } catch (e) { - showToast(t("error.events-timeout"), "error"); + showToast( + { + id: "events-timeout", + message: t("error.events-timeout.message"), + title: t("error.events-timeout.title"), + type: "error", + }, + { once: true }, + ); } })(); diff --git a/locales/de.yml b/locales/de.yml index eaa9edb8..9afab25a 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -28,6 +28,26 @@ error: invalid-auth: Benutzername und Passwort sind ungültig. logs-skipped: \{total} Einträge übersprungen container-not-found: Container nicht gefunden. + events-stream: + title: Unerwarteter Fehler + message: >- + Dozzle UI konnte keine Verbindung zur API herstellen. Bitte überprüfen Sie + Ihre Netzwerkeinstellungen. Wenn Sie einen Reverse-Proxy verwenden, + stellen Sie bitte sicher, dass er ordnungsgemäß konfiguriert ist. + events-timeout: + title: Etwas stimmt nicht + message: >- + Dozzle UI hat beim Verbinden mit der API ein Timeout. Bitte überprüfen Sie + die Netzwerkverbindung und versuchen Sie es erneut. +alert: + redirected: + title: Zu neuem Container umgeleitet + message: Dozzle hat Sie automatisch zu neuem Container {containerId} umgeleitet. + similar-container-found: + title: Ähnlicher Container gefunden + message: >- + Dozzle hat einen ähnlichen Container {containerId} gefunden, der auf dem + gleichen Host ausgeführt wird. Möchten Sie zu ihm wechseln? title: page-not-found: Seite nicht gefunden login: Authentifizierung erforderlich @@ -57,3 +77,4 @@ settings: update-available: >- Eine neue Version ist verfügbar! Aktualisiere auf {nextVersion}. show-std: Zeige stdout und stderr Labels + automatic-redirect: Automatische Weiterleitung diff --git a/locales/en.yml b/locales/en.yml index a076b6c5..6a446a4d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -25,16 +25,30 @@ tooltip: search: Search containers (⌘ + k, ⌃k) pin-column: Pin as column error: - page-not-found: This page does not exist. - invalid-auth: Username and password are not valid. + page-not-found: This page does not exist + invalid-auth: Username or password are not valid logs-skipped: Skipped {total} entries - container-not-found: Container not found. - events-stream: >- - Unexpected error received from Dozzle API. Please check Dozzle logs and make - sure proper installation. - events-timeout: >- - Something is not right. Dozzle still hasn't received any data over 8 - seconds. Please check network settings. + container-not-found: Container not found + events-stream: + title: Unexpected Error + message: >- + Dozzle UI wasn't able to connect API. Please check your network settings. + If you are using a reverse proxy, please make sure it is configured + properly. + events-timeout: + title: Something is not right + message: >- + Dozzle UI timeed out while connecting to API. Please check network + connection and try again. +alert: + redirected: + title: Redirected to new container + message: Dozzle automatically redirected you to new container {containerId}. + similar-container-found: + title: Similar container found + message: >- + Dozzle found a similar container {containerId} that is running on the same + host. Do you want to switch to it? title: page-not-found: Page not found login: Authentication Required @@ -65,3 +79,4 @@ settings: New version is available! Update to {nextVersion}. show-std: Show stdout and stderr labels + automatic-redirect: Automatische Weiterleitung zu neuen Containern mit demselben Namen diff --git a/locales/es.yml b/locales/es.yml index 4df0f235..7d06f0c9 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -28,6 +28,23 @@ error: invalid-auth: El nombre de usuario y la contraseña no son válidos. logs-skipped: Omitidas {total} entrada/s container-not-found: Contenedor no encontrado. + events-stream: + title: Error inesperado + message: >- + Dozzle UI no pudo conectar con la API. Por favor, compruebe la configuración de su red. + Si está utilizando un proxy inverso, por favor, asegúrese de que está configurado correctamente. + events-timeout: + title: Algo no está bien + message: >- + Dozzle UI se agotó el tiempo de espera al conectarse a la API. Por favor, compruebe la conexión de red y vuelva a intentarlo. +alert: + redirected: + title: Redirigido a nuevo contenedor + message: Dozzle le redirigió automáticamente al nuevo contenedor {containerId}. + similar-container-found: + title: Contenedor similar encontrado + message: >- + Dozzle encontró un contenedor similar {containerId} que se está ejecutando en el mismo host. ¿Quieres cambiar a él? title: page-not-found: Página no encontrada login: Autenticación requerida @@ -58,3 +75,4 @@ settings: ¡La nueva versión está disponible! Actualizar a la {nextVersion}. show-std: Mostrar etiquetas de salida estándar y salida de error estándar + automatic-redirect: Redireccionar automáticamente a nuevos contenedores con el mismo nombre diff --git a/locales/pr.yml b/locales/pr.yml index 46618454..4cc47749 100644 --- a/locales/pr.yml +++ b/locales/pr.yml @@ -28,6 +28,23 @@ error: invalid-auth: O nome de usuário e a senha não são válidos. logs-skipped: Saltado {total} entradas container-not-found: Contentor não encontrado. + events-stream: + title: Erro inesperado + message: >- + Dozzle UI não conseguiu ligar à API. Por favor, verifique as suas definições de rede. + Se estiver a utilizar um proxy reverso, certifique-se de que está configurado correctamente. + events-timeout: + title: Algo não está bem + message: >- + Dozzle UI esgotou o tempo limite ao ligar à API. Por favor, verifique a ligação de rede e tente novamente. +alert: + redirected: + title: Redirecionado para novo contentor + message: Dozzle redirecionou-o automaticamente para o novo contentor {containerId}. + similar-container-found: + title: Contentor semelhante encontrado + message: >- + Dozzle encontrou um contentor semelhante {containerId} que está a ser executado no mesmo anfitrião. Quer mudar para ele? title: page-not-found: Página não encontrada login: Autenticação Requerida @@ -58,3 +75,4 @@ settings: Está disponível uma nova versão! Actualização para {nextVersion}. show-std: Mostrar etiquetas de saída padrão e saída de erro padrão + automatic-redirect: Redirecionar automaticamente para novos contentores com o mesmo nome diff --git a/locales/ru.yml b/locales/ru.yml index 15e231d2..8e159cd5 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -28,6 +28,23 @@ error: invalid-auth: Имя пользователя или пароль неверны. logs-skipped: Пропущено {total} записей container-not-found: Контейнер не найден. + events-stream: + title: Неожиданная ошибка + message: >- + Dozzle UI не смог подключиться к API. Пожалуйста, проверьте настройки сети. + Если вы используете обратный прокси, убедитесь, что он настроен правильно. + events-timeout: + title: Что-то не так + message: >- + Dozzle UI превысил время ожидания при подключении к API. Пожалуйста, проверьте сетевое подключение и повторите попытку. +alert: + redirected: + title: Перенаправлен на новый контейнер + message: Dozzle автоматически перенаправил вас на новый контейнер {containerId}. + similar-container-found: + title: Найден похожий контейнер + message: >- + Dozzle нашел похожий контейнер {containerId}, который работает на том же хосте. Хотите переключиться на него? title: page-not-found: Страница не найдена login: Требуется авторизация @@ -56,3 +73,4 @@ settings: update-available: >- Доступна новая версия! Обновить до {nextVersion}. show-std: Показывать метки stdout и stderr + automatic-redirect: Автоматическое перенаправление на новые контейнеры с тем же именем. diff --git a/locales/zh.yml b/locales/zh.yml index 5d1e53b7..959d1976 100644 --- a/locales/zh.yml +++ b/locales/zh.yml @@ -28,6 +28,23 @@ error: invalid-auth: 用户名和密码无效。 logs-skipped: 跳过 {total} 条记录 container-not-found: 容器未找到。 + events-stream: + title: 意外错误 + message: >- + Dozzle UI无法连接到API。请检查您的网络设置。 + 如果您使用反向代理,请确保其正确配置。 + events-timeout: + title: 出了点问题 + message: >- + Dozzle UI在连接到API时超时。请检查网络连接并重试。 +alert: + redirected: + title: 重定向到新容器 + message: Dozzle自动将您重定向到新容器 {containerId}。 + similar-container-found: + title: 找到相似的容器 + message: >- + Dozzle发现了一个相似的容器 {containerId},它在同一主机上运行。您想切换到它吗?d title: page-not-found: 页面未找到 login: 需要身份验证 @@ -56,3 +73,4 @@ settings: update-available: >- 新版本可用!更新到 {nextVersion}。 show-std: 显示stdout和stderr标签 + automatic-redirect: 自动重定向到同名的新容器