diff --git a/web/packages/design/src/DataTable/DataTable.story.tsx b/web/packages/design/src/DataTable/DataTable.story.tsx index 12d126a241c9..710ef3cff9f4 100644 --- a/web/packages/design/src/DataTable/DataTable.story.tsx +++ b/web/packages/design/src/DataTable/DataTable.story.tsx @@ -17,8 +17,7 @@ */ import React, { useState } from 'react'; - -import ServersideSearchPanel from 'teleport/components/ServersideSearchPanel'; +import { ServersideSearchPanelWithPageIndicator } from 'teleport/components/ServersideSearchPanel'; import { SortType, TableProps } from 'design/DataTable/types'; @@ -43,7 +42,7 @@ export const WithServersideProps = () => { }; props.serversideProps = { serversideSearchPanel: ( - { }; props.serversideProps = { serversideSearchPanel: ( - . */ -import React, { JSX, SetStateAction } from 'react'; +import React, { JSX, SetStateAction, FormEvent } from 'react'; import styled from 'styled-components'; import { @@ -28,28 +28,37 @@ import { ColorProps, } from 'design/system'; +const searchInputName = 'searchValue'; + export default function InputSearch({ searchValue, setSearchValue, children, bigInputSize = false, }: Props) { + function submitSearch(e: FormEvent) { + e.preventDefault(); // prevent form default + + const formData = new FormData(e.currentTarget); + const searchValue = formData.get(searchInputName) as string; + + setSearchValue(searchValue); + } + return ( - +
) => - setSearchValue(e.target.value) - } + defaultValue={searchValue} + name={searchInputName} /> {children} - +
); } @@ -83,7 +92,7 @@ const ChildWrapperBackground = styled.div` border-radius: 0 200px 200px 0; `; -const Wrapper = styled.div` +const Form = styled.form` position: relative; display: flex; overflow: hidden; diff --git a/web/packages/design/src/DataTable/types.ts b/web/packages/design/src/DataTable/types.ts index a75b161ca635..53a0abe644e6 100644 --- a/web/packages/design/src/DataTable/types.ts +++ b/web/packages/design/src/DataTable/types.ts @@ -35,6 +35,22 @@ export type TableProps = { */ emptyHint?: string; pagination?: PaginationConfig; + /** + * config for client searching. + * supports any table except when "serversideProps" + * field is defined + */ + clientSearch?: { + /** + * By default, no initial search is applied (empty search), + * unless "initialSearchValue" is defined. + */ + initialSearchValue: string; + /** + * After setting a new search value, this function will be called. + */ + onSearchValueChange(searchString: string): void; + }; isSearchable?: boolean; searchableProps?: Extract[]; // customSearchMatchers contains custom functions to run when search matching. diff --git a/web/packages/design/src/DataTable/useTable.ts b/web/packages/design/src/DataTable/useTable.ts index b54a1d50bdc8..509227ab7339 100644 --- a/web/packages/design/src/DataTable/useTable.ts +++ b/web/packages/design/src/DataTable/useTable.ts @@ -28,6 +28,7 @@ export default function useTable({ columns, pagination, showFirst, + clientSearch, searchableProps, customSearchMatchers = [], serversideProps, @@ -50,8 +51,8 @@ export default function useTable({ } return { - data: serversideProps || disableFilter ? data : [], - searchValue: '', + data: [], + searchValue: clientSearch?.initialSearchValue || '', sort: col ? { key: (col.altSortKey || col.key) as string, @@ -61,7 +62,7 @@ export default function useTable({ : null, pagination: pagination ? { - paginatedData: paginateData(data, pagination.pageSize), + paginatedData: paginateData([], pagination.pageSize), currentPage: 0, pagerPosition: pagination.pagerPosition, pageSize: pagination.pageSize || 15, @@ -161,6 +162,9 @@ export default function useTable({ function setSearchValue(searchValue: string) { updateData(state.sort, searchValue, true /* resetCurrentPage */); + if (clientSearch?.onSearchValueChange) { + clientSearch.onSearchValueChange(searchValue); + } } function nextPage() { @@ -211,6 +215,7 @@ export default function useTable({ fetching, serversideProps, customSort, + clientSearch, ...props, }; } diff --git a/web/packages/design/src/Flex/Flex.tsx b/web/packages/design/src/Flex/Flex.tsx index 3df2471fe56d..f52b4da35afe 100644 --- a/web/packages/design/src/Flex/Flex.tsx +++ b/web/packages/design/src/Flex/Flex.tsx @@ -23,6 +23,8 @@ import { AlignItemsProps, justifyContent, JustifyContentProps, + flexBasis, + FlexBasisProps, flexWrap, FlexWrapProps, flexDirection, @@ -39,6 +41,7 @@ interface FlexProps JustifyContentProps, FlexWrapProps, FlexDirectionProps, + FlexBasisProps, GapProps {} const Flex = styled(Box)` @@ -46,6 +49,7 @@ const Flex = styled(Box)` ${alignItems} ${justifyContent} ${flexWrap} + ${flexBasis} ${flexDirection} ${gap}; `; diff --git a/web/packages/design/src/system/index.ts b/web/packages/design/src/system/index.ts index b1ec1b888e4a..b4225cee1513 100644 --- a/web/packages/design/src/system/index.ts +++ b/web/packages/design/src/system/index.ts @@ -31,6 +31,8 @@ import { ColorProps, flex, FlexProps, + flexBasis, + type FlexBasisProps, flexDirection, FlexDirectionProps, flexWrap, @@ -104,6 +106,8 @@ export { type ColorProps, flex, type FlexProps, + flexBasis, + type FlexBasisProps, flexDirection, type FlexDirectionProps, flexWrap, diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/__snapshots__/ResourceList.story.test.tsx.snap b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/__snapshots__/ResourceList.story.test.tsx.snap index 1a1ba1b322f9..7b98223ad77e 100644 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/__snapshots__/ResourceList.story.test.tsx.snap +++ b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/__snapshots__/ResourceList.story.test.tsx.snap @@ -1750,11 +1750,12 @@ exports[`render Roles 1`] = `
-
@@ -1765,7 +1766,7 @@ exports[`render Roles 1`] = ` class="c7" />
-
+
- + - {showSearchBar && ( - - {!hideAdvancedSearch && ( - - )} - - )} + + {!hideAdvancedSearch && ( + + )} + diff --git a/web/packages/shared/components/ToolTip/HoverTooltip.tsx b/web/packages/shared/components/ToolTip/HoverTooltip.tsx index f761be484d89..2195dce9ee31 100644 --- a/web/packages/shared/components/ToolTip/HoverTooltip.tsx +++ b/web/packages/shared/components/ToolTip/HoverTooltip.tsx @@ -19,6 +19,7 @@ import React, { PropsWithChildren, useState } from 'react'; import styled from 'styled-components'; import { Popover, Flex, Text } from 'design'; +import { JustifyContentProps, FlexBasisProps } from 'design/system'; type OriginProps = { vertical: string; @@ -32,7 +33,8 @@ export const HoverTooltip: React.FC< className?: string; anchorOrigin?: OriginProps; transformOrigin?: OriginProps; - justifyContentProps?: { justifyContent: string }; + justifyContentProps?: JustifyContentProps; + flexBasisProps?: FlexBasisProps; }> > = ({ tipContent, @@ -42,6 +44,7 @@ export const HoverTooltip: React.FC< anchorOrigin = { vertical: 'top', horizontal: 'center' }, transformOrigin = { vertical: 'bottom', horizontal: 'center' }, justifyContentProps = {}, + flexBasisProps = {}, }) => { const [anchorEl, setAnchorEl] = useState(); const open = Boolean(anchorEl); @@ -80,6 +83,7 @@ export const HoverTooltip: React.FC< onMouseLeave={handlePopoverClose} className={className} {...justifyContentProps} + {...flexBasisProps} > {children} { const { container } = render(); - await screen.findByText(/Audit Log/); - expect(container.firstChild).toMatchSnapshot(); + await screen.findAllByText(/Showing/); + expect(container).toMatchSnapshot(); }); test('list of all events', async () => { diff --git a/web/packages/teleport/src/Audit/Audit.story.tsx b/web/packages/teleport/src/Audit/Audit.story.tsx index 42b40b1d1c4b..9cc666a02cf6 100644 --- a/web/packages/teleport/src/Audit/Audit.story.tsx +++ b/web/packages/teleport/src/Audit/Audit.story.tsx @@ -32,6 +32,7 @@ export default { export const LoadedSample = () => { const ctx = new Context(); + ctx.clusterService.fetchClusters = () => Promise.resolve([]); ctx.auditService.fetchEvents = () => Promise.resolve({ events: eventsSample, startKey: '' }); @@ -40,6 +41,7 @@ export const LoadedSample = () => { export const LoadedFetchMore = () => { const ctx = new Context(); + ctx.clusterService.fetchClusters = () => Promise.resolve([]); ctx.auditService.fetchEvents = () => Promise.resolve({ events, startKey: 'any-text' }); @@ -48,12 +50,14 @@ export const LoadedFetchMore = () => { export const Processing = () => { const ctx = new Context(); + ctx.clusterService.fetchClusters = () => Promise.resolve([]); ctx.auditService.fetchEvents = () => new Promise(() => null); return render(ctx); }; export const Failed = () => { const ctx = new Context(); + ctx.clusterService.fetchClusters = () => Promise.resolve([]); ctx.auditService.fetchEvents = () => Promise.reject(new Error('server error')); return render(ctx); diff --git a/web/packages/teleport/src/Audit/__snapshots__/Audit.story.test.tsx.snap b/web/packages/teleport/src/Audit/__snapshots__/Audit.story.test.tsx.snap index e74cd3c6c96c..00469aa3aed8 100644 --- a/web/packages/teleport/src/Audit/__snapshots__/Audit.story.test.tsx.snap +++ b/web/packages/teleport/src/Audit/__snapshots__/Audit.story.test.tsx.snap @@ -1,6 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`list of all events 1`] = ` +.c12 { + box-sizing: border-box; +} + .c6 { box-sizing: border-box; margin-bottom: 4px; @@ -12,10 +16,6 @@ exports[`list of all events 1`] = ` margin-right: 8px; } -.c12 { - box-sizing: border-box; -} - .c20 { line-height: 1.5; margin: 0; @@ -364,11 +364,12 @@ exports[`list of all events 1`] = `
-
@@ -379,7 +380,7 @@ exports[`list of all events 1`] = ` class="c5" />
-
+
thead > tr > th, -.c16 > tbody > tr > th, -.c16 > tfoot > tr > th, -.c16 > thead > tr > td, -.c16 > tbody > tr > td, -.c16 > tfoot > tr > td { +.c25 > thead > tr > th, +.c25 > tbody > tr > th, +.c25 > tfoot > tr > th, +.c25 > thead > tr > td, +.c25 > tbody > tr > td, +.c25 > tfoot > tr > td { padding: 8px 8px; vertical-align: middle; } -.c16 > thead > tr > th:first-child, -.c16 > tbody > tr > th:first-child, -.c16 > tfoot > tr > th:first-child, -.c16 > thead > tr > td:first-child, -.c16 > tbody > tr > td:first-child, -.c16 > tfoot > tr > td:first-child { +.c25 > thead > tr > th:first-child, +.c25 > tbody > tr > th:first-child, +.c25 > tfoot > tr > th:first-child, +.c25 > thead > tr > td:first-child, +.c25 > tbody > tr > td:first-child, +.c25 > tfoot > tr > td:first-child { padding-left: 24px; } -.c16 > thead > tr > th:last-child, -.c16 > tbody > tr > th:last-child, -.c16 > tfoot > tr > th:last-child, -.c16 > thead > tr > td:last-child, -.c16 > tbody > tr > td:last-child, -.c16 > tfoot > tr > td:last-child { +.c25 > thead > tr > th:last-child, +.c25 > tbody > tr > th:last-child, +.c25 > tfoot > tr > th:last-child, +.c25 > thead > tr > td:last-child, +.c25 > tbody > tr > td:last-child, +.c25 > tfoot > tr > td:last-child { padding-right: 24px; } -.c16 > tbody > tr > td { +.c25 > tbody > tr > td { vertical-align: middle; } -.c16 > thead > tr > th { +.c25 > thead > tr > th { color: #FFFFFF; font-weight: 600; font-size: 14px; @@ -13892,11 +13996,11 @@ exports[`loaded audit log screen 1`] = ` white-space: nowrap; } -.c16 > thead > tr > th svg { +.c25 > thead > tr > th svg { height: 12px; } -.c16 > tbody > tr > td { +.c25 > tbody > tr > td { color: #FFFFFF; font-weight: 300; font-size: 14px; @@ -13904,18 +14008,18 @@ exports[`loaded audit log screen 1`] = ` letter-spacing: 0.035px; } -.c16 tbody tr { +.c25 tbody tr { transition: all 150ms; position: relative; border-top: 2px solid rgba(255,255,255,0.07); } -.c16 tbody tr:hover { +.c25 tbody tr:hover { border-top: 2px solid rgba(0,0,0,0); background-color: #222C59; } -.c16 tbody tr:hover:after { +.c25 tbody tr:hover:after { box-shadow: 0px 1px 10px 0px rgba(0,0,0,0.12),0px 4px 5px 0px rgba(0,0,0,0.14),0px 2px 4px -1px rgba(0,0,0,0.20); content: ''; position: absolute; @@ -13926,7 +14030,7 @@ exports[`loaded audit log screen 1`] = ` height: 100%; } -.c16 tbody tr:hover + tr { +.c25 tbody tr:hover + tr { border-top: 2px solid rgba(0,0,0,0); } @@ -13940,6 +14044,14 @@ exports[`loaded audit log screen 1`] = ` margin-top: 4px; } +.c22 svg { + font-size: 20px; +} + +.c22 svg:before { + padding-left: 1px; +} + .c15 { position: relative; height: 100%; @@ -13992,7 +14104,7 @@ exports[`loaded audit log screen 1`] = ` padding-left: 24px; } -.c18 { +.c26 { display: flex; align-items: center; min-width: 130px; @@ -14002,113 +14114,138 @@ exports[`loaded audit log screen 1`] = ` white-space: nowrap; } -
+
- Audit Log -
-
+ Audit Log +
+
- Today +
+ Today +
+
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- +
+
+ Showing + + 1 + + - + + 8 + + of + + + 8 + +
+
+
- Description - - + -
-
+
+ + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - + + + + + + - - - + + + + + + - - - + + + + + + - - - + + + + + + + + + + + +
+ + Type + + + + + + + + + Description + + + Created (UTC) + + + + + + + +
- - - - - - User Deleted - - - User [bob] has been deleted - - 2020-06-05T16:24:05Z - - -
-
+ + + + + + User Role Deleted +
+
+ User [moe] deleted a role [editor] + + 2023-10-26T22:10:50.076Z + + +
- - - - - - User Updated - - - User [bob] has been updated - - 2020-06-05T16:24:05Z - - -
-
+ + + + + + User Role Updated +
+
+ User [larry] updated a role [editor] + + 2023-10-26T22:09:23.29Z + + +
- - - - - - Session Network Connection - - - [ALLOW] Program [bash] successfully opened a connection [10.217.136.161 <-> 190.58.129.4:3000] within a session [44c6cea8-362f-11ea-83aa-125400432324] - - 2019-04-22T19:39:26.676Z - - -
-
+ + + + + + User Role Created +
+
+ User [system] created a role [editor] + + 2023-10-26T16:08:43.426Z + + +
- - - - - - Session File Access - - - Program [bash] accessed a file [/etc/profile.d/] within a session [44c6cea8-362f-11ea-83aa-125400432324] - - 2019-04-22T19:39:26.676Z - - + User [bob] has been updated + + 2020-06-05T16:24:05Z + + +
- Details - -
-
+ + + + + + User Deleted +
+
+ User [bob] has been deleted + + 2020-06-05T16:24:05Z + + +
- - - - - - - - Unknown Event - - - Unknown 'unimplemented.event' event (abc123) - - 2019-04-22T19:39:26.676Z - - + Unknown 'unimplemented.event' event (abc123) + + 2019-04-22T19:39:26.676Z + + +
- Details - -
-
+ + + + + + Session File Access +
+
+ Program [bash] accessed a file [/etc/profile.d/] within a session [44c6cea8-362f-11ea-83aa-125400432324] + + 2019-04-22T19:39:26.676Z + + +
- - - - - - - - User Role Created + + + + + Session Network Connection + + + [ALLOW] Program [bash] successfully opened a connection [10.217.136.161 <-> 190.58.129.4:3000] within a session [44c6cea8-362f-11ea-83aa-125400432324] + + 2019-04-22T19:39:26.676Z + + +
+
- User [system] created a role [editor] - - 2023-10-26T16:08:43.426Z - +
-
-
- - User Role Updated -
-
- User [larry] updated a role [editor] - - 2023-10-26T22:09:23.29Z - - -
-
- - - User Role Deleted -
-
- User [moe] deleted a role [editor] - - 2023-10-26T22:10:50.076Z - - -
-
+
+ +
`; diff --git a/web/packages/teleport/src/Clusters/Clusters.story.test.tsx b/web/packages/teleport/src/Clusters/Clusters.story.test.tsx index fa5300ad86e8..9ea9964075df 100644 --- a/web/packages/teleport/src/Clusters/Clusters.story.test.tsx +++ b/web/packages/teleport/src/Clusters/Clusters.story.test.tsx @@ -18,13 +18,13 @@ import React from 'react'; -import { render, waitFor } from 'design/utils/testing'; +import { render, screen } from 'design/utils/testing'; import { Story, createContext } from './Clusters.story'; test('render clusters', async () => { const ctx = createContext(); const { container } = render(); - await waitFor(() => document.querySelector('table')); - expect(container.firstChild).toMatchSnapshot(); + await screen.findByText(/ROOT/); + expect(container).toMatchSnapshot(); }); diff --git a/web/packages/teleport/src/Clusters/__snapshots__/Clusters.story.test.tsx.snap b/web/packages/teleport/src/Clusters/__snapshots__/Clusters.story.test.tsx.snap index 7f6c6f23c769..6be2c7731bf6 100644 --- a/web/packages/teleport/src/Clusters/__snapshots__/Clusters.story.test.tsx.snap +++ b/web/packages/teleport/src/Clusters/__snapshots__/Clusters.story.test.tsx.snap @@ -13,6 +13,21 @@ exports[`render clusters 1`] = ` } .c18 { + box-sizing: border-box; +} + +.c14 { + box-sizing: border-box; + margin-bottom: 4px; + width: 100%; +} + +.c16 { + box-sizing: border-box; + margin-right: 8px; +} + +.c26 { line-height: 1.5; margin: 0; display: inline-flex; @@ -40,28 +55,99 @@ exports[`render clusters 1`] = ` height: 24px; } -.c18:hover, -.c18:focus { +.c26:hover, +.c26:focus { background: rgba(255,255,255,0.07); } -.c18:active { +.c26:active { background: rgba(255,255,255,0.13); } -.c18:disabled { +.c26:disabled { background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.3); cursor: auto; } -.c16 { +.c19 { + align-items: center; + border: none; + cursor: pointer; + display: flex; + outline: none; + border-radius: 50%; + overflow: visible; + justify-content: center; + text-align: center; + flex: 0 0 auto; + background: transparent; + color: inherit; + transition: all 0.3s; + -webkit-font-smoothing: antialiased; + font-size: 16px; + height: 32px; + width: 32px; + margin-left: 0px; + margin-right: 0px; +} + +.c19:disabled { + color: rgba(255,255,255,0.36); + cursor: default; +} + +.c19:not(:disabled):hover, +.c19:not(:disabled):focus { + background: rgba(255,255,255,0.13); +} + +.c19:not(:disabled):active { + background: rgba(255,255,255,0.18); +} + +.c22 { + align-items: center; + border: none; + cursor: pointer; + display: flex; + outline: none; + border-radius: 50%; + overflow: visible; + justify-content: center; + text-align: center; + flex: 0 0 auto; + background: transparent; + color: inherit; + transition: all 0.3s; + -webkit-font-smoothing: antialiased; + font-size: 16px; + height: 32px; + width: 32px; + margin-left: 0px; +} + +.c22:disabled { + color: rgba(255,255,255,0.36); + cursor: default; +} + +.c22:not(:disabled):hover, +.c22:not(:disabled):focus { + background: rgba(255,255,255,0.13); +} + +.c22:not(:disabled):active { + background: rgba(255,255,255,0.18); +} + +.c21 { display: inline-flex; align-items: center; justify-content: center; } -.c19 { +.c27 { display: inline-flex; align-items: center; justify-content: center; @@ -80,6 +166,17 @@ exports[`render clusters 1`] = ` } .c17 { + overflow: hidden; + text-overflow: ellipsis; + font-weight: 400; + font-size: 12px; + line-height: 16px; + margin: 0px; + margin-right: 4px; + font-weight: 500; +} + +.c25 { box-sizing: border-box; border-radius: 10px; display: inline-block; @@ -92,7 +189,7 @@ exports[`render clusters 1`] = ` color: #000000; } -.c20 { +.c28 { box-sizing: border-box; border-radius: 10px; display: inline-block; @@ -115,6 +212,12 @@ exports[`render clusters 1`] = ` align-items: center; } +.c15 { + display: flex; + align-items: center; + justify-content: flex-end; +} + .c5 { flex-shrink: 0; border-bottom: 1px solid rgba(255,255,255,0.07); @@ -140,7 +243,7 @@ exports[`render clusters 1`] = ` padding-bottom: 24px; } -.c14 { +.c23 { border-collapse: collapse; border-spacing: 0; border-style: hidden; @@ -148,39 +251,39 @@ exports[`render clusters 1`] = ` width: 100%; } -.c14 > thead > tr > th, -.c14 > tbody > tr > th, -.c14 > tfoot > tr > th, -.c14 > thead > tr > td, -.c14 > tbody > tr > td, -.c14 > tfoot > tr > td { +.c23 > thead > tr > th, +.c23 > tbody > tr > th, +.c23 > tfoot > tr > th, +.c23 > thead > tr > td, +.c23 > tbody > tr > td, +.c23 > tfoot > tr > td { padding: 8px 8px; vertical-align: middle; } -.c14 > thead > tr > th:first-child, -.c14 > tbody > tr > th:first-child, -.c14 > tfoot > tr > th:first-child, -.c14 > thead > tr > td:first-child, -.c14 > tbody > tr > td:first-child, -.c14 > tfoot > tr > td:first-child { +.c23 > thead > tr > th:first-child, +.c23 > tbody > tr > th:first-child, +.c23 > tfoot > tr > th:first-child, +.c23 > thead > tr > td:first-child, +.c23 > tbody > tr > td:first-child, +.c23 > tfoot > tr > td:first-child { padding-left: 24px; } -.c14 > thead > tr > th:last-child, -.c14 > tbody > tr > th:last-child, -.c14 > tfoot > tr > th:last-child, -.c14 > thead > tr > td:last-child, -.c14 > tbody > tr > td:last-child, -.c14 > tfoot > tr > td:last-child { +.c23 > thead > tr > th:last-child, +.c23 > tbody > tr > th:last-child, +.c23 > tfoot > tr > th:last-child, +.c23 > thead > tr > td:last-child, +.c23 > tbody > tr > td:last-child, +.c23 > tfoot > tr > td:last-child { padding-right: 24px; } -.c14 > tbody > tr > td { +.c23 > tbody > tr > td { vertical-align: middle; } -.c14 > thead > tr > th { +.c23 > thead > tr > th { color: #FFFFFF; font-weight: 600; font-size: 14px; @@ -192,11 +295,11 @@ exports[`render clusters 1`] = ` white-space: nowrap; } -.c14 > thead > tr > th svg { +.c23 > thead > tr > th svg { height: 12px; } -.c14 > tbody > tr > td { +.c23 > tbody > tr > td { color: #FFFFFF; font-weight: 300; font-size: 14px; @@ -204,18 +307,18 @@ exports[`render clusters 1`] = ` letter-spacing: 0.035px; } -.c14 tbody tr { +.c23 tbody tr { transition: all 150ms; position: relative; border-top: 2px solid rgba(255,255,255,0.07); } -.c14 tbody tr:hover { +.c23 tbody tr:hover { border-top: 2px solid rgba(0,0,0,0); background-color: #222C59; } -.c14 tbody tr:hover:after { +.c23 tbody tr:hover:after { box-shadow: 0px 1px 10px 0px rgba(0,0,0,0.12),0px 4px 5px 0px rgba(0,0,0,0.14),0px 2px 4px -1px rgba(0,0,0,0.20); content: ''; position: absolute; @@ -226,7 +329,7 @@ exports[`render clusters 1`] = ` height: 100%; } -.c14 tbody tr:hover + tr { +.c23 tbody tr:hover + tr { border-top: 2px solid rgba(0,0,0,0); } @@ -240,6 +343,14 @@ exports[`render clusters 1`] = ` margin-top: 4px; } +.c20 svg { + font-size: 20px; +} + +.c20 svg:before { + padding-left: 1px; +} + .c13 { position: relative; height: 100%; @@ -292,1679 +403,1841 @@ exports[`render clusters 1`] = ` padding-left: 24px; } -.c15 td { +.c24 td { height: 22px; } -
+
- Manage Clusters +
+ Manage Clusters +
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Name - - - - - - - -
+
- ROOT + font-weight="500" + style="white-space: nowrap; letter-spacing: 0.15px;" + > + Showing + + 1 + + - + + 37 + + of + + + 37 +
-
- localhost - +
-
-
- LEAF -
-
- nidvojik - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + Name + + + + + + + +
+
+ ROOT +
+
+ localhost + + +
+
+ LEAF +
+
+ albertowens200 + + +
+
+ LEAF +
+
+ barrynelson110 + + +
+
+ LEAF +
+
+ beatricecarson171 + + +
+
+ LEAF +
+
+ besscarroll152 + + +
+
+ LEAF +
+
+ bessiecohen207 + + +
+
+ LEAF +
+
+ connorsharp137 + + +
+
+ LEAF +
+
+ dashawic + + +
+
+ LEAF +
+
+ dashawic + + +
+
+ LEAF +
+
+ dashawic + + +
+
+ LEAF +
+
+ farovluv + + +
+
+ LEAF +
+
+ francismoran134 + + +
+
+ LEAF +
+
+ hannahsutton232 + + +
+
+ LEAF +
+
+ hattiestanley34 + + +
+
+ LEAF +
+
+ henriettarios78 + + +
+
+ LEAF +
+
+ isabellekim81 + + +
+
+ LEAF +
+
+ jaysandoval137 + + +
+
+ LEAF +
+
+ jesushenry58 + + +
+
+ LEAF +
+
+ jordansimpson35 + + +
+
+ LEAF +
+
+ josephinewolfe55 + + +
+
+ LEAF +
+
+ leonamann249 + + +
+
+ LEAF +
+
+ lidtabih + + +
+
+ LEAF +
+
+ nellwheeler72 + + +
+
+ LEAF +
+
+ nidvojik + + +
+
+ LEAF +
+
+ philipjohnson10 + + +
+
+ LEAF +
+
+ ricardosingleton242 + + +
+
+ LEAF +
+
+ rozpaari + + +
+
+ LEAF +
+
+ rozpaari + + +
+
+ LEAF +
+
+ rozpaari + + +
+
+ LEAF +
+
+ samlewis176 + + +
+
+ LEAF +
+
+ teresastone14 + + +
+
+ LEAF +
+
+ theodorefrazier78 + + +
+
+ LEAF +
+
+ tommybrooks146 + + +
+
+ LEAF +
+
+ wetjolune + + +
+
+ LEAF +
+
+ wetjolune + + +
+
+ LEAF +
+
+ wetjolune + + +
+
+ LEAF +
+
+ williepayne223 + + +
+
- lidtabih - +
-
-
- LEAF -
-
- farovluv - -
-
- LEAF -
-
- rozpaari - - -
-
- LEAF -
-
- wetjolune - - -
-
- LEAF -
-
- dashawic - - -
-
- LEAF -
-
- jesushenry58 - - -
-
- LEAF -
-
- jordansimpson35 - - -
-
- LEAF -
-
- leonamann249 - - -
-
- LEAF -
-
- bessiecohen207 - - -
-
- LEAF -
-
- philipjohnson10 - - -
-
- LEAF -
-
- teresastone14 - - -
-
- LEAF -
-
- connorsharp137 - - -
-
- LEAF -
-
- ricardosingleton242 - - -
-
- LEAF -
-
- rozpaari - - -
-
- LEAF -
-
- wetjolune - - -
-
- LEAF -
-
- dashawic - - -
-
- LEAF -
-
- williepayne223 - - -
-
- LEAF -
-
- samlewis176 - - -
-
- LEAF -
-
- nellwheeler72 - - -
-
- LEAF -
-
- albertowens200 - - -
-
- LEAF -
-
- beatricecarson171 - - -
-
- LEAF -
-
- besscarroll152 - - -
-
- LEAF -
-
- hannahsutton232 - - -
-
- LEAF -
-
- barrynelson110 - - -
-
- LEAF -
-
- rozpaari - - -
-
- LEAF -
-
- wetjolune - - -
-
- LEAF -
-
- dashawic - - -
-
- LEAF -
-
- henriettarios78 - - -
-
- LEAF -
-
- josephinewolfe55 - - -
-
- LEAF -
-
- jaysandoval137 - - -
-
- LEAF -
-
- isabellekim81 - - -
-
- LEAF -
-
- francismoran134 - - -
-
- LEAF -
-
- theodorefrazier78 - - -
-
- LEAF -
-
- hattiestanley34 - - -
-
- LEAF -
-
- tommybrooks146 - - -
-
+
+ + `; diff --git a/web/packages/teleport/src/Console/DocumentNodes/__snapshots__/DocumentNodes.story.test.tsx.snap b/web/packages/teleport/src/Console/DocumentNodes/__snapshots__/DocumentNodes.story.test.tsx.snap index 779b63afea88..a8e0dc0c6488 100644 --- a/web/packages/teleport/src/Console/DocumentNodes/__snapshots__/DocumentNodes.story.test.tsx.snap +++ b/web/packages/teleport/src/Console/DocumentNodes/__snapshots__/DocumentNodes.story.test.tsx.snap @@ -279,6 +279,7 @@ exports[`render DocumentNodes 1`] = ` display: flex; align-items: center; justify-content: space-between; + gap: 16px; } .c20 { @@ -542,13 +543,14 @@ exports[`render DocumentNodes 1`] = ` position: relative; display: flex; overflow: hidden; + width: 100%; border-radius: 200px; height: 100%; background: transparent; - max-width: 725px; } .c14 { + background: #0C143D; border-radius: 200px; width: 100%; height: 48px; @@ -566,7 +568,7 @@ exports[`render DocumentNodes 1`] = ` color: #FFFFFF; background: rgba(255,255,255,0.07); padding-right: 184px; - padding-left: 24px; + padding-left: 32px; } .c4 { @@ -630,79 +632,83 @@ exports[`render DocumentNodes 1`] = ` @@ -536,7 +727,7 @@ exports[`rendering of Session Recordings 1`] = ` > Type Name User(s) Duration Created (UTC) - apple-node + im-a-nodename - one, two + test - 19 minutes + 10 minutes - 2019-04-22T00:00:51.543Z + 2021-07-27T23:20:05.346Z - 426485-6491-11e9-80a1-427cfde50f5a + d183ca84-dd94-434a-afee5c2c5f38 - - Play - + recording disabled
- peach-node + ip-172-31-30-254 - one, two + foo - 21 seconds + 32 seconds - 2019-04-22T00:00:51.543Z + 2021-05-21T22:54:27.123Z - 377875-6491-11e9-80a1-427cfde50f5a + 9d92ad96-a45c-4add-463cc7bc48b1 - - Play - + non-interactive
- im-a-nodename + peach-node - test + one, two - 10 minutes + 21 seconds - 2021-07-27T23:20:05.346Z + 2019-04-22T00:00:51.543Z - d183ca84-dd94-434a-afee5c2c5f38 + 377875-6491-11e9-80a1-427cfde50f5a - recording disabled + + Play +
- ip-172-31-30-254 + apple-node - foo + one, two - 32 seconds + 19 minutes - 2021-05-21T22:54:27.123Z + 2019-04-22T00:00:51.543Z - 9d92ad96-a45c-4add-463cc7bc48b1 + 426485-6491-11e9-80a1-427cfde50f5a - non-interactive + + Play +
`; diff --git a/web/packages/teleport/src/Roles/RoleList/RoleList.tsx b/web/packages/teleport/src/Roles/RoleList/RoleList.tsx index ca1151bf909e..d537eba3a43a 100644 --- a/web/packages/teleport/src/Roles/RoleList/RoleList.tsx +++ b/web/packages/teleport/src/Roles/RoleList/RoleList.tsx @@ -54,7 +54,6 @@ export function RoleList({ updateSearch={onSearchChange} updateQuery={null} hideAdvancedSearch={true} - showSearchBar={true} filter={{ search }} disableSearch={serversidePagination.attempt.status === 'processing'} /> diff --git a/web/packages/teleport/src/Sessions/__snapshots__/Sessions.story.test.tsx.snap b/web/packages/teleport/src/Sessions/__snapshots__/Sessions.story.test.tsx.snap index 273b15338eaa..2315c7f252be 100644 --- a/web/packages/teleport/src/Sessions/__snapshots__/Sessions.story.test.tsx.snap +++ b/web/packages/teleport/src/Sessions/__snapshots__/Sessions.story.test.tsx.snap @@ -21,12 +21,6 @@ exports[`active sessions CTA 1`] = ` box-sizing: border-box; } -.c31 { - box-sizing: border-box; - width: 80px; - text-align: center; -} - .c21 { box-sizing: border-box; margin-bottom: 4px; @@ -38,6 +32,12 @@ exports[`active sessions CTA 1`] = ` margin-right: 8px; } +.c31 { + box-sizing: border-box; + width: 80px; + text-align: center; +} + .c10 { line-height: 1.5; margin: 0; @@ -522,11 +522,12 @@ exports[`active sessions CTA 1`] = `
-
@@ -537,7 +538,7 @@ exports[`active sessions CTA 1`] = ` class="c20" />
-
+
-
@@ -1610,7 +1612,7 @@ exports[`loaded 1`] = ` class="c14" />
-
+
-
@@ -2683,7 +2686,7 @@ exports[`moderated sessions CTA for non-enterprise 1`] = ` class="c14" />
-
+
. - */ - -import React from 'react'; -import { Flex } from 'design'; - -import { AdvancedSearchToggle } from 'shared/components/AdvancedSearchToggle'; - -import useServersideSearchPanel, { - SearchPanelState, - HookProps, -} from 'teleport/components/ServersideSearchPanel/useServerSideSearchPanel'; - -import { SearchInput } from './SearchInput'; - -export default function Container(props: HookProps) { - const state = useServersideSearchPanel(props); - return ; -} - -// Adapted from teleport.components.ServersideSearchPanel -export function SearchPanel({ - searchString, - setSearchString, - isAdvancedSearch, - setIsAdvancedSearch, - onSubmitSearch, -}: SearchPanelState) { - function onToggle() { - setIsAdvancedSearch(wasAdvancedSearch => !wasAdvancedSearch); - } - - return ( - - - - - - ); -} diff --git a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx index df2215f13a40..7e1e83e1ee6d 100644 --- a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx +++ b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx @@ -53,9 +53,9 @@ import { useSamlAppAction, SamlAppActionProvider, } from 'teleport/SamlApplications/useSamlAppActions'; +import { ServersideSearchPanel } from 'teleport/components/ServersideSearchPanel'; import { ResourceActionButton } from './ResourceActionButton'; -import SearchPanel from './SearchPanel'; export function UnifiedResources() { const { clusterId, isLeafCluster } = useStickyClusterId(); @@ -275,14 +275,16 @@ export function ClusterResources({ setParams(newParams); const isAdvancedSearch = !!newParams.query; replaceHistory( - encodeUrlQueryParams( + encodeUrlQueryParams({ pathname, - isAdvancedSearch ? newParams.query : newParams.search, - newParams.sort, - newParams.kinds, + searchString: isAdvancedSearch + ? newParams.query + : newParams.search, + sort: newParams.sort, + kinds: newParams.kinds, isAdvancedSearch, - newParams.pinnedOnly - ) + pinnedOnly: newParams.pinnedOnly, + }) ); }} Header={ @@ -307,8 +309,8 @@ export function ClusterResources({ )} - - + ); diff --git a/web/packages/teleport/src/Users/Users.story.tsx b/web/packages/teleport/src/Users/Users.story.tsx index 59d9819c2f78..38d07d519319 100644 --- a/web/packages/teleport/src/Users/Users.story.tsx +++ b/web/packages/teleport/src/Users/Users.story.tsx @@ -17,6 +17,7 @@ */ import React from 'react'; +import { MemoryRouter } from 'react-router'; import { Users } from './Users'; @@ -31,11 +32,19 @@ export const Processing = () => { isSuccess: false, message: '', }; - return ; + return ( + + + + ); }; export const Loaded = () => { - return ; + return ( + + + + ); }; export const Failed = () => { @@ -45,7 +54,11 @@ export const Failed = () => { isSuccess: false, message: 'some error message', }; - return ; + return ( + + + + ); }; const users = [ diff --git a/web/packages/teleport/src/Users/__snapshots__/Users.story.test.tsx.snap b/web/packages/teleport/src/Users/__snapshots__/Users.story.test.tsx.snap index fb3b4e45e5b2..47f2f1e99484 100644 --- a/web/packages/teleport/src/Users/__snapshots__/Users.story.test.tsx.snap +++ b/web/packages/teleport/src/Users/__snapshots__/Users.story.test.tsx.snap @@ -243,17 +243,17 @@ exports[`success state 1`] = ` align-items: center; } -.c25 { - display: flex; - flex-wrap: wrap; -} - .c16 { display: flex; align-items: center; justify-content: flex-end; } +.c25 { + display: flex; + flex-wrap: wrap; +} + .c5 { flex-shrink: 0; border-bottom: 1px solid rgba(255,255,255,0.07); @@ -465,11 +465,12 @@ exports[`success state 1`] = `
-
@@ -480,7 +481,7 @@ exports[`success state 1`] = ` class="c14" />
-
+
. + */ + +import { useHistory, useLocation } from 'react-router'; + +import { TableProps } from 'design/DataTable/types'; +import Table from 'design/DataTable'; + +import { + decodeUrlQueryParam, + encodeUrlQueryParams, +} from '../hooks/useUrlFiltering'; + +export function ClientSearcheableTableWithQueryParamSupport( + props: Omit, 'serversideProps'> +) { + const loc = useLocation(); + const history = useHistory(); + + const searchParams = new URLSearchParams(loc.search); + + function updateUrlParams(searchString: string) { + history.replace( + encodeUrlQueryParams({ pathname: loc.pathname, searchString }) + ); + } + + return ( + + {...props} + clientSearch={{ + initialSearchValue: decodeUrlQueryParam( + searchParams.get('search') || '' + ), + onSearchValueChange: updateUrlParams, + }} + isSearchable + /> + ); +} diff --git a/web/packages/teleport/src/Nodes/index.ts b/web/packages/teleport/src/components/ClientSearcheableTableWithQueryParamSupport/index.ts similarity index 81% rename from web/packages/teleport/src/Nodes/index.ts rename to web/packages/teleport/src/components/ClientSearcheableTableWithQueryParamSupport/index.ts index cf78baeafff2..4a291724e4d6 100644 --- a/web/packages/teleport/src/Nodes/index.ts +++ b/web/packages/teleport/src/components/ClientSearcheableTableWithQueryParamSupport/index.ts @@ -1,6 +1,6 @@ /** * Teleport - * Copyright (C) 2023 Gravitational, Inc. + * Copyright (C) 2024 Gravitational, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -16,4 +16,4 @@ * along with this program. If not, see . */ -export { Nodes } from './Nodes'; +export { ClientSearcheableTableWithQueryParamSupport } from './ClientSearcheableTableWithQueryParamSupport'; diff --git a/web/packages/teleport/src/components/NodeList/NodeList.tsx b/web/packages/teleport/src/components/NodeList/NodeList.tsx index d2220b4a15a9..e8ca7d809d0d 100644 --- a/web/packages/teleport/src/components/NodeList/NodeList.tsx +++ b/web/packages/teleport/src/components/NodeList/NodeList.tsx @@ -23,7 +23,7 @@ import { LoginItem, MenuLogin } from 'shared/components/MenuLogin'; import { Node } from 'teleport/services/nodes'; import { ResourceLabel, ResourceFilter } from 'teleport/services/agents'; -import ServersideSearchPanel from 'teleport/components/ServersideSearchPanel'; +import { ServersideSearchPanelWithPageIndicator } from 'teleport/components/ServersideSearchPanel'; import type { PageIndicators } from 'teleport/components/hooks/useServersidePagination'; @@ -85,14 +85,13 @@ function NodeList(props: Props) { sort: params.sort, setSort, serversideSearchPanel: ( - ), }} diff --git a/web/packages/teleport/src/UnifiedResources/SearchInput.tsx b/web/packages/teleport/src/components/ServersideSearchPanel/SearchInput.tsx similarity index 100% rename from web/packages/teleport/src/UnifiedResources/SearchInput.tsx rename to web/packages/teleport/src/components/ServersideSearchPanel/SearchInput.tsx diff --git a/web/packages/teleport/src/components/ServersideSearchPanel/ServersideSearchPanel.tsx b/web/packages/teleport/src/components/ServersideSearchPanel/ServersideSearchPanel.tsx index 675f9739ec29..01ece4cb6e1f 100644 --- a/web/packages/teleport/src/components/ServersideSearchPanel/ServersideSearchPanel.tsx +++ b/web/packages/teleport/src/components/ServersideSearchPanel/ServersideSearchPanel.tsx @@ -18,90 +18,41 @@ import React from 'react'; import { Flex } from 'design'; -import InputSearch from 'design/DataTable/InputSearch'; -import { PageIndicatorText } from 'design/DataTable/Pager/PageIndicatorText'; import { AdvancedSearchToggle } from 'shared/components/AdvancedSearchToggle'; -import { PageIndicators } from 'teleport/components/hooks/useServersidePagination'; - import useServersideSearchPanel, { - HookProps, SearchPanelState, -} from './useServerSideSearchPanel'; - -interface ComponentProps { - pageIndicators: PageIndicators; - disabled?: boolean; -} - -export interface Props extends HookProps, ComponentProps { - bigInputSize?: boolean; -} + HookProps, +} from 'teleport/components/ServersideSearchPanel/useServerSideSearchPanel'; -export default function Container(props: Props) { - const { - pageIndicators, - disabled, - bigInputSize = false, - ...hookProps - } = props; - const state = useServersideSearchPanel(hookProps); - return ( - - ); -} +import { SearchInput } from './SearchInput'; -interface State extends SearchPanelState, ComponentProps { - bigInputSize?: boolean; +export function ServersideSearchPanel(props: HookProps) { + const state = useServersideSearchPanel(props); + return ; } -export function ServersideSearchPanel({ +export function SearchPanel({ searchString, setSearchString, isAdvancedSearch, setIsAdvancedSearch, onSubmitSearch, - pageIndicators, - disabled = false, - bigInputSize = false, -}: State) { +}: SearchPanelState) { function onToggle() { - setIsAdvancedSearch(!isAdvancedSearch); + setIsAdvancedSearch(wasAdvancedSearch => !wasAdvancedSearch); } return ( - - + + - - - - + ); } diff --git a/web/packages/teleport/src/components/ServersideSearchPanel/ServersideSearchPanelWithPageIndicator.tsx b/web/packages/teleport/src/components/ServersideSearchPanel/ServersideSearchPanelWithPageIndicator.tsx new file mode 100644 index 000000000000..be9ef01c1536 --- /dev/null +++ b/web/packages/teleport/src/components/ServersideSearchPanel/ServersideSearchPanelWithPageIndicator.tsx @@ -0,0 +1,64 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from 'react'; +import { Flex } from 'design'; +import { PageIndicatorText } from 'design/DataTable/Pager/PageIndicatorText'; + +import { PageIndicators } from 'teleport/components/hooks/useServersidePagination'; + +import useServersideSearchPanel, { + HookProps, +} from './useServerSideSearchPanel'; +import { SearchPanel } from './ServersideSearchPanel'; + +interface ComponentProps { + pageIndicators: PageIndicators; + disabled?: boolean; +} + +export interface Props extends HookProps, ComponentProps {} + +export function ServersideSearchPanelWithPageIndicator(props: Props) { + const { pageIndicators, disabled, ...hookProps } = props; + const state = useServersideSearchPanel(hookProps); + return ( + + + + + + + ); +} diff --git a/web/packages/teleport/src/components/ServersideSearchPanel/index.ts b/web/packages/teleport/src/components/ServersideSearchPanel/index.ts index fba6c024973f..4dd26572f8c3 100644 --- a/web/packages/teleport/src/components/ServersideSearchPanel/index.ts +++ b/web/packages/teleport/src/components/ServersideSearchPanel/index.ts @@ -16,6 +16,5 @@ * along with this program. If not, see . */ -import ServersideSearchPanel from './ServersideSearchPanel'; - -export default ServersideSearchPanel; +export { ServersideSearchPanelWithPageIndicator } from './ServersideSearchPanelWithPageIndicator'; +export { ServersideSearchPanel } from './ServersideSearchPanel'; diff --git a/web/packages/teleport/src/components/ServersideSearchPanel/useServerSideSearchPanel.ts b/web/packages/teleport/src/components/ServersideSearchPanel/useServerSideSearchPanel.ts index 1aab8cc3243e..d2ed418da3e9 100644 --- a/web/packages/teleport/src/components/ServersideSearchPanel/useServerSideSearchPanel.ts +++ b/web/packages/teleport/src/components/ServersideSearchPanel/useServerSideSearchPanel.ts @@ -55,14 +55,14 @@ export default function useServersideSearchPanel({ }); } replaceHistory( - encodeUrlQueryParams( + encodeUrlQueryParams({ pathname, searchString, - params.sort, - params.kinds, + sort: params.sort, + kinds: params.kinds, isAdvancedSearch, - params.pinnedOnly - ) + pinnedOnly: params.pinnedOnly, + }) ); } diff --git a/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.test.ts b/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.test.ts index 768a4f929a19..f68e5a0ca573 100644 --- a/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.test.ts +++ b/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.test.ts @@ -16,52 +16,69 @@ * along with this program. If not, see . */ -import { encodeUrlQueryParams } from './encodeUrlQueryParams'; +import { + encodeUrlQueryParams, + EncodeUrlQueryParamsProps, +} from './encodeUrlQueryParams'; -test.each([ +const testCases: { + title: string; + args: EncodeUrlQueryParamsProps; + expected: string; +}[] = [ { title: 'No query params', - args: ['/foo', '', null, null, false], + args: { pathname: '/foo' }, expected: '/foo', }, { title: 'Search string', - args: ['/test', 'something', null, null, false], + args: { pathname: '/test', searchString: 'something' }, expected: '/test?search=something', }, { title: 'Search string, encoded', - args: ['/test', 'a$b$c', null, null, false], + args: { pathname: '/test', searchString: 'a$b$c' }, expected: '/test?search=a%24b%24c', }, { title: 'Advanced search', - args: ['/test', 'foo=="bar"', null, null, true], + args: { + pathname: '/test', + searchString: 'foo=="bar"', + isAdvancedSearch: true, + }, expected: '/test?query=foo%3D%3D%22bar%22', }, { title: 'Search and sort', - args: ['/test', 'foobar', { fieldName: 'name', dir: 'ASC' }, null, false], + args: { + pathname: '/test', + searchString: 'foobar', + sort: { fieldName: 'name', dir: 'ASC' }, + }, expected: '/test?search=foobar&sort=name%3Aasc', }, { title: 'Sort only', - args: ['/test', '', { fieldName: 'name', dir: 'ASC' }, null, false], + args: { + pathname: '/test', + sort: { fieldName: 'name', dir: 'ASC' }, + }, expected: '/test?sort=name%3Aasc', }, { title: 'Search, sort, and filter by kind', - args: [ - '/test', - 'foo', - { fieldName: 'name', dir: 'DESC' }, - ['db', 'node'], - false, - ], + args: { + pathname: '/test', + searchString: 'foo', + sort: { fieldName: 'name', dir: 'DESC' }, + kinds: ['db', 'node'], + }, expected: '/test?search=foo&sort=name%3Adesc&kinds=db&kinds=node', }, -])('$title', ({ args, expected }) => { - expect( - encodeUrlQueryParams(...(args as Parameters)) - ).toBe(expected); +]; + +test.each(testCases)('$title', ({ args, expected }) => { + expect(encodeUrlQueryParams(args)).toBe(expected); }); diff --git a/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.ts b/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.ts index 9e569ec1c890..a00b57dc4769 100644 --- a/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.ts +++ b/web/packages/teleport/src/components/hooks/useUrlFiltering/encodeUrlQueryParams.ts @@ -18,14 +18,23 @@ import { SortType } from 'design/DataTable/types'; -export function encodeUrlQueryParams( - pathname: string, - searchString: string, - sort: SortType | null, - kinds: string[] | null, - isAdvancedSearch: boolean, - pinnedOnly: boolean -) { +export type EncodeUrlQueryParamsProps = { + pathname: string; + searchString?: string; + sort?: SortType | null; + kinds?: string[] | null; + isAdvancedSearch?: boolean; + pinnedOnly?: boolean; +}; + +export function encodeUrlQueryParams({ + pathname, + searchString = '', + sort, + kinds, + isAdvancedSearch = false, + pinnedOnly = false, +}: EncodeUrlQueryParamsProps) { const urlParams = new URLSearchParams(); if (searchString) { diff --git a/web/packages/teleport/src/components/hooks/useUrlFiltering/useUrlFiltering.ts b/web/packages/teleport/src/components/hooks/useUrlFiltering/useUrlFiltering.ts index 1e00f558d0f7..374a50d317ac 100644 --- a/web/packages/teleport/src/components/hooks/useUrlFiltering/useUrlFiltering.ts +++ b/web/packages/teleport/src/components/hooks/useUrlFiltering/useUrlFiltering.ts @@ -60,14 +60,14 @@ export function useUrlFiltering( setParams({ ...params, search: '', query: queryAfterLabelClick }); replaceHistory( - encodeUrlQueryParams( + encodeUrlQueryParams({ pathname, - queryAfterLabelClick, - params.sort, - params.kinds, - true /*isAdvancedSearch*/, - params.pinnedOnly - ) + searchString: queryAfterLabelClick, + sort: params.sort, + kinds: params.kinds, + isAdvancedSearch: true, + pinnedOnly: params.pinnedOnly, + }) ); }; diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index 1ef76f475022..e9c1b7d5cdec 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -557,8 +557,7 @@ const cfg = { }, getUsersRoute() { - const clusterId = cfg.proxyCluster; - return generatePath(cfg.routes.users, { clusterId }); + return cfg.routes.users; }, getBotsRoute() { diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index 006b5c14c4b2..61c6dffe7eee 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -55,7 +55,6 @@ import { UnifiedResources } from './UnifiedResources'; import { AccountPage } from './Account'; import { Support } from './Support'; import { Clusters } from './Clusters'; -import { Nodes } from './Nodes'; import { TrustedClusters } from './TrustedClusters'; import { Users } from './Users'; import { RolesContainer as Roles } from './Roles'; @@ -99,31 +98,6 @@ class AccessRequests implements TeleportFeature { topMenuItem = this.navigationItem; } -export class FeatureNodes implements TeleportFeature { - route = { - title: 'Servers', - path: cfg.routes.nodes, - exact: true, - component: Nodes, - }; - - navigationItem = { - title: NavTitle.Servers, - icon: Server, - exact: true, - getLink(clusterId: string) { - return cfg.getNodesRoute(clusterId); - }, - }; - - category = NavigationCategory.Resources; - - hasAccess(flags: FeatureFlags) { - return flags.nodes; - } -} - -// TODO (avatus) add navigationItem when ready to release export class FeatureJoinTokens implements TeleportFeature { category = NavigationCategory.Management; section = ManagementSection.Access; @@ -215,7 +189,7 @@ export class FeatureUsers implements TeleportFeature { title: 'Manage Users', path: cfg.routes.users, exact: true, - component: () => , + component: Users, }; hasAccess(flags: FeatureFlags): boolean { diff --git a/web/packages/teleport/src/services/integrations/oktaStatusTypes.ts b/web/packages/teleport/src/services/integrations/oktaStatusTypes.ts index 1761c6e7d18e..eaec026f3b2a 100644 --- a/web/packages/teleport/src/services/integrations/oktaStatusTypes.ts +++ b/web/packages/teleport/src/services/integrations/oktaStatusTypes.ts @@ -36,13 +36,11 @@ export enum PluginOktaSyncStatusCode { Error = 2, } +/** + * Contains statistics about the various sub-services in the Okta + * integration + */ export type PluginStatusOkta = { - orgUrl: string; - accessListSyncEnabled: boolean; - details?: PluginOktaDetails; -}; - -export type PluginOktaDetails = { ssoDetails?: OktaSsoDetails; appGroupSyncDetails?: OktaAppGroupSyncDetails; usersSyncDetails?: OktaUserSyncDetails; @@ -92,7 +90,6 @@ export type OktaAccessListSyncDetails = { numGroups: number; appFilters: string[]; groupFilters: string[]; - defaultOwners: string[]; /** * Error contains a textual description of the reason the last synchronization * failed. Only valid when StatusCode is OKTA_PLUGIN_SYNC_STATUS_CODE_ERROR. diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts index 80dd390f5ec9..d6c273cd4da3 100644 --- a/web/packages/teleport/src/services/integrations/types.ts +++ b/web/packages/teleport/src/services/integrations/types.ts @@ -33,18 +33,27 @@ import { Node } from '../nodes'; * while "plugin" resource is only supported in enterprise. Plugin * type exists in OS for easier typing when combining the resources * into one list. + * + * Generics: + * T is resource type "integration" or "plugin" + * K is the kind of integration (eg: aws-oidc) or plugin (eg: okta) + * SP is the provider-specific spec of integration or plugin + * SD is the provider-specific status containing status details + * - currently only defined for plugin resource */ export type Integration< T extends string = 'integration', K extends string = IntegrationKind, - S extends Record = IntegrationSpecAwsOidc, + SP extends Record = IntegrationSpecAwsOidc, + SD extends Record = null, > = { resourceType: T; kind: K; - spec?: S; + spec?: SP; name: string; details?: string; statusCode: IntegrationStatusCode; + status?: SD; }; // IntegrationKind string values should be in sync // with the backend value for defining the integration @@ -118,7 +127,32 @@ export type ExternalAuditStorageIntegration = Integration< ExternalAuditStorage >; -export type Plugin = Integration<'plugin', PluginKind, T>; +export type Plugin = Integration< + 'plugin', + PluginKind, + SP, + PluginStatus +>; + +export type PluginStatus = { + /** + * the status code of the plugin + */ + code: IntegrationStatusCode; + /** + * the time the plugin was last run + */ + lastRun: Date; + /** + * the last error message from the plugin + */ + errorMessage: string; + /** + * contains provider-specific status information + */ + details?: D; +}; + export type PluginSpec = | PluginOktaSpec | PluginSlackSpec @@ -143,13 +177,6 @@ export type PluginKind = | 'jamf' | 'entra-id'; -export type PluginStatus = { - name: string; - type: PluginKind; - statusCode: IntegrationStatusCode; - stats?: S; -}; - export type PluginOktaSpec = { // scimBearerToken is the plain text of the bearer token that Okta will use // to authenticate SCIM requests @@ -167,6 +194,15 @@ export type PluginOktaSpec = { // that were deemed not serious enough to fail the plugin installation, but // may effect the operation of advanced features like User Sync or SCIM. error: string; + /** + * is the set of usernames that the integration assigns as + * owners to any Access Lists that it creates + */ + defaultOwners: string[]; + /** + * the Okta org's base URL + */ + orgUrl: string; }; export type PluginSlackSpec = { diff --git a/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx b/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx index 54c594754187..1bff816f69e3 100644 --- a/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx +++ b/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx @@ -142,7 +142,6 @@ function Inner(props: { rootCluster: Cluster }) { updateSearch={updateSearch} pageIndicators={pageCount} filter={agentFilter} - showSearchBar={true} disableSearch={fetchStatus === 'loading'} /> )}