diff --git a/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx b/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx index 0f8007c0484b4..9cbb12638b136 100644 --- a/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx +++ b/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx @@ -1,7 +1,6 @@ import { Edit, MoreVertical, Search, Trash } from 'lucide-react' import { useState } from 'react' -import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { DocsButton } from 'components/ui/DocsButton' import SchemaSelector from 'components/ui/SchemaSelector' @@ -15,11 +14,18 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' import { Button, + Card, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, } from 'ui' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' import CreateEnumeratedTypeSidePanel from './CreateEnumeratedTypeSidePanel' @@ -94,75 +100,79 @@ const EnumeratedTypes = () => { )} {isSuccess && ( - Schema, - Name, - Values, - , - ]} - body={ - <> - {filteredEnumeratedTypes.length === 0 && search.length === 0 && ( - - -

No enumerated types created yet

-

- There are no enumerated types found in the schema "{selectedSchema}" -

-
-
- )} - {filteredEnumeratedTypes.length === 0 && search.length > 0 && ( - - -

No results found

-

- Your search for "{search}" did not return any results -

-
-
- )} - {filteredEnumeratedTypes.length > 0 && - filteredEnumeratedTypes.map((type) => ( - - -

{type.schema}

-
- {type.name} - {type.enums.join(', ')} - - {!isSchemaLocked && ( -
- - -
- )} -
-
- ))} - - } - /> + +
+ + + Schema + Name + Values + + + + + <> + {filteredEnumeratedTypes.length === 0 && search.length === 0 && ( + + +

No enumerated types created yet

+

+ There are no enumerated types found in the schema "{selectedSchema}" +

+
+
+ )} + {filteredEnumeratedTypes.length === 0 && search.length > 0 && ( + + +

No results found

+

+ Your search for "{search}" did not return any results +

+
+
+ )} + {filteredEnumeratedTypes.length > 0 && + filteredEnumeratedTypes.map((type) => ( + + +

{type.schema}

+
+ {type.name} + {type.enums.join(', ')} + + {!isSchemaLocked && ( +
+ + +
+ )} +
+
+ ))} + +
+
+ )} { +const ExtensionRow = ({ extension }: ExtensionRowProps) => { const { data: project } = useSelectedProjectQuery() const isOn = extension.installed_version !== null const isOrioleDb = useIsOrioleDb() @@ -35,7 +35,6 @@ const ExtensionCard = ({ extension }: ExtensionCardProps) => { const orioleDbCheck = isOrioleDb && extension.name === 'orioledb' const disabled = !canUpdateExtensions || orioleDbCheck - const X_PADDING = 'px-5' const extensionMeta = extensions.find((item) => item.name === extension.name) const docsUrl = extensionMeta?.link.startsWith('/guides') ? `https://supabase.com/docs${extensionMeta?.link}` @@ -60,20 +59,93 @@ const ExtensionCard = ({ extension }: ExtensionCardProps) => { return ( <> -
-
-
-

+ + +
+ {extension.name} -

-

- {extension?.installed_version ?? extension.default_version} -

+ + {extensionMeta?.deprecated && extensionMeta?.deprecated.length > 0 && ( + } + className="rounded-full" + tooltip={{ + content: { + text: `The extension is deprecated and will be removed in ${extensionMeta.deprecated.join(', ')}.`, + }, + }} + > + Deprecated + + )}
+ + + + {extension?.installed_version ?? extension.default_version} + + + {isOn ? extension.schema : '-'} + + +

+ {extension.comment} +

+
+ + + {extensionMeta?.product ? ( +
+ {extensionMeta.product_url ? ( + + {extensionMeta.product} + + ) : ( + {extensionMeta.product} + )} + {!isOn && ( + + Install extension to use {extensionMeta.product} + + )} +
+ ) : ( + - + )} +
+ + + {extensionMeta?.github_url && ( + + )} + {docsUrl !== undefined && ( + + )} + + {isDisabling ? ( ) : ( @@ -98,88 +170,8 @@ const ExtensionCard = ({ extension }: ExtensionCardProps) => { )} )} -
- - {isOn && ( -
-

- Installed in {extension.schema} schema -

-
- )} - -
-

{extension.comment}

-
- {extensionMeta?.github_url && ( - - )} - {docsUrl !== undefined && ( - - )} - {extensionMeta?.deprecated && extensionMeta?.deprecated.length > 0 && ( - } - className="rounded-full" - tooltip={{ - content: { - text: `The extension is deprecated and will be removed in ${extensionMeta.deprecated.join(', ')}.`, - }, - }} - > - Deprecated - - )} -
-
- - {extensionMeta?.product && ( -
-
- -
-
-

- {extension.name} is used by{' '} - {extensionMeta.product_url ? ( - - {extensionMeta.product} - - ) : ( - extensionMeta.product - )} -

- {!isOn && ( -

- Install extension to use {extensionMeta.product} -

- )} -
-
- )} -
+ + { ) } -export default ExtensionCard +export default ExtensionRow diff --git a/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx b/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx index b9e629dcda1c5..4aba0c0fa6156 100644 --- a/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx +++ b/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx @@ -7,13 +7,12 @@ import { useParams } from 'common' import { DocsButton } from 'components/ui/DocsButton' import InformationBox from 'components/ui/InformationBox' import NoSearchResults from 'components/ui/NoSearchResults' -import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { Input } from 'ui' -import ExtensionCard from './ExtensionCard' -import ExtensionCardSkeleton from './ExtensionCardSkeleton' +import { Card, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' +import ExtensionRow from './ExtensionRow' import { HIDDEN_EXTENSIONS, SEARCH_TERMS } from './Extensions.constants' const Extensions = () => { @@ -73,50 +72,46 @@ const Extensions = () => { )} {isLoading ? ( -
-
- - -
- {Array.from({ length: 6 }).map((_, index) => ( - - ))} -
-
-
+ ) : ( - <> - {extensions.length === 0 && ( - setFilterString('')} - /> - )} - -
- {enabledExtensions.length > 0 && ( -
-

Enabled extensions

-
- {enabledExtensions.map((extension) => ( - - ))} -
-
- )} - - {disabledExtensions.length > 0 && ( -
-

Available extensions

-
- {disabledExtensions.map((extension) => ( - - ))} -
-
- )} -
- +
+ + + + + Name + Version + Schema + Description + Used by + Links + + Enabled + + + + + {[...enabledExtensions, ...disabledExtensions].map((extension) => ( + + ))} + {extensions.length === 0 && ( + + + setFilterString('')} + /> + + + )} + +
+
+
)} ) diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx index 4d72806bd16a7..680755b6c3263 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx @@ -3,7 +3,6 @@ import { includes, noop, sortBy } from 'lodash' import { Edit, Edit2, FileText, MoreVertical, Trash } from 'lucide-react' import { useRouter } from 'next/router' -import Table from 'components/to-be-cleaned/Table' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabaseFunctionsQuery } from 'data/database-functions/database-functions-query' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' @@ -16,6 +15,8 @@ import { DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, + TableRow, + TableCell, } from 'ui' interface FunctionListProps { @@ -57,27 +58,27 @@ const FunctionList = ({ if (_functions.length === 0 && filterString.length === 0) { return ( - - + +

No functions created yet

There are no functions found in the schema "{schema}"

-
-
+ + ) } if (_functions.length === 0 && filterString.length > 0) { return ( - - + +

No results found

Your search for "{filterString}" did not return any results

-
-
+ + ) } @@ -87,8 +88,8 @@ const FunctionList = ({ const isApiDocumentAvailable = schema == 'public' && x.return_type !== 'trigger' return ( - - + + - - + +

{x.argument_types || '-'}

-
- + +

{x.return_type}

-
- {x.security_definer ? 'Definer' : 'Invoker'} - + + + {x.security_definer ? 'Definer' : 'Invoker'} + + {!isLocked && (
{canUpdateFunctions ? ( @@ -189,8 +192,8 @@ const FunctionList = ({ )}
)} -
-
+ + ) })} diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index 9f47830498920..bc8f73b826ac5 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -6,7 +6,6 @@ import { useRouter } from 'next/router' import { useParams } from 'common' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' -import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import SchemaSelector from 'components/ui/SchemaSelector' @@ -18,7 +17,17 @@ import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' -import { AiIconAnimation, Input } from 'ui' +import { + AiIconAnimation, + Input, + Table, + TableHeader, + TableHead, + TableBody, + TableRow, + TableCell, + Card, +} from 'ui' import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning' import FunctionList from './FunctionList' @@ -168,34 +177,34 @@ const FunctionsList = ({ {isSchemaLocked && } - - - Name - - Arguments - - - Return type - - - Security - - - - } - body={ - - } - /> + +
+ + + Name + + Arguments + + + Return type + + + Security + + + + + + + +
+ )} diff --git a/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx b/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx index 3fe2c0dff977f..8cabd076c5c82 100644 --- a/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx +++ b/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx @@ -4,7 +4,6 @@ import { useEffect, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' -import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import CodeEditor from 'components/ui/CodeEditor/CodeEditor' import SchemaSelector from 'components/ui/SchemaSelector' @@ -15,7 +14,18 @@ import { useSchemasQuery } from 'data/database/schemas-query' import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' -import { Button, Input, SidePanel } from 'ui' +import { + Button, + Input, + SidePanel, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + TableHeader, + Card, +} from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' import CreateIndexSidePanel from './CreateIndexSidePanel' @@ -139,49 +149,51 @@ const Indexes = () => { )} {isSuccessIndexes && ( -
- Schema, - Table, - Name, - , - ]} - body={ - <> +
+ +
+ + + Schema + Table + Name + + + + {sortedIndexes.length === 0 && search.length === 0 && ( - - + +

No indexes created yet

There are no indexes found in the schema "{selectedSchema}"

-
-
+ + )} {sortedIndexes.length === 0 && search.length > 0 && ( - - + +

No results found

Your search for "{search}" did not return any results

-
-
+ + )} {indexes.length > 0 && indexes.map((index) => ( - - + +

{index.schema}

-
- + +

{index.table}

-
- + +

{index.name}

-
- + +
-
-
+ + ))} - - } - /> +
+
+
)} diff --git a/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx b/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx index 0d7206b0fa7ed..ffe4a03f960f6 100644 --- a/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx +++ b/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx @@ -2,13 +2,23 @@ import dayjs from 'dayjs' import Link from 'next/link' import { useState } from 'react' -import Table from 'components/to-be-cleaned/Table' import CodeEditor from 'components/ui/CodeEditor/CodeEditor' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { DatabaseMigration, useMigrationsQuery } from 'data/database/migrations-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Search } from 'lucide-react' -import { Button, Input, SidePanel } from 'ui' +import { + Button, + Card, + Input, + SidePanel, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from 'ui' import { Admonition } from 'ui-patterns' import MigrationsEmptyState from './MigrationsEmptyState' @@ -77,59 +87,63 @@ const Migrations = () => { icon={} /> - - Version - , - Name, - Inserted at (UTC), - , - ]} - body={ - migrations.length > 0 ? ( - migrations.map((migration) => { - // [Joshen] LEFT OFF HERE - const insertedAt = dayjs(migration.version, 'YYYYMMDDHHmmss').format( - 'DD MMM YYYY, HH:mm:ss' - ) + +
+ + + + Version + + Name + Inserted at (UTC) + + + + + {migrations.length > 0 ? ( + migrations.map((migration) => { + // [Joshen] LEFT OFF HERE + const insertedAt = dayjs(migration.version, 'YYYYMMDDHHmmss').format( + 'DD MMM YYYY, HH:mm:ss' + ) - return ( - - {migration.version} - - {migration?.name ?? 'Name not available'} - - {insertedAt} - - - - - ) - }) - ) : ( - - -

No results found

-

- Your search for "{search}" did not return any results -

-
-
- ) - } - /> + {migration?.name ?? 'Name not available'} + + {insertedAt} + + + + + ) + }) + ) : ( + + +

No results found

+

+ Your search for "{search}" did not return any results +

+
+
+ )} +
+
+ )} diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx index ad6f35e7d96c9..b7a8425e0026d 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx @@ -14,7 +14,10 @@ interface PublicationsTableItemProps { selectedPublication: PostgresPublication } -const PublicationsTableItem = ({ table, selectedPublication }: PublicationsTableItemProps) => { +export const PublicationsTableItem = ({ + table, + selectedPublication, +}: PublicationsTableItemProps) => { const { data: project } = useSelectedProjectQuery() const { data: protectedSchemas } = useProtectedSchemas() const enabledForAllTables = selectedPublication.tables == null @@ -108,5 +111,3 @@ const PublicationsTableItem = ({ table, selectedPublication }: PublicationsTable ) } - -export default PublicationsTableItem diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx index 5200c4e3a0140..fe55650d79a6b 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx @@ -15,7 +15,7 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' import { Admonition } from 'ui-patterns' import { Input } from 'ui-patterns/DataInputs/Input' -import PublicationsTableItem from './PublicationsTableItem' +import { PublicationsTableItem } from './PublicationsTableItem' export const PublicationsTables = () => { const { ref, id } = useParams() @@ -95,44 +95,42 @@ export const PublicationsTables = () => { (tables.length === 0 ? ( ) : ( -
- - - - - Name - Schema - Description - {/* + +
+ + + Name + Schema + Description + {/* We've disabled All tables toggle for publications. See https://github.com/supabase/supabase/pull/7233. */} - + + + + + {!!selectedPublication ? ( + tables.map((table) => ( + + )) + ) : ( + + +

The selected publication with ID {id} cannot be found

+

+ Head back to the list of publications to select one from there +

+
- - - {!!selectedPublication ? ( - tables.map((table) => ( - - )) - ) : ( - - -

The selected publication with ID {id} cannot be found

-

- Head back to the list of publications to select one from there -

-
-
- )} -
-
-
-
+ )} + + + ))} ) diff --git a/apps/studio/components/interfaces/Database/Tables/TableList.tsx b/apps/studio/components/interfaces/Database/Tables/TableList.tsx index 68e4ead2a1432..5d08596a5218a 100644 --- a/apps/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/apps/studio/components/interfaces/Database/Tables/TableList.tsx @@ -20,7 +20,6 @@ import { useRouter } from 'next/router' import { useState } from 'react' import { useParams } from 'common' -import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' @@ -39,6 +38,7 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' import { Button, + Card, Checkbox_Shadcn_, DropdownMenu, DropdownMenuContent, @@ -53,6 +53,12 @@ import { Tooltip, TooltipContent, TooltipTrigger, + Table, + TableHeader, + TableHead, + TableBody, + TableRow, + TableCell, cn, } from 'ui' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' @@ -303,256 +309,265 @@ export const TableList = ({ {isSuccess && (
- , - Name, - - Description - , - - Rows (Estimated) - , - - Size (Estimated) - , - - Realtime Enabled - , - , - ]} - body={ - <> - {entities.length === 0 && filterString.length === 0 && ( - - - {visibleTypes.length === 0 ? ( - <> -

- Please select at least one entity type to filter with -

-

- There are currently no results based on the filter that you have applied -

- - ) : ( - <> -

No tables created yet

-

- There are no{' '} - {visibleTypes.length === 5 - ? 'tables' - : visibleTypes.length === 1 - ? `${formatTooltipText(visibleTypes[0])}s` - : `${visibleTypes - .slice(0, -1) - .map((x) => `${formatTooltipText(x)}s`) - .join( - ', ' - )}, and ${formatTooltipText(visibleTypes[visibleTypes.length - 1])}s`}{' '} - found in the schema "{selectedSchema}" -

- - )} -
-
- )} - {entities.length === 0 && filterString.length > 0 && ( - - -

No results found

-

- Your search for "{filterString}" did not return any results -

-
-
- )} - {entities.length > 0 && - entities.map((x) => ( - - - - - {x.type === ENTITY_TYPE.TABLE ? ( - - ) : x.type === ENTITY_TYPE.VIEW ? ( - - ) : ( -
- {Object.entries(ENTITY_TYPE) - .find(([, value]) => value === x.type)?.[0]?.[0] - ?.toUpperCase()} -
- )} -
- - {formatTooltipText(x.type)} - -
-
- - {/* only show tooltips if required, to reduce noise */} - {x.name.length > 20 ? ( - - -

{x.name}

-
- - {x.name} -
+ +
+ + + + Name + + Description + + + Rows (Estimated) + + + Size (Estimated) + + + Realtime Enabled + + + + + + <> + {entities.length === 0 && filterString.length === 0 && ( + + + {visibleTypes.length === 0 ? ( + <> +

+ Please select at least one entity type to filter with +

+

+ There are currently no results based on the filter that you have + applied +

+ ) : ( -

{x.name}

+ <> +

No tables created yet

+

+ There are no{' '} + {visibleTypes.length === 5 + ? 'tables' + : visibleTypes.length === 1 + ? `${formatTooltipText(visibleTypes[0])}s` + : `${visibleTypes + .slice(0, -1) + .map((x) => `${formatTooltipText(x)}s`) + .join( + ', ' + )}, and ${formatTooltipText(visibleTypes[visibleTypes.length - 1])}s`}{' '} + found in the schema "{selectedSchema}" +

+ )} - - - {x.comment !== null ? ( - - {x.comment} - - ) : ( -

No description

- )} -
- - {x.rows !== undefined ? x.rows.toLocaleString() : '-'} - - - {x.size !== undefined ? {x.size} : '-'} - - - {(realtimePublication?.tables ?? []).find((table) => table.id === x.id) ? ( -
- -
- ) : ( -
- -
- )} -
- -
- - - {!isSchemaLocked && ( - - -
-
- - ))} - - } - /> +
+ + {x.comment !== null ? ( + + {x.comment} + + ) : ( +

No description

+ )} +
+ + {x.rows !== undefined ? x.rows.toLocaleString() : '-'} + + + {x.size !== undefined ? {x.size} : '-'} + + + {(realtimePublication?.tables ?? []).find( + (table) => table.id === x.id + ) ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ +
+ + + {!isSchemaLocked && ( + + +
+
+
+ ))} + +
+
+
)} diff --git a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx index cc3f4b5283f81..f78e526252bc5 100644 --- a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx +++ b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx @@ -18,6 +18,8 @@ import { Tooltip, TooltipContent, TooltipTrigger, + TableRow, + TableCell, } from 'ui' import { generateTriggerCreateSQL } from './TriggerList.utils' @@ -58,35 +60,35 @@ const TriggerList = ({ if (_triggers.length === 0 && filterString.length === 0) { return ( - - + +

No triggers created yet

There are no triggers found in the schema "{schema}"

-
-
+ + ) } if (_triggers.length === 0 && filterString.length > 0) { return ( - - + +

No results found

Your search for "{filterString}" did not return any results

-
-
+ + ) } return ( <> {_triggers.map((x: any) => ( - - + + editTrigger(x)} @@ -98,35 +100,35 @@ const TriggerList = ({ {x.name} - + - +

{x.table}

-
+ - +

{x.function_name}

-
+ - +
{x.events.map((event: string) => ( {`${x.activation} ${event}`} ))}
-
+ - +

{x.orientation}

-
+ - +
{x.enabled_mode !== 'DISABLED' ? ( @@ -134,9 +136,9 @@ const TriggerList = ({ )}
-
+ - + {!isLocked && (
{canUpdateTriggers ? ( @@ -216,8 +218,8 @@ const TriggerList = ({ )}
)} -
-
+ + ))} ) diff --git a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx index 2978d9d307489..a9097f7190d0a 100644 --- a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx +++ b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx @@ -6,7 +6,6 @@ import { useState } from 'react' import AlphaPreview from 'components/to-be-cleaned/AlphaPreview' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' -import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import SchemaSelector from 'components/ui/SchemaSelector' @@ -18,7 +17,17 @@ import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema, useProtectedSchemas } from 'hooks/useProtectedSchemas' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' -import { AiIconAnimation, Input } from 'ui' +import { + AiIconAnimation, + Input, + Card, + Table, + TableHeader, + TableHead, + TableBody, + TableRow, + TableCell, +} from 'ui' import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning' import TriggerList from './TriggerList' @@ -181,30 +190,32 @@ const TriggersList = ({ {isSchemaLocked && }
- - Name - Table - Function - Events - Orientation - - Enabled - - - - } - body={ - - } - /> + +
+ + + Name + Table + Function + Events + Orientation + + Enabled + + + + + + + +
+
)} diff --git a/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx b/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx index 5ea6f0cfb08fd..2478f55f7cda8 100644 --- a/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx +++ b/apps/studio/components/interfaces/Integrations/GraphQL/GraphiQLTab.tsx @@ -5,14 +5,11 @@ import { useMemo } from 'react' import { toast } from 'sonner' import { useParams } from 'common' -import ExtensionCard from 'components/interfaces/Database/Extensions/ExtensionCard' import GraphiQL from 'components/interfaces/GraphQL/GraphiQL' import { Loading } from 'components/ui/Loading' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useSessionAccessTokenQuery } from 'data/auth/session-access-token-query' import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query' -import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' -import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { API_URL, IS_PLATFORM } from 'lib/constants' import { getRoleImpersonationJWT } from 'lib/role-impersonation' import { useGetImpersonatedRoleState } from 'state/role-impersonation-state' @@ -20,15 +17,8 @@ import { useGetImpersonatedRoleState } from 'state/role-impersonation-state' export const GraphiQLTab = () => { const { resolvedTheme } = useTheme() const { ref: projectRef } = useParams() - const { data: project } = useSelectedProjectQuery() const currentTheme = resolvedTheme?.includes('dark') ? 'dark' : 'light' - const { data, isLoading: isExtensionsLoading } = useDatabaseExtensionsQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - }) - const pgGraphqlExtension = (data ?? []).find((ext) => ext.name === 'pg_graphql') - const { data: accessToken } = useSessionAccessTokenQuery({ enabled: IS_PLATFORM }) const { data: apiKeys, isFetched } = useAPIKeysQuery({ projectRef, reveal: true }) @@ -80,29 +70,11 @@ export const GraphiQLTab = () => { } return customFetcher - }, [projectRef, getImpersonatedRoleState, jwtSecret, accessToken, serviceKey]) + }, [projectRef, getImpersonatedRoleState, jwtSecret, accessToken, serviceKey, secretKey?.api_key]) - if ((IS_PLATFORM && !accessToken) || !isFetched || (isExtensionsLoading && !pgGraphqlExtension)) { + if ((IS_PLATFORM && !accessToken) || !isFetched) { return } - if (pgGraphqlExtension?.installed_version === null) { - return ( -
-
-
-

Enable the GraphQL Extension

-

- Toggle the switch below to enable the GraphQL extension. You can then use the GraphQL - API with your Supabase Database. -

-
- - -
-
- ) - } - return } diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx index b69576d5454a5..d15afd50960b0 100644 --- a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx @@ -8,7 +8,6 @@ import { DropdownMenuContent, DropdownMenuSeparator, DropdownMenuTrigger, - Input, } from 'ui' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' @@ -17,19 +16,22 @@ import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Edit3, Eye, EyeOff, Key, Loader, MoreVertical, Trash } from 'lucide-react' import type { VaultSecret } from 'types' +import { Input } from 'ui-patterns/DataInputs/Input' import EditSecretModal from './EditSecretModal' +import type { SecretTableColumn } from './Secrets.utils' interface SecretRowProps { - secret: VaultSecret + row: VaultSecret + col: SecretTableColumn onSelectRemove: (secret: VaultSecret) => void } -const SecretRow = ({ secret, onSelectRemove }: SecretRowProps) => { +const SecretRow = ({ row, col, onSelectRemove }: SecretRowProps) => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() const [modal, setModal] = useState(null) const [revealSecret, setRevealSecret] = useState(false) - const name = secret?.name ?? 'No name provided' + const name = row?.name ?? 'No name provided' const { can: canManageSecrets } = useAsyncCheckProjectPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, @@ -40,69 +42,23 @@ const SecretRow = ({ secret, onSelectRemove }: SecretRowProps) => { { projectRef: ref!, connectionString: project?.connectionString, - id: secret.id, + id: row.id, }, { - enabled: !!(ref! && secret.id) && revealSecret, + enabled: !!(ref! && row.id) && revealSecret, } ) const onCloseModal = () => setModal(null) - return ( -
-
-
-

- {name} -

- {secret.description !== undefined && ( -

- {secret.description} -

- )} -
-
- -

- {secret.id} -

-
-
-
-
-
-

- {secret.updated_at === secret.created_at ? 'Added' : 'Updated'} on{' '} - {dayjs(secret.updated_at).format('MMM D, YYYY')} -

- + if (col.id === 'actions') { + return ( +
e.stopPropagation()}>
+ ) + } + + if (col.id === 'secret_value') { + return ( +
e.stopPropagation()}> +
+ ) + } + + if (col.id === 'updated_at') { + return ( +
+

+ {row.updated_at === row.created_at ? 'Added' : 'Updated'} on{' '} + {dayjs(row.updated_at).format('MMM D, YYYY')} +

+
+ ) + } + + if (col.id === 'id') { + return ( +
+ +

+ {row.id} +

+
+ ) + } + + return ( +
+

+ {name} +

+ {row.description !== undefined && row.description !== '' && ( +
+

{row.description}

+
+ )}
) } diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/Secrets.utils.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/Secrets.utils.tsx new file mode 100644 index 0000000000000..a1bec3a55dbc1 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/Secrets.utils.tsx @@ -0,0 +1,57 @@ +import type { Column } from 'react-data-grid' + +import type { VaultSecret } from 'types' +import { cn } from 'ui' +import SecretRow from './SecretRow' + +export type SecretColumnId = 'secret' | 'id' | 'secret_value' | 'updated_at' | 'actions' + +export interface SecretTableColumn { + id: SecretColumnId + name: string + minWidth?: number + width?: number + maxWidth?: number +} + +export const SECRET_TABLE_COLUMNS: SecretTableColumn[] = [ + { id: 'secret', name: 'Secret', minWidth: 300, width: 360 }, + { id: 'id', name: 'ID', minWidth: 220, width: 260 }, + { id: 'secret_value', name: 'Value', minWidth: 320, width: 420 }, + { id: 'updated_at', name: 'Last updated', minWidth: 180 }, + { id: 'actions', name: '', minWidth: 75, width: 75 }, +] + +export const formatSecretColumns = ({ + onSelectRemove, +}: { + onSelectRemove: (secret: VaultSecret) => void +}): Column[] => { + return SECRET_TABLE_COLUMNS.map((col) => { + const result: Column = { + key: col.id, + name: col.name, + minWidth: col.minWidth ?? 100, + maxWidth: col.maxWidth, + width: col.width, + resizable: false, + sortable: false, + draggable: false, + headerCellClass: undefined, + renderHeaderCell: () => { + return ( +
+

{col.name}

+
+ ) + }, + renderCell: ({ row }) => , + } + return result + }) +} diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx index e1278afbbb8a1..05bd8a8f7264f 100644 --- a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx @@ -1,7 +1,8 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import DataGrid, { Row } from 'react-data-grid' import { sortBy } from 'lodash' -import { Loader, Search, X } from 'lucide-react' -import { Fragment, useEffect, useState } from 'react' +import { Loader, RefreshCw, Search, X } from 'lucide-react' +import { useEffect, useMemo, useState } from 'react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' @@ -13,16 +14,17 @@ import type { VaultSecret } from 'types' import { Button, Input, + LoadingLine, + cn, Select_Shadcn_, - SelectContent_Shadcn_, - SelectItem_Shadcn_, SelectTrigger_Shadcn_, SelectValue_Shadcn_, - Separator, + SelectContent_Shadcn_, + SelectItem_Shadcn_, } from 'ui' import AddNewSecretModal from './AddNewSecretModal' import DeleteSecretModal from './DeleteSecretModal' -import SecretRow from './SecretRow' +import { formatSecretColumns } from './Secrets.utils' export const SecretsManagement = () => { const { search } = useParams() @@ -31,140 +33,162 @@ export const SecretsManagement = () => { const [searchValue, setSearchValue] = useState('') const [showAddSecretModal, setShowAddSecretModal] = useState(false) const [selectedSecretToRemove, setSelectedSecretToRemove] = useState() - const [selectedSort, setSelectedSort] = useState('updated_at') + const [selectedSort, setSelectedSort] = useState<'updated_at' | 'name'>('updated_at') const { can: canManageSecrets } = useAsyncCheckProjectPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) - const { data, isLoading } = useVaultSecretsQuery({ + const { data, isLoading, isRefetching, refetch, error, isError } = useVaultSecretsQuery({ projectRef: project?.ref!, connectionString: project?.connectionString, }) - const allSecrets = data || [] - const secrets = sortBy( - searchValue.length > 0 - ? allSecrets.filter( - (secret) => - (secret?.name ?? '').toLowerCase().includes(searchValue.toLowerCase()) || - (secret?.id ?? '').toLowerCase().includes(searchValue.toLowerCase()) - ) - : allSecrets, - (s) => { - if (selectedSort === 'updated_at') { - return Number(new Date(s.updated_at)) - } else { - return s[selectedSort as keyof VaultSecret] - } + const allSecrets = useMemo(() => data || [], [data]) + const secrets = useMemo(() => { + const filtered = + searchValue.length > 0 + ? allSecrets.filter( + (secret) => + (secret?.name ?? '').toLowerCase().includes(searchValue.trim().toLowerCase()) || + (secret?.id ?? '').toLowerCase().includes(searchValue.trim().toLowerCase()) + ) + : allSecrets + + if (selectedSort === 'updated_at') { + return sortBy(filtered, (s) => Number(new Date(s.updated_at))).reverse() } - ) + return sortBy(filtered, (s) => (s.name || '').toLowerCase()) + }, [allSecrets, searchValue, selectedSort]) useEffect(() => { if (search !== undefined) setSearchValue(search) }, [search]) + const columns = useMemo( + () => + formatSecretColumns({ + onSelectRemove: (secret) => setSelectedSecretToRemove(secret), + }), + [] + ) + return ( <> -
-
-
- setSearchValue(event.target.value)} - icon={} - actions={ - searchValue.length > 0 - ? [ -
-
- - setShowAddSecretModal(true)} - tooltip={{ - content: { - side: 'bottom', - text: !canManageSecrets - ? 'You need additional permissions to add secrets' - : undefined, - }, - }} - > - Add new secret - +
+
+
+
+ } + value={searchValue ?? ''} + onChange={(e) => setSearchValue(e.target.value)} + actions={[ + searchValue && ( +
+ +
+ + + setShowAddSecretModal(true)} + tooltip={{ + content: { + side: 'bottom', + text: !canManageSecrets + ? 'You need additional permissions to add secrets' + : undefined, + }, + }} + > + Add new secret + +
-
- {/* Table of secrets */} -
- {isLoading ? ( + + + {isError ? (
- -

Loading secrets from the Vault

+

Failed to load secrets

) : ( - <> - {secrets.map((secret, idx) => { - return ( - - - {idx !== secrets.length - 1 && } - + row.id} + rowClass={() => { + return cn( + 'cursor-pointer', + '[&>.rdg-cell]:border-box [&>.rdg-cell]:outline-none [&>.rdg-cell]:shadow-none', + '[&>.rdg-cell:first-child>div]:pl-8' ) - })} - {secrets.length === 0 && ( - <> - {searchValue.length === 0 ? ( -
-

No secrets added yet

-

- The Vault allows you to store sensitive information like API keys -

-
- ) : ( -
-

No results found

-

- Your search for "{searchValue}" did not return any results -

-
- )} - - )} - + }} + renderers={{ + renderRow(_, props) { + return + }, + }} + /> )} + + {secrets.length === 0 && !isLoading && !isError ? ( +
+
+

+ {searchValue ? 'No secrets found' : 'No secrets added yet'} +

+

+ {searchValue + ? `There are currently no secrets based on the search "${searchValue}"` + : 'The Vault allows you to store sensitive information like API keys'} +

+
+
+ ) : null}
diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/MemberRow.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/MemberRow.tsx index ea047d76d18d2..ce683309f802e 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/MemberRow.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/MemberRow.tsx @@ -1,7 +1,6 @@ import { ArrowRight, Check, Minus, User, X } from 'lucide-react' import Link from 'next/link' -import Table from 'components/to-be-cleaned/Table' import PartnerIcon from 'components/ui/PartnerIcon' import { ProfileImage } from 'components/ui/ProfileImage' import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query' @@ -17,6 +16,8 @@ import { HoverCard_Shadcn_, ScrollArea, cn, + TableRow, + TableCell, } from 'ui' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' import { isInviteExpired } from '../Organization.utils' @@ -68,8 +69,8 @@ export const MemberRow = ({ member }: MemberRowProps) => { }).length > 0 return ( - - + +
{ /> )}
-
+ - + {isInvitedUser && member.invited_at && ( {isInviteExpired(member.invited_at) ? 'Expired' : 'Invited'} )} {member.is_sso_user && SSO} - + - +
{member.mfa_enabled ? ( @@ -122,9 +123,9 @@ export const MemberRow = ({ member }: MemberRowProps) => { )}
-
+ - + {isLoadingRoles ? ( ) : isObfuscated ? ( @@ -201,11 +202,11 @@ export const MemberRow = ({ member }: MemberRowProps) => { ) }) )} - + - + - -
+ + ) } diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx index 3ecc3b3de9041..22b3d06998234 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx @@ -1,7 +1,6 @@ import { AlertCircle, HelpCircle } from 'lucide-react' import { useParams } from 'common' -import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query' @@ -9,7 +8,20 @@ import { useOrganizationMembersQuery } from 'data/organizations/organization-mem import { useProfile } from 'lib/profile' import { partition } from 'lodash' import { useMemo } from 'react' -import { Button, Loading, Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { + Button, + Loading, + Tooltip, + TooltipContent, + TooltipTrigger, + Table, + TableHeader, + TableHead, + TableBody, + TableCell, + TableRow, + Card, +} from 'ui' import { Admonition } from 'ui-patterns' import { MemberRow } from './MemberRow' @@ -78,77 +90,86 @@ const MembersView = ({ searchString }: MembersViewProps) => { {isSuccessMembers && (
- - User, - , - - Enabled MFA - , - - Role - - - - - How to configure access control? - - , - , - ]} - body={[ - ...(isSuccessRoles && isSuccessMembers && !isOrgScopedRole - ? [ - - - - - , - ] - : []), - ...(!!user ? [] : []), - ...sortedMembers.map((member) => ( - - )), - ...(searchString.length > 0 && filteredMembers.length === 0 - ? [ - - -
- -

- No users matched the search query "{searchString}" -

-
-
-
, - ] - : []), - - -

- {searchString ? `${filteredMembers.length} of ` : ''} - {members.length || '0'} {members.length == 1 ? 'user' : 'users'} -

-
-
, - ]} - /> - + + +
+ + + User + + + Enabled MFA + + + Role + + + + + + How to configure access control? + + + + + + + + + {[ + ...(isSuccessRoles && isSuccessMembers && !isOrgScopedRole + ? [ + + + + + , + ] + : []), + ...(!!user ? [] : []), + ...sortedMembers.map((member) => ( + + )), + ...(searchString.length > 0 && filteredMembers.length === 0 + ? [ + + +
+ +

+ No users matched the search query "{searchString}" +

+
+
+
, + ] + : []), + + +

+ {searchString ? `${filteredMembers.length} of ` : ''} + {members.length || '0'} {members.length == 1 ? 'user' : 'users'} +

+
+
, + ]} +
+
+
+
)} diff --git a/apps/studio/pages/project/[ref]/database/extensions.tsx b/apps/studio/pages/project/[ref]/database/extensions.tsx index fd663c2a53f34..4239872f36ade 100644 --- a/apps/studio/pages/project/[ref]/database/extensions.tsx +++ b/apps/studio/pages/project/[ref]/database/extensions.tsx @@ -4,7 +4,7 @@ import { Extensions } from 'components/interfaces/Database' import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' -import { FormHeader } from 'components/ui/Forms/FormHeader' +import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import NoPermission from 'components/ui/NoPermission' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' @@ -18,14 +18,17 @@ const DatabaseExtensions: NextPageWithLayout = () => { } return ( - - -
- + + + -
-
-
+ + + ) } diff --git a/apps/studio/styles/typography.scss b/apps/studio/styles/typography.scss index 453f3df940405..b284618ace54e 100644 --- a/apps/studio/styles/typography.scss +++ b/apps/studio/styles/typography.scss @@ -35,32 +35,32 @@ @layer utilities { /* Heading */ .heading-title { - @apply scroll-m-20 text-2xl tracking-tight text-foreground; + @apply scroll-m-20 text-2xl tracking-tight; } .heading-section { - @apply scroll-m-20 text-xl text-foreground; + @apply scroll-m-20 text-xl; } .heading-subSection { - @apply scroll-m-20 text-base text-foreground; + @apply scroll-m-20 text-base; } .heading-default { - @apply scroll-m-20 text-sm font-medium text-foreground; + @apply scroll-m-20 text-sm font-medium; } .heading-compact { - @apply scroll-m-20 text-xs font-medium text-foreground; + @apply scroll-m-20 text-xs font-medium; } .heading-meta { - @apply text-xs font-mono uppercase tracking-wider font-medium text-foreground-light; + @apply text-xs font-mono uppercase tracking-wider font-medium; } /* Text */ .text-default { - @apply text-base text-foreground-light; + @apply text-base; } .text-subTitle { diff --git a/packages/ui/src/components/shadcn/ui/card.tsx b/packages/ui/src/components/shadcn/ui/card.tsx index 59529534d41f8..e72a26a4066ad 100644 --- a/packages/ui/src/components/shadcn/ui/card.tsx +++ b/packages/ui/src/components/shadcn/ui/card.tsx @@ -6,7 +6,10 @@ const Card = React.forwardRef (
) diff --git a/packages/ui/src/components/shadcn/ui/table.tsx b/packages/ui/src/components/shadcn/ui/table.tsx index 186fadfe9e6ff..ef716e89d09ea 100644 --- a/packages/ui/src/components/shadcn/ui/table.tsx +++ b/packages/ui/src/components/shadcn/ui/table.tsx @@ -19,7 +19,7 @@ const TableHeader = React.forwardRef< HTMLTableSectionElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( - + tr]:bg-200', className)} {...props} /> )) TableHeader.displayName = 'TableHeader' @@ -64,7 +64,7 @@ const TableHead = React.forwardRef<