diff --git a/web/packages/teleport/src/Console/Console.tsx b/web/packages/teleport/src/Console/Console.tsx
index 36cf572586dbd..e0bc17f879f4d 100644
--- a/web/packages/teleport/src/Console/Console.tsx
+++ b/web/packages/teleport/src/Console/Console.tsx
@@ -37,6 +37,7 @@ import usePageTitle from './usePageTitle';
import useTabRouting from './useTabRouting';
import useOnExitConfirmation from './useOnExitConfirmation';
import useKeyboardNav from './useKeyboardNav';
+import { DocumentDb } from './DocumentDb';
const POLL_INTERVAL = 5000; // every 5 sec
@@ -77,7 +78,9 @@ export default function Console() {
return consoleCtx.refreshParties();
}
- const disableNewTab = storeDocs.getNodeDocuments().length > 0;
+ const disableNewTab =
+ storeDocs.getNodeDocuments().length > 0 ||
+ storeDocs.getDbDocuments().length > 0;
const $docs = documents.map(doc => (
));
@@ -139,6 +142,8 @@ function MemoizedDocument(props: { doc: stores.Document; visible: boolean }) {
return ;
case 'kubeExec':
return ;
+ case 'db':
+ return ;
default:
return ;
}
diff --git a/web/packages/teleport/src/Console/DocumentDb/ConnectDialog.tsx b/web/packages/teleport/src/Console/DocumentDb/ConnectDialog.tsx
new file mode 100644
index 0000000000000..6d91f451fe0ae
--- /dev/null
+++ b/web/packages/teleport/src/Console/DocumentDb/ConnectDialog.tsx
@@ -0,0 +1,219 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import React, { useCallback, useEffect, useState } from 'react';
+import Dialog, {
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from 'design/Dialog';
+import { Box, ButtonPrimary, ButtonSecondary, Flex, Indicator } from 'design';
+
+import Validation from 'shared/components/Validation';
+import { Option } from 'shared/components/Select';
+import {
+ FieldSelect,
+ FieldSelectCreatable,
+} from 'shared/components/FieldSelect';
+
+import { Danger } from 'design/Alert';
+import { requiredField } from 'shared/components/Validation/rules';
+import { useAsync } from 'shared/hooks/useAsync';
+
+import { useTeleport } from 'teleport';
+import { Database } from 'teleport/services/databases';
+import { DbConnectData } from 'teleport/lib/term/tty';
+
+export function ConnectDialog(props: {
+ clusterId: string;
+ serviceName: string;
+ onClose(): void;
+ onConnect(data: DbConnectData): void;
+}) {
+ // Fetch database information to pre-fill the connection parameters.
+ const ctx = useTeleport();
+ const [attempt, getDatabase] = useAsync(
+ useCallback(async () => {
+ const response = await ctx.resourceService.fetchUnifiedResources(
+ props.clusterId,
+ {
+ query: `name == "${props.serviceName}"`,
+ kinds: ['db'],
+ sort: { fieldName: 'name', dir: 'ASC' },
+ limit: 1,
+ }
+ );
+
+ // TODO(gabrielcorado): Handle scenarios where there is conflict on the name.
+ if (response.agents.length !== 1 || response.agents[0].kind !== 'db') {
+ throw new Error('Unable to retrieve database information.');
+ }
+
+ return response.agents[0];
+ }, [props.clusterId, ctx.resourceService, props.serviceName])
+ );
+
+ useEffect(() => {
+ void getDatabase();
+ }, [getDatabase]);
+
+ return (
+
+ );
+}
+
+function ConnectForm(props: {
+ db: Database;
+ onConnect(data: DbConnectData): void;
+ onClose(): void;
+}) {
+ const dbUserOpts = props.db.users
+ ?.map(user => ({
+ value: user,
+ label: user,
+ }))
+ .filter(removeWildcardOption);
+ const dbNamesOpts = props.db.names
+ ?.map(name => ({
+ value: name,
+ label: name,
+ }))
+ .filter(removeWildcardOption);
+ const dbRolesOpts = props.db.roles
+ ?.map(role => ({
+ value: role,
+ label: role,
+ }))
+ .filter(removeWildcardOption);
+
+ const [selectedName, setSelectedName] = useState