@datum-cloud/datum-ui 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,4 +2,4 @@ export { resolveLogColumns } from './components/columns';
|
|
|
2
2
|
export { useLogs } from './hooks/use-logs';
|
|
3
3
|
export { Logs } from './logs';
|
|
4
4
|
export type { BuildLogQLOptions, LogColumn, LogColumnCellContext, LogColumnId, LogColumnSize, LogColumnSkeletonContext, LogColumnSpec, LogEntry, LogFacet, LogFacetOption, LogFilters, LogHistogramBucket, LogsContextValue, LogsRootProps, LogTimeRange, LokiQueryRangeResponse, LokiStream, ParsedHttpLogLine, ParsedLogLine, ParsedTextLogLine, } from './types';
|
|
5
|
-
export { buildLogQL, facetsFromEntries, filterEntries, flattenLokiStreams, formatHttpLogLine, histogramFromEntries, httpStatusBadgeType, httpStatusTextClass, lastThirtyMinutes, LOG_TIME_PRESETS, logLineDisplay, logRequestHost, logTimeRangeLabel, nsToDate, parseLogLine, resolveLogTimeRange, searchableLogText, severityBadgeType, splitPathQuery, } from './utils';
|
|
5
|
+
export { buildLogQL, facetsFromEntries, filterEntries, flattenLokiStreams, formatHttpLogLine, histogramFromEntries, httpStatusBadgeType, httpStatusTextClass, lastThirtyMinutes, LOG_TIME_PRESETS, logLineDisplay, logRequestHost, logTimeRangeLabel, logUserAgent, nsToDate, parseLogLine, parseUserAgent, resolveLogTimeRange, searchableLogText, severityBadgeType, splitPathQuery, } from './utils';
|
|
@@ -8,3 +8,5 @@ export { logRequestHost } from './host';
|
|
|
8
8
|
export { formatHttpLogLine, logLineDisplay, parseLogLine, splitPathQuery } from './parse-log-line';
|
|
9
9
|
export { httpStatusBadgeType, httpStatusTextClass, severityBadgeType } from './severity';
|
|
10
10
|
export { filtersAreActive, lastThirtyMinutes, LOG_TIME_PRESETS, logTimeRangeLabel, resolveLogTimeRange, } from './time-range';
|
|
11
|
+
export { logUserAgent, parseUserAgent } from './user-agent';
|
|
12
|
+
export type { ParsedUserAgent, UserAgentDevice } from './user-agent';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type UserAgentDevice = 'desktop' | 'mobile' | 'tablet' | 'bot' | 'unknown';
|
|
2
|
+
export interface ParsedUserAgent {
|
|
3
|
+
/** Browser or client name, e.g. "Chrome", "Safari", "curl". */
|
|
4
|
+
browser?: string;
|
|
5
|
+
/** Major version of the browser or client, e.g. "128". */
|
|
6
|
+
browserVersion?: string;
|
|
7
|
+
/** Operating system, e.g. "macOS", "iOS", "Android", "Windows". */
|
|
8
|
+
os?: string;
|
|
9
|
+
/** OS version where it is meaningful (iOS, Android). Desktop OS versions are frozen or ambiguous in UA strings. */
|
|
10
|
+
osVersion?: string;
|
|
11
|
+
device: UserAgentDevice;
|
|
12
|
+
/** Human summary such as "Chrome 128 on macOS" or "curl 8.7". */
|
|
13
|
+
summary: string;
|
|
14
|
+
raw: string;
|
|
15
|
+
}
|
|
16
|
+
/** The User-Agent header from an entry's labels, if any. */
|
|
17
|
+
export declare function logUserAgent(labels: Record<string, string>): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Parse a User-Agent header into browser, OS and device type. Deliberately
|
|
20
|
+
* small: it recognises the mainstream browsers, mobile platforms, common
|
|
21
|
+
* command-line / SDK clients, and anything that calls itself a bot. Returns
|
|
22
|
+
* `null` for an empty string; unrecognised strings still return `summary`
|
|
23
|
+
* as the raw value with `device: 'unknown'`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseUserAgent(raw: string): ParsedUserAgent | null;
|
package/dist/logs/index.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "../table
|
|
|
12
12
|
import { t as DateTimeRangePicker } from "../date-time-range-picker-DlFTh3DF.mjs";
|
|
13
13
|
import { c as getBrowserTimezone, t as DateTime, u as getTimezoneAbbreviation } from "../date-time-lZdefM4H.mjs";
|
|
14
14
|
import { t as useCopyToClipboard } from "../use-copy-to-clipboard-DZDq_85T.mjs";
|
|
15
|
-
import { ChevronDown, ChevronUp, Clock, Copy, Download, Inbox, RefreshCw, Search, SlidersHorizontal, TriangleAlert, X } from "lucide-react";
|
|
15
|
+
import { Bot, ChevronDown, ChevronUp, Clock, Copy, Download, HelpCircle, Inbox, Monitor, RefreshCw, Search, SlidersHorizontal, Smartphone, Tablet, TriangleAlert, X } from "lucide-react";
|
|
16
16
|
import { createContext, memo, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
17
17
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
18
18
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
@@ -438,7 +438,238 @@ function stripOnce(line, token) {
|
|
|
438
438
|
return `${line.slice(0, index)}${line.slice(index + token.length)}`.replace(/\s+/g, " ").trim();
|
|
439
439
|
}
|
|
440
440
|
//#endregion
|
|
441
|
+
//#region src/components/features/logs/utils/user-agent.ts
|
|
442
|
+
/** Label keys, in order, that may carry the client's User-Agent header. */
|
|
443
|
+
const USER_AGENT_LABEL_KEYS = [
|
|
444
|
+
"user_agent",
|
|
445
|
+
"http_user_agent",
|
|
446
|
+
"useragent",
|
|
447
|
+
"ua",
|
|
448
|
+
"http.user_agent"
|
|
449
|
+
];
|
|
450
|
+
/** The User-Agent header from an entry's labels, if any. */
|
|
451
|
+
function logUserAgent(labels) {
|
|
452
|
+
for (const key of USER_AGENT_LABEL_KEYS) {
|
|
453
|
+
const value = labels[key]?.trim();
|
|
454
|
+
if (value) return value;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const BOT_RE = /bot|crawl|spider|slurp|facebookexternalhit|preview|monitor|uptime|pingdom|kube-probe|prometheus|blackbox|healthcheck|health-check|datadog|newrelic|statuscake/i;
|
|
458
|
+
/** Order matters: forks embed the names of what they fork. */
|
|
459
|
+
const CLIENT_RULES = [
|
|
460
|
+
{
|
|
461
|
+
name: "curl",
|
|
462
|
+
re: /\bcurl\/(\d+(?:\.\d+)*)/i,
|
|
463
|
+
tool: true
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "Wget",
|
|
467
|
+
re: /\bWget\/(\d+(?:\.\d+)*)/i,
|
|
468
|
+
tool: true
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: "Python Requests",
|
|
472
|
+
re: /\bpython-requests\/(\d+(?:\.\d+)*)/i,
|
|
473
|
+
tool: true
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "Python urllib",
|
|
477
|
+
re: /\bPython-urllib\/(\d+(?:\.\d+)*)/i,
|
|
478
|
+
tool: true
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: "Go HTTP client",
|
|
482
|
+
re: /\bGo-http-client\/(\d+(?:\.\d+)*)/i,
|
|
483
|
+
tool: true
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "okhttp",
|
|
487
|
+
re: /\bokhttp\/(\d+(?:\.\d+)*)/i,
|
|
488
|
+
tool: true
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "axios",
|
|
492
|
+
re: /\baxios\/(\d+(?:\.\d+)*)/i,
|
|
493
|
+
tool: true
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "node-fetch",
|
|
497
|
+
re: /\bnode-fetch\/(\d+(?:\.\d+)*)/i,
|
|
498
|
+
tool: true
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: "Node.js",
|
|
502
|
+
re: /\bnode\/v?(\d+(?:\.\d+)*)/i,
|
|
503
|
+
tool: true
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "Postman",
|
|
507
|
+
re: /\bPostmanRuntime\/(\d+(?:\.\d+)*)/i,
|
|
508
|
+
tool: true
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "Insomnia",
|
|
512
|
+
re: /\binsomnia\/(\d+(?:\.\d+)*)/i,
|
|
513
|
+
tool: true
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "Java",
|
|
517
|
+
re: /\bJava\/(\d+(?:\.\d+)*)/i,
|
|
518
|
+
tool: true
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: "Edge",
|
|
522
|
+
re: /\bEdgA?\/(\d+(?:\.\d+)*)/
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "Edge",
|
|
526
|
+
re: /\bEdgiOS\/(\d+(?:\.\d+)*)/
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: "Opera",
|
|
530
|
+
re: /\bOPR\/(\d+(?:\.\d+)*)/
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
name: "Opera",
|
|
534
|
+
re: /\bOpera\/(\d+(?:\.\d+)*)/
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: "Samsung Internet",
|
|
538
|
+
re: /\bSamsungBrowser\/(\d+(?:\.\d+)*)/
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "Brave",
|
|
542
|
+
re: /\bBrave\/(\d+(?:\.\d+)*)/
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "Vivaldi",
|
|
546
|
+
re: /\bVivaldi\/(\d+(?:\.\d+)*)/
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: "Firefox",
|
|
550
|
+
re: /\b(?:Firefox|FxiOS)\/(\d+(?:\.\d+)*)/
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: "Chrome",
|
|
554
|
+
re: /\b(?:Chrome|CriOS|HeadlessChrome)\/(\d+(?:\.\d+)*)/
|
|
555
|
+
}
|
|
556
|
+
];
|
|
557
|
+
/** Safari carries its version under `Version/`; WebKit views on iOS have neither. */
|
|
558
|
+
function detectSafari(ua) {
|
|
559
|
+
if (!/\bSafari\//.test(ua) && !/\bAppleWebKit\//.test(ua)) return void 0;
|
|
560
|
+
const version = /\bVersion\/(\d+(?:\.\d+)*)/.exec(ua);
|
|
561
|
+
if (version && /\bSafari\//.test(ua)) return { version: majorVersion(version[1]) };
|
|
562
|
+
if (/\b(?:iPhone|iPad|iPod)\b/.test(ua)) return {};
|
|
563
|
+
}
|
|
564
|
+
function majorVersion(version) {
|
|
565
|
+
if (!version) return void 0;
|
|
566
|
+
return version.split(".")[0];
|
|
567
|
+
}
|
|
568
|
+
function detectOs(ua) {
|
|
569
|
+
let match;
|
|
570
|
+
if (/\b(?:iPhone|iPad|iPod)\b/i.test(ua)) {
|
|
571
|
+
match = /\bOS (\d+)[._](\d+)(?:[._](\d+))?/.exec(ua);
|
|
572
|
+
if (!match) return { os: "iOS" };
|
|
573
|
+
const patch = match[3] && match[3] !== "0" ? `.${match[3]}` : "";
|
|
574
|
+
return {
|
|
575
|
+
os: "iOS",
|
|
576
|
+
osVersion: `${match[1]}.${match[2]}${patch}`
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
match = /\bAndroid[ /](\d+(?:\.\d+)?)/i.exec(ua);
|
|
580
|
+
if (match) return {
|
|
581
|
+
os: "Android",
|
|
582
|
+
osVersion: match[1]
|
|
583
|
+
};
|
|
584
|
+
if (/\bAndroid\b/i.test(ua)) return { os: "Android" };
|
|
585
|
+
if (/\bWindows Phone\b/i.test(ua)) return { os: "Windows Phone" };
|
|
586
|
+
if (/\bWindows\b/i.test(ua)) return { os: "Windows" };
|
|
587
|
+
if (/\bCrOS\b/.test(ua)) return { os: "ChromeOS" };
|
|
588
|
+
if (/\bMac OS X\b|\bMacintosh\b/i.test(ua)) return { os: "macOS" };
|
|
589
|
+
if (/\bLinux\b|\bX11\b/i.test(ua)) return { os: "Linux" };
|
|
590
|
+
if (/\bFreeBSD\b/i.test(ua)) return { os: "FreeBSD" };
|
|
591
|
+
return {};
|
|
592
|
+
}
|
|
593
|
+
function detectDevice(ua, os, bot, tool) {
|
|
594
|
+
if (bot || tool) return "bot";
|
|
595
|
+
if (/\biPad\b|\bTablet\b|\bKindle\b|\bSilk\b/i.test(ua)) return "tablet";
|
|
596
|
+
if (os === "Android" && !/\bMobile\b/i.test(ua)) return "tablet";
|
|
597
|
+
if (/\bMobile\b|\biPhone\b|\biPod\b|\bWindows Phone\b/i.test(ua)) return "mobile";
|
|
598
|
+
if (os === "iOS") return "mobile";
|
|
599
|
+
if (os === "Windows" || os === "macOS" || os === "Linux" || os === "ChromeOS" || os === "FreeBSD") return "desktop";
|
|
600
|
+
return "unknown";
|
|
601
|
+
}
|
|
602
|
+
function botClient(ua) {
|
|
603
|
+
const match = /([\w.-]*(?:bot|crawl|spider|probe|monitor|uptime|check)[\w.-]*)(?:\/(\d+(?:\.\d+)*))?/i.exec(ua);
|
|
604
|
+
if (!match?.[1]) return void 0;
|
|
605
|
+
return {
|
|
606
|
+
name: match[1],
|
|
607
|
+
version: majorVersion(match[2])
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Parse a User-Agent header into browser, OS and device type. Deliberately
|
|
612
|
+
* small: it recognises the mainstream browsers, mobile platforms, common
|
|
613
|
+
* command-line / SDK clients, and anything that calls itself a bot. Returns
|
|
614
|
+
* `null` for an empty string; unrecognised strings still return `summary`
|
|
615
|
+
* as the raw value with `device: 'unknown'`.
|
|
616
|
+
*/
|
|
617
|
+
function parseUserAgent(raw) {
|
|
618
|
+
const ua = raw.trim();
|
|
619
|
+
if (!ua) return null;
|
|
620
|
+
let browser;
|
|
621
|
+
let browserVersion;
|
|
622
|
+
let tool = false;
|
|
623
|
+
for (const rule of CLIENT_RULES) {
|
|
624
|
+
const match = rule.re.exec(ua);
|
|
625
|
+
if (match) {
|
|
626
|
+
browser = rule.name;
|
|
627
|
+
browserVersion = majorVersion(match[1]);
|
|
628
|
+
tool = Boolean(rule.tool);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (!browser) {
|
|
633
|
+
const safari = detectSafari(ua);
|
|
634
|
+
if (safari) {
|
|
635
|
+
browser = "Safari";
|
|
636
|
+
browserVersion = safari.version;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const { os, osVersion } = tool ? {} : detectOs(ua);
|
|
640
|
+
const bot = !tool && BOT_RE.test(ua);
|
|
641
|
+
const device = detectDevice(ua, os, bot, tool);
|
|
642
|
+
if (bot && !browser) {
|
|
643
|
+
const named = botClient(ua);
|
|
644
|
+
browser = named?.name;
|
|
645
|
+
browserVersion = named?.version;
|
|
646
|
+
}
|
|
647
|
+
const client = browser ? browserVersion ? `${browser} ${browserVersion}` : browser : void 0;
|
|
648
|
+
const platform = os ? osVersion ? `${os} ${osVersion}` : os : void 0;
|
|
649
|
+
let summary;
|
|
650
|
+
if (client && platform) summary = `${client} on ${platform}`;
|
|
651
|
+
else if (client) summary = client;
|
|
652
|
+
else if (platform) summary = platform;
|
|
653
|
+
else summary = ua;
|
|
654
|
+
return {
|
|
655
|
+
browser,
|
|
656
|
+
browserVersion,
|
|
657
|
+
os,
|
|
658
|
+
osVersion,
|
|
659
|
+
device,
|
|
660
|
+
summary,
|
|
661
|
+
raw: ua
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
//#endregion
|
|
441
665
|
//#region src/components/features/logs/components/detail.tsx
|
|
666
|
+
const DEVICE_ICONS = {
|
|
667
|
+
desktop: Monitor,
|
|
668
|
+
mobile: Smartphone,
|
|
669
|
+
tablet: Tablet,
|
|
670
|
+
bot: Bot,
|
|
671
|
+
unknown: HelpCircle
|
|
672
|
+
};
|
|
442
673
|
function MetaCell({ label, children, className }) {
|
|
443
674
|
return /* @__PURE__ */ jsxs("div", {
|
|
444
675
|
className: cn("bg-background min-w-0 px-3 py-2.5", className),
|
|
@@ -474,6 +705,8 @@ const DetailBody = memo(({ showClose = true }) => {
|
|
|
474
705
|
const request = splitPathQuery(parsed.kind === "http" ? parsed.path : "");
|
|
475
706
|
const title = parsed.kind === "http" ? `${parsed.method} ${request.pathname}` : selectedEntry.labels.service_name ?? "Log";
|
|
476
707
|
const service = selectedEntry.labels.service_name ?? logRequestHost(selectedEntry.labels);
|
|
708
|
+
const userAgent = logUserAgent(selectedEntry.labels);
|
|
709
|
+
const client = userAgent ? parseUserAgent(userAgent) : null;
|
|
477
710
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("header", {
|
|
478
711
|
className: "border-b px-4 py-3",
|
|
479
712
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -593,6 +826,53 @@ const DetailBody = memo(({ showClose = true }) => {
|
|
|
593
826
|
className: "col-span-3",
|
|
594
827
|
children: request.pathname
|
|
595
828
|
}),
|
|
829
|
+
client && /* @__PURE__ */ jsxs("div", {
|
|
830
|
+
className: "bg-background col-span-3 min-w-0 px-3 py-2.5",
|
|
831
|
+
"data-slot": "logs-client",
|
|
832
|
+
children: [
|
|
833
|
+
/* @__PURE__ */ jsxs("div", {
|
|
834
|
+
className: "mb-1 flex items-center justify-between",
|
|
835
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
836
|
+
className: "text-muted-foreground text-[11px] font-medium",
|
|
837
|
+
children: "Client"
|
|
838
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
839
|
+
type: "secondary",
|
|
840
|
+
theme: "borderless",
|
|
841
|
+
size: "icon",
|
|
842
|
+
className: "-my-1 size-6",
|
|
843
|
+
"aria-label": "Copy user agent",
|
|
844
|
+
onClick: () => copy(client.raw, { withToast: true }),
|
|
845
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
846
|
+
icon: Copy,
|
|
847
|
+
size: 14
|
|
848
|
+
})
|
|
849
|
+
})]
|
|
850
|
+
}),
|
|
851
|
+
/* @__PURE__ */ jsxs("div", {
|
|
852
|
+
className: "flex items-center gap-2 text-xs",
|
|
853
|
+
children: [
|
|
854
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
855
|
+
icon: DEVICE_ICONS[client.device],
|
|
856
|
+
className: "text-muted-foreground shrink-0"
|
|
857
|
+
}),
|
|
858
|
+
/* @__PURE__ */ jsx("span", {
|
|
859
|
+
className: "min-w-0 truncate font-medium",
|
|
860
|
+
"data-slot": "logs-client-summary",
|
|
861
|
+
children: client.summary
|
|
862
|
+
}),
|
|
863
|
+
/* @__PURE__ */ jsx("span", {
|
|
864
|
+
className: "text-muted-foreground shrink-0 text-[11px] capitalize",
|
|
865
|
+
children: client.device === "unknown" ? "" : client.device
|
|
866
|
+
})
|
|
867
|
+
]
|
|
868
|
+
}),
|
|
869
|
+
client.summary !== client.raw && /* @__PURE__ */ jsx("p", {
|
|
870
|
+
className: "text-muted-foreground mt-1 font-mono text-[11px] leading-4 break-all",
|
|
871
|
+
title: client.raw,
|
|
872
|
+
children: client.raw
|
|
873
|
+
})
|
|
874
|
+
]
|
|
875
|
+
}),
|
|
596
876
|
request.params.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
597
877
|
className: "bg-background col-span-3 min-w-0 px-3 py-2.5",
|
|
598
878
|
"data-slot": "logs-search-params",
|
|
@@ -984,36 +1264,32 @@ function LogsFilters({ className }) {
|
|
|
984
1264
|
//#endregion
|
|
985
1265
|
//#region src/components/features/logs/components/table.tsx
|
|
986
1266
|
const SKELETON_ROWS = 12;
|
|
987
|
-
/** Centred message
|
|
988
|
-
function LogsTableState({ icon, title, description, action,
|
|
989
|
-
return /* @__PURE__ */ jsx(
|
|
990
|
-
className: "
|
|
991
|
-
children: /* @__PURE__ */
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
"
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
children: action
|
|
1014
|
-
})
|
|
1015
|
-
]
|
|
1016
|
-
})
|
|
1267
|
+
/** Centred message shared by the empty and error states. */
|
|
1268
|
+
function LogsTableState({ icon, title, description, action, slot, role, tone = "muted" }) {
|
|
1269
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1270
|
+
className: "flex min-h-48 flex-1 items-center justify-center px-4 py-8 text-center",
|
|
1271
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1272
|
+
role,
|
|
1273
|
+
"data-slot": slot,
|
|
1274
|
+
className: "flex max-w-md flex-col items-center gap-2",
|
|
1275
|
+
children: [
|
|
1276
|
+
/* @__PURE__ */ jsx("span", {
|
|
1277
|
+
className: cn("flex size-9 items-center justify-center rounded-full border", tone === "destructive" ? "border-destructive/20 bg-destructive/10 text-destructive" : "border-border bg-muted text-muted-foreground"),
|
|
1278
|
+
children: icon
|
|
1279
|
+
}),
|
|
1280
|
+
/* @__PURE__ */ jsx("p", {
|
|
1281
|
+
className: "text-foreground text-sm font-medium",
|
|
1282
|
+
children: title
|
|
1283
|
+
}),
|
|
1284
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
1285
|
+
className: "text-muted-foreground text-xs",
|
|
1286
|
+
children: description
|
|
1287
|
+
}),
|
|
1288
|
+
action && /* @__PURE__ */ jsx("div", {
|
|
1289
|
+
className: "mt-1.5",
|
|
1290
|
+
children: action
|
|
1291
|
+
})
|
|
1292
|
+
]
|
|
1017
1293
|
})
|
|
1018
1294
|
});
|
|
1019
1295
|
}
|
|
@@ -1068,92 +1344,62 @@ function LogsTable({ className }) {
|
|
|
1068
1344
|
return /* @__PURE__ */ jsxs("div", {
|
|
1069
1345
|
ref: scrollerRef,
|
|
1070
1346
|
"data-slot": "logs-table",
|
|
1071
|
-
className: cn("min-h-0 flex-1 overflow-auto outline-none", className),
|
|
1347
|
+
className: cn("flex min-h-0 flex-1 flex-col overflow-auto outline-none", className),
|
|
1072
1348
|
tabIndex: 0,
|
|
1073
1349
|
"aria-busy": isLoading || void 0,
|
|
1074
1350
|
onKeyDown,
|
|
1075
|
-
children: [
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
className: "
|
|
1113
|
-
children: /* @__PURE__ */ jsx(
|
|
1114
|
-
className:
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1351
|
+
children: [
|
|
1352
|
+
showErrorBanner && /* @__PURE__ */ jsxs("div", {
|
|
1353
|
+
role: "alert",
|
|
1354
|
+
"data-slot": "logs-error-banner",
|
|
1355
|
+
className: "border-destructive/30 bg-destructive/5 text-foreground sticky top-0 z-20 flex items-center gap-2 border-b px-3 py-1.5 text-xs",
|
|
1356
|
+
children: [
|
|
1357
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
1358
|
+
icon: TriangleAlert,
|
|
1359
|
+
size: 14,
|
|
1360
|
+
className: "text-destructive shrink-0"
|
|
1361
|
+
}),
|
|
1362
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1363
|
+
className: "min-w-0 truncate",
|
|
1364
|
+
children: [
|
|
1365
|
+
/* @__PURE__ */ jsx("span", {
|
|
1366
|
+
className: "font-medium",
|
|
1367
|
+
children: "Refresh failed."
|
|
1368
|
+
}),
|
|
1369
|
+
" ",
|
|
1370
|
+
/* @__PURE__ */ jsx("span", {
|
|
1371
|
+
className: "text-muted-foreground",
|
|
1372
|
+
children: "Showing the last successful result."
|
|
1373
|
+
}),
|
|
1374
|
+
" ",
|
|
1375
|
+
/* @__PURE__ */ jsx("code", {
|
|
1376
|
+
className: "font-mono",
|
|
1377
|
+
children: error
|
|
1378
|
+
})
|
|
1379
|
+
]
|
|
1380
|
+
}),
|
|
1381
|
+
/* @__PURE__ */ jsx("span", {
|
|
1382
|
+
className: "ml-auto shrink-0",
|
|
1383
|
+
children: retry
|
|
1384
|
+
})
|
|
1385
|
+
]
|
|
1386
|
+
}),
|
|
1387
|
+
/* @__PURE__ */ jsxs("table", {
|
|
1388
|
+
className: "w-full shrink-0 caption-bottom table-auto text-sm [&_th]:px-3 [&_th]:py-2 [&_td]:px-3 [&_td]:py-1.5",
|
|
1389
|
+
children: [/* @__PURE__ */ jsx(TableHeader, {
|
|
1390
|
+
className: "bg-background sticky top-0 z-10",
|
|
1391
|
+
children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((column) => /* @__PURE__ */ jsx(TableHead, {
|
|
1392
|
+
className: cn(columnHeadClass(column), column.headerClassName),
|
|
1393
|
+
style: columnWidthStyle(column),
|
|
1394
|
+
children: columnHeader(column)
|
|
1395
|
+
}, column.id)) })
|
|
1396
|
+
}), /* @__PURE__ */ jsxs(TableBody, { children: [showSkeleton && Array.from({ length: SKELETON_ROWS }, (_, index) => /* @__PURE__ */ jsx(TableRow, {
|
|
1120
1397
|
"data-slot": "logs-skeleton-row",
|
|
1121
1398
|
children: columns.map((column) => /* @__PURE__ */ jsx(LogCell, {
|
|
1122
1399
|
column,
|
|
1123
1400
|
children: columnSkeleton(column, index)
|
|
1124
1401
|
}, column.id))
|
|
1125
|
-
}, `skeleton-${index}`)),
|
|
1126
|
-
showError && /* @__PURE__ */ jsx(LogsTableState, {
|
|
1127
|
-
role: "alert",
|
|
1128
|
-
slot: "logs-error",
|
|
1129
|
-
tone: "destructive",
|
|
1130
|
-
colSpan: columns.length,
|
|
1131
|
-
icon: /* @__PURE__ */ jsx(Icon, { icon: TriangleAlert }),
|
|
1132
|
-
title: "Couldn't load logs",
|
|
1133
|
-
description: /* @__PURE__ */ jsx("code", {
|
|
1134
|
-
className: "bg-muted text-foreground inline-block max-w-full rounded-md border px-2 py-1 text-left font-mono text-[11px] leading-4 break-all",
|
|
1135
|
-
children: error
|
|
1136
|
-
}),
|
|
1137
|
-
action: retry
|
|
1138
|
-
}),
|
|
1139
|
-
showEmpty && /* @__PURE__ */ jsx(LogsTableState, {
|
|
1140
|
-
slot: "logs-empty",
|
|
1141
|
-
colSpan: columns.length,
|
|
1142
|
-
icon: /* @__PURE__ */ jsx(Icon, { icon: Inbox }),
|
|
1143
|
-
title: "No logs in this time range",
|
|
1144
|
-
description: hasActiveFilters || search ? "Try widening the range or clearing the filters." : "Try widening the range or check back in a moment.",
|
|
1145
|
-
action: (hasActiveFilters || search) && /* @__PURE__ */ jsx(Button, {
|
|
1146
|
-
type: "secondary",
|
|
1147
|
-
theme: "outline",
|
|
1148
|
-
size: "small",
|
|
1149
|
-
onClick: () => {
|
|
1150
|
-
resetFilters();
|
|
1151
|
-
setSearch("");
|
|
1152
|
-
},
|
|
1153
|
-
children: "Clear filters"
|
|
1154
|
-
})
|
|
1155
|
-
}),
|
|
1156
|
-
rows.map(({ entry, ctx }) => {
|
|
1402
|
+
}, `skeleton-${index}`)), rows.map(({ entry, ctx }) => {
|
|
1157
1403
|
const selected = entry.id === selectedId;
|
|
1158
1404
|
return /* @__PURE__ */ jsx(TableRow, {
|
|
1159
1405
|
"data-state": selected ? "selected" : void 0,
|
|
@@ -1168,9 +1414,37 @@ function LogsTable({ className }) {
|
|
|
1168
1414
|
children: column.cell(ctx)
|
|
1169
1415
|
}, column.id))
|
|
1170
1416
|
}, entry.id);
|
|
1417
|
+
})] })]
|
|
1418
|
+
}),
|
|
1419
|
+
showError && /* @__PURE__ */ jsx(LogsTableState, {
|
|
1420
|
+
role: "alert",
|
|
1421
|
+
slot: "logs-error",
|
|
1422
|
+
tone: "destructive",
|
|
1423
|
+
icon: /* @__PURE__ */ jsx(Icon, { icon: TriangleAlert }),
|
|
1424
|
+
title: "Couldn't load logs",
|
|
1425
|
+
description: /* @__PURE__ */ jsx("code", {
|
|
1426
|
+
className: "bg-muted text-foreground inline-block max-w-full rounded-md border px-2 py-1 text-left font-mono text-[11px] leading-4 break-all",
|
|
1427
|
+
children: error
|
|
1428
|
+
}),
|
|
1429
|
+
action: retry
|
|
1430
|
+
}),
|
|
1431
|
+
showEmpty && /* @__PURE__ */ jsx(LogsTableState, {
|
|
1432
|
+
slot: "logs-empty",
|
|
1433
|
+
icon: /* @__PURE__ */ jsx(Icon, { icon: Inbox }),
|
|
1434
|
+
title: "No logs in this time range",
|
|
1435
|
+
description: hasActiveFilters || search ? "Try widening the range or clearing the filters." : "Try widening the range or check back in a moment.",
|
|
1436
|
+
action: (hasActiveFilters || search) && /* @__PURE__ */ jsx(Button, {
|
|
1437
|
+
type: "secondary",
|
|
1438
|
+
theme: "outline",
|
|
1439
|
+
size: "small",
|
|
1440
|
+
onClick: () => {
|
|
1441
|
+
resetFilters();
|
|
1442
|
+
setSearch("");
|
|
1443
|
+
},
|
|
1444
|
+
children: "Clear filters"
|
|
1171
1445
|
})
|
|
1172
|
-
|
|
1173
|
-
|
|
1446
|
+
})
|
|
1447
|
+
]
|
|
1174
1448
|
});
|
|
1175
1449
|
}
|
|
1176
1450
|
const MINUTE_MS = 6e4;
|
|
@@ -1772,4 +2046,4 @@ function flattenLokiStreams(response) {
|
|
|
1772
2046
|
return entries;
|
|
1773
2047
|
}
|
|
1774
2048
|
//#endregion
|
|
1775
|
-
export { LOG_TIME_PRESETS, Logs, buildLogQL, facetsFromEntries, filterEntries, flattenLokiStreams, formatHttpLogLine, histogramFromEntries, httpStatusBadgeType, httpStatusTextClass, lastThirtyMinutes, logLineDisplay, logRequestHost, logTimeRangeLabel, nsToDate, parseLogLine, resolveLogColumns, resolveLogTimeRange, searchableLogText, severityBadgeType, splitPathQuery, useLogs };
|
|
2049
|
+
export { LOG_TIME_PRESETS, Logs, buildLogQL, facetsFromEntries, filterEntries, flattenLokiStreams, formatHttpLogLine, histogramFromEntries, httpStatusBadgeType, httpStatusTextClass, lastThirtyMinutes, logLineDisplay, logRequestHost, logTimeRangeLabel, logUserAgent, nsToDate, parseLogLine, parseUserAgent, resolveLogColumns, resolveLogTimeRange, searchableLogText, severityBadgeType, splitPathQuery, useLogs };
|