@datum-cloud/datum-ui 3.7.1 → 3.10.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.
- package/dist/app-navigation/index.mjs +1 -1
- package/dist/{app-navigation-Bcx7hr9L.mjs → app-navigation-DyNZwlSG.mjs} +4 -4
- package/dist/assistant/index.mjs +9 -8
- package/dist/autocomplete/index.mjs +1 -1
- package/dist/{autocomplete-fSSmRLYK.mjs → autocomplete-CrCbO2VS.mjs} +12 -6
- package/dist/autosearch/index.mjs +3 -3
- package/dist/badge/index.mjs +182 -1
- package/dist/button/index.mjs +2 -2
- package/dist/{button-Dj6cKrWj.mjs → button-Cn4KagBO.mjs} +1 -1
- package/dist/{button-BU8TEbJ5.mjs → button-IK-Lnu_9.mjs} +1 -1
- package/dist/button-group/index.mjs +1 -1
- package/dist/{button-group-C3GKo-0p.mjs → button-group-DJ6X1SSj.mjs} +2 -2
- package/dist/{calendar-date-picker-BUwgVaI1.mjs → calendar-date-picker-BRKs56gR.mjs} +1 -1
- package/dist/card/index.mjs +1 -1
- package/dist/{card-save-bar-0XDpAJo_.mjs → card-save-bar-Te8jf-ie.mjs} +1 -1
- package/dist/checkbox/index.mjs +1 -1
- package/dist/checkbox-BAo5sbHk.mjs +84 -0
- package/dist/collapsible/index.mjs +1 -6
- package/dist/collapsible-B-KP3JZ-.mjs +104 -0
- package/dist/components/base/checkbox/checkbox.d.ts +11 -3
- package/dist/components/base/collapsible/collapsible.d.ts +28 -0
- package/dist/components/base/collapsible/index.d.ts +1 -1
- package/dist/components/base/radio-group/radio-group.d.ts +11 -5
- package/dist/components/base/spinner/index.d.ts +1 -1
- package/dist/components/base/spinner/spinner.d.ts +11 -0
- package/dist/components/base/switch/switch.d.ts +6 -1
- package/dist/components/base/typography/typography.d.ts +1 -1
- package/dist/components/features/_shared/copy-state-icon.d.ts +12 -0
- package/dist/components/features/_shared/index.d.ts +2 -0
- package/dist/components/features/_shared/motion-chip.d.ts +293 -0
- package/dist/components/features/logs/hooks/use-logs-url-state.d.ts +59 -0
- package/dist/components/features/logs/index.d.ts +4 -1
- package/dist/components/features/logs/utils/facet-counts.d.ts +50 -0
- package/dist/components/features/logs/utils/facets.d.ts +2 -0
- package/dist/components/features/logs/utils/index.d.ts +4 -0
- package/dist/components/features/logs/utils/url-state.d.ts +61 -0
- package/dist/components/icons/spinner.icon.d.ts +17 -3
- package/dist/copy-state-icon-DMvXXsZm.mjs +61 -0
- package/dist/data-table/index.mjs +1 -1
- package/dist/{data-table-s2YrE79S.mjs → data-table-DFUnUydN.mjs} +8 -8
- package/dist/date-picker/index.mjs +3 -3
- package/dist/date-time-picker/index.mjs +1 -1
- package/dist/{date-time-picker-CLdaGiGC.mjs → date-time-picker-BRMm2QSz.mjs} +1 -1
- package/dist/{date-time-range-picker-RZX4CS4b.mjs → date-time-range-picker-CqGGJTLS.mjs} +20 -9
- package/dist/dropzone/index.mjs +1 -1
- package/dist/{dropzone-Cer9E-VH.mjs → dropzone-qKYfSe7o.mjs} +1 -1
- package/dist/empty-content/index.mjs +1 -1
- package/dist/{empty-content-B2FZ9cyt.mjs → empty-content-CTMHak36.mjs} +1 -1
- package/dist/field-styles-D-FJatHt.mjs +31 -0
- package/dist/form/index.mjs +1 -1
- package/dist/form/stepper/index.mjs +1 -1
- package/dist/{form-C4XcxxjK.mjs → form-VdbSqDjL.mjs} +20 -13
- package/dist/grouped-table/index.mjs +1 -1
- package/dist/{grouped-table-32RO2v7U.mjs → grouped-table-B4HzLXq6.mjs} +3 -3
- package/dist/hooks/index.mjs +1 -1
- package/dist/hooks/use-breakpoint.d.ts +7 -2
- package/dist/icons/index.mjs +1 -1
- package/dist/index.mjs +37 -37
- package/dist/{layers-XEND2WKv.mjs → layers-BC1Ny49w.mjs} +11 -3
- package/dist/loader-overlay/index.mjs +1 -1
- package/dist/{loader-overlay-JvB8IQo3.mjs → loader-overlay-DP0pvHuG.mjs} +1 -1
- package/dist/logs/index.mjs +412 -115
- package/dist/map/index.mjs +1 -1
- package/dist/more-actions/index.mjs +1 -1
- package/dist/{more-actions-D9k1ILzT.mjs → more-actions-DXQiV-bN.mjs} +2 -2
- package/dist/motion-chip-BczV7spP.mjs +52 -0
- package/dist/multi-select/index.mjs +1 -1
- package/dist/{multi-select-BUgx6VZF.mjs → multi-select-DwcsJdHt.mjs} +82 -66
- package/dist/picker/index.mjs +2 -2
- package/dist/{picker-C0h30DHf.mjs → picker-BHPtncpv.mjs} +2 -2
- package/dist/radio-group/index.mjs +1 -1
- package/dist/radio-group-Boxi2hm0.mjs +83 -0
- package/dist/responsive-dropdown/index.mjs +1 -1
- package/dist/{responsive-dropdown-DsWgpE_i.mjs → responsive-dropdown-B9cDUeHt.mjs} +1 -1
- package/dist/responsive-popover/index.mjs +1 -1
- package/dist/{responsive-popover-DJYd8lOK.mjs → responsive-popover-B_pUxrZ1.mjs} +1 -1
- package/dist/rich-text-editor/index.mjs +1 -1
- package/dist/{rich-text-editor-ibngjhQV.mjs → rich-text-editor-CPd_6vdZ.mjs} +2 -2
- package/dist/select/index.mjs +1 -1
- package/dist/{select-DemYhwnG.mjs → select-B0Xlz-wD.mjs} +3 -2
- package/dist/{select-jgK6wyXj.mjs → select-CGsHf70u.mjs} +7 -2
- package/dist/spinner/index.mjs +1 -1
- package/dist/spinner-DSlDJdWl.mjs +23 -0
- package/dist/spinner.icon-BiUnMAxw.mjs +104 -0
- package/dist/switch/index.mjs +1 -1
- package/dist/switch-IduVwiPZ.mjs +114 -0
- package/dist/tabs/index.mjs +1 -1
- package/dist/tag-input/index.mjs +1 -1
- package/dist/{tag-input-DAMY703F.mjs → tag-input-a3ktkGrH.mjs} +39 -32
- package/dist/task-queue/index.mjs +1 -1
- package/dist/{task-queue-dropdown-BgQSGDwg.mjs → task-queue-dropdown-BflEFiMi.mjs} +4 -4
- package/dist/time-picker/index.mjs +1 -1
- package/dist/{time-picker-DgHjdcMZ.mjs → time-picker-BFwH-Vd3.mjs} +1 -1
- package/dist/{to-api-format-EZInfj0_.mjs → to-api-format-BiWOTFha.mjs} +4 -4
- package/dist/transfer/index.mjs +1 -1
- package/dist/{transfer-91lwTOhX.mjs → transfer-DGUeAQUL.mjs} +1 -1
- package/dist/{use-breakpoint-zY_JYINK.mjs → use-breakpoint-Ce5tS4cU.mjs} +11 -5
- package/dist/{use-option-picker-CBMU72LU.mjs → use-option-picker-DXY2SNnT.mjs} +2 -2
- package/dist/utils/field-styles.d.ts +21 -0
- package/package.json +1 -4
- package/dist/badge-D7A6GfWn.mjs +0 -182
- package/dist/checkbox-CNljVnKj.mjs +0 -28
- package/dist/radio-group-0RxTmRnF.mjs +0 -43
- package/dist/spinner-DjEpx6Q3.mjs +0 -13
- package/dist/spinner.icon-D0SKuGFG.mjs +0 -47
- package/dist/switch-CWvVN9ea.mjs +0 -33
package/dist/logs/index.mjs
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { t as cn } from "../utils-CTIuhCOs.mjs";
|
|
2
2
|
import { Skeleton } from "../skeleton/index.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { t as useBreakpoint } from "../use-breakpoint-
|
|
3
|
+
import { Badge } from "../badge/index.mjs";
|
|
4
|
+
import { t as useBreakpoint } from "../use-breakpoint-Ce5tS4cU.mjs";
|
|
5
5
|
import { t as useCopyToClipboard } from "../use-copy-to-clipboard-CWKWHLxI.mjs";
|
|
6
|
-
import { t as Button } from "../button-
|
|
6
|
+
import { t as Button } from "../button-Cn4KagBO.mjs";
|
|
7
7
|
import { t as Sheet } from "../sheet-DEpEJ_Rk.mjs";
|
|
8
8
|
import { t as Icon } from "../icon-wrapper-CIk2th6R.mjs";
|
|
9
|
+
import { t as CopyStateIcon } from "../copy-state-icon-DMvXXsZm.mjs";
|
|
9
10
|
import { c as getBrowserTimezone, t as DateTime, u as getTimezoneAbbreviation } from "../date-time-BBRVM9qb.mjs";
|
|
10
|
-
import { t as Checkbox } from "../checkbox-
|
|
11
|
-
import {
|
|
12
|
-
import { t as DateTimeRangePicker } from "../date-time-range-picker-
|
|
11
|
+
import { t as Checkbox } from "../checkbox-BAo5sbHk.mjs";
|
|
12
|
+
import { n as CollapsibleContent, r as CollapsibleTrigger, t as Collapsible } from "../collapsible-B-KP3JZ-.mjs";
|
|
13
|
+
import { t as DateTimeRangePicker } from "../date-time-range-picker-CqGGJTLS.mjs";
|
|
13
14
|
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "../table/index.mjs";
|
|
14
15
|
import { t as Input } from "../input-DsT5qkJn.mjs";
|
|
15
|
-
import { Bot, ChevronDown, ChevronUp, Clock,
|
|
16
|
-
import { createContext, memo, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
16
|
+
import { Bot, ChevronDown, ChevronUp, Clock, Download, HelpCircle, Inbox, Monitor, RefreshCw, Search, SlidersHorizontal, Smartphone, Tablet, TriangleAlert, X } from "lucide-react";
|
|
17
|
+
import { createContext, memo, use, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
17
18
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
18
19
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
19
20
|
import { format, isSameDay } from "date-fns";
|
|
@@ -319,6 +320,322 @@ function useLogs() {
|
|
|
319
320
|
if (!context) throw new Error("Logs components must be used within <Logs.Root>");
|
|
320
321
|
return context;
|
|
321
322
|
}
|
|
323
|
+
// src/components/features/logs/utils/time-range.ts
|
|
324
|
+
const MINUTE_MS$1 = 6e4;
|
|
325
|
+
const HOUR_MS = 60 * MINUTE_MS$1;
|
|
326
|
+
const DAY_MS$1 = 24 * HOUR_MS;
|
|
327
|
+
function relativePreset(key, label, durationMs, shortcut) {
|
|
328
|
+
return {
|
|
329
|
+
key,
|
|
330
|
+
label,
|
|
331
|
+
shortcut,
|
|
332
|
+
getRange: () => {
|
|
333
|
+
const now = /* @__PURE__ */ new Date();
|
|
334
|
+
return {
|
|
335
|
+
from: new Date(now.getTime() - durationMs),
|
|
336
|
+
to: now
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Relative "Last N" windows offered by `Logs.Filters`. Keys are persisted on
|
|
343
|
+
* `LogTimeRange.preset` so hosts can slide the window forward with
|
|
344
|
+
* `resolveLogTimeRange` on refresh or while live tailing.
|
|
345
|
+
*
|
|
346
|
+
* Shortcuts follow the `picker` datetime presets (`1` / `2` / `H` / `W`) that
|
|
347
|
+
* the logs picker is built on. Keys that mean something else in the pickers
|
|
348
|
+
* (`3` is "Last 30 minutes" in `time-range-picker`, `D` is "Today") are not
|
|
349
|
+
* reused, so muscle memory carries over between the two.
|
|
350
|
+
*/
|
|
351
|
+
const LOG_TIME_PRESETS = [
|
|
352
|
+
relativePreset("last-15m", "Last 15 minutes", 15 * MINUTE_MS$1, "1"),
|
|
353
|
+
relativePreset("last-30m", "Last 30 minutes", 30 * MINUTE_MS$1, "2"),
|
|
354
|
+
relativePreset("last-1h", "Last hour", HOUR_MS, "H"),
|
|
355
|
+
relativePreset("last-3h", "Last 3 hours", 3 * HOUR_MS),
|
|
356
|
+
relativePreset("last-6h", "Last 6 hours", 6 * HOUR_MS, "6"),
|
|
357
|
+
relativePreset("last-12h", "Last 12 hours", 12 * HOUR_MS),
|
|
358
|
+
relativePreset("last-24h", "Last 24 hours", DAY_MS$1),
|
|
359
|
+
relativePreset("last-7d", "Last 7 days", 7 * DAY_MS$1, "W")
|
|
360
|
+
];
|
|
361
|
+
function lastThirtyMinutes(now = /* @__PURE__ */ new Date()) {
|
|
362
|
+
return {
|
|
363
|
+
from: (/* @__PURE__ */ new Date(now.getTime() - 30 * MINUTE_MS$1)).toISOString(),
|
|
364
|
+
to: now.toISOString(),
|
|
365
|
+
preset: "last-30m"
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Re-anchor a preset range to `now`. Absolute ranges (no `preset`, or an
|
|
370
|
+
* unknown key) are returned unchanged.
|
|
371
|
+
*/
|
|
372
|
+
function resolveLogTimeRange(range, now = /* @__PURE__ */ new Date()) {
|
|
373
|
+
if (!range.preset) return range;
|
|
374
|
+
const preset = LOG_TIME_PRESETS.find((item) => item.key === range.preset);
|
|
375
|
+
if (!preset) return range;
|
|
376
|
+
const { from, to } = preset.getRange("UTC");
|
|
377
|
+
const shift = now.getTime() - to.getTime();
|
|
378
|
+
return {
|
|
379
|
+
from: new Date(from.getTime() + shift).toISOString(),
|
|
380
|
+
to: now.toISOString(),
|
|
381
|
+
preset: range.preset
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/** Human label for the time-range trigger: preset name, or a compact absolute range. */
|
|
385
|
+
function logTimeRangeLabel(range) {
|
|
386
|
+
const preset = range.preset ? LOG_TIME_PRESETS.find((item) => item.key === range.preset) : void 0;
|
|
387
|
+
if (preset) return preset.label;
|
|
388
|
+
const from = new Date(range.from);
|
|
389
|
+
const to = new Date(range.to);
|
|
390
|
+
if (Number.isNaN(from.getTime()) || Number.isNaN(to.getTime())) return "Select range";
|
|
391
|
+
if (isSameDay(from, to)) return `${format(from, "MMM d, HH:mm")} – ${format(to, "HH:mm")}`;
|
|
392
|
+
return `${format(from, "MMM d, HH:mm")} – ${format(to, "MMM d, HH:mm")}`;
|
|
393
|
+
}
|
|
394
|
+
function filtersAreActive(filters) {
|
|
395
|
+
return Object.values(filters).some((values) => values.length > 0);
|
|
396
|
+
}
|
|
397
|
+
// src/components/features/logs/utils/url-state.ts
|
|
398
|
+
/**
|
|
399
|
+
* Query-string keys, before `prefix`. A preset range is written as
|
|
400
|
+
* `range=last-1h`; a hand-picked one as `from` and `to` ISO timestamps. Each
|
|
401
|
+
* selected facet value is its own `filter=<label>:<value>` entry.
|
|
402
|
+
*/
|
|
403
|
+
const LOGS_URL_KEYS = {
|
|
404
|
+
search: "q",
|
|
405
|
+
range: "range",
|
|
406
|
+
from: "from",
|
|
407
|
+
to: "to",
|
|
408
|
+
filter: "filter",
|
|
409
|
+
live: "live",
|
|
410
|
+
selected: "log"
|
|
411
|
+
};
|
|
412
|
+
/** Same rule as `buildLogQL`: anything else can't be a Loki label. */
|
|
413
|
+
const LABEL_NAME$1 = /^[a-z_]\w*$/i;
|
|
414
|
+
function keysFor(prefix = "") {
|
|
415
|
+
return {
|
|
416
|
+
search: `${prefix}${LOGS_URL_KEYS.search}`,
|
|
417
|
+
range: `${prefix}${LOGS_URL_KEYS.range}`,
|
|
418
|
+
from: `${prefix}${LOGS_URL_KEYS.from}`,
|
|
419
|
+
to: `${prefix}${LOGS_URL_KEYS.to}`,
|
|
420
|
+
filter: `${prefix}${LOGS_URL_KEYS.filter}`,
|
|
421
|
+
live: `${prefix}${LOGS_URL_KEYS.live}`,
|
|
422
|
+
selected: `${prefix}${LOGS_URL_KEYS.selected}`
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function toParams(input) {
|
|
426
|
+
return typeof input === "string" ? new URLSearchParams(input) : input;
|
|
427
|
+
}
|
|
428
|
+
function isValidDate(value) {
|
|
429
|
+
return value !== null && value !== "" && !Number.isNaN(new Date(value).getTime());
|
|
430
|
+
}
|
|
431
|
+
function isKnownPreset(key) {
|
|
432
|
+
return LOG_TIME_PRESETS.some((preset) => preset.key === key);
|
|
433
|
+
}
|
|
434
|
+
function sameRange(a, b) {
|
|
435
|
+
if (a.preset || b.preset) return a.preset === b.preset;
|
|
436
|
+
return a.from === b.from && a.to === b.to;
|
|
437
|
+
}
|
|
438
|
+
function parseTimeRange(params, keys, fallback, now) {
|
|
439
|
+
const preset = params.get(keys.range);
|
|
440
|
+
if (preset && isKnownPreset(preset)) return resolveLogTimeRange({
|
|
441
|
+
from: "",
|
|
442
|
+
to: "",
|
|
443
|
+
preset
|
|
444
|
+
}, now);
|
|
445
|
+
const from = params.get(keys.from);
|
|
446
|
+
const to = params.get(keys.to);
|
|
447
|
+
if (isValidDate(from) && isValidDate(to) && new Date(from) < new Date(to)) return {
|
|
448
|
+
from: new Date(from).toISOString(),
|
|
449
|
+
to: new Date(to).toISOString()
|
|
450
|
+
};
|
|
451
|
+
return fallback.preset ? resolveLogTimeRange(fallback, now) : fallback;
|
|
452
|
+
}
|
|
453
|
+
function parseFilters(params, key) {
|
|
454
|
+
const filters = {};
|
|
455
|
+
for (const entry of params.getAll(key)) {
|
|
456
|
+
const split = entry.indexOf(":");
|
|
457
|
+
if (split <= 0) continue;
|
|
458
|
+
const name = entry.slice(0, split);
|
|
459
|
+
const value = entry.slice(split + 1);
|
|
460
|
+
if (!LABEL_NAME$1.test(name) || value === "") continue;
|
|
461
|
+
const values = filters[name] ?? (filters[name] = []);
|
|
462
|
+
if (!values.includes(value)) values.push(value);
|
|
463
|
+
}
|
|
464
|
+
return filters;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Read logs state from a query string. Missing or malformed values fall back
|
|
468
|
+
* to the defaults (`Logs.Root`'s own defaults), so a hand-edited or stale link
|
|
469
|
+
* still opens. A preset range is resolved against `now`.
|
|
470
|
+
*/
|
|
471
|
+
function parseLogsUrlState(input, options = {}) {
|
|
472
|
+
const params = toParams(input);
|
|
473
|
+
const keys = keysFor(options.prefix);
|
|
474
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
475
|
+
const fallback = options.defaultTimeRange ?? lastThirtyMinutes(now);
|
|
476
|
+
const live = params.get(keys.live);
|
|
477
|
+
return {
|
|
478
|
+
timeRange: parseTimeRange(params, keys, fallback, now),
|
|
479
|
+
filters: parseFilters(params, keys.filter),
|
|
480
|
+
search: params.get(keys.search) ?? "",
|
|
481
|
+
live: live === "1" || live === "true",
|
|
482
|
+
selectedId: params.get(keys.selected) || null
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Return a copy of `input` with the given parts of logs state written in.
|
|
487
|
+
* Only the parts present in `state` are touched, and params the app owns are
|
|
488
|
+
* kept. Defaults (no search, no filters, not live, the default range) are
|
|
489
|
+
* left out so a fresh explorer has a clean URL.
|
|
490
|
+
*/
|
|
491
|
+
function writeLogsUrlState(input, state, options = {}) {
|
|
492
|
+
const params = new URLSearchParams(toParams(input));
|
|
493
|
+
const keys = keysFor(options.prefix);
|
|
494
|
+
if (state.timeRange !== void 0) {
|
|
495
|
+
params.delete(keys.range);
|
|
496
|
+
params.delete(keys.from);
|
|
497
|
+
params.delete(keys.to);
|
|
498
|
+
const range = state.timeRange;
|
|
499
|
+
if (!sameRange(range, options.defaultTimeRange ?? {
|
|
500
|
+
from: "",
|
|
501
|
+
to: "",
|
|
502
|
+
preset: "last-30m"
|
|
503
|
+
})) {
|
|
504
|
+
if (range.preset && isKnownPreset(range.preset)) params.set(keys.range, range.preset);
|
|
505
|
+
else if (isValidDate(range.from) && isValidDate(range.to)) {
|
|
506
|
+
params.set(keys.from, range.from);
|
|
507
|
+
params.set(keys.to, range.to);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (state.filters !== void 0) {
|
|
512
|
+
params.delete(keys.filter);
|
|
513
|
+
for (const [name, values] of Object.entries(state.filters)) {
|
|
514
|
+
if (!LABEL_NAME$1.test(name)) continue;
|
|
515
|
+
for (const value of values) if (value !== "") params.append(keys.filter, `${name}:${value}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (state.search !== void 0) {
|
|
519
|
+
if (state.search === "") params.delete(keys.search);
|
|
520
|
+
else params.set(keys.search, state.search);
|
|
521
|
+
}
|
|
522
|
+
if (state.live !== void 0) {
|
|
523
|
+
if (state.live) params.set(keys.live, "1");
|
|
524
|
+
else params.delete(keys.live);
|
|
525
|
+
}
|
|
526
|
+
if (state.selectedId !== void 0) {
|
|
527
|
+
if (state.selectedId) params.set(keys.selected, state.selectedId);
|
|
528
|
+
else params.delete(keys.selected);
|
|
529
|
+
}
|
|
530
|
+
return params;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Just this explorer's params, as a query string in a fixed key order. It
|
|
534
|
+
* parses to the same state as the full query string, and only changes when
|
|
535
|
+
* one of these params does, so it works as a memo key: an unrelated app param
|
|
536
|
+
* changing won't re-resolve a preset range (which would hand the host a new
|
|
537
|
+
* `timeRange` and trigger a refetch).
|
|
538
|
+
*/
|
|
539
|
+
function logsUrlStateKey(input, options = {}) {
|
|
540
|
+
const params = toParams(input);
|
|
541
|
+
const own = new URLSearchParams();
|
|
542
|
+
for (const key of Object.values(keysFor(options.prefix))) for (const value of params.getAll(key)) own.append(key, value);
|
|
543
|
+
return own.toString();
|
|
544
|
+
}
|
|
545
|
+
// src/components/features/logs/hooks/use-logs-url-state.ts
|
|
546
|
+
/** pushState/replaceState don't fire `popstate`, so writes announce themselves. */
|
|
547
|
+
const LOCATION_CHANGE_EVENT = "datum-ui:logs-url-change";
|
|
548
|
+
function subscribeToLocation(onChange) {
|
|
549
|
+
window.addEventListener("popstate", onChange);
|
|
550
|
+
window.addEventListener(LOCATION_CHANGE_EVENT, onChange);
|
|
551
|
+
return () => {
|
|
552
|
+
window.removeEventListener("popstate", onChange);
|
|
553
|
+
window.removeEventListener(LOCATION_CHANGE_EVENT, onChange);
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
const getLocationSearch = () => window.location.search;
|
|
557
|
+
const getServerSearch = () => "";
|
|
558
|
+
const setWindowSearchParams = (update, options) => {
|
|
559
|
+
const next = update(new URLSearchParams(window.location.search)).toString();
|
|
560
|
+
const url = `${window.location.pathname}${next ? `?${next}` : ""}${window.location.hash}`;
|
|
561
|
+
if (options?.replace === false) window.history.pushState(window.history.state, "", url);
|
|
562
|
+
else window.history.replaceState(window.history.state, "", url);
|
|
563
|
+
window.dispatchEvent(new Event(LOCATION_CHANGE_EVENT));
|
|
564
|
+
};
|
|
565
|
+
/**
|
|
566
|
+
* Keep `Logs.Root`'s time range, filters, search, live toggle and open row in
|
|
567
|
+
* the address bar, so a refresh or a shared link reopens the same view.
|
|
568
|
+
*
|
|
569
|
+
* ```tsx
|
|
570
|
+
* const logsState = useLogsUrlState()
|
|
571
|
+
* const query = buildLogQL({ matchers: logsState.filters, lineContains: logsState.search })
|
|
572
|
+
* // fetch with query + resolveLogTimeRange(logsState.timeRange)
|
|
573
|
+
*
|
|
574
|
+
* <Logs.Root {...logsState} entries={entries} onRefresh={refetch}>
|
|
575
|
+
* <Logs.Explorer />
|
|
576
|
+
* </Logs.Root>
|
|
577
|
+
* ```
|
|
578
|
+
*
|
|
579
|
+
* With React Router, hand it the router's search params instead:
|
|
580
|
+
*
|
|
581
|
+
* ```tsx
|
|
582
|
+
* const [searchParams, setSearchParams] = useSearchParams()
|
|
583
|
+
* const logsState = useLogsUrlState({ searchParams, setSearchParams })
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
function useLogsUrlState(options = {}) {
|
|
587
|
+
const { searchParams, setSearchParams, prefix = "", defaultTimeRange, history = "replace" } = options;
|
|
588
|
+
const locationSearch = useSyncExternalStore(subscribeToLocation, getLocationSearch, getServerSearch);
|
|
589
|
+
const write = setSearchParams ?? setWindowSearchParams;
|
|
590
|
+
const replace = history !== "push";
|
|
591
|
+
const hasDefault = defaultTimeRange !== void 0;
|
|
592
|
+
const defaultFrom = defaultTimeRange?.from;
|
|
593
|
+
const defaultTo = defaultTimeRange?.to;
|
|
594
|
+
const defaultPreset = defaultTimeRange?.preset;
|
|
595
|
+
const urlOptions = useMemo(() => ({
|
|
596
|
+
prefix,
|
|
597
|
+
defaultTimeRange: hasDefault ? {
|
|
598
|
+
from: defaultFrom ?? "",
|
|
599
|
+
to: defaultTo ?? "",
|
|
600
|
+
preset: defaultPreset
|
|
601
|
+
} : void 0
|
|
602
|
+
}), [
|
|
603
|
+
prefix,
|
|
604
|
+
hasDefault,
|
|
605
|
+
defaultFrom,
|
|
606
|
+
defaultTo,
|
|
607
|
+
defaultPreset
|
|
608
|
+
]);
|
|
609
|
+
const ownQuery = logsUrlStateKey(searchParams ?? locationSearch, { prefix });
|
|
610
|
+
const state = useMemo(() => parseLogsUrlState(ownQuery, urlOptions), [ownQuery, urlOptions]);
|
|
611
|
+
const update = useCallback((patch) => {
|
|
612
|
+
write((prev) => writeLogsUrlState(prev, patch, urlOptions), { replace });
|
|
613
|
+
}, [
|
|
614
|
+
write,
|
|
615
|
+
urlOptions,
|
|
616
|
+
replace
|
|
617
|
+
]);
|
|
618
|
+
const onTimeRangeChange = useCallback((timeRange) => update({ timeRange }), [update]);
|
|
619
|
+
const onFiltersChange = useCallback((filters) => update({ filters }), [update]);
|
|
620
|
+
const onSearchChange = useCallback((search) => update({ search }), [update]);
|
|
621
|
+
const onLiveChange = useCallback((live) => update({ live }), [update]);
|
|
622
|
+
const onSelectedIdChange = useCallback((selectedId) => update({ selectedId }), [update]);
|
|
623
|
+
return useMemo(() => ({
|
|
624
|
+
...state,
|
|
625
|
+
onTimeRangeChange,
|
|
626
|
+
onFiltersChange,
|
|
627
|
+
onSearchChange,
|
|
628
|
+
onLiveChange,
|
|
629
|
+
onSelectedIdChange
|
|
630
|
+
}), [
|
|
631
|
+
state,
|
|
632
|
+
onTimeRangeChange,
|
|
633
|
+
onFiltersChange,
|
|
634
|
+
onSearchChange,
|
|
635
|
+
onLiveChange,
|
|
636
|
+
onSelectedIdChange
|
|
637
|
+
]);
|
|
638
|
+
}
|
|
322
639
|
function clampLogsDetailWidth(width, maxWidth) {
|
|
323
640
|
return Math.min(Math.max(Math.round(width), 400), maxWidth > 400 ? maxWidth : 400);
|
|
324
641
|
}
|
|
@@ -688,9 +1005,29 @@ function keyedParams(params) {
|
|
|
688
1005
|
};
|
|
689
1006
|
});
|
|
690
1007
|
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Icon button that copies `text` and shows a check until the copied state
|
|
1010
|
+
* resets. Each button owns its hook so only the one clicked flips.
|
|
1011
|
+
*/
|
|
1012
|
+
function LogsCopyButton({ text, label, small = false }) {
|
|
1013
|
+
const [copied, copy] = useCopyToClipboard();
|
|
1014
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
1015
|
+
type: "secondary",
|
|
1016
|
+
theme: "borderless",
|
|
1017
|
+
size: "icon",
|
|
1018
|
+
className: small ? "-my-1 size-6" : void 0,
|
|
1019
|
+
"aria-label": label,
|
|
1020
|
+
onClick: () => copy(text, { withToast: true }),
|
|
1021
|
+
children: /* @__PURE__ */ jsx(CopyStateIcon, {
|
|
1022
|
+
copied,
|
|
1023
|
+
size: small ? 14 : 16,
|
|
1024
|
+
strokeWidth: 1,
|
|
1025
|
+
absoluteStrokeWidth: true
|
|
1026
|
+
})
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
691
1029
|
const DetailBody = memo(({ showClose = true }) => {
|
|
692
1030
|
const { selectedEntry, selectPrevious, selectNext, setSelectedId } = useLogs();
|
|
693
|
-
const [, copy] = useCopyToClipboard();
|
|
694
1031
|
if (!selectedEntry) return null;
|
|
695
1032
|
const parsed = parseLogLine(selectedEntry.line, selectedEntry.labels);
|
|
696
1033
|
const displayLine = parsed.kind === "http" && !selectedEntry.line.trim() ? formatHttpLogLine(parsed) : selectedEntry.line;
|
|
@@ -729,13 +1066,9 @@ const DetailBody = memo(({ showClose = true }) => {
|
|
|
729
1066
|
onClick: selectNext,
|
|
730
1067
|
children: /* @__PURE__ */ jsx(Icon, { icon: ChevronDown })
|
|
731
1068
|
}),
|
|
732
|
-
/* @__PURE__ */ jsx(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
size: "icon",
|
|
736
|
-
"aria-label": "Copy log line",
|
|
737
|
-
onClick: () => copy(displayLine, { withToast: true }),
|
|
738
|
-
children: /* @__PURE__ */ jsx(Icon, { icon: Copy })
|
|
1069
|
+
/* @__PURE__ */ jsx(LogsCopyButton, {
|
|
1070
|
+
text: displayLine,
|
|
1071
|
+
label: "Copy log line"
|
|
739
1072
|
}),
|
|
740
1073
|
showClose && /* @__PURE__ */ jsx(Button, {
|
|
741
1074
|
type: "secondary",
|
|
@@ -827,17 +1160,10 @@ const DetailBody = memo(({ showClose = true }) => {
|
|
|
827
1160
|
children: [/* @__PURE__ */ jsx("span", {
|
|
828
1161
|
className: "text-muted-foreground text-3xs font-medium",
|
|
829
1162
|
children: "Client"
|
|
830
|
-
}), /* @__PURE__ */ jsx(
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
className: "-my-1 size-6",
|
|
835
|
-
"aria-label": "Copy user agent",
|
|
836
|
-
onClick: () => copy(client.raw, { withToast: true }),
|
|
837
|
-
children: /* @__PURE__ */ jsx(Icon, {
|
|
838
|
-
icon: Copy,
|
|
839
|
-
size: 14
|
|
840
|
-
})
|
|
1163
|
+
}), /* @__PURE__ */ jsx(LogsCopyButton, {
|
|
1164
|
+
text: client.raw,
|
|
1165
|
+
label: "Copy user agent",
|
|
1166
|
+
small: true
|
|
841
1167
|
})]
|
|
842
1168
|
}),
|
|
843
1169
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -873,17 +1199,10 @@ const DetailBody = memo(({ showClose = true }) => {
|
|
|
873
1199
|
children: [/* @__PURE__ */ jsx("span", {
|
|
874
1200
|
className: "text-muted-foreground text-3xs font-medium",
|
|
875
1201
|
children: "Search params"
|
|
876
|
-
}), /* @__PURE__ */ jsx(
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
className: "-my-1 size-6",
|
|
881
|
-
"aria-label": "Copy search params",
|
|
882
|
-
onClick: () => copy(`?${request.search}`, { withToast: true }),
|
|
883
|
-
children: /* @__PURE__ */ jsx(Icon, {
|
|
884
|
-
icon: Copy,
|
|
885
|
-
size: 14
|
|
886
|
-
})
|
|
1202
|
+
}), /* @__PURE__ */ jsx(LogsCopyButton, {
|
|
1203
|
+
text: `?${request.search}`,
|
|
1204
|
+
label: "Copy search params",
|
|
1205
|
+
small: true
|
|
887
1206
|
})]
|
|
888
1207
|
}), /* @__PURE__ */ jsx("dl", {
|
|
889
1208
|
className: "flex flex-wrap gap-1.5",
|
|
@@ -1027,84 +1346,15 @@ function LogsDetail({ className }) {
|
|
|
1027
1346
|
}), /* @__PURE__ */ jsx(DetailBody, {})]
|
|
1028
1347
|
}) });
|
|
1029
1348
|
}
|
|
1030
|
-
// src/components/features/logs/utils/time-range.ts
|
|
1031
|
-
const MINUTE_MS$1 = 6e4;
|
|
1032
|
-
const HOUR_MS = 60 * MINUTE_MS$1;
|
|
1033
|
-
const DAY_MS$1 = 24 * HOUR_MS;
|
|
1034
|
-
function relativePreset(key, label, durationMs, shortcut) {
|
|
1035
|
-
return {
|
|
1036
|
-
key,
|
|
1037
|
-
label,
|
|
1038
|
-
shortcut,
|
|
1039
|
-
getRange: () => {
|
|
1040
|
-
const now = /* @__PURE__ */ new Date();
|
|
1041
|
-
return {
|
|
1042
|
-
from: new Date(now.getTime() - durationMs),
|
|
1043
|
-
to: now
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
/**
|
|
1049
|
-
* Relative "Last N" windows offered by `Logs.Filters`. Keys are persisted on
|
|
1050
|
-
* `LogTimeRange.preset` so hosts can slide the window forward with
|
|
1051
|
-
* `resolveLogTimeRange` on refresh or while live tailing.
|
|
1052
|
-
*
|
|
1053
|
-
* Shortcuts follow the `picker` datetime presets (`1` / `2` / `H` / `W`) that
|
|
1054
|
-
* the logs picker is built on. Keys that mean something else in the pickers
|
|
1055
|
-
* (`3` is "Last 30 minutes" in `time-range-picker`, `D` is "Today") are not
|
|
1056
|
-
* reused, so muscle memory carries over between the two.
|
|
1057
|
-
*/
|
|
1058
|
-
const LOG_TIME_PRESETS = [
|
|
1059
|
-
relativePreset("last-15m", "Last 15 minutes", 15 * MINUTE_MS$1, "1"),
|
|
1060
|
-
relativePreset("last-30m", "Last 30 minutes", 30 * MINUTE_MS$1, "2"),
|
|
1061
|
-
relativePreset("last-1h", "Last hour", HOUR_MS, "H"),
|
|
1062
|
-
relativePreset("last-3h", "Last 3 hours", 3 * HOUR_MS),
|
|
1063
|
-
relativePreset("last-6h", "Last 6 hours", 6 * HOUR_MS, "6"),
|
|
1064
|
-
relativePreset("last-12h", "Last 12 hours", 12 * HOUR_MS),
|
|
1065
|
-
relativePreset("last-24h", "Last 24 hours", DAY_MS$1),
|
|
1066
|
-
relativePreset("last-7d", "Last 7 days", 7 * DAY_MS$1, "W")
|
|
1067
|
-
];
|
|
1068
|
-
function lastThirtyMinutes(now = /* @__PURE__ */ new Date()) {
|
|
1069
|
-
return {
|
|
1070
|
-
from: (/* @__PURE__ */ new Date(now.getTime() - 30 * MINUTE_MS$1)).toISOString(),
|
|
1071
|
-
to: now.toISOString(),
|
|
1072
|
-
preset: "last-30m"
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Re-anchor a preset range to `now`. Absolute ranges (no `preset`, or an
|
|
1077
|
-
* unknown key) are returned unchanged.
|
|
1078
|
-
*/
|
|
1079
|
-
function resolveLogTimeRange(range, now = /* @__PURE__ */ new Date()) {
|
|
1080
|
-
if (!range.preset) return range;
|
|
1081
|
-
const preset = LOG_TIME_PRESETS.find((item) => item.key === range.preset);
|
|
1082
|
-
if (!preset) return range;
|
|
1083
|
-
const { from, to } = preset.getRange("UTC");
|
|
1084
|
-
const shift = now.getTime() - to.getTime();
|
|
1085
|
-
return {
|
|
1086
|
-
from: new Date(from.getTime() + shift).toISOString(),
|
|
1087
|
-
to: now.toISOString(),
|
|
1088
|
-
preset: range.preset
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
/** Human label for the time-range trigger: preset name, or a compact absolute range. */
|
|
1092
|
-
function logTimeRangeLabel(range) {
|
|
1093
|
-
const preset = range.preset ? LOG_TIME_PRESETS.find((item) => item.key === range.preset) : void 0;
|
|
1094
|
-
if (preset) return preset.label;
|
|
1095
|
-
const from = new Date(range.from);
|
|
1096
|
-
const to = new Date(range.to);
|
|
1097
|
-
if (Number.isNaN(from.getTime()) || Number.isNaN(to.getTime())) return "Select range";
|
|
1098
|
-
if (isSameDay(from, to)) return `${format(from, "MMM d, HH:mm")} – ${format(to, "HH:mm")}`;
|
|
1099
|
-
return `${format(from, "MMM d, HH:mm")} – ${format(to, "MMM d, HH:mm")}`;
|
|
1100
|
-
}
|
|
1101
|
-
function filtersAreActive(filters) {
|
|
1102
|
-
return Object.values(filters).some((values) => values.length > 0);
|
|
1103
|
-
}
|
|
1104
1349
|
// src/components/features/logs/components/filters.tsx
|
|
1105
1350
|
function LogsFilterGroup({ facet, defaultOpen }) {
|
|
1106
1351
|
const { filters, toggleFilterValue } = useLogs();
|
|
1107
1352
|
const selected = filters[facet.name] ?? [];
|
|
1353
|
+
const known = new Set(facet.options.map((option) => option.value));
|
|
1354
|
+
const options = [...facet.options, ...selected.filter((value) => !known.has(value)).map((value) => ({
|
|
1355
|
+
value,
|
|
1356
|
+
count: void 0
|
|
1357
|
+
}))];
|
|
1108
1358
|
return /* @__PURE__ */ jsxs(Collapsible, {
|
|
1109
1359
|
defaultOpen,
|
|
1110
1360
|
className: "border-b border-border",
|
|
@@ -1116,7 +1366,7 @@ function LogsFilterGroup({ facet, defaultOpen }) {
|
|
|
1116
1366
|
})]
|
|
1117
1367
|
}), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("ul", {
|
|
1118
1368
|
className: "flex flex-col gap-1 px-3 pb-3",
|
|
1119
|
-
children:
|
|
1369
|
+
children: options.map((option) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
|
|
1120
1370
|
className: "hover:bg-muted/40 flex cursor-pointer items-center gap-2 rounded-md px-1 py-1 text-sm",
|
|
1121
1371
|
children: [
|
|
1122
1372
|
/* @__PURE__ */ jsx(Checkbox, {
|
|
@@ -1593,7 +1843,7 @@ function LogsSearch({ placeholder = "Search logs...", className }) {
|
|
|
1593
1843
|
onChange: (event) => setValue(event.target.value),
|
|
1594
1844
|
placeholder,
|
|
1595
1845
|
"aria-label": placeholder,
|
|
1596
|
-
className: "pl-9",
|
|
1846
|
+
className: "h-9 pl-9",
|
|
1597
1847
|
"data-slot": "logs-search"
|
|
1598
1848
|
})]
|
|
1599
1849
|
});
|
|
@@ -1981,6 +2231,53 @@ function buildLogQL({ matchers, lineContains } = {}) {
|
|
|
1981
2231
|
if (!text) return selector;
|
|
1982
2232
|
return `${selector} |= "${escapeQuoted(text)}"`;
|
|
1983
2233
|
}
|
|
2234
|
+
// src/components/features/logs/utils/facet-counts.ts
|
|
2235
|
+
/** Same rule as `buildLogQL`: anything else can't be a Loki label. */
|
|
2236
|
+
const LABEL_NAME = /^[a-z_]\w*$/i;
|
|
2237
|
+
/** Whole seconds between `from` and `to`, at least 1. */
|
|
2238
|
+
function rangeSeconds({ from, to }) {
|
|
2239
|
+
const ms = new Date(to).getTime() - new Date(from).getTime();
|
|
2240
|
+
return Number.isFinite(ms) ? Math.max(1, Math.round(ms / 1e3)) : 1;
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* LogQL that counts log lines per value of one label across a time range:
|
|
2244
|
+
*
|
|
2245
|
+
* ```
|
|
2246
|
+
* sum by (response_code) (count_over_time({route_name="my-alb"}[1800s]))
|
|
2247
|
+
* ```
|
|
2248
|
+
*
|
|
2249
|
+
* Run it as an instant query (`/loki/api/v1/query`) with `time` set to the
|
|
2250
|
+
* range's `to`, then pass the response to `facetFromLokiCounts`. Returns
|
|
2251
|
+
* `null` for a label name LogQL can't express. Works for indexed stream labels
|
|
2252
|
+
* and for structured metadata, which Loki can also group by.
|
|
2253
|
+
*/
|
|
2254
|
+
function buildLogFacetCountQuery({ label, matchers, timeRange }) {
|
|
2255
|
+
if (!LABEL_NAME.test(label)) return null;
|
|
2256
|
+
return `sum by (${label}) (count_over_time(${buildLogQL({ matchers })}[${rangeSeconds(timeRange)}s]))`;
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Turn the response to a `buildLogFacetCountQuery` query into a facet for
|
|
2260
|
+
* `Logs.Root`. Series without the label (lines that don't carry it) and zero
|
|
2261
|
+
* counts are skipped. Options are ordered the same way as `facetsFromEntries`.
|
|
2262
|
+
* Pass `label` to override the display name.
|
|
2263
|
+
*/
|
|
2264
|
+
function facetFromLokiCounts(name, response, options = {}) {
|
|
2265
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2266
|
+
if (response?.status === "success" && response.data?.resultType === "vector") for (const sample of response.data.result) {
|
|
2267
|
+
const value = sample.metric[name];
|
|
2268
|
+
const count = Number(sample.value?.[1]);
|
|
2269
|
+
if (!value || !Number.isFinite(count) || count <= 0) continue;
|
|
2270
|
+
counts.set(value, (counts.get(value) ?? 0) + Math.round(count));
|
|
2271
|
+
}
|
|
2272
|
+
return {
|
|
2273
|
+
name,
|
|
2274
|
+
label: options.label ?? facetLabel(name),
|
|
2275
|
+
options: [...counts.entries()].sort(([a], [b]) => compareFacetValues(name, a, b)).map(([value, count]) => ({
|
|
2276
|
+
value,
|
|
2277
|
+
count
|
|
2278
|
+
}))
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
1984
2281
|
// src/components/features/logs/utils/flatten-loki.ts
|
|
1985
2282
|
function nsToDate(timestampNs) {
|
|
1986
2283
|
try {
|
|
@@ -2024,4 +2321,4 @@ function flattenLokiStreams(response) {
|
|
|
2024
2321
|
});
|
|
2025
2322
|
return entries;
|
|
2026
2323
|
}
|
|
2027
|
-
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 };
|
|
2324
|
+
export { LOGS_URL_KEYS, LOG_TIME_PRESETS, Logs, buildLogFacetCountQuery, buildLogQL, facetFromLokiCounts, facetsFromEntries, filterEntries, flattenLokiStreams, formatHttpLogLine, histogramFromEntries, httpStatusBadgeType, httpStatusTextClass, lastThirtyMinutes, logLineDisplay, logRequestHost, logTimeRangeLabel, logUserAgent, nsToDate, parseLogLine, parseLogsUrlState, parseUserAgent, resolveLogColumns, resolveLogTimeRange, searchableLogText, severityBadgeType, splitPathQuery, useLogs, useLogsUrlState, writeLogsUrlState };
|
package/dist/map/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { k as Map, b as MapCircle, x as MapCircleMarker, h as MapControlContainer, o as MapDrawCircle, s as MapDrawControl, c as MapDrawDelete, l as MapDrawEdit, u as MapDrawMarker, d as MapDrawPolygon, f as MapDrawPolyline, p as MapDrawRectangle, m as MapDrawUndo, t as MapFeatureGroup, g as MapFullscreenControl, n as MapLayerGroup, r as MapLayers, i as MapLayersControl, _ as MapLocateControl, S as MapMarker, C as MapMarkerClusterGroup, w as MapPolygon, T as MapPolyline, E as MapPopup, D as MapRectangle, v as MapSearchControl, a as MapTileLayer, O as MapTooltip, y as MapZoomControl, j as PlaceAutocomplete, A as useLeaflet } from "../layers-
|
|
1
|
+
export { k as Map, b as MapCircle, x as MapCircleMarker, h as MapControlContainer, o as MapDrawCircle, s as MapDrawControl, c as MapDrawDelete, l as MapDrawEdit, u as MapDrawMarker, d as MapDrawPolygon, f as MapDrawPolyline, p as MapDrawRectangle, m as MapDrawUndo, t as MapFeatureGroup, g as MapFullscreenControl, n as MapLayerGroup, r as MapLayers, i as MapLayersControl, _ as MapLocateControl, S as MapMarker, C as MapMarkerClusterGroup, w as MapPolygon, T as MapPolyline, E as MapPopup, D as MapRectangle, v as MapSearchControl, a as MapTileLayer, O as MapTooltip, y as MapZoomControl, j as PlaceAutocomplete, A as useLeaflet } from "../layers-BC1Ny49w.mjs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as Button } from "./button-
|
|
1
|
+
import { t as Button } from "./button-Cn4KagBO.mjs";
|
|
2
2
|
import { t as Tooltip } from "./tooltip-OIySjJWW.mjs";
|
|
3
3
|
import { t as cn } from "./utils-CTIuhCOs.mjs";
|
|
4
|
-
import { t as ResponsiveDropdown } from "./responsive-dropdown-
|
|
4
|
+
import { t as ResponsiveDropdown } from "./responsive-dropdown-B9cDUeHt.mjs";
|
|
5
5
|
import { t as ActionRow } from "./action-row-BEzE9x37.mjs";
|
|
6
6
|
import { Ellipsis } from "lucide-react";
|
|
7
7
|
import { useState } from "react";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { n as EASE, t as DURATION } from "./motion-DOrRbLmy.mjs";
|
|
2
|
+
import { Badge } from "./badge/index.mjs";
|
|
3
|
+
import { motion, useReducedMotion } from "motion/react";
|
|
4
|
+
// src/components/features/_shared/motion-chip.tsx
|
|
5
|
+
/** `Badge` as a Motion component, so chips can enter, exit and reflow. */
|
|
6
|
+
const MotionBadge = motion.create(Badge);
|
|
7
|
+
const ENTER = {
|
|
8
|
+
duration: DURATION.fast,
|
|
9
|
+
ease: EASE.out
|
|
10
|
+
};
|
|
11
|
+
const EXIT = {
|
|
12
|
+
duration: .1,
|
|
13
|
+
ease: EASE.out
|
|
14
|
+
};
|
|
15
|
+
/** Neighbours slide into a removed chip's gap a touch slower than it fades. */
|
|
16
|
+
const REFLOW = {
|
|
17
|
+
duration: .2,
|
|
18
|
+
ease: EASE.out
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Motion props for a chip in a wrapping list, for use with `MotionBadge`
|
|
22
|
+
* inside `<AnimatePresence initial={false} mode="popLayout">`.
|
|
23
|
+
*
|
|
24
|
+
* A chip scales up slightly as it fades in, and fades out a little faster
|
|
25
|
+
* when removed. `popLayout` takes the leaving chip out of flow straight away,
|
|
26
|
+
* and `layout="position"` slides the chips after it into the gap rather than
|
|
27
|
+
* snapping. The list needs `position: relative` for `popLayout` to place the
|
|
28
|
+
* leaving chip. Under reduced motion chips only fade and nothing slides.
|
|
29
|
+
*/
|
|
30
|
+
function useChipMotion() {
|
|
31
|
+
const reduceMotion = Boolean(useReducedMotion());
|
|
32
|
+
const scale = reduceMotion ? 1 : .85;
|
|
33
|
+
return {
|
|
34
|
+
initial: {
|
|
35
|
+
opacity: 0,
|
|
36
|
+
scale
|
|
37
|
+
},
|
|
38
|
+
animate: {
|
|
39
|
+
opacity: 1,
|
|
40
|
+
scale: 1,
|
|
41
|
+
transition: ENTER
|
|
42
|
+
},
|
|
43
|
+
exit: {
|
|
44
|
+
opacity: 0,
|
|
45
|
+
scale,
|
|
46
|
+
transition: EXIT
|
|
47
|
+
},
|
|
48
|
+
transition: { layout: REFLOW },
|
|
49
|
+
layout: reduceMotion ? false : "position"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export { useChipMotion as n, MotionBadge as t };
|