1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-24 14:31:44 +01:00

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
This commit is contained in:
Amir Raminfar
2023-09-27 16:00:44 -07:00
committed by GitHub
parent c3b5991dc7
commit 58edb4f602
15 changed files with 239 additions and 22 deletions

View File

@@ -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<typeof import('./utils/index')['arrayEquals']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly automaticRedirect: UnwrapRef<typeof import('./composables/settings')['automaticRedirect']>
readonly collapseNav: UnwrapRef<typeof import('./composables/settings')['collapseNav']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
@@ -714,6 +716,7 @@ declare module '@vue/runtime-core' {
readonly arrayEquals: UnwrapRef<typeof import('./utils/index')['arrayEquals']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly automaticRedirect: UnwrapRef<typeof import('./composables/settings')['automaticRedirect']>
readonly collapseNav: UnwrapRef<typeof import('./composables/settings')['collapseNav']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>

View File

@@ -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']
}

View File

@@ -6,12 +6,17 @@
v-if="nextContainer && logEntry.event === 'container-stopped'"
>
<carbon:information class="h-6 w-6 shrink-0 stroke-current" />
<span>
Another container instance with the same name was created <distance-time :date="nextContainer.created" />. Do
you want to redirect to the new one?
</span>
<div>
<router-link :to="{ name: 'container-id', params: { id: nextContainer.id } }" class="btn btn-primary btn-sm">
<h3 class="text-lg font-bold">{{ $t("alert.similar-container-found.title") }}</h3>
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
</div>
<div>
<TimedButton v-if="automaticRedirect" class="btn-primary btn-sm" @finished="redirectNow()">Cancel</TimedButton>
<router-link
:to="{ name: 'container-id', params: { id: nextContainer.id } }"
class="btn btn-primary btn-sm"
v-else
>
Redirect
</router-link>
</div>
@@ -20,6 +25,9 @@
</template>
<script lang="ts" setup>
import { DockerEventLogEntry } from "@/models/LogEntry";
const router = useRouter();
const { showToast } = useToast();
const { t } = useI18n();
const { logEntry } = defineProps<{
logEntry: DockerEventLogEntry;
@@ -41,6 +49,18 @@ const nextContainer = computed(
)
.toSorted((a, b) => +a.created - +b.created)[0],
);
function redirectNow() {
showToast(
{
title: t("alert.redirected.title"),
message: t("alert.redirected.message", { containerId: nextContainer.value.id }),
type: "info",
},
{ expire: 5000 },
);
router.push({ name: "container-id", params: { id: nextContainer.value.id } });
}
</script>
<style lang="postcss" scoped>

View File

@@ -0,0 +1,39 @@
<template>
<button class="btn relative overflow-hidden" @click="cancel()">
<div class="absolute inset-0 origin-left bg-white/30" ref="progress"></div>
<div class="z-10">
<slot></slot>
</div>
</button>
</template>
<script lang="ts" setup>
const progress = ref<HTMLElement>();
const finished = defineEmit();
const cancelled = defineEmit();
let animation: Animation | undefined;
onMounted(async () => {
animation = progress.value?.animate([{ transform: "scaleX(0)" }, { transform: "scaleX(1)" }], {
duration: 4000,
easing: "linear",
fill: "forwards",
});
try {
await animation?.finished;
finished();
} catch (e) {
progress.value?.animate([{ transform: "scaleX(1)" }, { transform: "scaleX(0)" }], {
duration: 0,
fill: "forwards",
});
cancelled();
}
});
const cancel = () => {
animation?.cancel();
};
</script>
<style scoped lang="postcss"></style>

View File

@@ -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);

View File

@@ -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<Toast[]>([]);
const showToast = (message: string, type: Toast["type"]) => {
const showToast = (
toast: Omit<Toast, "id" | "createdAt"> & { 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"]) => {

View File

@@ -49,7 +49,12 @@
:key="toast.id"
:class="{ 'alert-error': toast.type === 'error', 'alert-info': toast.type === 'info' }"
>
<span>{{ toast.message }}</span>
<carbon:information class="h-6 w-6 shrink-0 stroke-current" v-if="toast.type === 'info'" />
<carbon:warning class="h-6 w-6 shrink-0 stroke-current" v-else-if="toast.type === 'error'" />
<div>
<h3 class="text-lg font-bold" v-if="toast.title">{{ toast.title }}</h3>
{{ toast.message }}
</div>
<div>
<button class="btn btn-circle btn-xs" @click="removeToast(toast.id)"><mdi:close /></button>
</div>

View File

@@ -80,6 +80,10 @@
<div>
<toggle v-model="showAllContainers">{{ $t("settings.show-stopped-containers") }}</toggle>
</div>
<div>
<toggle v-model="automaticRedirect">{{ $t("settings.automatic-redirect") }}</toggle>
</div>
</section>
</div>
</template>
@@ -95,6 +99,7 @@ import {
showAllContainers,
size,
softWrap,
automaticRedirect,
} from "@/composables/settings";
const { t } = useI18n();

View File

@@ -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 },
);
}
})();

View File

@@ -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 <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
show-std: Zeige stdout und stderr Labels
automatic-redirect: Automatische Weiterleitung

View File

@@ -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 <a href="{href}" target="_blank"
rel="noreferrer noopener">{nextVersion}</a>.
show-std: Show stdout and stderr labels
automatic-redirect: Automatische Weiterleitung zu neuen Containern mit demselben Namen

View File

@@ -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
<a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
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

View File

@@ -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
<a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
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

View File

@@ -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: >-
Доступна новая версия! Обновить до <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
show-std: Показывать метки stdout и stderr
automatic-redirect: Автоматическое перенаправление на новые контейнеры с тем же именем.

View File

@@ -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: >-
新版本可用!更新到 <a href="{href}" rel="noreferrer noopener">{nextVersion}</a>。
show-std: 显示stdout和stderr标签
automatic-redirect: 自动重定向到同名的新容器