77// the Business Source License, use of this software will be governed
88// by the Apache License, Version 2.0.
99
10- import {
11- Box ,
12- Button ,
13- HStack ,
14- Input ,
15- Text ,
16- useTheme ,
17- VStack ,
18- } from "@chakra-ui/react" ;
10+ import { Box , HStack , useTheme , VStack } from "@chakra-ui/react" ;
1911import React , { useState } from "react" ;
2012
21- import { SecretCopyableBox } from "~/components/copyableComponents" ;
22- import TextLink from "~/components/TextLink" ;
23- import { useCreateApiToken } from "~/queries/frontegg" ;
2413import { MaterializeTheme } from "~/theme" ;
2514import { obfuscateSecret } from "~/utils/format" ;
2615
2716import {
2817 ConnectionDetailRow ,
2918 ConnectStep ,
19+ CreateAppPasswordRow ,
20+ IdTokenRow ,
3021 LabeledCommandBox ,
3122} from "./connectComponents" ;
3223import {
@@ -35,167 +26,6 @@ import {
3526 ExternalToolId ,
3627} from "./connectOptions" ;
3728
38- const DETAIL_LABEL_WIDTH = "88px" ;
39-
40- /** Inline app password creation: name it, create it, copy it once. */
41- const CreateAppPasswordRow = ( {
42- onCreated,
43- } : {
44- onCreated : ( password : string ) => void ;
45- } ) => {
46- const { colors } = useTheme < MaterializeTheme > ( ) ;
47- const [ isNaming , setIsNaming ] = useState ( false ) ;
48- const [ name , setName ] = useState ( "" ) ;
49- const {
50- mutate : createAppPassword ,
51- isPending,
52- data : newPassword ,
53- } = useCreateApiToken ( ) ;
54-
55- React . useEffect ( ( ) => {
56- if ( newPassword ?. password ) {
57- onCreated ( newPassword . password ) ;
58- }
59- } , [ newPassword , onCreated ] ) ;
60-
61- const create = ( ) => {
62- if ( name . trim ( ) . length === 0 || isPending ) return ;
63- createAppPassword ( { type : "personal" , description : name . trim ( ) } ) ;
64- } ;
65-
66- return (
67- < Box >
68- < HStack alignItems = "center" spacing = "3" >
69- < Text
70- fontSize = "sm"
71- color = { colors . foreground . secondary }
72- w = { DETAIL_LABEL_WIDTH }
73- flexShrink = { 0 }
74- >
75- Password
76- </ Text >
77- { newPassword ?. password ? (
78- < SecretCopyableBox
79- label = "Password"
80- contents = { newPassword . password }
81- obfuscatedContent = { newPassword . obfuscatedPassword }
82- overflow = "hidden"
83- flex = "1"
84- w = "auto"
85- minWidth = { 0 }
86- />
87- ) : isNaming ? (
88- < HStack spacing = "2" flex = "1" >
89- < Input
90- value = { name }
91- onChange = { ( event ) => setName ( event . target . value ) }
92- maxW = "220px"
93- isDisabled = { isPending }
94- placeholder = "Password name"
95- aria-label = "App password name"
96- autoFocus
97- onKeyDown = { ( event ) => {
98- if ( event . key === "Enter" ) create ( ) ;
99- } }
100- />
101- < Button
102- variant = "primary"
103- size = "sm"
104- px = "3"
105- flexShrink = { 0 }
106- isLoading = { isPending }
107- loadingText = "Creating"
108- isDisabled = { name . trim ( ) . length === 0 }
109- onClick = { create }
110- >
111- Create
112- </ Button >
113- < Button
114- variant = "borderless"
115- size = "sm"
116- flexShrink = { 0 }
117- isDisabled = { isPending }
118- onClick = { ( ) => {
119- setIsNaming ( false ) ;
120- setName ( "" ) ;
121- } }
122- >
123- Cancel
124- </ Button >
125- </ HStack >
126- ) : (
127- < Button
128- variant = "secondary"
129- size = "sm"
130- onClick = { ( ) => setIsNaming ( true ) }
131- >
132- Create new password
133- </ Button >
134- ) }
135- </ HStack >
136- { isNaming && ! newPassword ?. password && (
137- < Text
138- fontSize = "sm"
139- color = { colors . foreground . secondary }
140- mt = "1.5"
141- ml = { `calc(${ DETAIL_LABEL_WIDTH } + 12px)` }
142- >
143- You are naming this password. The password itself is generated for
144- you.
145- </ Text >
146- ) }
147- { newPassword ?. password && (
148- < Text
149- fontSize = "sm"
150- color = { colors . foreground . secondary }
151- mt = "1.5"
152- ml = { `calc(${ DETAIL_LABEL_WIDTH } + 12px)` }
153- >
154- Copy it now. It will not be shown again. Manage in{ " " }
155- < TextLink href = "/access/app-passwords" > App Passwords</ TextLink > .
156- </ Text >
157- ) }
158- </ Box >
159- ) ;
160- } ;
161-
162- /** Self-managed OIDC deployments use the ID token as the SQL password. */
163- const IdTokenRow = ( { idToken } : { idToken : string } ) => {
164- const { colors } = useTheme < MaterializeTheme > ( ) ;
165-
166- return (
167- < Box >
168- < HStack alignItems = "center" spacing = "3" >
169- < Text
170- fontSize = "sm"
171- color = { colors . foreground . secondary }
172- w = { DETAIL_LABEL_WIDTH }
173- flexShrink = { 0 }
174- >
175- Password
176- </ Text >
177- < SecretCopyableBox
178- label = "idToken"
179- contents = { idToken }
180- obfuscatedContent = { obfuscateSecret ( idToken ) }
181- overflow = "hidden"
182- flex = "1"
183- w = "auto"
184- minWidth = { 0 }
185- />
186- </ HStack >
187- < Text
188- fontSize = "sm"
189- color = { colors . foreground . secondary }
190- mt = "1.5"
191- ml = { `calc(${ DETAIL_LABEL_WIDTH } + 12px)` }
192- >
193- When prompted for a password, paste this ID token.
194- </ Text >
195- </ Box >
196- ) ;
197- } ;
198-
19929export interface ConnectExternalToolsPanelProps {
20030 ctx : ConnectContext ;
20131}
@@ -219,9 +49,7 @@ export const ConnectExternalToolsPanel = ({
21949 password : createdPassword ,
22050 ssl : ctx . ssl ,
22151 } ) ;
222- // Mask the password in the rendered snippet. The copy button copies the
223- // real value.
224- const displaySnippet = createdPassword
52+ const obfuscatedSnippet = createdPassword
22553 ? snippet . replaceAll ( createdPassword , obfuscateSecret ( createdPassword ) )
22654 : undefined ;
22755
@@ -277,7 +105,7 @@ export const ConnectExternalToolsPanel = ({
277105 < LabeledCommandBox
278106 label = { tool . instruction }
279107 contents = { snippet }
280- displayContents = { displaySnippet }
108+ obfuscatedContents = { obfuscatedSnippet }
281109 />
282110 </ VStack >
283111 </ ConnectStep >
0 commit comments