mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 08:54:45 +00:00
feat(analytics): maximize drawer and export SQL results (#4871)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+3
@@ -105,6 +105,8 @@ declare module 'vue' {
|
||||
'Mdi:alertCircleOutline': typeof import('~icons/mdi/alert-circle-outline')['default']
|
||||
'Mdi:alertOutline': typeof import('~icons/mdi/alert-outline')['default']
|
||||
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
|
||||
'Mdi:arrowCollapse': typeof import('~icons/mdi/arrow-collapse')['default']
|
||||
'Mdi:arrowExpand': typeof import('~icons/mdi/arrow-expand')['default']
|
||||
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
|
||||
'Mdi:beer': typeof import('~icons/mdi/beer')['default']
|
||||
'Mdi:bell': typeof import('~icons/mdi/bell')['default']
|
||||
@@ -169,6 +171,7 @@ declare module 'vue' {
|
||||
'Ph:controlBold': typeof import('~icons/ph/control-bold')['default']
|
||||
'Ph:database': typeof import('~icons/ph/database')['default']
|
||||
'Ph:dotsThreeVerticalBold': typeof import('~icons/ph/dots-three-vertical-bold')['default']
|
||||
'Ph:downloadSimple': typeof import('~icons/ph/download-simple')['default']
|
||||
'Ph:fileSql': typeof import('~icons/ph/file-sql')['default']
|
||||
'Ph:globeSimple': typeof import('~icons/ph/globe-simple')['default']
|
||||
'Ph:stack': typeof import('~icons/ph/stack')['default']
|
||||
|
||||
@@ -48,6 +48,25 @@
|
||||
}}</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="dropdown dropdown-end shrink-0" v-if="canExport">
|
||||
<div tabindex="0" role="button" class="btn btn-xs btn-ghost cursor-pointer gap-1">
|
||||
<ph:download-simple class="size-4" />
|
||||
{{ $t("analytics.export") }}
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-30 w-44 p-2 shadow-sm">
|
||||
<li>
|
||||
<a class="cursor-pointer whitespace-nowrap" @click="exportResults('csv')">{{
|
||||
$t("analytics.export_csv")
|
||||
}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="cursor-pointer whitespace-nowrap" @click="exportResults('json')">{{
|
||||
$t("analytics.export_json")
|
||||
}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -238,5 +257,54 @@ whenever(evaluating, () => {
|
||||
const page = computed(() =>
|
||||
results.value.numRows > pageLimit ? results.value.slice(0, pageLimit) : results.value,
|
||||
) as unknown as ComputedRef<Table<Record<string, any>>>;
|
||||
|
||||
const canExport = computed(() => state.value === "ready" && !evaluating.value && results.value.numRows > 0);
|
||||
|
||||
function stringify(value: unknown): string {
|
||||
if (value === null || value === undefined) return "";
|
||||
if (typeof value === "bigint") return value.toString();
|
||||
if (typeof value === "object") return JSON.stringify(value, (_, v) => (typeof v === "bigint" ? v.toString() : v));
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function toCSV(table: Table<Record<string, any>>, columns: string[]): string {
|
||||
const escape = (value: string) => (/[",\n\r]/.test(value) ? `"${value.replaceAll('"', '""')}"` : value);
|
||||
const lines = [columns.map(escape).join(",")];
|
||||
for (const row of table) {
|
||||
lines.push(columns.map((column) => escape(stringify((row as Record<string, any>)[column]))).join(","));
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function toJSON(table: Table<Record<string, any>>, columns: string[]): string {
|
||||
const rows = [];
|
||||
for (const row of table) {
|
||||
const record: Record<string, unknown> = {};
|
||||
for (const column of columns) {
|
||||
const value = (row as Record<string, any>)[column];
|
||||
record[column] = typeof value === "bigint" ? value.toString() : value;
|
||||
}
|
||||
rows.push(record);
|
||||
}
|
||||
return JSON.stringify(rows, null, 2);
|
||||
}
|
||||
|
||||
function exportResults(format: "csv" | "json") {
|
||||
const table = results.value as unknown as Table<Record<string, any>>;
|
||||
if (table.numRows === 0) return;
|
||||
|
||||
const columns = Object.keys(table.get(0) as Record<string, any>);
|
||||
const content = format === "csv" ? toCSV(table, columns) : toJSON(table, columns);
|
||||
const type = format === "csv" ? "text/csv;charset=utf-8" : "application/json";
|
||||
const name = container.name.replace(/[^\w.-]+/g, "-");
|
||||
|
||||
const url = URL.createObjectURL(new Blob([content], { type }));
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `${name}-query.${format}`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
(document.activeElement as HTMLElement | null)?.blur();
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
<template>
|
||||
<dialog ref="panel" class="modal-right modal items-start outline-hidden backdrop:bg-none">
|
||||
<div class="modal-box" :width="width">
|
||||
<div class="modal-box" :width="maximized ? 'full' : width">
|
||||
<div class="pt-safe relative">
|
||||
<form method="dialog" class="absolute right-0">
|
||||
<button v-if="isMobile">
|
||||
<div class="absolute right-0 flex items-center gap-3">
|
||||
<button
|
||||
v-if="!isMobile"
|
||||
class="hover:text-base-content/60 cursor-pointer outline-hidden"
|
||||
type="button"
|
||||
:title="maximized ? $t('drawer.restore') : $t('drawer.maximize')"
|
||||
:aria-label="maximized ? $t('drawer.restore') : $t('drawer.maximize')"
|
||||
@click="maximized = !maximized"
|
||||
>
|
||||
<mdi:arrow-collapse v-if="maximized" />
|
||||
<mdi:arrow-expand v-else />
|
||||
</button>
|
||||
<form method="dialog">
|
||||
<button v-if="isMobile" class="cursor-pointer">
|
||||
<mdi:close />
|
||||
</button>
|
||||
<button v-else class="swap hover:swap-active outline-hidden">
|
||||
<button v-else class="swap hover:swap-active cursor-pointer outline-hidden">
|
||||
<mdi:keyboard-esc class="swap-off" />
|
||||
<mdi:close class="swap-on" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<slot v-if="open" :close="close"></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,6 +37,7 @@ import { type DrawerWidth } from "@/composable/drawer";
|
||||
const panel = useTemplateRef<HTMLDialogElement>("panel");
|
||||
|
||||
const open = ref(false);
|
||||
const maximized = ref(false);
|
||||
const { width } = defineProps<{
|
||||
width: DrawerWidth;
|
||||
}>();
|
||||
@@ -35,6 +49,7 @@ function close() {
|
||||
defineExpose({
|
||||
open: () => {
|
||||
open.value = true;
|
||||
maximized.value = false;
|
||||
panel.value?.showModal();
|
||||
},
|
||||
close,
|
||||
@@ -55,6 +70,10 @@ useEventListener(panel, "close", () => (open.value = false));
|
||||
&[width="lg"] {
|
||||
@apply max-w-5xl;
|
||||
}
|
||||
|
||||
&[width="full"] {
|
||||
@apply w-full max-w-full;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-right[open] .modal-box {
|
||||
|
||||
@@ -213,6 +213,12 @@ analytics:
|
||||
example_all: Alle logfiler
|
||||
example_count: Antal rækker
|
||||
example_group: Antal efter {column}
|
||||
export: Eksporter
|
||||
export_csv: Download CSV
|
||||
export_json: Download JSON
|
||||
drawer:
|
||||
maximize: Maksimer
|
||||
restore: Gendan
|
||||
notifications:
|
||||
title: Notifikationer
|
||||
description: Konfigurer hvor og hvornår du vil modtage alarmer
|
||||
|
||||
@@ -213,6 +213,12 @@ analytics:
|
||||
example_all: Alle Logs
|
||||
example_count: Anzahl Zeilen
|
||||
example_group: Anzahl nach {column}
|
||||
export: Exportieren
|
||||
export_csv: CSV herunterladen
|
||||
export_json: JSON herunterladen
|
||||
drawer:
|
||||
maximize: Maximieren
|
||||
restore: Wiederherstellen
|
||||
notifications:
|
||||
title: Benachrichtigungen
|
||||
description: Konfigurieren Sie, wo und wann Sie Alarme erhalten möchten
|
||||
|
||||
@@ -263,6 +263,12 @@ analytics:
|
||||
example_all: All logs
|
||||
example_count: Row count
|
||||
example_group: Count by {column}
|
||||
export: Export
|
||||
export_csv: Download CSV
|
||||
export_json: Download JSON
|
||||
drawer:
|
||||
maximize: Maximize
|
||||
restore: Restore
|
||||
notifications:
|
||||
title: Notifications
|
||||
description: Configure where and when to receive alerts
|
||||
|
||||
@@ -257,6 +257,12 @@ analytics:
|
||||
example_all: Todos los registros
|
||||
example_count: Número de filas
|
||||
example_group: Conteo por {column}
|
||||
export: Exportar
|
||||
export_csv: Descargar CSV
|
||||
export_json: Descargar JSON
|
||||
drawer:
|
||||
maximize: Maximizar
|
||||
restore: Restaurar
|
||||
notifications:
|
||||
title: Notificaciones
|
||||
description: Configure dónde y cuándo recibir alertas
|
||||
|
||||
@@ -213,6 +213,12 @@ analytics:
|
||||
example_all: Tous les journaux
|
||||
example_count: Nombre de lignes
|
||||
example_group: Nombre par {column}
|
||||
export: Exporter
|
||||
export_csv: Télécharger le CSV
|
||||
export_json: Télécharger le JSON
|
||||
drawer:
|
||||
maximize: Agrandir
|
||||
restore: Restaurer
|
||||
notifications:
|
||||
title: Notifications
|
||||
description: Configurez où et quand recevoir des alertes
|
||||
|
||||
@@ -225,6 +225,12 @@ analytics:
|
||||
example_all: Semua log
|
||||
example_count: Jumlah baris
|
||||
example_group: Hitung berdasarkan {column}
|
||||
export: Ekspor
|
||||
export_csv: Unduh CSV
|
||||
export_json: Unduh JSON
|
||||
drawer:
|
||||
maximize: Maksimalkan
|
||||
restore: Pulihkan
|
||||
notifications:
|
||||
title: Notifikasi
|
||||
description: Konfigurasikan di mana dan kapan menerima peringatan
|
||||
|
||||
@@ -213,6 +213,12 @@ analytics:
|
||||
example_all: Tutti i log
|
||||
example_count: Numero di righe
|
||||
example_group: Conteggio per {column}
|
||||
export: Esporta
|
||||
export_csv: Scarica CSV
|
||||
export_json: Scarica JSON
|
||||
drawer:
|
||||
maximize: Massimizza
|
||||
restore: Ripristina
|
||||
notifications:
|
||||
title: Notifiche
|
||||
description: Configura dove e quando ricevere avvisi
|
||||
|
||||
@@ -217,6 +217,12 @@ analytics:
|
||||
example_all: 모든 로그
|
||||
example_count: 행 수
|
||||
example_group: "{column}별 개수"
|
||||
export: 내보내기
|
||||
export_csv: CSV 다운로드
|
||||
export_json: JSON 다운로드
|
||||
drawer:
|
||||
maximize: 최대화
|
||||
restore: 복원
|
||||
notifications:
|
||||
title: 알림
|
||||
description: 알림을 받을 위치와 시간을 설정합니다
|
||||
|
||||
@@ -214,6 +214,12 @@ analytics:
|
||||
example_all: Alle logs
|
||||
example_count: Aantal rijen
|
||||
example_group: Aantal per {column}
|
||||
export: Exporteren
|
||||
export_csv: CSV downloaden
|
||||
export_json: JSON downloaden
|
||||
drawer:
|
||||
maximize: Maximaliseren
|
||||
restore: Herstellen
|
||||
notifications:
|
||||
title: Meldingen
|
||||
description: Configureer waar en wanneer je waarschuwingen ontvangt
|
||||
|
||||
@@ -216,6 +216,12 @@ analytics:
|
||||
example_all: Wszystkie logi
|
||||
example_count: Liczba wierszy
|
||||
example_group: Liczba według {column}
|
||||
export: Eksportuj
|
||||
export_csv: Pobierz CSV
|
||||
export_json: Pobierz JSON
|
||||
drawer:
|
||||
maximize: Maksymalizuj
|
||||
restore: Przywróć
|
||||
notifications:
|
||||
title: Powiadomienia
|
||||
description: Skonfiguruj gdzie i kiedy otrzymywać alerty
|
||||
|
||||
@@ -213,6 +213,12 @@ analytics:
|
||||
example_all: Todos os logs
|
||||
example_count: Número de linhas
|
||||
example_group: Contagem por {column}
|
||||
export: Exportar
|
||||
export_csv: Baixar CSV
|
||||
export_json: Baixar JSON
|
||||
drawer:
|
||||
maximize: Maximizar
|
||||
restore: Restaurar
|
||||
notifications:
|
||||
title: Notificações
|
||||
description: Configure onde e quando receber alertas
|
||||
|
||||
@@ -213,6 +213,12 @@ analytics:
|
||||
example_all: Все логи
|
||||
example_count: Количество строк
|
||||
example_group: Количество по {column}
|
||||
export: Экспорт
|
||||
export_csv: Скачать CSV
|
||||
export_json: Скачать JSON
|
||||
drawer:
|
||||
maximize: Развернуть
|
||||
restore: Восстановить
|
||||
notifications:
|
||||
title: Уведомления
|
||||
description: Настройте где и когда получать оповещения
|
||||
|
||||
@@ -219,6 +219,12 @@ analytics:
|
||||
example_all: Vsi dnevniki
|
||||
example_count: Število vrstic
|
||||
example_group: Štetje po {column}
|
||||
export: Izvozi
|
||||
export_csv: Prenesi CSV
|
||||
export_json: Prenesi JSON
|
||||
drawer:
|
||||
maximize: Maksimiraj
|
||||
restore: Obnovi
|
||||
notifications:
|
||||
title: Obvestila
|
||||
description: Nastavite kje in kdaj prejemati opozorila
|
||||
|
||||
@@ -217,6 +217,12 @@ analytics:
|
||||
example_all: Tüm günlükler
|
||||
example_count: Satır sayısı
|
||||
example_group: "{column} bazında sayı"
|
||||
export: Dışa aktar
|
||||
export_csv: CSV indir
|
||||
export_json: JSON indir
|
||||
drawer:
|
||||
maximize: Büyüt
|
||||
restore: Geri yükle
|
||||
notifications:
|
||||
title: Bildirimler
|
||||
description: Uyarıları nerede ve ne zaman alacağınızı yapılandırın
|
||||
|
||||
@@ -216,6 +216,12 @@ analytics:
|
||||
example_all: 所有日誌
|
||||
example_count: 資料列數
|
||||
example_group: 依 {column} 計數
|
||||
export: 匯出
|
||||
export_csv: 下載 CSV
|
||||
export_json: 下載 JSON
|
||||
drawer:
|
||||
maximize: 最大化
|
||||
restore: 還原
|
||||
notifications:
|
||||
title: 通知
|
||||
description: 設定接收警報的位置和時間
|
||||
|
||||
@@ -213,6 +213,12 @@ analytics:
|
||||
example_all: 所有日志
|
||||
example_count: 行数
|
||||
example_group: 按 {column} 计数
|
||||
export: 导出
|
||||
export_csv: 下载 CSV
|
||||
export_json: 下载 JSON
|
||||
drawer:
|
||||
maximize: 最大化
|
||||
restore: 还原
|
||||
notifications:
|
||||
title: 通知
|
||||
description: 配置接收警报的位置和时间
|
||||
|
||||
Reference in New Issue
Block a user