chore: replace MUI tables (#20201)

I tried to break this work into smaller pieces, but since there are a
lot of dependent components, I decided to handle it in one larger chunk
and rely on Storybook to catch any bugs.

That said, let me know if you’d prefer a different approach!
This commit is contained in:
Bruno Quaresma
2025-10-09 10:09:37 -03:00
committed by GitHub
parent ed90ecf00e
commit 8f2394c256
33 changed files with 796 additions and 922 deletions
+1
View File
@@ -53,6 +53,7 @@
"@mui/material/Box": "Use a <div> instead.",
"@mui/material/Button": "Use a components/Button/Button instead.",
"@mui/material/styles": "Import from @emotion/react instead.",
"@mui/material/Table*": "Import from components/Table/Table instead.",
"lodash": "Use lodash/<name> instead."
}
}
+6 -6
View File
@@ -213,7 +213,7 @@ export async function verifyConfigFlagBoolean(
const value = opt.value ? "Enabled" : "Disabled";
const configOption = page.locator(
`div.options-table .option-${flag} .${type}`,
`table.options-table .option-${flag} .${type}`,
);
await expect(configOption).toHaveText(value);
}
@@ -225,7 +225,7 @@ export async function verifyConfigFlagNumber(
) {
const opt = findConfigOption(config, flag);
const configOption = page.locator(
`div.options-table .option-${flag} .option-value-number`,
`table.options-table .option-${flag} .option-value-number`,
);
await expect(configOption).toHaveText(String(opt.value));
}
@@ -238,7 +238,7 @@ export async function verifyConfigFlagString(
const opt = findConfigOption(config, flag);
const configOption = page.locator(
`div.options-table .option-${flag} .option-value-string`,
`table.options-table .option-${flag} .option-value-string`,
);
// biome-ignore lint/suspicious/noExplicitAny: opt.value is any
await expect(configOption).toHaveText(opt.value as any);
@@ -251,7 +251,7 @@ export async function verifyConfigFlagArray(
) {
const opt = findConfigOption(config, flag);
const configOption = page.locator(
`div.options-table .option-${flag} .option-array`,
`table.options-table .option-${flag} .option-array`,
);
// Verify array of options with simple dots
@@ -268,7 +268,7 @@ export async function verifyConfigFlagEntries(
) {
const opt = findConfigOption(config, flag);
const configOption = page.locator(
`div.options-table .option-${flag} .option-array`,
`table.options-table .option-${flag} .option-array`,
);
// Verify array of options with green marks.
@@ -296,7 +296,7 @@ export async function verifyConfigFlagDuration(
);
}
const configOption = page.locator(
`div.options-table .option-${flag} .option-value-string`,
`table.options-table .option-${flag} .option-value-string`,
);
await expect(configOption).toHaveText(humanDuration(opt.value / 1e6));
}
+1 -1
View File
@@ -19,7 +19,7 @@ test("experiments", async ({ page }) => {
await page.goto("/deployment/overview", { waitUntil: "domcontentloaded" });
const experimentsLocator = page.locator(
"div.options-table tr.option-experiments ul.option-array",
"table.options-table tr.option-experiments ul.option-array",
);
await expect(experimentsLocator).toBeVisible();
+1 -1
View File
@@ -47,7 +47,7 @@ async function verifyStrictTransportSecurity(
}
const configOption = page.locator(
`div.options-table .option-${flag} .option-value-string`,
`table.options-table .option-${flag} .option-value-string`,
);
await expect(configOption).toHaveText("Disabled");
}
+18 -17
View File
@@ -47,7 +47,7 @@ export const TableBody = React.forwardRef<
/>
));
const _TableFooter = React.forwardRef<
export const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
@@ -82,22 +82,23 @@ const tableRowVariants = cva(
},
);
export const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement> &
VariantProps<typeof tableRowVariants>
>(({ className, hover, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-0 border-b border-solid border-border transition-colors",
"data-[state=selected]:bg-muted",
tableRowVariants({ hover }),
className,
)}
{...props}
/>
));
export type TableRowProps = React.HTMLAttributes<HTMLTableRowElement> &
VariantProps<typeof tableRowVariants>;
export const TableRow = React.forwardRef<HTMLTableRowElement, TableRowProps>(
({ className, hover, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-0 border-b border-solid border-border transition-colors",
"data-[state=selected]:bg-muted",
tableRowVariants({ hover }),
className,
)}
{...props}
/>
),
);
export const TableHead = React.forwardRef<
HTMLTableCellElement,
@@ -1,8 +1,6 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableContainer from "@mui/material/TableContainer";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { CodeExample } from "components/CodeExample/CodeExample";
import { Table, TableBody } from "components/Table/Table";
import { TableEmpty } from "./TableEmpty";
const meta: Meta<typeof TableEmpty> = {
@@ -13,13 +11,11 @@ const meta: Meta<typeof TableEmpty> = {
},
decorators: [
(Story) => (
<TableContainer>
<Table>
<TableBody>
<Story />
</TableBody>
</Table>
</TableContainer>
<Table>
<TableBody>
<Story />
</TableBody>
</Table>
),
],
};
@@ -1,9 +1,8 @@
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import {
EmptyState,
type EmptyStateProps,
} from "components/EmptyState/EmptyState";
import { TableCell, TableRow } from "components/Table/Table";
import type { FC } from "react";
type TableEmptyProps = EmptyStateProps;
@@ -1,7 +1,5 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableContainer from "@mui/material/TableContainer";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Table, TableBody } from "components/Table/Table";
import { TableLoader } from "./TableLoader";
const meta: Meta<typeof TableLoader> = {
@@ -9,13 +7,11 @@ const meta: Meta<typeof TableLoader> = {
component: TableLoader,
decorators: [
(Story) => (
<TableContainer>
<Table>
<TableBody>
<Story />
</TableBody>
</Table>
</TableContainer>
<Table>
<TableBody>
<Story />
</TableBody>
</Table>
),
],
};
@@ -1,5 +1,8 @@
import TableCell from "@mui/material/TableCell";
import TableRow, { type TableRowProps } from "@mui/material/TableRow";
import {
TableCell,
TableRow,
type TableRowProps,
} from "components/Table/Table";
import { cloneElement, type FC, isValidElement, type ReactNode } from "react";
import { Loader } from "../Loader/Loader";
@@ -1,6 +1,5 @@
import { css, useTheme } from "@emotion/react";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import { TableCell, TableRow } from "components/Table/Table";
import type { FC } from "react";
import { createDisplayDate } from "./utils";
@@ -1,4 +1,4 @@
import TableRow, { type TableRowProps } from "@mui/material/TableRow";
import { TableRow, type TableRowProps } from "components/Table/Table";
import { forwardRef } from "react";
import { cn } from "utils/cn";
@@ -8,13 +8,8 @@ import {
MockAuditLogWithWorkspaceBuild,
MockUserOwner,
} from "testHelpers/entities";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Table, TableBody } from "components/Table/Table";
import { AuditLogRow } from "./AuditLogRow";
const meta: Meta<typeof AuditLogRow> = {
@@ -22,18 +17,11 @@ const meta: Meta<typeof AuditLogRow> = {
component: AuditLogRow,
decorators: [
(Story) => (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell style={{ paddingLeft: 32 }}>Logs</TableCell>
</TableRow>
</TableHead>
<TableBody>
<Story />
</TableBody>
</Table>
</TableContainer>
<Table>
<TableBody>
<Story />
</TableBody>
</Table>
),
],
};
@@ -1,13 +1,13 @@
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
import Collapse from "@mui/material/Collapse";
import Link from "@mui/material/Link";
import TableCell from "@mui/material/TableCell";
import Tooltip from "@mui/material/Tooltip";
import type { AuditLog, BuildReason } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { Stack } from "components/Stack/Stack";
import { StatusPill } from "components/StatusPill/StatusPill";
import { TableCell } from "components/Table/Table";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { InfoIcon, NetworkIcon } from "lucide-react";
import { type FC, useState } from "react";
+51 -57
View File
@@ -1,8 +1,3 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import type { AuditLog } from "api/typesGenerated";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import { EmptyState } from "components/EmptyState/EmptyState";
@@ -18,6 +13,7 @@ import {
} from "components/PaginationWidget/PaginationContainer";
import { Paywall } from "components/Paywall/Paywall";
import { Stack } from "components/Stack/Stack";
import { Table, TableBody, TableCell, TableRow } from "components/Table/Table";
import { TableLoader } from "components/TableLoader/TableLoader";
import { Timeline } from "components/Timeline/Timeline";
import type { ComponentProps, FC } from "react";
@@ -76,62 +72,60 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
query={paginationResult}
paginationUnitLabel="logs"
>
<TableContainer>
<Table>
<TableBody>
<ChooseOne>
{/* Error condition should just show an empty table. */}
<Cond condition={Boolean(error)}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="An error occurred while loading audit logs" />
</TableCell>
</TableRow>
</Cond>
<Table>
<TableBody>
<ChooseOne>
{/* Error condition should just show an empty table. */}
<Cond condition={Boolean(error)}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="An error occurred while loading audit logs" />
</TableCell>
</TableRow>
</Cond>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={isEmpty}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No audit logs available on this page" />
</TableCell>
</TableRow>
</Cond>
<Cond condition={isEmpty}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No audit logs available on this page" />
</TableCell>
</TableRow>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No audit logs available" />
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No audit logs available" />
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
{auditLogs && (
<Timeline
items={auditLogs}
getDate={(log) => new Date(log.time)}
row={(log) => (
<AuditLogRow
key={log.id}
auditLog={log}
showOrgDetails={showOrgDetails}
/>
)}
/>
)}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</TableContainer>
<Cond>
{auditLogs && (
<Timeline
items={auditLogs}
getDate={(log) => new Date(log.time)}
row={(log) => (
<AuditLogRow
key={log.id}
auditLog={log}
showOrgDetails={showOrgDetails}
/>
)}
/>
)}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</PaginationContainer>
</Cond>
@@ -1,8 +1,3 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import type { ConnectionLog } from "api/typesGenerated";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import { EmptyState } from "components/EmptyState/EmptyState";
@@ -18,6 +13,7 @@ import {
} from "components/PaginationWidget/PaginationContainer";
import { Paywall } from "components/Paywall/Paywall";
import { Stack } from "components/Stack/Stack";
import { Table, TableBody, TableCell, TableRow } from "components/Table/Table";
import { TableLoader } from "components/TableLoader/TableLoader";
import { Timeline } from "components/Timeline/Timeline";
import type { ComponentProps, FC } from "react";
@@ -75,61 +71,56 @@ export const ConnectionLogPageView: FC<ConnectionLogPageViewProps> = ({
query={paginationResult}
paginationUnitLabel="logs"
>
<TableContainer>
<Table>
<TableBody>
<ChooseOne>
{/* Error condition should just show an empty table. */}
<Cond condition={Boolean(error)}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="An error occurred while loading connection logs" />
</TableCell>
</TableRow>
</Cond>
<Table>
<TableBody>
<ChooseOne>
{/* Error condition should just show an empty table. */}
<Cond condition={Boolean(error)}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="An error occurred while loading connection logs" />
</TableCell>
</TableRow>
</Cond>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={isEmpty}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No connection logs available on this page" />
</TableCell>
</TableRow>
</Cond>
<Cond condition={isEmpty}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No connection logs available on this page" />
</TableCell>
</TableRow>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No connection logs available" />
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No connection logs available" />
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
{connectionLogs && (
<Timeline
items={connectionLogs}
getDate={(log) => new Date(log.connect_time)}
row={(log) => (
<ConnectionLogRow
key={log.id}
connectionLog={log}
/>
)}
/>
)}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</TableContainer>
<Cond>
{connectionLogs && (
<Timeline
items={connectionLogs}
getDate={(log) => new Date(log.connect_time)}
row={(log) => (
<ConnectionLogRow key={log.id} connectionLog={log} />
)}
/>
)}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</PaginationContainer>
</Cond>
@@ -3,7 +3,6 @@ import {
MockDisconnectedSSHConnectionLog,
MockWebConnectionLog,
} from "testHelpers/entities";
import TableContainer from "@mui/material/TableContainer";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Table, TableBody } from "components/Table/Table";
import { ConnectionLogRow } from "./ConnectionLogRow";
@@ -13,13 +12,11 @@ const meta: Meta<typeof ConnectionLogRow> = {
component: ConnectionLogRow,
decorators: [
(Story) => (
<TableContainer>
<Table>
<TableBody>
<Story />
</TableBody>
</Table>
</TableContainer>
<Table>
<TableBody>
<Story />
</TableBody>
</Table>
),
],
};
@@ -1,11 +1,11 @@
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import TableCell from "@mui/material/TableCell";
import Tooltip from "@mui/material/Tooltip";
import type { ConnectionLog } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { Stack } from "components/Stack/Stack";
import { StatusPill } from "components/StatusPill/StatusPill";
import { TableCell } from "components/Table/Table";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { InfoIcon, NetworkIcon } from "lucide-react";
import type { FC } from "react";
@@ -1,7 +1,5 @@
import type { Interpolation, Theme } from "@emotion/react";
import Checkbox from "@mui/material/Checkbox";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import type { BannerConfig } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import {
@@ -10,6 +8,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "components/DropdownMenu/DropdownMenu";
import { TableCell, TableRow } from "components/Table/Table";
import { EllipsisVertical } from "lucide-react";
import type { FC } from "react";
@@ -1,16 +1,18 @@
import { type CSSObject, useTheme } from "@emotion/react";
import Link from "@mui/material/Link";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type { BannerConfig } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { EmptyState } from "components/EmptyState/EmptyState";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { PlusIcon } from "lucide-react";
import { type FC, useState } from "react";
import { AnnouncementBannerDialog } from "./AnnouncementBannerDialog";
@@ -71,7 +73,7 @@ export const AnnouncementBannerSettings: FC<
overflow: "hidden",
}}
>
<div css={{ padding: "24px 24px 0" }}>
<div className="p-6">
<Stack
direction="row"
justifyContent="space-between"
@@ -105,49 +107,42 @@ export const AnnouncementBannerSettings: FC<
Display message banners to all users.
</div>
<div
css={[
theme.typography.body2 as CSSObject,
{ paddingTop: 16, margin: "0 -32px" },
]}
>
<TableContainer css={{ borderRadius: 0, borderBottom: "none" }}>
<Table>
<TableHead>
<TableRow>
<TableCell width="1%">Enabled</TableCell>
<TableCell>Message</TableCell>
<TableCell width="2%">Color</TableCell>
<TableCell width="1%" />
</TableRow>
</TableHead>
<TableBody>
{!isEntitled || banners.length < 1 ? (
<TableCell colSpan={999}>
<EmptyState
className="min-h-[160px]"
message="No announcement banners"
/>
</TableCell>
) : (
banners.map((banner, i) => (
<AnnouncementBannerItem
key={banner.message}
enabled={banner.enabled && Boolean(banner.message)}
backgroundColor={banner.background_color}
message={banner.message}
onEdit={() => setEditingBannerId(i)}
onUpdate={async (banner) => {
const newBanners = updateBanner(i, banner);
await onSubmit(newBanners);
}}
onDelete={() => setDeletingBannerId(i)}
/>
))
)}
</TableBody>
</Table>
</TableContainer>
<div css={[theme.typography.body2 as CSSObject, { paddingTop: 16 }]}>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[1%]">Enabled</TableHead>
<TableHead>Message</TableHead>
<TableHead className="w-[2%]">Color</TableHead>
<TableHead className="w-[1%]" />
</TableRow>
</TableHeader>
<TableBody>
{!isEntitled || banners.length < 1 ? (
<TableCell colSpan={999}>
<EmptyState
className="min-h-[160px]"
message="No announcement banners"
/>
</TableCell>
) : (
banners.map((banner, i) => (
<AnnouncementBannerItem
key={banner.message}
enabled={banner.enabled && Boolean(banner.message)}
backgroundColor={banner.background_color}
message={banner.message}
onEdit={() => setEditingBannerId(i)}
onUpdate={async (banner) => {
const newBanners = updateBanner(i, banner);
await onSubmit(newBanners);
}}
onDelete={() => setDeletingBannerId(i)}
/>
))
)}
</TableBody>
</Table>
</div>
</div>
@@ -1,10 +1,4 @@
import { css } from "@emotion/react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated";
import { Alert } from "components/Alert/Alert";
import { PremiumBadge } from "components/Badges/Badges";
@@ -14,6 +8,14 @@ import {
SettingsHeaderDocsLink,
SettingsHeaderTitle,
} from "components/SettingsHeader/SettingsHeader";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import type { FC } from "react";
import { docs } from "utils/docs";
@@ -60,9 +62,8 @@ export const ExternalAuthSettingsPageView: FC<
</Alert>
</div>
<TableContainer>
<Table
css={css`
<Table
css={css`
& td {
padding-top: 24px;
padding-bottom: 24px;
@@ -73,38 +74,37 @@ export const ExternalAuthSettingsPageView: FC<
padding-left: 32px;
}
`}
>
<TableHead>
>
<TableHeader>
<TableRow>
<TableHead className="w-1/3">ID</TableHead>
<TableHead className="w-1/3">Client ID</TableHead>
<TableHead className="w-1/3">Match</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{((config.external_auth === null ||
config.external_auth?.length === 0) && (
<TableRow>
<TableCell width="25%">ID</TableCell>
<TableCell width="25%">Client ID</TableCell>
<TableCell width="25%">Match</TableCell>
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No providers have been configured!
</div>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{((config.external_auth === null ||
config.external_auth?.length === 0) && (
<TableRow>
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No providers have been configured!
</div>
</TableCell>
</TableRow>
)) ||
config.external_auth?.map((git: ExternalAuthConfig) => {
const name = git.id || git.type;
return (
<TableRow key={name}>
<TableCell>{name}</TableCell>
<TableCell>{git.client_id}</TableCell>
<TableCell>{git.regex || "Not Set"}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
)) ||
config.external_auth?.map((git: ExternalAuthConfig) => {
const name = git.id || git.type;
return (
<TableRow key={name}>
<TableCell>{name}</TableCell>
<TableCell>{git.client_id}</TableCell>
<TableCell>{git.regex || "Not Set"}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</>
);
};
@@ -1,11 +1,5 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import Divider from "@mui/material/Divider";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type * as TypesGen from "api/typesGenerated";
import { Alert } from "components/Alert/Alert";
import { ErrorAlert } from "components/Alert/ErrorAlert";
@@ -22,6 +16,14 @@ import {
} from "components/SettingsHeader/SettingsHeader";
import { Spinner } from "components/Spinner/Spinner";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { TableLoader } from "components/TableLoader/TableLoader";
import { ChevronLeftIcon, CopyIcon } from "lucide-react";
import { type FC, useState } from "react";
@@ -233,38 +235,36 @@ const OAuth2AppSecretsTable: FC<OAuth2AppSecretsTableProps> = ({
</Button>
</Stack>
<TableContainer>
<Table>
<TableHead>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[80%]">Secret</TableHead>
<TableHead className="w-[20%]">Last Used</TableHead>
<TableHead className="w-[1%]" />
</TableRow>
</TableHeader>
<TableBody>
{isLoadingSecrets && <TableLoader />}
{!isLoadingSecrets && (!secrets || secrets.length === 0) && (
<TableRow>
<TableCell width="80%">Secret</TableCell>
<TableCell width="20%">Last Used</TableCell>
<TableCell width="1%" />
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No client secrets have been generated.
</div>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isLoadingSecrets && <TableLoader />}
{!isLoadingSecrets && (!secrets || secrets.length === 0) && (
<TableRow>
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No client secrets have been generated.
</div>
</TableCell>
</TableRow>
)}
{!isLoadingSecrets &&
secrets?.map((secret) => (
<OAuth2SecretRow
key={secret.id}
secret={secret}
mutatingResource={mutatingResource}
deleteAppSecret={deleteAppSecret}
/>
))}
</TableBody>
</Table>
</TableContainer>
)}
{!isLoadingSecrets &&
secrets?.map((secret) => (
<OAuth2SecretRow
key={secret.id}
secret={secret}
mutatingResource={mutatingResource}
deleteAppSecret={deleteAppSecret}
/>
))}
</TableBody>
</Table>
</>
);
};
@@ -1,10 +1,4 @@
import { useTheme } from "@emotion/react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type * as TypesGen from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Avatar } from "components/Avatar/Avatar";
@@ -15,6 +9,14 @@ import {
SettingsHeaderTitle,
} from "components/SettingsHeader/SettingsHeader";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { TableLoader } from "components/TableLoader/TableLoader";
import { useClickableTableRow } from "hooks/useClickableTableRow";
import { ChevronRightIcon, PlusIcon } from "lucide-react";
@@ -58,31 +60,29 @@ const OAuth2AppsSettingsPageView: FC<OAuth2AppsSettingsProps> = ({
{error && <ErrorAlert error={error} />}
<TableContainer css={{ marginTop: 32 }}>
<Table>
<TableHead>
<Table className="mt-8">
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead className="w-[1%]" />
</TableRow>
</TableHeader>
<TableBody>
{isLoading && <TableLoader />}
{apps?.map((app) => (
<OAuth2AppRow key={app.id} app={app} />
))}
{apps?.length === 0 && (
<TableRow>
<TableCell width="100%">Name</TableCell>
<TableCell width="1%" />
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No OAuth2 applications have been configured.
</div>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isLoading && <TableLoader />}
{apps?.map((app) => (
<OAuth2AppRow key={app.id} app={app} />
))}
{apps?.length === 0 && (
<TableRow>
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No OAuth2 applications have been configured.
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
)}
</TableBody>
</Table>
</>
);
};
@@ -1,11 +1,13 @@
import { css } from "@emotion/react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type { SerpentOption } from "api/typesGenerated";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import type { FC } from "react";
import {
OptionConfig,
@@ -27,9 +29,9 @@ const OptionsTable: FC<OptionsTableProps> = ({ options, additionalValues }) => {
}
return (
<TableContainer className="options-table">
<Table
css={css`
<Table
className="options-table"
css={css`
& td {
padding-top: 24px;
padding-bottom: 24px;
@@ -40,66 +42,65 @@ const OptionsTable: FC<OptionsTableProps> = ({ options, additionalValues }) => {
padding-left: 32px;
}
`}
>
<TableHead>
<TableRow>
<TableCell width="50%">Option</TableCell>
<TableCell width="50%">Value</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.values(options).map((option) => {
return (
<TableRow key={option.flag} className={`option-${option.flag}`}>
<TableCell>
<OptionName>{option.name}</OptionName>
<OptionDescription>{option.description}</OptionDescription>
<div
css={{
marginTop: 24,
display: "flex",
flexWrap: "wrap",
gap: 8,
}}
>
{option.flag && (
<OptionConfig isSource={option.value_source === "flag"}>
<OptionConfigFlag>CLI</OptionConfigFlag>
--{option.flag}
</OptionConfig>
)}
{option.flag_shorthand && (
<OptionConfig isSource={option.value_source === "flag"}>
<OptionConfigFlag>CLI</OptionConfigFlag>-
{option.flag_shorthand}
</OptionConfig>
)}
{option.env && (
<OptionConfig isSource={option.value_source === "env"}>
<OptionConfigFlag>ENV</OptionConfigFlag>
{option.env}
</OptionConfig>
)}
{option.yaml && (
<OptionConfig isSource={option.value_source === "yaml"}>
<OptionConfigFlag>YAML</OptionConfigFlag>
{option.yaml}
</OptionConfig>
)}
</div>
</TableCell>
>
<TableHeader>
<TableRow>
<TableHead className="w-1/2">Option</TableHead>
<TableHead className="w-1/2">Value</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Object.values(options).map((option) => {
return (
<TableRow key={option.flag} className={`option-${option.flag}`}>
<TableCell>
<OptionName>{option.name}</OptionName>
<OptionDescription>{option.description}</OptionDescription>
<div
css={{
marginTop: 24,
display: "flex",
flexWrap: "wrap",
gap: 8,
}}
>
{option.flag && (
<OptionConfig isSource={option.value_source === "flag"}>
<OptionConfigFlag>CLI</OptionConfigFlag>
--{option.flag}
</OptionConfig>
)}
{option.flag_shorthand && (
<OptionConfig isSource={option.value_source === "flag"}>
<OptionConfigFlag>CLI</OptionConfigFlag>-
{option.flag_shorthand}
</OptionConfig>
)}
{option.env && (
<OptionConfig isSource={option.value_source === "env"}>
<OptionConfigFlag>ENV</OptionConfigFlag>
{option.env}
</OptionConfig>
)}
{option.yaml && (
<OptionConfig isSource={option.value_source === "yaml"}>
<OptionConfigFlag>YAML</OptionConfigFlag>
{option.yaml}
</OptionConfig>
)}
</div>
</TableCell>
<TableCell>
<OptionValue>
{optionValue(option, additionalValues)}
</OptionValue>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<TableCell>
<OptionValue>
{optionValue(option, additionalValues)}
</OptionValue>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
};
@@ -1,13 +1,6 @@
import type { Interpolation, Theme } from "@emotion/react";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableFooter from "@mui/material/TableFooter";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import TextField from "@mui/material/TextField";
import { isApiValidationError } from "api/errors";
import { RBACResourceActions } from "api/rbacresourcesGenerated";
@@ -29,6 +22,15 @@ import {
} from "components/SettingsHeader/SettingsHeader";
import { Spinner } from "components/Spinner/Spinner";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { useFormik } from "formik";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { type ChangeEvent, type FC, useState } from "react";
@@ -255,55 +257,46 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
};
return (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Permission</TableCell>
<TableCell
align="right"
sx={{ paddingTop: 0.4, paddingBottom: 0.4 }}
>
<ShowAllResourcesCheckbox
showAllResources={showAllResources}
setShowAllResources={setShowAllResources}
/>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.entries(resourceActions).map(([resourceKey, value]) => {
return (
<PermissionCheckboxGroup
key={resourceKey}
checkedActions={checkedActions?.filter(
(a) => a.resource_type === resourceKey,
)}
resourceKey={resourceKey}
value={value}
form={form}
handleActionCheckChange={handleActionCheckChange}
handleResourceCheckChange={handleResourceCheckChange}
/>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TableCell
align="right"
colSpan={2}
sx={{ paddingTop: 0.4, paddingBottom: 0.4, paddingRight: 4 }}
>
<ShowAllResourcesCheckbox
showAllResources={showAllResources}
setShowAllResources={setShowAllResources}
/>
</TableCell>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
<Table>
<TableHeader>
<TableRow>
<TableHead>Permission</TableHead>
<TableHead className="py-1 text-right">
<ShowAllResourcesCheckbox
showAllResources={showAllResources}
setShowAllResources={setShowAllResources}
/>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Object.entries(resourceActions).map(([resourceKey, value]) => {
return (
<PermissionCheckboxGroup
key={resourceKey}
checkedActions={checkedActions?.filter(
(a) => a.resource_type === resourceKey,
)}
resourceKey={resourceKey}
value={value}
form={form}
handleActionCheckChange={handleActionCheckChange}
handleResourceCheckChange={handleResourceCheckChange}
/>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TableCell align="right" colSpan={2} className="py-1 pr-1">
<ShowAllResourcesCheckbox
showAllResources={showAllResources}
setShowAllResources={setShowAllResources}
/>
</TableCell>
</TableRow>
</TableFooter>
</Table>
);
};
@@ -333,7 +326,7 @@ const PermissionCheckboxGroup: FC<PermissionCheckboxGroupProps> = ({
}) => {
return (
<TableRow key={resourceKey}>
<TableCell sx={{ paddingLeft: 2 }} colSpan={2}>
<TableCell className="pl-0.5" colSpan={2}>
<li key={resourceKey} css={styles.checkBoxes}>
<Checkbox
size="small"
@@ -1,11 +1,11 @@
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
import TableCell from "@mui/material/TableCell";
import type { TemplateVersion } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { Button } from "components/Button/Button";
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
import { TableCell } from "components/Table/Table";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { useClickableTableRow } from "hooks/useClickableTableRow";
import type { FC } from "react";
@@ -1,10 +1,6 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import type * as TypesGen from "api/typesGenerated";
import { EmptyState } from "components/EmptyState/EmptyState";
import { Table, TableBody, TableCell, TableRow } from "components/Table/Table";
import { TableLoader } from "components/TableLoader/TableLoader";
import { Timeline } from "components/Timeline/Timeline";
import type { FC } from "react";
@@ -49,39 +45,37 @@ export const VersionsTable: FC<VersionsTableProps> = ({
)?.id;
return (
<TableContainer>
<Table data-testid="versions-table">
<TableBody>
{versions ? (
<Timeline
items={[...versions].reverse()}
getDate={(version) => new Date(version.created_at)}
row={(version) => (
<VersionRow
onArchiveClick={onArchiveClick}
onPromoteClick={onPromoteClick}
version={version}
key={version.id}
isActive={activeVersionId === version.id}
isLatest={latestVersionId === version.id}
/>
)}
/>
) : (
<TableLoader />
)}
<Table data-testid="versions-table">
<TableBody>
{versions ? (
<Timeline
items={[...versions].reverse()}
getDate={(version) => new Date(version.created_at)}
row={(version) => (
<VersionRow
onArchiveClick={onArchiveClick}
onPromoteClick={onPromoteClick}
version={version}
key={version.id}
isActive={activeVersionId === version.id}
isLatest={latestVersionId === version.id}
/>
)}
/>
) : (
<TableLoader />
)}
{versions && versions.length === 0 && (
<TableRow>
<TableCell colSpan={999}>
<div css={{ padding: 32 }}>
<EmptyState message={Language.emptyMessage} />
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
{versions && versions.length === 0 && (
<TableRow>
<TableCell colSpan={999}>
<div css={{ padding: 32 }}>
<EmptyState message={Language.emptyMessage} />
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
);
};
@@ -1,12 +1,6 @@
import type { Interpolation, Theme } from "@emotion/react";
import MenuItem from "@mui/material/MenuItem";
import Select, { type SelectProps } from "@mui/material/Select";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type {
Group,
ReducedUser,
@@ -29,6 +23,14 @@ import { EmptyState } from "components/EmptyState/EmptyState";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { Spinner } from "components/Spinner/Spinner";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { TableLoader } from "components/TableLoader/TableLoader";
import { EllipsisVertical, UserPlusIcon } from "lucide-react";
import { type FC, useState } from "react";
@@ -227,153 +229,151 @@ export const TemplatePermissionsPageView: FC<
}
/>
)}
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell width="60%">Member</TableCell>
<TableCell width="40%">Role</TableCell>
<TableCell width="1%" />
</TableRow>
</TableHead>
<TableBody>
<ChooseOne>
<Cond condition={!templateACL}>
<TableLoader />
</Cond>
<Cond condition={isEmpty}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState
message="No members yet"
description="Add a member using the controls above"
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[60%]">Member</TableHead>
<TableHead className="w-[40%]">Role</TableHead>
<TableHead className="w-[1%]" />
</TableRow>
</TableHeader>
<TableBody>
<ChooseOne>
<Cond condition={!templateACL}>
<TableLoader />
</Cond>
<Cond condition={isEmpty}>
<TableRow>
<TableCell colSpan={999}>
<EmptyState
message="No members yet"
description="Add a member using the controls above"
/>
</TableCell>
</TableRow>
</Cond>
<Cond>
{templateACL?.group.map((group) => (
<TableRow key={group.id}>
<TableCell>
<AvatarData
avatar={
<Avatar
size="lg"
fallback={group.display_name || group.name}
src={group.avatar_url}
/>
}
title={group.display_name || group.name}
subtitle={getGroupSubtitle(group)}
/>
</TableCell>
<TableCell>
<ChooseOne>
<Cond condition={canUpdatePermissions}>
<RoleSelect
value={group.role}
disabled={updatingGroupId === group.id}
onChange={(event) => {
onUpdateGroup(
group,
event.target.value as TemplateRole,
);
}}
/>
</Cond>
<Cond>
<div css={styles.role}>{group.role}</div>
</Cond>
</ChooseOne>
</TableCell>
<TableCell>
{canUpdatePermissions && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon-lg"
variant="subtle"
aria-label="Open menu"
>
<EllipsisVertical aria-hidden="true" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
onClick={() => onRemoveGroup(group)}
>
Remove
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</TableCell>
</TableRow>
</Cond>
<Cond>
{templateACL?.group.map((group) => (
<TableRow key={group.id}>
<TableCell>
<AvatarData
avatar={
<Avatar
size="lg"
fallback={group.display_name || group.name}
src={group.avatar_url}
/>
}
title={group.display_name || group.name}
subtitle={getGroupSubtitle(group)}
/>
</TableCell>
<TableCell>
<ChooseOne>
<Cond condition={canUpdatePermissions}>
<RoleSelect
value={group.role}
disabled={updatingGroupId === group.id}
onChange={(event) => {
onUpdateGroup(
group,
event.target.value as TemplateRole,
);
}}
/>
</Cond>
<Cond>
<div css={styles.role}>{group.role}</div>
</Cond>
</ChooseOne>
</TableCell>
))}
<TableCell>
{canUpdatePermissions && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon-lg"
variant="subtle"
aria-label="Open menu"
>
<EllipsisVertical aria-hidden="true" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
onClick={() => onRemoveGroup(group)}
>
Remove
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</TableCell>
</TableRow>
))}
{templateACL?.users.map((user) => (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
src={user.avatar_url}
/>
</TableCell>
<TableCell>
<ChooseOne>
<Cond condition={canUpdatePermissions}>
<RoleSelect
value={user.role}
disabled={updatingUserId === user.id}
onChange={(event) => {
onUpdateUser(
user,
event.target.value as TemplateRole,
);
}}
/>
</Cond>
<Cond>
<div css={styles.role}>{user.role}</div>
</Cond>
</ChooseOne>
</TableCell>
{templateACL?.users.map((user) => (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
src={user.avatar_url}
/>
</TableCell>
<TableCell>
<ChooseOne>
<Cond condition={canUpdatePermissions}>
<RoleSelect
value={user.role}
disabled={updatingUserId === user.id}
onChange={(event) => {
onUpdateUser(
user,
event.target.value as TemplateRole,
);
}}
/>
</Cond>
<Cond>
<div css={styles.role}>{user.role}</div>
</Cond>
</ChooseOne>
</TableCell>
<TableCell>
{canUpdatePermissions && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon-lg"
variant="subtle"
aria-label="Open menu"
>
<EllipsisVertical aria-hidden="true" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
onClick={() => onRemoveUser(user)}
>
Remove
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</TableCell>
</TableRow>
))}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</TableContainer>
<TableCell>
{canUpdatePermissions && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon-lg"
variant="subtle"
aria-label="Open menu"
>
<EllipsisVertical aria-hidden="true" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
onClick={() => onRemoveUser(user)}
>
Remove
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</TableCell>
</TableRow>
))}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</Stack>
</>
);
@@ -1,12 +1,5 @@
import { useTheme } from "@emotion/react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Tooltip from "@mui/material/Tooltip";
import visuallyHidden from "@mui/utils/visuallyHidden";
import { externalAuthProvider } from "api/queries/externalAuth";
import type {
ExternalAuthLink,
@@ -25,6 +18,14 @@ import {
import { Loader } from "components/Loader/Loader";
import { Spinner } from "components/Spinner/Spinner";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { TableEmpty } from "components/TableEmpty/TableEmpty";
import type { ExternalAuthPollingState } from "hooks/useExternalAuth";
import { EllipsisVertical, RefreshCcwIcon } from "lucide-react";
@@ -58,41 +59,39 @@ export const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
}
return (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Application</TableCell>
<TableCell>
<span aria-hidden css={{ ...visuallyHidden }}>
Link to connect
</span>
</TableCell>
<TableCell width="1%" />
</TableRow>
</TableHead>
<TableBody>
{auths.providers === null || auths.providers?.length === 0 ? (
<TableEmpty message="No providers have been configured" />
) : (
auths.providers?.map((app) => (
<ExternalAuthRow
key={app.id}
app={app}
unlinked={unlinked}
link={auths.links.find((l) => l.provider_id === app.id)}
onUnlinkExternalAuth={() => {
onUnlinkExternalAuth(app);
}}
onValidateExternalAuth={() => {
onValidateExternalAuth(app.id);
}}
/>
))
)}
</TableBody>
</Table>
</TableContainer>
<Table>
<TableHeader>
<TableRow>
<TableHead>Application</TableHead>
<TableHead>
<span aria-hidden className="sr-only">
Link to connect
</span>
</TableHead>
<TableHead className="w-[1%]" />
</TableRow>
</TableHeader>
<TableBody>
{auths.providers === null || auths.providers?.length === 0 ? (
<TableEmpty message="No providers have been configured" />
) : (
auths.providers?.map((app) => (
<ExternalAuthRow
key={app.id}
app={app}
unlinked={unlinked}
link={auths.links.find((l) => l.provider_id === app.id)}
onUnlinkExternalAuth={() => {
onUnlinkExternalAuth(app);
}}
onValidateExternalAuth={() => {
onValidateExternalAuth(app.id);
}}
/>
))
)}
</TableBody>
</Table>
);
};
@@ -1,14 +1,16 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type * as TypesGen from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Avatar } from "components/Avatar/Avatar";
import { Button } from "components/Button/Button";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { TableLoader } from "components/TableLoader/TableLoader";
import type { FC } from "react";
@@ -29,31 +31,29 @@ const OAuth2ProviderPageView: FC<OAuth2ProviderPageViewProps> = ({
<>
{error && <ErrorAlert error={error} />}
<TableContainer>
<Table>
<TableHead>
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead className="w-[1%]" />
</TableRow>
</TableHeader>
<TableBody>
{isLoading && <TableLoader />}
{apps?.map((app) => (
<OAuth2AppRow key={app.id} app={app} revoke={revoke} />
))}
{apps?.length === 0 && (
<TableRow>
<TableCell width="100%">Name</TableCell>
<TableCell width="1%" />
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No OAuth2 applications have been authorized.
</div>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isLoading && <TableLoader />}
{apps?.map((app) => (
<OAuth2AppRow key={app.id} app={app} revoke={revoke} />
))}
{apps?.length === 0 && (
<TableRow>
<TableCell colSpan={999}>
<div css={{ textAlign: "center" }}>
No OAuth2 applications have been authorized.
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
)}
</TableBody>
</Table>
</>
);
};
@@ -1,15 +1,17 @@
import { useTheme } from "@emotion/react";
import IconButton from "@mui/material/IconButton";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type { APIKeyWithOwner } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { TableEmpty } from "components/TableEmpty/TableEmpty";
import { TableLoader } from "components/TableLoader/TableLoader";
import dayjs from "dayjs";
@@ -49,84 +51,83 @@ export const TokensPageView: FC<TokensPageViewProps> = ({
<Stack>
{Boolean(getTokensError) && <ErrorAlert error={getTokensError} />}
{Boolean(deleteTokenError) && <ErrorAlert error={deleteTokenError} />}
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell width="20%">ID</TableCell>
<TableCell width="20%">Name</TableCell>
<TableCell width="20%">Last Used</TableCell>
<TableCell width="20%">Expires At</TableCell>
<TableCell width="20%">Created At</TableCell>
<TableCell width="0%" />
</TableRow>
</TableHead>
<TableBody>
<ChooseOne>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={hasLoaded && (!tokens || tokens.length === 0)}>
<TableEmpty message="No tokens found" />
</Cond>
<Cond>
{tokens?.map((token) => {
return (
<TableRow
key={token.id}
data-testid={`token-${token.id}`}
tabIndex={0}
>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
{token.id}
</span>
</TableCell>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
{token.token_name}
</span>
</TableCell>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-1/5">ID</TableHead>
<TableHead className="w-1/5">Name</TableHead>
<TableHead className="w-1/5">Last Used</TableHead>
<TableHead className="w-1/5">Expires At</TableHead>
<TableHead className="w-1/5">Created At</TableHead>
<TableHead className="w-[1%]" />
</TableRow>
</TableHeader>
<TableBody>
<ChooseOne>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={hasLoaded && (!tokens || tokens.length === 0)}>
<TableEmpty message="No tokens found" />
</Cond>
<Cond>
{tokens?.map((token) => {
return (
<TableRow
key={token.id}
data-testid={`token-${token.id}`}
tabIndex={0}
>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
{token.id}
</span>
</TableCell>
<TableCell>{lastUsedOrNever(token.last_used)}</TableCell>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
{token.token_name}
</span>
</TableCell>
<TableCell>
<span
style={{ color: theme.palette.text.secondary }}
data-chromatic="ignore"
<TableCell>{lastUsedOrNever(token.last_used)}</TableCell>
<TableCell>
<span
style={{ color: theme.palette.text.secondary }}
data-chromatic="ignore"
>
{dayjs(token.expires_at).fromNow()}
</span>
</TableCell>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
{dayjs(token.created_at).fromNow()}
</span>
</TableCell>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
<IconButton
onClick={() => {
onDelete(token);
}}
size="medium"
aria-label="Delete token"
>
{dayjs(token.expires_at).fromNow()}
</span>
</TableCell>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
{dayjs(token.created_at).fromNow()}
</span>
</TableCell>
<TableCell>
<span style={{ color: theme.palette.text.secondary }}>
<IconButton
onClick={() => {
onDelete(token);
}}
size="medium"
aria-label="Delete token"
>
<TrashIcon className="size-icon-sm" />
</IconButton>
</span>
</TableCell>
</TableRow>
);
})}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</TableContainer>
<TrashIcon className="size-icon-sm" />
</IconButton>
</span>
</TableCell>
</TableRow>
);
})}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</Stack>
);
};
@@ -1,6 +1,4 @@
import { useTheme } from "@emotion/react";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import type { Region, WorkspaceProxy } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { AvatarData } from "components/Avatar/AvatarData";
@@ -10,6 +8,7 @@ import {
NotReachableBadge,
NotRegisteredBadge,
} from "components/Badges/Badges";
import { TableCell, TableRow } from "components/Table/Table";
import type { ProxyLatencyReport } from "contexts/useProxyLatency";
import type { FC, ReactNode } from "react";
import { getLatencyColor } from "utils/latency";
@@ -1,9 +1,3 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import type { Region } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
@@ -13,6 +7,13 @@ import {
SettingsHeaderTitle,
} from "components/SettingsHeader/SettingsHeader";
import { Stack } from "components/Stack/Stack";
import {
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from "components/Table/Table";
import { TableEmpty } from "components/TableEmpty/TableEmpty";
import { TableLoader } from "components/TableLoader/TableLoader";
import type { ProxyLatencyReport } from "contexts/useProxyLatency";
@@ -51,40 +52,35 @@ export const WorkspaceProxyView: FC<WorkspaceProxyViewProps> = ({
<ErrorAlert error={getWorkspaceProxiesError} />
)}
{Boolean(selectProxyError) && <ErrorAlert error={selectProxyError} />}
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell width="70%">Proxy</TableCell>
<TableCell width="10%" css={{ textAlign: "right" }}>
Status
</TableCell>
<TableCell width="20%" css={{ textAlign: "right" }}>
Latency
</TableCell>
</TableRow>
</TableHead>
<TableBody>
<ChooseOne>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={hasLoaded && proxies?.length === 0}>
<TableEmpty message="No workspace proxies found" />
</Cond>
<Cond>
{proxies?.map((proxy) => (
<ProxyRow
latency={proxyLatencies?.[proxy.id]}
key={proxy.id}
proxy={proxy}
/>
))}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</TableContainer>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[70%]">Proxy</TableHead>
<TableHead className="w-[10%] text-right">Status</TableHead>
<TableHead className="w-[20%] text-right">Latency</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<ChooseOne>
<Cond condition={isLoading}>
<TableLoader />
</Cond>
<Cond condition={hasLoaded && proxies?.length === 0}>
<TableEmpty message="No workspace proxies found" />
</Cond>
<Cond>
{proxies?.map((proxy) => (
<ProxyRow
latency={proxyLatencies?.[proxy.id]}
key={proxy.id}
proxy={proxy}
/>
))}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</Stack>
);
};
-68
View File
@@ -6,7 +6,6 @@ import {
BODY_FONT_FAMILY,
BUTTON_LG_HEIGHT,
BUTTON_MD_HEIGHT,
borderRadius,
} from "./constants";
import tw from "./tailwindColors";
@@ -59,73 +58,6 @@ export const components = {
}),
},
},
MuiTableContainer: {
styleOverrides: {
root: ({ theme }) => ({
borderRadius,
border: `1px solid ${theme.palette.divider}`,
}),
},
},
MuiTable: {
styleOverrides: {
root: ({ theme }) => ({
borderCollapse: "unset",
border: "none",
boxShadow: `0 0 0 1px ${theme.palette.background.default} inset`,
overflow: "hidden",
"& td": {
paddingTop: 16,
paddingBottom: 16,
background: "transparent",
},
[theme.breakpoints.down("md")]: {
minWidth: 1000,
},
}),
},
},
MuiTableCell: {
styleOverrides: {
head: ({ theme }) => ({
fontSize: 14,
color: theme.palette.text.secondary,
fontWeight: 500,
background: theme.palette.background.paper,
borderWidth: 2,
}),
root: ({ theme }) => ({
fontSize: 14,
background: theme.palette.background.paper,
borderBottom: `1px solid ${theme.palette.divider}`,
padding: "12px 8px",
// This targets the first+last td elements, and also the first+last elements
// of a TableCellLink.
"&:not(:only-child):first-of-type, &:not(:only-child):first-of-type > a":
{
paddingLeft: 32,
},
"&:not(:only-child):last-child, &:not(:only-child):last-child > a": {
paddingRight: 32,
},
}),
},
},
MuiTableRow: {
styleOverrides: {
root: ({ theme }) => ({
"&:last-child .MuiTableCell-body": {
borderBottom: 0,
},
"&.MuiTableRow-hover:hover": {
backgroundColor: theme.palette.background.paper,
},
}),
},
},
MuiLink: {
defaultProps: {
underline: "hover",