mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Feat/update query doc sidepanel (#8214)
* create new components for query side panel * add reusable icon component that uses svg for icons * integrate with new osquery_fleet_schema.json data * update UI to work with osquery_fleet_schema.json * add remark-gfm to safely support direct urls in markdown * move fleet ace into markdown component so we can render code with ace editor * add testing for new query sidebar * remove incomplete tests for query sidepanel
This commit is contained in:
parent
c16ab5f823
commit
a950e9d095
37 changed files with 1648 additions and 516 deletions
1
changes/issue-7080-new-query-sidebar-docs
Normal file
1
changes/issue-7080-new-query-sidebar-docs
Normal file
|
|
@ -0,0 +1 @@
|
|||
- add new query sidebar with updated and improved docs
|
||||
|
|
@ -21,7 +21,7 @@ describe("Labels flow", () => {
|
|||
it("creates a custom label", () => {
|
||||
cy.getAttached(".label-filter-select__control").click();
|
||||
cy.findByRole("button", { name: /add label/i }).click();
|
||||
cy.getAttached(".ace_content").type(
|
||||
cy.getAttached(".label-form__text-editor-wrapper .ace_content").type(
|
||||
"{selectall}{backspace}SELECT * FROM users;"
|
||||
);
|
||||
cy.findByLabelText(/name/i).click().type("Show all MAC users");
|
||||
|
|
@ -62,7 +62,7 @@ describe("Labels flow", () => {
|
|||
it("creates labels with special characters", () => {
|
||||
cy.getAttached(".label-filter-select__control").click();
|
||||
cy.findByRole("button", { name: /add label/i }).click();
|
||||
cy.getAttached(".ace_content").type(
|
||||
cy.getAttached(".label-form__text-editor-wrapper .ace_content").type(
|
||||
"{selectall}{backspace}SELECT * FROM users;"
|
||||
);
|
||||
cy.findByLabelText(/name/i)
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ describe("Policies flow (empty)", () => {
|
|||
cy.findByText(/add a policy/i).click();
|
||||
});
|
||||
cy.findByText(/create your own policy/i).click();
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".policy-page__form .ace_scroller")
|
||||
.click({ force: true })
|
||||
.type(
|
||||
"{selectall}SELECT 1 FROM users WHERE username = 'backup' LIMIT 1;"
|
||||
|
|
@ -217,7 +217,7 @@ describe("Policies flow (empty)", () => {
|
|||
});
|
||||
|
||||
// Query with unknown table name displays error message
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".policy-page__form .ace_scroller")
|
||||
.first()
|
||||
.click({ force: true })
|
||||
.type("{selectall}SELECT 1 FROM foo WHERE start_time > 1;");
|
||||
|
|
@ -230,7 +230,7 @@ describe("Policies flow (empty)", () => {
|
|||
});
|
||||
|
||||
// Query with syntax error displays error message
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".policy-page__form .ace_scroller")
|
||||
.first()
|
||||
.click({ force: true })
|
||||
.type("{selectall}SELEC 1 FRO osquery_info WHER start_time > 1;");
|
||||
|
|
@ -243,7 +243,7 @@ describe("Policies flow (empty)", () => {
|
|||
});
|
||||
|
||||
// Query with no tables treated as compatible with all platforms
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".policy-page__form .ace_scroller")
|
||||
.first()
|
||||
.click({ force: true })
|
||||
.type("{selectall}SELECT * WHERE 1 = 1;");
|
||||
|
|
@ -254,7 +254,7 @@ describe("Policies flow (empty)", () => {
|
|||
});
|
||||
|
||||
// Tables defined in common table expression not factored into compatibility check
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".policy-page__form .ace_scroller")
|
||||
.first()
|
||||
.click({ force: true })
|
||||
.type("{selectall} ")
|
||||
|
|
@ -270,7 +270,7 @@ describe("Policies flow (empty)", () => {
|
|||
|
||||
// Query with only macOS tables treated as compatible only with macOS
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(" .policy-page__form .ace_scroller")
|
||||
.first()
|
||||
.click({ force: true })
|
||||
.type("{selectall} ")
|
||||
|
|
@ -285,7 +285,7 @@ describe("Policies flow (empty)", () => {
|
|||
});
|
||||
|
||||
// Query with macadmins extension table is not treated as incompatible
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".policy-page__form .ace_scroller")
|
||||
.first()
|
||||
.click({ force: true })
|
||||
.type("{selectall}SELECT 1 FROM mdm WHERE enrolled='true';");
|
||||
|
|
@ -477,7 +477,7 @@ describe("Policies flow (seeded)", () => {
|
|||
cy.getAttached("tbody").within(() => {
|
||||
cy.getAttached(".name__cell .button--text-link").first().click();
|
||||
});
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".policy-page__form .ace_scroller")
|
||||
.click({ force: true })
|
||||
.type(
|
||||
"{selectall}SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const manageQueriesPage = {
|
|||
allowsCreateNewQuery: () => {
|
||||
cy.getAttached(".button--brand"); // ensures cta button loads
|
||||
cy.findByRole("button", { name: /new query/i }).click();
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".query-page__form .ace_scroller")
|
||||
.click({ force: true })
|
||||
.type("{selectall}SELECT * FROM windows_crashes;");
|
||||
cy.findByRole("button", { name: /save/i }).click();
|
||||
|
|
@ -52,7 +52,7 @@ const manageQueriesPage = {
|
|||
cy.getAttached(".name__cell .button--text-link")
|
||||
.first()
|
||||
.click({ force: true });
|
||||
cy.getAttached(".ace_text-input")
|
||||
cy.getAttached(".query-page__form .ace_text-input")
|
||||
.click({ force: true })
|
||||
.clear({ force: true })
|
||||
.type("SELECT 1 FROM cypress;", {
|
||||
|
|
@ -72,7 +72,7 @@ const manageQueriesPage = {
|
|||
cy.findByText(/get authorized/i).click();
|
||||
});
|
||||
cy.findByRole("button", { name: /run query/i }).should("exist");
|
||||
cy.getAttached(".ace_scroller")
|
||||
cy.getAttached(".query-page__form .ace_scroller")
|
||||
.click()
|
||||
.type("{selectall}SELECT datetime, username FROM windows_crashes;");
|
||||
cy.findByRole("button", { name: /save as new/i }).should("be.enabled");
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export interface IFleetAceProps {
|
|||
wrapperClassName?: string;
|
||||
hint?: string;
|
||||
labelActionComponent?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
onBlur?: (editor?: IAceEditor) => void;
|
||||
onLoad?: (editor: IAceEditor) => void;
|
||||
onChange?: (value: string) => void;
|
||||
handleSubmit?: () => void;
|
||||
|
|
@ -42,6 +44,8 @@ const FleetAce = ({
|
|||
wrapEnabled = false,
|
||||
wrapperClassName,
|
||||
hint,
|
||||
style,
|
||||
onBlur,
|
||||
onLoad,
|
||||
onChange,
|
||||
handleSubmit = noop,
|
||||
|
|
@ -54,9 +58,17 @@ const FleetAce = ({
|
|||
const fixHotkeys = (editor: IAceEditor) => {
|
||||
editor.commands.removeCommand("gotoline");
|
||||
editor.commands.removeCommand("find");
|
||||
};
|
||||
|
||||
const onLoadHandler = (editor: IAceEditor) => {
|
||||
fixHotkeys(editor);
|
||||
onLoad && onLoad(editor);
|
||||
};
|
||||
|
||||
const onBlurHandler = (event: any, editor?: IAceEditor): void => {
|
||||
onBlur && onBlur(editor);
|
||||
};
|
||||
|
||||
const handleDelete = (deleteCommand: string) => {
|
||||
const currentText = editorRef.current?.editor.getValue();
|
||||
const selectedText = editorRef.current?.editor.getSelectedText();
|
||||
|
|
@ -114,7 +126,8 @@ const FleetAce = ({
|
|||
maxLines={20}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onLoad={fixHotkeys}
|
||||
onBlur={onBlurHandler}
|
||||
onLoad={onLoadHandler}
|
||||
readOnly={readOnly}
|
||||
setOptions={{ enableLinking: true }}
|
||||
showGutter={showGutter}
|
||||
|
|
@ -123,6 +136,7 @@ const FleetAce = ({
|
|||
value={value}
|
||||
width="100%"
|
||||
wrapEnabled={wrapEnabled}
|
||||
style={style}
|
||||
commands={[
|
||||
{
|
||||
name: "commandName",
|
||||
|
|
|
|||
87
frontend/components/FleetMarkdown/FleetMarkdown.tsx
Normal file
87
frontend/components/FleetMarkdown/FleetMarkdown.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import classnames from "classnames";
|
||||
import { IAceEditor } from "react-ace/lib/types";
|
||||
import { noop } from "lodash";
|
||||
|
||||
import FleetAce from "components/FleetAce";
|
||||
|
||||
import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png";
|
||||
|
||||
interface ICustomLinkProps {
|
||||
text: React.ReactNode;
|
||||
href: string;
|
||||
newTab?: boolean;
|
||||
}
|
||||
|
||||
const CustomLink = ({ text, href, newTab = false }: ICustomLinkProps) => {
|
||||
const target = newTab ? "__blank" : "";
|
||||
return (
|
||||
<a href={href} target={target} rel="noopener noreferrer">
|
||||
{text}
|
||||
<img src={ExternalLinkIcon} alt="Open external link" />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
interface IFleetMarkdownProps {
|
||||
markdown: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const baseClass = "fleet-markdown";
|
||||
|
||||
/** This will give us sensible defaults for how we render markdown across the fleet application.
|
||||
* NOTE: can be extended later to take custom components, but dont need that at the moment.
|
||||
*/
|
||||
const FleetMarkdown = ({ markdown, className }: IFleetMarkdownProps) => {
|
||||
const classNames = classnames(baseClass, className);
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className={classNames}
|
||||
// enables some more markdown features such as direct urls and strikethroughts.
|
||||
// more info here: https://github.com/remarkjs/remark-gfm
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
a: ({ href = "", children }) => {
|
||||
return <CustomLink text={children} href={href} newTab />;
|
||||
},
|
||||
|
||||
// Overrides code display to use FleetAce with Readonly overrides.
|
||||
code: ({ children }) => {
|
||||
const onEditorBlur = (editor?: IAceEditor) => {
|
||||
editor && editor.clearSelection();
|
||||
};
|
||||
|
||||
const onEditorLoad = (editor: IAceEditor) => {
|
||||
editor.setOptions({
|
||||
indentedSoftWrap: false, // removes automatic indentation when wrapping
|
||||
});
|
||||
|
||||
// removes focus UI styling
|
||||
editor.renderer.visualizeFocus = noop;
|
||||
};
|
||||
|
||||
return (
|
||||
<FleetAce
|
||||
wrapperClassName={`${baseClass}__ace-display`}
|
||||
value={String(children).replace(/\n/, "")}
|
||||
showGutter={false}
|
||||
onBlur={onEditorBlur}
|
||||
onLoad={onEditorLoad}
|
||||
style={{ border: "none" }}
|
||||
wrapEnabled
|
||||
readOnly
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{markdown}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default FleetMarkdown;
|
||||
21
frontend/components/FleetMarkdown/_styles.scss
Normal file
21
frontend/components/FleetMarkdown/_styles.scss
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
.fleet-markdown {
|
||||
font-size: $x-small;
|
||||
|
||||
ul {
|
||||
// We need 20px here to keep the list items in line with the left side of
|
||||
// the container.
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 12px;
|
||||
border: 1px solid $ui-blue-gray;
|
||||
background-color: $ui-light-grey; // copy fleet ace background color
|
||||
|
||||
.ace_cursor {
|
||||
// We have the !important here as there doesnt seen a way to programatically
|
||||
// hide only the cursor in the editor.
|
||||
display: none !important
|
||||
}
|
||||
}
|
||||
}
|
||||
1
frontend/components/FleetMarkdown/index.ts
Normal file
1
frontend/components/FleetMarkdown/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./FleetMarkdown";
|
||||
0
frontend/components/Icon/_styles.scss
Normal file
0
frontend/components/Icon/_styles.scss
Normal file
|
|
@ -1,3 +1,4 @@
|
|||
import classnames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
interface ITooltipWrapperProps {
|
||||
|
|
@ -5,6 +6,7 @@ interface ITooltipWrapperProps {
|
|||
tipContent: string;
|
||||
position?: "top" | "bottom";
|
||||
isDelayed?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const baseClass = "component__tooltip-wrapper";
|
||||
|
|
@ -14,13 +16,15 @@ const TooltipWrapper = ({
|
|||
tipContent,
|
||||
position = "bottom",
|
||||
isDelayed,
|
||||
className,
|
||||
}: ITooltipWrapperProps): JSX.Element => {
|
||||
const classname = classnames(baseClass, className);
|
||||
const tipClass = isDelayed
|
||||
? `${baseClass}__tip-text delayed-tip`
|
||||
: `${baseClass}__tip-text`;
|
||||
|
||||
return (
|
||||
<div className={baseClass} data-position={position}>
|
||||
<div className={classname} data-position={position}>
|
||||
<div className={`${baseClass}__element`}>
|
||||
{children}
|
||||
<div className={`${baseClass}__underline`} data-text={children} />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,15 @@ import Apple from "./Apple";
|
|||
import Windows from "./Windows";
|
||||
import Linux from "./Linux";
|
||||
|
||||
// a mapping of the usable names of icons to the icon source.
|
||||
export const ICON_MAP = {
|
||||
"calendar-check": CalendarCheck,
|
||||
darwin: Apple,
|
||||
macOS: Apple,
|
||||
windows: Windows,
|
||||
Windows,
|
||||
linux: Linux,
|
||||
Linux,
|
||||
};
|
||||
|
||||
export type IconNames = keyof typeof ICON_MAP;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { IOsqueryTable } from "interfaces/osquery_table";
|
||||
import { IOsQueryTable } from "interfaces/osquery_table";
|
||||
import { osqueryTableNames } from "utilities/osquery_tables";
|
||||
import { PLATFORM_DISPLAY_NAMES } from "utilities/constants";
|
||||
|
||||
// @ts-ignore
|
||||
import Dropdown from "components/forms/fields/Dropdown";
|
||||
// @ts-ignore
|
||||
import FleetIcon from "components/icons/FleetIcon";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
import FleetMarkdown from "components/FleetMarkdown";
|
||||
import Icon from "components/Icon";
|
||||
|
||||
import QueryTableColumns from "./QueryTableColumns";
|
||||
import QueryTablePlatforms from "./QueryTablePlatforms";
|
||||
|
||||
// @ts-ignore
|
||||
import AppleIcon from "../../../../assets/images/icon-apple-dark-20x20@2x.png";
|
||||
import LinuxIcon from "../../../../assets/images/icon-linux-dark-20x20@2x.png";
|
||||
import WindowsIcon from "../../../../assets/images/icon-windows-dark-20x20@2x.png";
|
||||
import CloseIcon from "../../../../assets/images/icon-close-black-50-8x8@2x.png";
|
||||
import QueryTableExample from "./QueryTableExample";
|
||||
import QueryTableNotes from "./QueryTableNotes";
|
||||
|
||||
interface IQuerySidePanel {
|
||||
selectedOsqueryTable: IOsqueryTable;
|
||||
selectedOsqueryTable: IOsQueryTable;
|
||||
onOsqueryTableSelect: (tableName: string) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
|
@ -30,64 +29,35 @@ const QuerySidePanel = ({
|
|||
onOsqueryTableSelect,
|
||||
onClose,
|
||||
}: IQuerySidePanel): JSX.Element => {
|
||||
const displayTypeForDataType = (dataType: string) => {
|
||||
switch (dataType) {
|
||||
case "TEXT_TYPE":
|
||||
return "text";
|
||||
case "BIGINT_TYPE":
|
||||
return "big int";
|
||||
case "INTEGER_TYPE":
|
||||
return "integer";
|
||||
default:
|
||||
return dataType;
|
||||
}
|
||||
};
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
platforms,
|
||||
columns,
|
||||
examples,
|
||||
notes,
|
||||
evented,
|
||||
} = selectedOsqueryTable;
|
||||
|
||||
const onSelectTable = (value: string) => {
|
||||
onOsqueryTableSelect(value);
|
||||
};
|
||||
|
||||
const renderColumns = () => {
|
||||
const columns = selectedOsqueryTable?.columns;
|
||||
const columnBaseClass = "query-column-list";
|
||||
|
||||
return columns?.map((column) => (
|
||||
<li key={column.name} className={`${columnBaseClass}__item`}>
|
||||
<span className={`${columnBaseClass}__name`}>
|
||||
<TooltipWrapper tipContent={column.description}>
|
||||
{column.name}
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
<div className={`${columnBaseClass}__description`}>
|
||||
<span className={`${columnBaseClass}__type`}>
|
||||
{displayTypeForDataType(column.type)}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
));
|
||||
};
|
||||
|
||||
const renderTableSelect = () => {
|
||||
const tableNames = osqueryTableNames?.map((name: string) => {
|
||||
return { label: name, value: name };
|
||||
const tableNames = osqueryTableNames?.map((tableName: string) => {
|
||||
return { label: tableName, value: tableName };
|
||||
});
|
||||
|
||||
if (!tableNames) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
options={tableNames}
|
||||
value={selectedOsqueryTable?.name}
|
||||
value={name}
|
||||
onChange={onSelectTable}
|
||||
placeholder="Choose Table..."
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const { description, platforms } = selectedOsqueryTable || {};
|
||||
const iconClasses = classnames([`${baseClass}__icon`], "icon");
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
|
@ -99,60 +69,27 @@ const QuerySidePanel = ({
|
|||
<img alt="Close sidebar" src={CloseIcon} />
|
||||
</div>
|
||||
<div className={`${baseClass}__choose-table`}>
|
||||
<h2 className={`${baseClass}__header`}>Tables</h2>
|
||||
<h2 className={`${baseClass}__header`}>
|
||||
Tables
|
||||
<span className={`${baseClass}__table-count`}>
|
||||
{osqueryTableNames.length}
|
||||
</span>
|
||||
</h2>
|
||||
{renderTableSelect()}
|
||||
<p className={`${baseClass}__description`}>{description}</p>
|
||||
</div>
|
||||
<div className={`${baseClass}__os-availability`}>
|
||||
<h2 className={`${baseClass}__header`}>Compatible with:</h2>
|
||||
<ul className={`${baseClass}__platforms`}>
|
||||
{platforms?.map((platform) => {
|
||||
if (platform === "all") {
|
||||
return (
|
||||
<li key={platform}>
|
||||
<FleetIcon name="hosts" />{" "}
|
||||
{PLATFORM_DISPLAY_NAMES[platform] || platform}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
platform = platform.toLowerCase();
|
||||
let icon = (
|
||||
<img
|
||||
src={AppleIcon}
|
||||
alt={`${platform} icon`}
|
||||
className={iconClasses}
|
||||
/>
|
||||
);
|
||||
if (platform === "linux") {
|
||||
icon = (
|
||||
<img
|
||||
src={LinuxIcon}
|
||||
alt={`${platform} icon`}
|
||||
className={iconClasses}
|
||||
/>
|
||||
);
|
||||
} else if (platform === "windows") {
|
||||
icon = (
|
||||
<img
|
||||
src={WindowsIcon}
|
||||
alt={`${platform} icon`}
|
||||
className={iconClasses}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={platform}>
|
||||
{icon} {PLATFORM_DISPLAY_NAMES[platform] || platform}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<div className={`${baseClass}__columns`}>
|
||||
<h2 className={`${baseClass}__header`}>Columns</h2>
|
||||
<ul className={`${baseClass}__column-list`}>{renderColumns()}</ul>
|
||||
{evented && (
|
||||
<div className={`${baseClass}__evented-table-tag`}>
|
||||
<Icon name="calendar-check" className={`${baseClass}__event-icon`} />
|
||||
<span>EVENTED TABLE</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={`${baseClass}__description`}>
|
||||
<FleetMarkdown markdown={description} />
|
||||
</div>
|
||||
<QueryTablePlatforms platforms={platforms} />
|
||||
<QueryTableColumns columns={columns} />
|
||||
{examples && <QueryTableExample example={examples} />}
|
||||
{notes && <QueryTableNotes notes={notes} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { ColumnType, IQueryTableColumn } from "interfaces/osquery_table";
|
||||
import { PLATFORM_DISPLAY_NAMES } from "utilities/constants";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
|
||||
interface IColumnListItemProps {
|
||||
column: IQueryTableColumn;
|
||||
}
|
||||
|
||||
const baseClass = "column-list-item";
|
||||
|
||||
const FOOTNOTES = {
|
||||
required: "Required in WHERE clause.",
|
||||
requires_user_context: "Defaults to root.",
|
||||
platform: "Only available on",
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is to create the html string for the tooltip. We do this as the
|
||||
* current tooltip only supports strings. we can change this when it support ReactNodes
|
||||
* in the future.
|
||||
*/
|
||||
const createTooltipHtml = (column: IQueryTableColumn) => {
|
||||
const toolTipHtml = [];
|
||||
|
||||
const descriptionHtml = `<span class="${baseClass}__column-description">${column.description}</span>`;
|
||||
toolTipHtml.push(descriptionHtml);
|
||||
|
||||
if (column.required) {
|
||||
toolTipHtml.push(
|
||||
`<span class="${baseClass}__footnote">${FOOTNOTES.required}</span>`
|
||||
);
|
||||
}
|
||||
|
||||
if (column.requires_user_context) {
|
||||
toolTipHtml.push(
|
||||
`<span class="${baseClass}__footnote">${FOOTNOTES.requires_user_context}</span>`
|
||||
);
|
||||
}
|
||||
|
||||
if (column.platforms?.length === 1) {
|
||||
const platform = column.platforms[0];
|
||||
toolTipHtml.push(
|
||||
`<span class="${baseClass}__footnote">${FOOTNOTES.platform} ${platform}</span>`
|
||||
);
|
||||
}
|
||||
|
||||
if (column.platforms?.length === 2) {
|
||||
const platform1 = PLATFORM_DISPLAY_NAMES[column.platforms[0]];
|
||||
const platform2 = PLATFORM_DISPLAY_NAMES[column.platforms[1]];
|
||||
toolTipHtml.push(
|
||||
`<span class="${baseClass}__footnote">${FOOTNOTES.platform} ${platform1} and ${platform2}.</span>`
|
||||
);
|
||||
}
|
||||
|
||||
const tooltip = toolTipHtml.join("");
|
||||
return tooltip;
|
||||
};
|
||||
|
||||
const hasFootnotes = (column: IQueryTableColumn) => {
|
||||
return (
|
||||
column.required ||
|
||||
column.requires_user_context ||
|
||||
(column.platforms !== undefined && column.platforms.length !== 0)
|
||||
);
|
||||
};
|
||||
|
||||
const createTypeDisplayText = (type: ColumnType) => {
|
||||
return type.replace("_", " ").toUpperCase();
|
||||
};
|
||||
|
||||
const createListItemClassnames = (column: IQueryTableColumn) => {
|
||||
return classnames(`${baseClass}__name`, {
|
||||
[`${baseClass}__has-footnotes`]: hasFootnotes(column),
|
||||
});
|
||||
};
|
||||
|
||||
const ColumnListItem = ({ column }: IColumnListItemProps) => {
|
||||
const columnNameClasses = createListItemClassnames(column);
|
||||
|
||||
return (
|
||||
<li key={column.name} className={baseClass}>
|
||||
<div className={`${baseClass}__name-wrapper`}>
|
||||
<span className={columnNameClasses}>
|
||||
<TooltipWrapper
|
||||
tipContent={createTooltipHtml(column)}
|
||||
className={`${baseClass}__tooltip`}
|
||||
>
|
||||
{column.name}
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
{column.required && <span className={`${baseClass}__asterisk`}>*</span>}
|
||||
</div>
|
||||
<span className={`${baseClass}__type`}>
|
||||
{createTypeDisplayText(column.type)}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnListItem;
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
.column-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__name-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: $x-small;
|
||||
}
|
||||
|
||||
// this is to override the tooltip text style from TooltipWrapper. When we
|
||||
// change the component to support ReactNode we wont need this.
|
||||
&__tooltip {
|
||||
.component__tooltip-wrapper__tip-text {
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&__asterisk {
|
||||
// specific height to align it vertically with the column name
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
&__has-footnotes {
|
||||
font-style: italic;
|
||||
margin-right: $pad-xsmall;
|
||||
}
|
||||
|
||||
&__type {
|
||||
font-size: $xx-small;
|
||||
}
|
||||
|
||||
&__footnote {
|
||||
font-weight: $bold;
|
||||
margin: $pad-small 0;
|
||||
display: block;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./ColumnListItem";
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import React from "react";
|
||||
|
||||
import { IQueryTableColumn } from "interfaces/osquery_table";
|
||||
|
||||
import ColumnListItem from "./ColumnListItem";
|
||||
|
||||
const sortAlphabetically = (
|
||||
columnA: IQueryTableColumn,
|
||||
columnB: IQueryTableColumn
|
||||
) => {
|
||||
return columnA.name.localeCompare(columnB.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Orders the columns by required columns first sorted alphabetically,
|
||||
* then the rest of the columns sorted alphabetically.
|
||||
*/
|
||||
const orderColumns = (columns: IQueryTableColumn[]) => {
|
||||
const requiredColumns = columns.filter((column) => column.required);
|
||||
const nonRequiredColumns = columns.filter((column) => !column.required);
|
||||
|
||||
const sortedRequiredColumns = requiredColumns.sort(sortAlphabetically);
|
||||
const sortedNonRequiredColumns = nonRequiredColumns.sort(sortAlphabetically);
|
||||
|
||||
return [...sortedRequiredColumns, ...sortedNonRequiredColumns];
|
||||
};
|
||||
|
||||
interface IQueryTableColumnsProps {
|
||||
columns: IQueryTableColumn[];
|
||||
}
|
||||
|
||||
const baseClass = "query-table-columns";
|
||||
|
||||
const QueryTableColumns = ({ columns }: IQueryTableColumnsProps) => {
|
||||
const columnListItems = orderColumns(columns).map((column) => {
|
||||
return <ColumnListItem key={column.name} column={column} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<h3>Columns</h3>
|
||||
<ul className={`${baseClass}__column-list`}>{columnListItems}</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryTableColumns;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
.query-table-columns {
|
||||
margin-bottom: $pad-xlarge;
|
||||
|
||||
h3 {
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
&__column-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./QueryTableColumns";
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react";
|
||||
|
||||
import FleetMarkdown from "components/FleetMarkdown";
|
||||
|
||||
interface IQueryTableExampleProps {
|
||||
example: string;
|
||||
}
|
||||
|
||||
const baseClass = "query-table-example";
|
||||
|
||||
const QueryTableExample = ({ example }: IQueryTableExampleProps) => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<h3>Example</h3>
|
||||
<FleetMarkdown markdown={example} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryTableExample;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
.query-table-example {
|
||||
|
||||
h3 {
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./QueryTableExample";
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import React from "react";
|
||||
|
||||
import FleetMarkdown from "components/FleetMarkdown";
|
||||
|
||||
interface IQueryTableNotesProps {
|
||||
notes: string;
|
||||
}
|
||||
|
||||
const baseClass = "query-table-notes";
|
||||
|
||||
const QueryTableNotes = ({ notes }: IQueryTableNotesProps) => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<h3>Notes</h3>
|
||||
<FleetMarkdown
|
||||
markdown={notes}
|
||||
className={`${baseClass}__notes-markdown`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryTableNotes;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
.query-table-notes {
|
||||
h3 {
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
// overriding default FleetMarkdown styles here
|
||||
&__notes-markdown {
|
||||
|
||||
li {
|
||||
margin-bottom: $pad-medium;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./QueryTableNotes";
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import React from "react";
|
||||
|
||||
import { IOsqueryPlatform } from "interfaces/platform";
|
||||
import { PLATFORM_DISPLAY_NAMES } from "utilities/constants";
|
||||
import Icon from "components/Icon";
|
||||
|
||||
interface IPLatformListItemProps {
|
||||
platform: IOsqueryPlatform;
|
||||
}
|
||||
|
||||
const baseClassListItem = "platform-list-item";
|
||||
|
||||
const PlatformListItem = ({ platform }: IPLatformListItemProps) => {
|
||||
return (
|
||||
<li key={platform} className={baseClassListItem}>
|
||||
<Icon name={platform} />
|
||||
<span>{PLATFORM_DISPLAY_NAMES[platform]}</span>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: remove when freebsd is removed
|
||||
type IPlatformsWithFreebsd = IOsqueryPlatform | "freebsd";
|
||||
|
||||
interface IQueryTablePlatformsProps {
|
||||
platforms: IPlatformsWithFreebsd[];
|
||||
}
|
||||
|
||||
const baseClass = "query-table-platforms";
|
||||
|
||||
const QueryTablePlatforms = ({ platforms }: IQueryTablePlatformsProps) => {
|
||||
const platformListItems = platforms
|
||||
.filter((platform) => platform !== "freebsd")
|
||||
.map((platform) => {
|
||||
return (
|
||||
<PlatformListItem
|
||||
key={platform}
|
||||
platform={platform as IOsqueryPlatform} // TODO: remove when freebsd is removed
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<h3>Compatible with</h3>
|
||||
<ul className={`${baseClass}__platform-list`}>{platformListItems}</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryTablePlatforms;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
.query-table-platforms {
|
||||
h3 {
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
&__platform-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.platform-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: $pad-medium;
|
||||
font-size: $x-small;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: $pad-small;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./QueryTablePlatforms";
|
||||
|
|
@ -26,121 +26,47 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
margin: 0 0 $pad-small;
|
||||
font-size: $x-small;
|
||||
font-weight: $bold;
|
||||
color: $core-fleet-black;
|
||||
}
|
||||
|
||||
&__choose-table {
|
||||
margin: 0 0 $pad-xlarge;
|
||||
margin: 0 0 $pad-large;
|
||||
|
||||
.form-field {
|
||||
margin-bottom: $pad-medium;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: $x-small;
|
||||
font-style: italic;
|
||||
color: $core-fleet-black;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__platforms {
|
||||
font-size: $x-small;
|
||||
color: $core-fleet-black;
|
||||
list-style: none;
|
||||
margin: 0 0 $pad-xlarge;
|
||||
padding: 0;
|
||||
|
||||
.fleeticon {
|
||||
font-size: 18px;
|
||||
margin-right: $pad-medium;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: $pad-medium;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: $pad-medium;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__columns,
|
||||
&__suggested-queries {
|
||||
margin: 0 0 $pad-large;
|
||||
}
|
||||
|
||||
&__column-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
&__column-wrapper {
|
||||
display: flex;
|
||||
margin: 0 0 15px;
|
||||
padding-top: $pad-small;
|
||||
border-top: 1px solid $ui-fleet-blue-15;
|
||||
}
|
||||
|
||||
&__suggestion {
|
||||
flex-grow: 1;
|
||||
font-size: $x-small;
|
||||
line-height: 1.71;
|
||||
letter-spacing: 0.5px;
|
||||
text-align: left;
|
||||
color: $core-fleet-black;
|
||||
}
|
||||
|
||||
&__load-suggestion {
|
||||
align-self: center;
|
||||
padding: 1px 5px;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.query-column-list {
|
||||
&__item {
|
||||
&__header {
|
||||
margin: 0 0 $pad-medium;
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: $core-fleet-black;
|
||||
font-size: $x-small;
|
||||
padding: $pad-small 0;
|
||||
|
||||
&:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
border-radius: $border-radius;
|
||||
margin-right: $pad-small;
|
||||
&__table-count {
|
||||
line-height: normal;
|
||||
margin-left: $pad-small;
|
||||
background-color: $ui-fleet-blue-15;
|
||||
padding: $pad-xsmall $pad-small;
|
||||
border-radius: 8px;
|
||||
font-size: $x-small;
|
||||
}
|
||||
|
||||
&__evented-table-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: $ui-fleet-blue-15;
|
||||
padding: $pad-xsmall $pad-small;
|
||||
border-radius: 6px;
|
||||
font-size: $xxx-small;
|
||||
font-weight: $bold;
|
||||
color: $core-fleet-black;
|
||||
}
|
||||
|
||||
&__event-icon {
|
||||
margin-right: $pad-small;
|
||||
}
|
||||
|
||||
&__description {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__type {
|
||||
font-size: $x-small;
|
||||
color: $core-fleet-black;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { createContext, useReducer, ReactNode } from "react";
|
|||
import { find } from "lodash";
|
||||
|
||||
import { osqueryTables } from "utilities/osquery_tables";
|
||||
import { IOsqueryTable, DEFAULT_OSQUERY_TABLE } from "interfaces/osquery_table";
|
||||
import { IOsQueryTable, DEFAULT_OSQUERY_TABLE } from "interfaces/osquery_table";
|
||||
import { IPlatformString } from "interfaces/platform";
|
||||
|
||||
enum ACTIONS {
|
||||
|
|
@ -55,10 +55,14 @@ type InitialStateType = {
|
|||
setLastEditedQueryPlatform: (value: IPlatformString | null) => void;
|
||||
policyTeamId: number;
|
||||
setPolicyTeamId: (id: number) => void;
|
||||
selectedOsqueryTable: IOsqueryTable;
|
||||
selectedOsqueryTable: IOsQueryTable;
|
||||
setSelectedOsqueryTable: (tableName: string) => void;
|
||||
};
|
||||
|
||||
const initTable =
|
||||
osqueryTables.find((table) => table.name === "users") ||
|
||||
DEFAULT_OSQUERY_TABLE;
|
||||
|
||||
const initialState = {
|
||||
lastEditedQueryId: null,
|
||||
lastEditedQueryName: "",
|
||||
|
|
@ -74,8 +78,7 @@ const initialState = {
|
|||
setLastEditedQueryPlatform: () => null,
|
||||
policyTeamId: 0,
|
||||
setPolicyTeamId: () => null,
|
||||
selectedOsqueryTable:
|
||||
find(osqueryTables, { name: "users" }) || DEFAULT_OSQUERY_TABLE,
|
||||
selectedOsqueryTable: initTable,
|
||||
setSelectedOsqueryTable: () => null,
|
||||
};
|
||||
|
||||
|
|
@ -89,7 +92,9 @@ const reducer = (state: InitialStateType, action: IAction) => {
|
|||
case ACTIONS.SET_SELECTED_OSQUERY_TABLE:
|
||||
return {
|
||||
...state,
|
||||
selectedOsqueryTable: find(osqueryTables, { name: action.tableName }),
|
||||
selectedOsqueryTable:
|
||||
find(osqueryTables, { name: action.tableName }) ||
|
||||
DEFAULT_OSQUERY_TABLE,
|
||||
};
|
||||
case ACTIONS.SET_LAST_EDITED_QUERY_INFO:
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import { find } from "lodash";
|
|||
|
||||
import { osqueryTables } from "utilities/osquery_tables";
|
||||
import { DEFAULT_QUERY } from "utilities/constants";
|
||||
import { DEFAULT_OSQUERY_TABLE, IOsqueryTable } from "interfaces/osquery_table";
|
||||
import { DEFAULT_OSQUERY_TABLE, IOsQueryTable } from "interfaces/osquery_table";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type InitialStateType = {
|
||||
selectedOsqueryTable: IOsqueryTable;
|
||||
selectedOsqueryTable: IOsQueryTable;
|
||||
lastEditedQueryId: number | null;
|
||||
lastEditedQueryName: string;
|
||||
lastEditedQueryDescription: string;
|
||||
|
|
@ -45,7 +45,7 @@ const actions = {
|
|||
SET_LAST_EDITED_QUERY_INFO: "SET_LAST_EDITED_QUERY_INFO",
|
||||
} as const;
|
||||
|
||||
const reducer = (state: any, action: any) => {
|
||||
const reducer = (state: InitialStateType, action: any) => {
|
||||
switch (action.type) {
|
||||
case actions.SET_SELECTED_OSQUERY_TABLE:
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ should be discussed within the team and documented before merged.
|
|||
|
||||
## Table of contents
|
||||
- [Typing](#typing)
|
||||
- [Utilities](#utilities)
|
||||
- [Components](#components)
|
||||
- [React Hooks](#react-hooks)
|
||||
- [React Context](#react-context)
|
||||
|
|
@ -60,6 +61,23 @@ const functionWithTableName = (tableName: string): boolean => {
|
|||
};
|
||||
```
|
||||
|
||||
## Utilities
|
||||
|
||||
### Named exports
|
||||
|
||||
We export individual utility functions and avoid exporting default objects when exporting utilities.
|
||||
|
||||
```ts
|
||||
|
||||
// good
|
||||
export const replaceNewLines = () => {...}
|
||||
|
||||
// bad
|
||||
export default {
|
||||
replaceNewLines
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### React Functional Components
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import PropTypes from "prop-types";
|
||||
import { IOsqueryPlatform } from "./platform";
|
||||
|
||||
export default PropTypes.shape({
|
||||
columns: PropTypes.arrayOf(
|
||||
|
|
@ -13,27 +14,37 @@ export default PropTypes.shape({
|
|||
platform: PropTypes.string,
|
||||
});
|
||||
|
||||
interface ITableColumn {
|
||||
description: string;
|
||||
export type ColumnType =
|
||||
| "integer"
|
||||
| "bigint"
|
||||
| "double"
|
||||
| "text"
|
||||
| "unsigned_bigint";
|
||||
|
||||
export interface IQueryTableColumn {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
type: ColumnType;
|
||||
hidden: boolean;
|
||||
required: boolean;
|
||||
index: boolean;
|
||||
platforms?: IOsqueryPlatform[];
|
||||
requires_user_context?: boolean;
|
||||
}
|
||||
|
||||
export interface IOsqueryTable {
|
||||
columns: ITableColumn[];
|
||||
description: string;
|
||||
export interface IOsQueryTable {
|
||||
name: string;
|
||||
platform?: string;
|
||||
description: string;
|
||||
url: string;
|
||||
platforms: string[];
|
||||
platforms: IOsqueryPlatform[];
|
||||
evented: boolean;
|
||||
cacheable: boolean;
|
||||
columns: IQueryTableColumn[];
|
||||
examples?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_OSQUERY_TABLE: IOsqueryTable = {
|
||||
export const DEFAULT_OSQUERY_TABLE: IOsQueryTable = {
|
||||
name: "users",
|
||||
description:
|
||||
"Local user accounts (including domain accounts that have logged on locally (Windows)).",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import URL_PREFIX from "router/url_prefix";
|
||||
import { IOsqueryPlatform, IPlatformString } from "interfaces/platform";
|
||||
import { IOsqueryPlatform } from "interfaces/platform";
|
||||
|
||||
const { origin } = global.window.location;
|
||||
export const BASE_URL = `${origin}${URL_PREFIX}/api`;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
import { flatMap, sortBy } from "lodash";
|
||||
// @ts-ignore
|
||||
import osqueryTablesJSON from "../osquery_tables.json";
|
||||
import { flatMap } from "lodash";
|
||||
|
||||
export const normalizeTables = (
|
||||
tablesJSON: Record<string, unknown> | string
|
||||
) => {
|
||||
// osquery JSON needs less parsing than it used to
|
||||
const parsedTables =
|
||||
typeof tablesJSON === "object" ? tablesJSON : JSON.parse(tablesJSON);
|
||||
return sortBy(parsedTables, (table) => {
|
||||
return table.name;
|
||||
});
|
||||
};
|
||||
import { IOsQueryTable } from "interfaces/osquery_table";
|
||||
import osqueryFleetTablesJSON from "../../schema/osquery_fleet_schema.json";
|
||||
|
||||
// Typecasting explicity here as we are adding more rigid types such as
|
||||
// IOsqueryPlatform for platform names, instead of just any strings.
|
||||
const queryTable = osqueryFleetTablesJSON as IOsQueryTable[];
|
||||
|
||||
export const osqueryTables = queryTable.sort((a, b) => {
|
||||
return a.name >= b.name ? 1 : -1;
|
||||
});
|
||||
|
||||
export const osqueryTables = normalizeTables(osqueryTablesJSON);
|
||||
export const osqueryTableNames = flatMap(osqueryTables, (table) => {
|
||||
return table.name;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@
|
|||
"react-dom": "16.14.0",
|
||||
"react-entity-getter": "0.0.8",
|
||||
"react-error-boundary": "3.1.4",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-query": "3.34.16",
|
||||
"react-router": "3.2.6",
|
||||
"react-router-transition": "1.2.1",
|
||||
|
|
@ -60,6 +61,7 @@
|
|||
"react-table": "7.7.0",
|
||||
"react-tabs": "3.2.3",
|
||||
"react-tooltip": "4.2.21",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"select": "1.1.2",
|
||||
"sockjs-client": "1.6.1",
|
||||
"sqlite-parser": "1.0.1",
|
||||
|
|
@ -172,7 +174,7 @@
|
|||
"@typescript-eslint/parser": "4.33.0",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "9.0.0",
|
||||
"babel-jest": "23.6.0",
|
||||
"babel-jest": "^29.2.0",
|
||||
"babel-loader": "8.2.3",
|
||||
"css-loader": "1.0.1",
|
||||
"cypress": "9.5.1",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
"target": "ES2019",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"allowSyntheticDefaultImports": true
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"./frontend/**/*"
|
||||
|
|
|
|||
Loading…
Reference in a new issue