@oneuptime/common 10.0.31 → 10.0.34
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/Models/AnalyticsModels/ExceptionInstance.ts +29 -4
- package/Models/AnalyticsModels/Log.ts +110 -4
- package/Models/AnalyticsModels/Metric.ts +16 -9
- package/Models/AnalyticsModels/MonitorLog.ts +4 -2
- package/Models/AnalyticsModels/Span.ts +79 -6
- package/Models/DatabaseModels/Index.ts +8 -0
- package/Models/DatabaseModels/LogDropFilter.ts +480 -0
- package/Models/DatabaseModels/LogPipeline.ts +412 -0
- package/Models/DatabaseModels/LogPipelineProcessor.ts +430 -0
- package/Models/DatabaseModels/LogScrubRule.ts +516 -0
- package/Server/API/TelemetryAPI.ts +261 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.ts +57 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/WhatsAppAuthorization.ts +87 -0
- package/Server/Services/AnalyticsDatabaseService.ts +61 -0
- package/Server/Services/LogAggregationService.ts +238 -1
- package/Server/Services/LogDropFilterService.ts +10 -0
- package/Server/Services/LogPipelineProcessorService.ts +10 -0
- package/Server/Services/LogPipelineService.ts +10 -0
- package/Server/Services/LogScrubRuleService.ts +10 -0
- package/Server/Services/TelemetryAttributeService.ts +4 -6
- package/Server/Utils/AnalyticsDatabase/Statement.ts +15 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +138 -11
- package/Tests/Server/Services/LogAggregationService.test.ts +3 -2
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +9 -0
- package/Types/AnalyticsDatabase/TableColumnType.ts +4 -0
- package/Types/Date.ts +22 -0
- package/Types/Log/LogDropFilterAction.ts +6 -0
- package/Types/Log/LogPipelineProcessorType.ts +44 -0
- package/Types/Log/LogScrubAction.ts +7 -0
- package/Types/Log/LogScrubPatternType.ts +10 -0
- package/Types/Permission.ts +174 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +152 -4
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +92 -0
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +332 -117
- package/UI/Components/LogsViewer/components/LogSearchBar.tsx +294 -274
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +513 -234
- package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +37 -29
- package/UI/Components/LogsViewer/components/LogsTable.tsx +6 -1
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +106 -0
- package/UI/Utils/LogExport.ts +160 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +28 -4
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +97 -4
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +16 -9
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +4 -2
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +73 -6
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +8 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +508 -0
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LogPipeline.js +438 -0
- package/build/dist/Models/DatabaseModels/LogPipeline.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LogPipelineProcessor.js +452 -0
- package/build/dist/Models/DatabaseModels/LogPipelineProcessor.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LogScrubRule.js +545 -0
- package/build/dist/Models/DatabaseModels/LogScrubRule.js.map +1 -0
- package/build/dist/Server/API/TelemetryAPI.js +155 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.js +52 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js +58 -0
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +30 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +188 -1
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogDropFilterService.js +9 -0
- package/build/dist/Server/Services/LogDropFilterService.js.map +1 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js +9 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -0
- package/build/dist/Server/Services/LogPipelineService.js +9 -0
- package/build/dist/Server/Services/LogPipelineService.js.map +1 -0
- package/build/dist/Server/Services/LogScrubRuleService.js +9 -0
- package/build/dist/Server/Services/LogScrubRuleService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js +4 -6
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +13 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +98 -2
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js +3 -2
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +10 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/TableColumnType.js +4 -0
- package/build/dist/Types/AnalyticsDatabase/TableColumnType.js.map +1 -1
- package/build/dist/Types/Date.js +16 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Log/LogDropFilterAction.js +7 -0
- package/build/dist/Types/Log/LogDropFilterAction.js.map +1 -0
- package/build/dist/Types/Log/LogPipelineProcessorType.js +9 -0
- package/build/dist/Types/Log/LogPipelineProcessorType.js.map +1 -0
- package/build/dist/Types/Log/LogScrubAction.js +8 -0
- package/build/dist/Types/Log/LogScrubAction.js.map +1 -0
- package/build/dist/Types/Log/LogScrubPatternType.js +11 -0
- package/build/dist/Types/Log/LogScrubPatternType.js.map +1 -0
- package/build/dist/Types/Permission.js +152 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +124 -11
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +36 -0
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +114 -4
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +17 -5
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +229 -122
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +5 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +4 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +28 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Utils/LogExport.js +129 -0
- package/build/dist/UI/Utils/LogExport.js.map +1 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import React, {
|
|
|
4
4
|
useCallback,
|
|
5
5
|
useEffect,
|
|
6
6
|
useMemo,
|
|
7
|
+
useRef,
|
|
7
8
|
useState,
|
|
8
9
|
} from "react";
|
|
9
10
|
import Query from "../../../Types/BaseDatabase/Query";
|
|
@@ -51,9 +52,12 @@ import {
|
|
|
51
52
|
normalizeLogsTableColumns,
|
|
52
53
|
} from "./types";
|
|
53
54
|
import LogsAnalyticsView from "./components/LogsAnalyticsView";
|
|
55
|
+
import { LogSearchBarRef } from "./components/LogSearchBar";
|
|
54
56
|
import { queryStringToFilter } from "../../../Types/Log/LogQueryToFilter";
|
|
55
57
|
import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
|
|
56
58
|
import TimeRange from "../../../Types/Time/TimeRange";
|
|
59
|
+
import { exportLogs, LogExportFormat } from "../../Utils/LogExport";
|
|
60
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
57
61
|
|
|
58
62
|
export interface ComponentProps {
|
|
59
63
|
logs: Array<Log>;
|
|
@@ -64,6 +68,7 @@ export interface ComponentProps {
|
|
|
64
68
|
noLogsMessage?: string | undefined;
|
|
65
69
|
getTraceRoute?: (traceId: string, log: Log) => Route | URL | undefined;
|
|
66
70
|
getSpanRoute?: (spanId: string, log: Log) => Route | URL | undefined;
|
|
71
|
+
projectId?: ObjectID | undefined;
|
|
67
72
|
totalCount?: number | undefined;
|
|
68
73
|
page?: number | undefined;
|
|
69
74
|
pageSize?: number | undefined;
|
|
@@ -195,6 +200,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
195
200
|
const [serviceMap, setServiceMap] = useState<Dictionary<Service>>({});
|
|
196
201
|
|
|
197
202
|
const [selectedLogId, setSelectedLogId] = useState<string | null>(null);
|
|
203
|
+
const [focusedRowIndex, setFocusedRowIndex] = useState<number>(-1);
|
|
204
|
+
const searchBarRef: React.RefObject<LogSearchBarRef> =
|
|
205
|
+
useRef<LogSearchBarRef>(null!);
|
|
198
206
|
|
|
199
207
|
const [internalPage, setInternalPage] = useState<number>(1);
|
|
200
208
|
const [internalPageSize, setInternalPageSize] =
|
|
@@ -211,6 +219,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
211
219
|
const [internalViewMode, setInternalViewMode] =
|
|
212
220
|
useState<LogsViewMode>("list");
|
|
213
221
|
|
|
222
|
+
const [showKeyboardShortcuts, setShowKeyboardShortcuts] =
|
|
223
|
+
useState<boolean>(false);
|
|
224
|
+
|
|
214
225
|
useEffect(() => {
|
|
215
226
|
setFilterData(props.filterData);
|
|
216
227
|
}, [props.filterData]);
|
|
@@ -425,7 +436,12 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
425
436
|
}
|
|
426
437
|
}, [attributesLoaded, attributesLoading, loadAttributes]);
|
|
427
438
|
|
|
428
|
-
|
|
439
|
+
// Reset focused row when displayed logs change
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
setFocusedRowIndex(-1);
|
|
442
|
+
}, [displayedLogs]);
|
|
443
|
+
|
|
444
|
+
const resetPage: () => void = useCallback((): void => {
|
|
429
445
|
if (props.onPageChange) {
|
|
430
446
|
props.onPageChange(1);
|
|
431
447
|
}
|
|
@@ -433,9 +449,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
433
449
|
if (props.page === undefined) {
|
|
434
450
|
setInternalPage(1);
|
|
435
451
|
}
|
|
436
|
-
};
|
|
452
|
+
}, [props.onPageChange, props.page]);
|
|
437
453
|
|
|
438
|
-
const handleSearchSubmit: () => void = (): void => {
|
|
454
|
+
const handleSearchSubmit: () => void = useCallback((): void => {
|
|
439
455
|
const queryFilter: Record<string, unknown> = queryStringToFilter(
|
|
440
456
|
searchQuery,
|
|
441
457
|
) as Record<string, unknown>;
|
|
@@ -464,7 +480,122 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
464
480
|
resetPage();
|
|
465
481
|
setSelectedLogId(null);
|
|
466
482
|
props.onFilterChanged(mergedFilter);
|
|
467
|
-
};
|
|
483
|
+
}, [searchQuery, serviceMap, filterData, resetPage, props]);
|
|
484
|
+
|
|
485
|
+
// Scroll focused row into view
|
|
486
|
+
useEffect(() => {
|
|
487
|
+
if (focusedRowIndex < 0) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Use requestAnimationFrame to ensure the DOM has updated with data-focused
|
|
492
|
+
requestAnimationFrame(() => {
|
|
493
|
+
const focusedRow: Element | null = document.querySelector(
|
|
494
|
+
`tr[data-focused="true"]`,
|
|
495
|
+
);
|
|
496
|
+
if (focusedRow) {
|
|
497
|
+
focusedRow.scrollIntoView({ block: "nearest" });
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}, [focusedRowIndex]);
|
|
501
|
+
|
|
502
|
+
// Keyboard shortcuts: j/k navigate, Enter expand/collapse, Escape close, / focus search, Ctrl+Enter apply
|
|
503
|
+
useEffect(() => {
|
|
504
|
+
const handleKeyDown: (e: KeyboardEvent) => void = (
|
|
505
|
+
e: KeyboardEvent,
|
|
506
|
+
): void => {
|
|
507
|
+
const target: EventTarget | null = e.target;
|
|
508
|
+
const isInputFocused: boolean =
|
|
509
|
+
target instanceof HTMLInputElement ||
|
|
510
|
+
target instanceof HTMLTextAreaElement ||
|
|
511
|
+
target instanceof HTMLSelectElement ||
|
|
512
|
+
(target instanceof HTMLElement && target.isContentEditable);
|
|
513
|
+
|
|
514
|
+
// Ctrl+Enter applies filters even when search bar is focused
|
|
515
|
+
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
|
516
|
+
e.preventDefault();
|
|
517
|
+
handleSearchSubmit();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Skip other shortcuts when an input is focused
|
|
522
|
+
if (isInputFocused) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (e.key === "/") {
|
|
527
|
+
e.preventDefault();
|
|
528
|
+
searchBarRef.current?.focus();
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (e.key === "?") {
|
|
533
|
+
e.preventDefault();
|
|
534
|
+
setShowKeyboardShortcuts((prev: boolean) => {
|
|
535
|
+
return !prev;
|
|
536
|
+
});
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (e.key === "Escape") {
|
|
541
|
+
if (showKeyboardShortcuts) {
|
|
542
|
+
setShowKeyboardShortcuts(false);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (selectedLogId) {
|
|
546
|
+
setSelectedLogId(null);
|
|
547
|
+
}
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const logCount: number = displayedLogs.length;
|
|
552
|
+
|
|
553
|
+
if (logCount === 0) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (e.key === "j") {
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
setFocusedRowIndex((prev: number): number => {
|
|
560
|
+
return Math.min(prev + 1, logCount - 1);
|
|
561
|
+
});
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (e.key === "k") {
|
|
566
|
+
e.preventDefault();
|
|
567
|
+
setFocusedRowIndex((prev: number): number => {
|
|
568
|
+
return Math.max(prev - 1, 0);
|
|
569
|
+
});
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (e.key === "Enter") {
|
|
574
|
+
if (focusedRowIndex >= 0 && focusedRowIndex < logCount) {
|
|
575
|
+
const log: Log = displayedLogs[focusedRowIndex]!;
|
|
576
|
+
const rowId: string = resolveLogIdentifier(log, focusedRowIndex);
|
|
577
|
+
setSelectedLogId((currentSelected: string | null) => {
|
|
578
|
+
if (currentSelected === rowId) {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
return rowId;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
589
|
+
return () => {
|
|
590
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
591
|
+
};
|
|
592
|
+
}, [
|
|
593
|
+
displayedLogs,
|
|
594
|
+
focusedRowIndex,
|
|
595
|
+
selectedLogId,
|
|
596
|
+
showKeyboardShortcuts,
|
|
597
|
+
handleSearchSubmit,
|
|
598
|
+
]);
|
|
468
599
|
|
|
469
600
|
const handlePageChange: (page: number) => void = (page: number): void => {
|
|
470
601
|
if (props.onPageChange) {
|
|
@@ -655,6 +786,12 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
655
786
|
},
|
|
656
787
|
}
|
|
657
788
|
: {}),
|
|
789
|
+
onExportCSV: () => {
|
|
790
|
+
exportLogs(displayedLogs, LogExportFormat.CSV, selectedColumns);
|
|
791
|
+
},
|
|
792
|
+
onExportJSON: () => {
|
|
793
|
+
exportLogs(displayedLogs, LogExportFormat.JSON, selectedColumns);
|
|
794
|
+
},
|
|
658
795
|
...(props.liveOptions ? { liveOptions: props.liveOptions } : {}),
|
|
659
796
|
...(props.timeRange && props.onTimeRangeChange
|
|
660
797
|
? {
|
|
@@ -662,6 +799,12 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
662
799
|
onTimeRangeChange: props.onTimeRangeChange,
|
|
663
800
|
}
|
|
664
801
|
: {}),
|
|
802
|
+
showKeyboardShortcuts,
|
|
803
|
+
onToggleKeyboardShortcuts: () => {
|
|
804
|
+
setShowKeyboardShortcuts((prev: boolean) => {
|
|
805
|
+
return !prev;
|
|
806
|
+
});
|
|
807
|
+
},
|
|
665
808
|
};
|
|
666
809
|
|
|
667
810
|
const showSidebar: boolean =
|
|
@@ -672,6 +815,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
672
815
|
{props.showFilters && (
|
|
673
816
|
<div>
|
|
674
817
|
<LogsFilterCard
|
|
818
|
+
ref={searchBarRef}
|
|
675
819
|
logAttributes={logAttributes}
|
|
676
820
|
searchQuery={searchQuery}
|
|
677
821
|
onSearchQueryChange={setSearchQuery}
|
|
@@ -737,6 +881,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
737
881
|
logs={displayedLogs}
|
|
738
882
|
serviceMap={serviceMap}
|
|
739
883
|
isLoading={props.isLoading}
|
|
884
|
+
focusedRowIndex={
|
|
885
|
+
focusedRowIndex >= 0 ? focusedRowIndex : undefined
|
|
886
|
+
}
|
|
740
887
|
emptyMessage={
|
|
741
888
|
props.noLogsMessage ||
|
|
742
889
|
getEmptyMessageWithTimeRange(props.timeRange)
|
|
@@ -766,6 +913,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
766
913
|
getTraceRoute={props.getTraceRoute}
|
|
767
914
|
getSpanRoute={props.getSpanRoute}
|
|
768
915
|
variant="embedded"
|
|
916
|
+
projectId={props.projectId}
|
|
769
917
|
/>
|
|
770
918
|
);
|
|
771
919
|
}}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export interface KeyboardShortcutsHelpProps {
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ShortcutRow {
|
|
8
|
+
keys: Array<string>;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SHORTCUT_ROWS: Array<ShortcutRow> = [
|
|
13
|
+
{ keys: ["j"], description: "Move to next log row" },
|
|
14
|
+
{ keys: ["k"], description: "Move to previous log row" },
|
|
15
|
+
{ keys: ["Enter"], description: "Expand / collapse selected log" },
|
|
16
|
+
{ keys: ["Esc"], description: "Close detail panel" },
|
|
17
|
+
{ keys: ["/"], description: "Focus search bar" },
|
|
18
|
+
{ keys: ["Ctrl", "Enter"], description: "Apply search filters" },
|
|
19
|
+
{ keys: ["?"], description: "Toggle this help" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const KeyboardShortcutsHelp: FunctionComponent<KeyboardShortcutsHelpProps> = (
|
|
23
|
+
props: KeyboardShortcutsHelpProps,
|
|
24
|
+
): ReactElement => {
|
|
25
|
+
return (
|
|
26
|
+
<div className="absolute right-0 top-full z-50 mt-1 w-72 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-lg">
|
|
27
|
+
<div className="flex items-center justify-between border-b border-gray-100 px-3 py-2">
|
|
28
|
+
<span className="text-[11px] font-semibold uppercase tracking-wider text-gray-400">
|
|
29
|
+
Keyboard shortcuts
|
|
30
|
+
</span>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
className="text-gray-400 transition-colors hover:text-gray-600"
|
|
34
|
+
onClick={props.onClose}
|
|
35
|
+
>
|
|
36
|
+
<svg
|
|
37
|
+
className="h-3.5 w-3.5"
|
|
38
|
+
fill="none"
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
strokeWidth={2}
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
>
|
|
43
|
+
<path
|
|
44
|
+
strokeLinecap="round"
|
|
45
|
+
strokeLinejoin="round"
|
|
46
|
+
d="M6 18 18 6M6 6l12 12"
|
|
47
|
+
/>
|
|
48
|
+
</svg>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="py-1">
|
|
53
|
+
{SHORTCUT_ROWS.map((row: ShortcutRow) => {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
key={row.description}
|
|
57
|
+
className="flex items-center justify-between px-3 py-1.5"
|
|
58
|
+
>
|
|
59
|
+
<span className="text-xs text-gray-600">{row.description}</span>
|
|
60
|
+
<div className="flex items-center gap-1">
|
|
61
|
+
{row.keys.map((key: string, index: number) => {
|
|
62
|
+
return (
|
|
63
|
+
<React.Fragment key={key}>
|
|
64
|
+
{index > 0 && (
|
|
65
|
+
<span className="text-[10px] text-gray-400">+</span>
|
|
66
|
+
)}
|
|
67
|
+
<kbd className="inline-flex min-w-[1.5rem] items-center justify-center rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 font-mono text-[11px] font-medium text-gray-600">
|
|
68
|
+
{key}
|
|
69
|
+
</kbd>
|
|
70
|
+
</React.Fragment>
|
|
71
|
+
);
|
|
72
|
+
})}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className="border-t border-gray-100 px-3 py-1.5">
|
|
80
|
+
<span className="text-[10px] text-gray-400">
|
|
81
|
+
Press{" "}
|
|
82
|
+
<kbd className="rounded border border-gray-200 bg-gray-50 px-1 py-0.5 font-mono text-[10px]">
|
|
83
|
+
?
|
|
84
|
+
</kbd>{" "}
|
|
85
|
+
to toggle this panel
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default KeyboardShortcutsHelp;
|