mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat(LogsViewer): add keyboard shortcuts functionality and help component
This commit is contained in:
7
Common/Types/Log/LogScrubAction.ts
Normal file
7
Common/Types/Log/LogScrubAction.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
enum LogScrubAction {
|
||||
Mask = "mask",
|
||||
Hash = "hash",
|
||||
Redact = "redact",
|
||||
}
|
||||
|
||||
export default LogScrubAction;
|
||||
10
Common/Types/Log/LogScrubPatternType.ts
Normal file
10
Common/Types/Log/LogScrubPatternType.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
enum LogScrubPatternType {
|
||||
Email = "email",
|
||||
CreditCard = "creditCard",
|
||||
SSN = "ssn",
|
||||
PhoneNumber = "phoneNumber",
|
||||
IPAddress = "ipAddress",
|
||||
Custom = "custom",
|
||||
}
|
||||
|
||||
export default LogScrubPatternType;
|
||||
@@ -219,6 +219,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
const [internalViewMode, setInternalViewMode] =
|
||||
useState<LogsViewMode>("list");
|
||||
|
||||
const [showKeyboardShortcuts, setShowKeyboardShortcuts] =
|
||||
useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setFilterData(props.filterData);
|
||||
}, [props.filterData]);
|
||||
@@ -526,7 +529,19 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "?") {
|
||||
e.preventDefault();
|
||||
setShowKeyboardShortcuts((prev: boolean) => {
|
||||
return !prev;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Escape") {
|
||||
if (showKeyboardShortcuts) {
|
||||
setShowKeyboardShortcuts(false);
|
||||
return;
|
||||
}
|
||||
if (selectedLogId) {
|
||||
setSelectedLogId(null);
|
||||
}
|
||||
@@ -574,7 +589,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [displayedLogs, focusedRowIndex, selectedLogId, handleSearchSubmit]);
|
||||
}, [displayedLogs, focusedRowIndex, selectedLogId, showKeyboardShortcuts, handleSearchSubmit]);
|
||||
|
||||
const handlePageChange: (page: number) => void = (page: number): void => {
|
||||
if (props.onPageChange) {
|
||||
@@ -778,6 +793,12 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
onTimeRangeChange: props.onTimeRangeChange,
|
||||
}
|
||||
: {}),
|
||||
showKeyboardShortcuts,
|
||||
onToggleKeyboardShortcuts: () => {
|
||||
setShowKeyboardShortcuts((prev: boolean) => {
|
||||
return !prev;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const showSidebar: boolean =
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface KeyboardShortcutsHelpProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface ShortcutRow {
|
||||
keys: Array<string>;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const SHORTCUT_ROWS: Array<ShortcutRow> = [
|
||||
{ keys: ["j"], description: "Move to next log row" },
|
||||
{ keys: ["k"], description: "Move to previous log row" },
|
||||
{ keys: ["Enter"], description: "Expand / collapse selected log" },
|
||||
{ keys: ["Esc"], description: "Close detail panel" },
|
||||
{ keys: ["/"], description: "Focus search bar" },
|
||||
{ keys: ["Ctrl", "Enter"], description: "Apply search filters" },
|
||||
{ keys: ["?"], description: "Toggle this help" },
|
||||
];
|
||||
|
||||
const KeyboardShortcutsHelp: FunctionComponent<KeyboardShortcutsHelpProps> = (
|
||||
props: KeyboardShortcutsHelpProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<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">
|
||||
<div className="flex items-center justify-between border-b border-gray-100 px-3 py-2">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-gray-400">
|
||||
Keyboard shortcuts
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 transition-colors hover:text-gray-600"
|
||||
onClick={props.onClose}
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 18 18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
{SHORTCUT_ROWS.map((row: ShortcutRow) => {
|
||||
return (
|
||||
<div
|
||||
key={row.description}
|
||||
className="flex items-center justify-between px-3 py-1.5"
|
||||
>
|
||||
<span className="text-xs text-gray-600">{row.description}</span>
|
||||
<div className="flex items-center gap-1">
|
||||
{row.keys.map((key: string, index: number) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{index > 0 && (
|
||||
<span className="text-[10px] text-gray-400">+</span>
|
||||
)}
|
||||
<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">
|
||||
{key}
|
||||
</kbd>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-100 px-3 py-1.5">
|
||||
<span className="text-[10px] text-gray-400">
|
||||
Press{" "}
|
||||
<kbd className="rounded border border-gray-200 bg-gray-50 px-1 py-0.5 font-mono text-[10px]">
|
||||
?
|
||||
</kbd>{" "}
|
||||
to toggle this panel
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeyboardShortcutsHelp;
|
||||
@@ -3,6 +3,7 @@ import LiveLogsToggle from "./LiveLogsToggle";
|
||||
import LogTimeRangePicker from "./LogTimeRangePicker";
|
||||
import ColumnSelector from "./ColumnSelector";
|
||||
import SavedViewsDropdown from "./SavedViewsDropdown";
|
||||
import KeyboardShortcutsHelp from "./KeyboardShortcutsHelp";
|
||||
import {
|
||||
LiveLogsOptions,
|
||||
LogsSavedViewOption,
|
||||
@@ -34,6 +35,8 @@ export interface LogsViewerToolbarProps {
|
||||
onViewModeChange?: ((mode: LogsViewMode) => void) | undefined;
|
||||
onExportCSV?: (() => void) | undefined;
|
||||
onExportJSON?: (() => void) | undefined;
|
||||
showKeyboardShortcuts?: boolean | undefined;
|
||||
onToggleKeyboardShortcuts?: (() => void) | undefined;
|
||||
}
|
||||
|
||||
const LogsViewerToolbar: FunctionComponent<LogsViewerToolbarProps> = (
|
||||
@@ -153,6 +156,40 @@ const LogsViewerToolbar: FunctionComponent<LogsViewerToolbarProps> = (
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.onToggleKeyboardShortcuts && (
|
||||
<div className="relative">
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium shadow-sm transition-colors ${
|
||||
props.showKeyboardShortcuts
|
||||
? "border-indigo-300 bg-indigo-50 text-indigo-700"
|
||||
: "border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50"
|
||||
}`}
|
||||
onClick={props.onToggleKeyboardShortcuts}
|
||||
title="Keyboard shortcuts (?)"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{props.showKeyboardShortcuts && (
|
||||
<KeyboardShortcutsHelp
|
||||
onClose={props.onToggleKeyboardShortcuts}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showExport && (
|
||||
<div className="relative" ref={exportDropdownRef}>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user