mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
-2
@@ -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";
|
||||
|
||||
|
||||
+45
-50
@@ -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>
|
||||
|
||||
|
||||
+39
-39
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+36
-36
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+29
-29
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+50
-57
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+148
-148
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user