diff --git a/web/src/composables/useLogs/useHistogram.ts b/web/src/composables/useLogs/useHistogram.ts index d8ece93d83..1d4cc4a9d1 100644 --- a/web/src/composables/useLogs/useHistogram.ts +++ b/web/src/composables/useLogs/useHistogram.ts @@ -119,7 +119,7 @@ export const useHistogram = () => { if (searchObj.meta.logsVisualizeToggle === "logs") { title += - "(" + + " (" + scanSizeLabel + ": " + formatSizeFromMB(searchObj.data.queryResults.scan_size) + diff --git a/web/src/locales/languages/de.json b/web/src/locales/languages/de.json index ce780a4c39..f8bbf2325e 100644 --- a/web/src/locales/languages/de.json +++ b/web/src/locales/languages/de.json @@ -276,7 +276,8 @@ "actionLabel": "Aktion", "transformLabel": "Verwandeln", "addToDashboard": "Zum Dashboard hinzufügen", - "selectStream": "Wählen Sie Stream" + "selectStream": "Wählen Sie Stream", + "pattern_summary": "Es werden 1 bis 50 von {totalEvents} -Ereignissen und {patternsFound} -Mustern angezeigt, die in {logsAnalyzed} -Ereignissen in {totalTime} ms gefunden wurden." }, "menu": { "home": "Startseite", diff --git a/web/src/locales/languages/en.json b/web/src/locales/languages/en.json index c5b4dd2b15..eadb9d6587 100644 --- a/web/src/locales/languages/en.json +++ b/web/src/locales/languages/en.json @@ -276,7 +276,8 @@ "actionLabel": "Action", "transformLabel": "Transform", "addToDashboard": "Add To Dashboard", - "selectStream": "Select Stream" + "selectStream": "Select Stream", + "pattern_summary": "Showing 1 to 50 out of {totalEvents} events & {patternsFound} patterns found in {logsAnalyzed} events in {totalTime} ms." }, "menu": { "home": "Home", diff --git a/web/src/locales/languages/es.json b/web/src/locales/languages/es.json index b04d6270d6..2b5f8aec63 100644 --- a/web/src/locales/languages/es.json +++ b/web/src/locales/languages/es.json @@ -276,7 +276,8 @@ "actionLabel": "Acción", "transformLabel": "Transformar", "addToDashboard": "Agregar al panel", - "selectStream": "Selecciona Stream" + "selectStream": "Selecciona Stream", + "pattern_summary": "Se muestran de 1 a 50 de los eventos y {patternsFound} patrones de {totalEvents} encontrados en eventos de {logsAnalyzed} en {totalTime} ms." }, "menu": { "home": "Inicio", diff --git a/web/src/locales/languages/fr.json b/web/src/locales/languages/fr.json index e2f28e971f..2f44e1ed1f 100644 --- a/web/src/locales/languages/fr.json +++ b/web/src/locales/languages/fr.json @@ -276,7 +276,8 @@ "actionLabel": "Action", "transformLabel": "Transformez", "addToDashboard": "Ajouter au tableau de bord", - "selectStream": "Sélectionnez Stream" + "selectStream": "Sélectionnez Stream", + "pattern_summary": "Affichage de 1 à 50 événements sur {TotalEvents} et des modèles {PatternsFound} trouvés dans les événements {LogsAnalyzed} en {TotalTime} ms." }, "menu": { "home": "Accueil", diff --git a/web/src/locales/languages/it.json b/web/src/locales/languages/it.json index de6cb935b2..759fd46c6d 100644 --- a/web/src/locales/languages/it.json +++ b/web/src/locales/languages/it.json @@ -276,7 +276,8 @@ "actionLabel": "Azione", "transformLabel": "Trasforma", "addToDashboard": "Aggiungi al pannello di controllo", - "selectStream": "Seleziona Stream" + "selectStream": "Seleziona Stream", + "pattern_summary": "Mostrando da 1 a 50 eventi su {totalEvents} e {patternsFound} pattern trovati in {logsAnalyzed} eventi in {totalTime} ms." }, "menu": { "home": "Home", diff --git a/web/src/locales/languages/ja.json b/web/src/locales/languages/ja.json index 54ec9476c6..575f081368 100644 --- a/web/src/locales/languages/ja.json +++ b/web/src/locales/languages/ja.json @@ -276,7 +276,8 @@ "actionLabel": "アクション", "transformLabel": "トランスフォーム", "addToDashboard": "ダッシュボードに追加", - "selectStream": "[ストリーム] を選択" + "selectStream": "[ストリーム] を選択", + "pattern_summary": "{totalTime} ミリ秒で {logsAnalyzed} イベントで見つかった {totalEvents} イベントと {PatternsFound} パターンのうち 1 ~ 50 件を表示しています。" }, "menu": { "home": "ホーム", diff --git a/web/src/locales/languages/ko.json b/web/src/locales/languages/ko.json index 6680f821d7..d9d974f477 100644 --- a/web/src/locales/languages/ko.json +++ b/web/src/locales/languages/ko.json @@ -276,7 +276,8 @@ "actionLabel": "액션", "transformLabel": "트랜스폼", "addToDashboard": "대시보드에 추가", - "selectStream": "스트림 선택" + "selectStream": "스트림 선택", + "pattern_summary": "{TotalTime} 밀리초 내에 {logsAnallets} 이벤트에서 발견된 {TotalEvents} 이벤트 및 {PatternsFound} 패턴 중 1~50개를 표시합니다." }, "menu": { "home": "홈", diff --git a/web/src/locales/languages/nl.json b/web/src/locales/languages/nl.json index bf171ad59c..b659d2aa23 100644 --- a/web/src/locales/languages/nl.json +++ b/web/src/locales/languages/nl.json @@ -276,7 +276,8 @@ "actionLabel": "Actie", "transformLabel": "Transformeren", "addToDashboard": "Aan dashboard toevoegen", - "selectStream": "Selecteer Stream" + "selectStream": "Selecteer Stream", + "pattern_summary": "Toont 1 tot 50 van de {totalEvents} -gebeurtenissen en {PatternsFound} -patronen gevonden in {logsAnalyzed} -gebeurtenissen in {totalTime} ms." }, "menu": { "home": "Home", diff --git a/web/src/locales/languages/pt.json b/web/src/locales/languages/pt.json index 6752b9b448..817b535c0e 100644 --- a/web/src/locales/languages/pt.json +++ b/web/src/locales/languages/pt.json @@ -276,7 +276,8 @@ "actionLabel": "Ação", "transformLabel": "Transformar", "addToDashboard": "Adicionar ao painel", - "selectStream": "Selecione Stream" + "selectStream": "Selecione Stream", + "pattern_summary": "Mostrando de 1 a 50 dos eventos {totalEvents} e padrões {patternsFound} encontrados em eventos {logsAnalyzed} em {totalTime} ms." }, "menu": { "home": "Início", diff --git a/web/src/locales/languages/tr.json b/web/src/locales/languages/tr.json index 2925c6bf3a..bc646511c1 100644 --- a/web/src/locales/languages/tr.json +++ b/web/src/locales/languages/tr.json @@ -276,7 +276,8 @@ "actionLabel": "Eylem", "transformLabel": "Dönüştürün", "addToDashboard": "Kontrol Paneline Ekle", - "selectStream": "Akışı Seç" + "selectStream": "Akışı Seç", + "pattern_summary": "{totalTime} ms içinde {logsAnalyzed} olaylarında bulunan {totalEvents} olay ve {patternsFound} modelinden 1 ila 50 arası gösteriliyor." }, "menu": { "home": "Ana Sayfa", diff --git a/web/src/locales/languages/zh.json b/web/src/locales/languages/zh.json index 61a6ee3ddf..f5177e5d13 100644 --- a/web/src/locales/languages/zh.json +++ b/web/src/locales/languages/zh.json @@ -276,7 +276,8 @@ "actionLabel": "行动", "transformLabel": "转型", "addToDashboard": "添加到控制面板", - "selectStream": "选择直播" + "selectStream": "选择直播", + "pattern_summary": "在 {totalTime} 毫秒内显示在 {LogsAnalyzed} 事件中找到的 {TotalEvents} 事件和 {PatternsFound} 模式中的 1 到 50 个。" }, "menu": { "home": "主页", diff --git a/web/src/plugins/logs/SearchResult.vue b/web/src/plugins/logs/SearchResult.vue index 7087c2dfe7..90fe08d386 100644 --- a/web/src/plugins/logs/SearchResult.vue +++ b/web/src/plugins/logs/SearchResult.vue @@ -38,7 +38,7 @@ along with this program. If not, see . />
- {{ noOfRecordsTitle }} + {{ searchObj.meta.logsVisualizeToggle === 'patterns' ? patternSummaryText : noOfRecordsTitle }} Error while : 'table-container--with-histogram', ]" > - - - import("./patterns/PatternStatistics.vue"), - ), PatternList: defineAsyncComponent( () => import("./patterns/PatternList.vue"), ), @@ -501,6 +492,22 @@ export default defineComponent({ }, }, methods: { + formatPatternSummary(stats: any, totalEvents: number, histogramMs: number) { + const patternsFound = stats?.total_patterns_found || 0; + const logsAnalyzed = (stats?.total_logs_analyzed || 0).toLocaleString(); + const totalEventsStr = totalEvents ? totalEvents.toLocaleString() : logsAnalyzed; + + // Combine histogram time + pattern extraction time + const patternMs = stats?.extraction_time_ms || 0; + const totalTimeMs = histogramMs + patternMs; + + return this.$t("search.pattern_summary", { + totalEvents: totalEventsStr, + patternsFound: patternsFound, + logsAnalyzed: logsAnalyzed, + totalTime: totalTimeMs + }); + }, handleColumnSizesUpdate(newColSizes: any) { const prevColSizes = this.searchObj.data.resultGrid?.colSizes[ @@ -689,6 +696,7 @@ export default defineComponent({ const $q = useQuasar(); const searchListContainer = ref(null); const noOfRecordsTitle = ref(""); + const patternSummaryText = ref(""); const scrollPosition = ref(0); const rowsPerPageOptions = [10, 25, 50, 100]; const disableMoreErrorDetails = ref(false); @@ -1054,14 +1062,16 @@ export default defineComponent({ const extractConstantsFromPattern = (template: string): string[] => { // Extract longest non-variable strings from pattern template // Pattern template has format like: "INFO action <*> at 14:47.1755283" - // We want continuous strings between <*> that are longer than 10 chars + // Variables can be: <*>, <:IDENTIFIERS>, <:TIMESTAMP>, <:UNIX_TIMESTAMP>, etc. + // We want continuous strings between these variables that are longer than 10 chars const constants: string[] = []; - const parts = template.split("<*>"); + + // Split by all variable markers using regex + // Matches: <*>, <:WORD>, etc. + const parts = template.split(/<[*:][^>]*>/); for (const part of parts) { const trimmed = part.trim(); - // For now, use the string as-is without sanitization - // const sanitized = sanitizeForMatchAll(trimmed); // Only include strings longer than 10 characters if (trimmed.length > 10) { constants.push(trimmed); @@ -1090,10 +1100,15 @@ export default defineComponent({ // Build multiple match_all() clauses, one for each constant // Each match_all takes a single string const matchAllClauses = constants.map((constant) => { - // Escape backslashes first, then single quotes in the constant + // Escape special characters for match_all query + // Order matters: backslash must be escaped first const escapedConstant = constant - .replace(/\\/g, "\\\\") - .replace(/'/g, "\\'"); + .replace(/\\/g, "\\\\") // Escape backslashes + .replace(/'/g, "\\'") // Escape single quotes + .replace(/"/g, '\\"') // Escape double quotes + .replace(/\n/g, "\\n") // Escape newlines + .replace(/\r/g, "\\r") // Escape carriage returns + .replace(/\t/g, "\\t"); // Escape tabs return `match_all('${escapedConstant}')`; }); @@ -1111,6 +1126,9 @@ export default defineComponent({ // Set the filter to be added to the query searchObj.data.stream.addToFilter = filterExpression; + + // Switch to logs view to show the filtered results + searchObj.meta.logsVisualizeToggle = "logs"; }; const getRowIndex = (next: boolean, prev: boolean, oldIndex: number) => { @@ -1374,6 +1392,7 @@ export default defineComponent({ extractFTSFields, useLocalWrapContent, noOfRecordsTitle, + patternSummaryText, scrollPosition, rowsPerPageOptions, pageNumberInput, @@ -1429,6 +1448,9 @@ export default defineComponent({ updateTitle() { return this.searchObj.data.histogram.chartParams.title; }, + updatePatternSummary() { + return this.patternsState?.patterns?.statistics; + }, reDrawChartData() { return this.searchObj.data.histogram; }, @@ -1451,6 +1473,20 @@ export default defineComponent({ updateTitle() { this.noOfRecordsTitle = this.searchObj.data.histogram.chartParams.title; }, + updatePatternSummary() { + if (this.patternsState?.patterns?.statistics) { + // Reuse the same summary logic from PatternStatistics component + const stats = this.patternsState.patterns.statistics; + const totalEvents = this.searchObj.data.queryResults?.total || stats.total_logs_analyzed || 0; + const histogramMs = this.searchObj.data.queryResults?.took || 0; + + this.patternSummaryText = this.formatPatternSummary( + stats, + totalEvents, + histogramMs + ); + } + }, reDrawChartData: { deep: true, handler: function () { diff --git a/web/src/plugins/logs/patterns/PatternCard.vue b/web/src/plugins/logs/patterns/PatternCard.vue index 5cfd5ecc69..332755e6c8 100644 --- a/web/src/plugins/logs/patterns/PatternCard.vue +++ b/web/src/plugins/logs/patterns/PatternCard.vue @@ -16,14 +16,14 @@ along with this program. If not, see .