LCOV - code coverage report
Current view: top level - dashql-app/src/connections - trino_connection_settings.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 99.2 % 485 481
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import * as connection from '@ankoh/dashql-jsonschema/connection.js';
       4              : import * as auth from '@ankoh/dashql-jsonschema/auth.js';
       5              : 
       6              : import * as style from './connection_settings.module.css';
       7              : 
       8              : import { BookIcon, KeyIcon, PlugIcon, XIcon } from '../ui/foundations/symbol_icon.js';
       9              : 
      10              : import { Button, ButtonVariant } from '../ui/foundations/button.js';
      11              : import { ConnectionHealth } from './connection_state.js';
      12              : import { Dispatch } from '../utils/variant.js';
      13              : import { KeyValueListBuilder, KeyValueListElement, UpdateKeyValueList } from '../ui/foundations/keyvalue_list.js';
      14              : import { TextField, VALIDATION_WARNING } from '../ui/foundations/text_field.js';
      15              : import { classNames } from '../utils/classnames.js';
      16              : import { useConnectionById, useConnectionDraftForm } from './connection_registry.js';
      17              : import { useConnectionSave } from './connection_persistence.js';
      18              : import { useConnectionSetup } from './connection_setup.js';
      19              : import { useLogger } from '../platform/logger/logger_provider.js';
      20              : import { useQueryExecutor } from './query_executor.js';
      21              : import { performHealthCheck } from './health_check.js';
      22              : import { useTrinoSetup } from './trino/trino_connector.js';
      23              : import { CONNECTOR_INFOS, ConnectorType, requiresSwitchingToNative, TRINO_CONNECTOR } from './connector_info.js';
      24              : import { UpdateValueList, ValueListBuilder } from '../ui/foundations/value_list.js';
      25              : import { ConnectionInlineHeader } from './connection_inline_header.js';
      26              : import { SegmentedControl } from '../ui/foundations/segmented_control.js';
      27              : import { LoggableException, stringifyError } from '../platform/logger/logger.js';
      28              : import { withTrinoCatalogFields } from './trino/trino_catalogs.js';
      29              : 
      30            1 : const LOG_CTX = "trino_connector";
      31              : 
      32              : interface PageState {
      33              :     activeParams: connection.TrinoConnectionParams | null;
      34              :     newParams: connection.TrinoConnectionParams;
      35              :     newParamsMetadata: KeyValueListElement[];
      36              : };
      37              : 
      38            1 : const DEFAULT_TRINO_PARAMS: connection.TrinoConnectionParams = {
      39            1 :     endpoint: "http://localhost:8080",
      40            1 :     auth: {
      41            1 :         authType: "AUTH_BASIC",
      42            1 :         basic: {
      43            1 :             username: "",
      44            1 :             secret: "",
      45            1 :         }
      46            1 :     },
      47            1 :     metadata: {
      48            1 :         message: ""
      49            1 :     },
      50            1 :     defaultCatalog: "",
      51            1 :     defaultSchema: "",
      52            1 :     catalogs: [],
      53            1 : };
      54              : 
      55            1 : export function buildPageStateFromParams(params: connection.TrinoConnectionParams | null | undefined): PageState {
      56            1 :     const active = params ?? null;
      57            1 :     const newParams = params ? withTrinoCatalogFields(params) : DEFAULT_TRINO_PARAMS;
      58            1 :     const metadataData = newParams.metadata ?? {};
      59            1 :     return {
      60            1 :         activeParams: active,
      61            1 :         newParams,
      62            1 :         newParamsMetadata: params?.draftRows?.metadata ?? Object.entries(metadataData).map(([k, v]) => ({ key: k, value: v ?? "" })),
      63            1 :     };
      64            1 : }
      65              : 
      66              : interface Props {
      67              :     connectionId: string | null;
      68              :     onClose?: () => void;
      69              : }
      70              : 
      71            1 : export const TrinoConnectorSettings: React.FC<Props> = (props: Props) => {
      72            1 :     const logger = useLogger();
      73            1 :     const trinoSetup = useTrinoSetup();
      74            1 :     const queryExecutor = useQueryExecutor();
      75              : 
      76              :     // Can we use the connector here?
      77            1 :     const connectorInfo = CONNECTOR_INFOS[ConnectorType.TRINO];
      78            1 :     const wrongPlatform = requiresSwitchingToNative(connectorInfo);
      79              : 
      80              :     // Resolve connection state
      81            1 :     const [connectionState, dispatchConnectionState] = useConnectionById(props.connectionId);
      82            1 :     const connectionSetup = useConnectionSetup(props.connectionId);
      83            1 :     const { saving } = useConnectionSave(props.connectionId);
      84              : 
      85            1 :     const [pageState, setPageState] = useConnectionDraftForm(props.connectionId,
      86            1 :         params => buildPageStateFromParams(params && 'trino' in params ? params.trino as connection.TrinoConnectionParams : undefined),
      87            1 :         form => ({ trino: { ...form.newParams, draftRows: { metadata: form.newParamsMetadata } } }));
      88              : 
      89            1 :     const endpoint = pageState.newParams?.endpoint;
      90            1 :     const setEndpoint = (v: string) => setPageState(s => ({
      91            1 :         ...s,
      92            1 :         newParams: {
      93            1 :             ...s.newParams,
      94            1 :             endpoint: v
      95            1 :         },
      96            1 :     }));
      97            1 :     const authType = pageState.newParams?.auth?.authType ?? "AUTH_BASIC";
      98            1 :     const setAuthType = (t: auth.AuthType) => setPageState(s => ({
      99            1 :         ...s,
     100            1 :         newParams: {
     101            1 :             ...s.newParams,
     102            1 :             auth: {
     103            1 :                 authType: t,
     104            1 :             },
     105            1 :         },
     106            1 :     }));
     107            1 :     const basicAuthUsername = pageState.newParams?.auth?.basic?.username;
     108            1 :     const setBasicAuthUsername: Dispatch<string> = (v: string) => setPageState(s => ({
     109            1 :         ...s,
     110            1 :         newParams: {
     111            1 :             ...s.newParams,
     112            1 :             auth: {
     113            1 :                 authType: "AUTH_BASIC",
     114            1 :                 basic: {
     115            1 :                     username: v,
     116            1 :                     secret: s.newParams.auth?.basic?.secret ?? "",
     117            1 :                 },
     118            1 :             },
     119            1 :         }
     120            1 :     }));
     121            1 :     const basicAuthSecret = pageState.newParams?.auth?.basic?.secret;
     122            1 :     const setBasicAuthSecret: Dispatch<string> = (v: string) => setPageState(s => ({
     123            1 :         ...s,
     124            1 :         newParams: {
     125            1 :             ...s.newParams,
     126            1 :             auth: {
     127            1 :                 authType: "AUTH_BASIC",
     128            1 :                 basic: {
     129            1 :                     username: s.newParams?.auth?.basic?.username ?? "",
     130            1 :                     secret: v,
     131            1 :                 },
     132            1 :             },
     133            1 :         }
     134            1 :     }));
     135            1 :     const oauthAuthEndpoint = pageState.newParams.auth?.oauth?.authorizationUrl;
     136            1 :     const setOAuthAuthEndpoint: Dispatch<string> = (v: string) => setPageState(s => ({
     137            1 :         ...s,
     138            1 :         newParams: {
     139            1 :             ...s.newParams,
     140            1 :             auth: {
     141            1 :                 authType: "AUTH_OAUTH",
     142            1 :                 oauth: {
     143            1 :                     authorizationUrl: v,
     144            1 :                     tokenUrl: s.newParams?.auth?.oauth?.tokenUrl ?? "",
     145            1 :                     clientId: s.newParams?.auth?.oauth?.clientId ?? "",
     146            1 :                     callbackUrl: s.newParams?.auth?.oauth?.callbackUrl ?? "",
     147            1 :                     scopes: s.newParams?.auth?.oauth?.scopes ?? [],
     148            1 :                 },
     149            1 :             },
     150            1 :         }
     151            1 :     }));
     152              : 
     153            1 :     const oauthTokenEndpoint = pageState.newParams.auth?.oauth?.tokenUrl;
     154            1 :     const setOAuthTokenEndpoint: Dispatch<string> = (v: string) => setPageState(s => ({
     155            1 :         ...s,
     156            1 :         newParams: {
     157            1 :             ...s.newParams,
     158            1 :             auth: {
     159            1 :                 authType: "AUTH_OAUTH",
     160            1 :                 oauth: {
     161            1 :                     authorizationUrl: s.newParams?.auth?.oauth?.authorizationUrl ?? "",
     162            1 :                     tokenUrl: v,
     163            1 :                     clientId: s.newParams?.auth?.oauth?.clientId ?? "",
     164            1 :                     callbackUrl: s.newParams?.auth?.oauth?.callbackUrl ?? "",
     165            1 :                     scopes: s.newParams?.auth?.oauth?.scopes ?? [],
     166            1 :                 },
     167            1 :             },
     168            1 :         }
     169            1 :     }));
     170              : 
     171            1 :     const oauthClientId = pageState.newParams.auth?.oauth?.clientId;
     172            1 :     const setOAuthClientId: Dispatch<string> = (v: string) => setPageState(s => ({
     173            1 :         ...s,
     174            1 :         newParams: {
     175            1 :             ...s.newParams,
     176            1 :             auth: {
     177            1 :                 authType: "AUTH_OAUTH",
     178            1 :                 oauth: {
     179            1 :                     authorizationUrl: s.newParams?.auth?.oauth?.authorizationUrl ?? "",
     180            1 :                     tokenUrl: s.newParams?.auth?.oauth?.tokenUrl ?? "",
     181            1 :                     clientId: v,
     182            1 :                     callbackUrl: s.newParams?.auth?.oauth?.callbackUrl ?? "",
     183            1 :                     scopes: s.newParams?.auth?.oauth?.scopes ?? [],
     184            1 :                 },
     185            1 :             },
     186            1 :         }
     187            1 :     }));
     188              : 
     189            1 :     const oauthRedirectUrl = pageState.newParams.auth?.oauth?.callbackUrl;
     190            1 :     const setOAuthRedirectUrl: Dispatch<string> = (v: string) => setPageState(s => ({
     191            1 :         ...s,
     192            1 :         newParams: {
     193            1 :             ...s.newParams,
     194            1 :             auth: {
     195            1 :                 authType: "AUTH_OAUTH",
     196            1 :                 oauth: {
     197            1 :                     authorizationUrl: s.newParams?.auth?.oauth?.authorizationUrl ?? "",
     198            1 :                     tokenUrl: s.newParams?.auth?.oauth?.tokenUrl ?? "",
     199            1 :                     clientId: s.newParams?.auth?.oauth?.clientId ?? "",
     200            1 :                     callbackUrl: v,
     201            1 :                     scopes: s.newParams?.auth?.oauth?.scopes ?? [],
     202            1 :                 },
     203            1 :             },
     204            1 :         }
     205            1 :     }));
     206              : 
     207            1 :     const oauthScopes = pageState.newParams.auth?.oauth?.scopes ?? [];
     208            1 :     const modifyOAuthScopes: Dispatch<UpdateValueList> = (action: UpdateValueList) => setPageState(s => ({
     209            1 :         ...s,
     210            1 :         newParams: {
     211            1 :             ...s.newParams,
     212            1 :             auth: {
     213            1 :                 authType: "AUTH_OAUTH",
     214            1 :                 oauth: {
     215            1 :                     authorizationUrl: s.newParams?.auth?.oauth?.authorizationUrl ?? "",
     216            1 :                     tokenUrl: s.newParams?.auth?.oauth?.tokenUrl ?? "",
     217            1 :                     clientId: s.newParams?.auth?.oauth?.clientId ?? "",
     218            1 :                     callbackUrl: s.newParams?.auth?.oauth?.callbackUrl ?? "",
     219            1 :                     scopes: action(s.newParams?.auth?.oauth?.scopes ?? []),
     220            1 :                 },
     221            1 :             },
     222            1 :         }
     223            1 :     }));
     224              : 
     225              : 
     226            1 :     const defaultCatalog = pageState.newParams.defaultCatalog ?? "";
     227            1 :     const setDefaultCatalog: Dispatch<string> = (v: string) => setPageState(s => ({
     228            1 :         ...s,
     229            1 :         newParams: {
     230            1 :             ...s.newParams,
     231            1 :             defaultCatalog: v
     232            1 :         }
     233            1 :     }));
     234            1 :     const defaultSchema = pageState.newParams.defaultSchema ?? "";
     235            1 :     const setDefaultSchema: Dispatch<string> = (v: string) => setPageState(s => ({
     236            1 :         ...s,
     237            1 :         newParams: {
     238            1 :             ...s.newParams,
     239            1 :             defaultSchema: v
     240            1 :         }
     241            1 :     }));
     242            1 :     const catalogs = pageState.newParams.catalogs ?? [];
     243            1 :     const modifyCatalogs: Dispatch<UpdateValueList> = (action: UpdateValueList) => setPageState(s => ({
     244            1 :         ...s,
     245            1 :         newParams: {
     246            1 :             ...s.newParams,
     247            1 :             catalogs: action(s.newParams.catalogs ?? [])
     248            1 :         }
     249            1 :     }));
     250            1 :     const modifyMetadata: Dispatch<UpdateKeyValueList> = (action: UpdateKeyValueList) => setPageState(s => {
     251            1 :         const metadata = action(s.newParamsMetadata);
     252              : 
     253              :         // Flatten the key-value list eagerly for the new params.
     254              :         // We could decide to do this lazily but it shouldn't really matter.
     255            1 :         const metadataObj: Record<string, string> = {};
     256            1 :         for (const entry of metadata) {
     257            1 :             metadataObj[entry.key] = entry.value;
     258              :         }
     259            1 :         return {
     260            1 :             ...s,
     261            1 :             newParamsMetadata: metadata,
     262            1 :             newParams: {
     263            1 :                 ...s.newParams,
     264            1 :                 metadata: metadataObj
     265            1 :             }
     266            1 :         };
     267            1 :     });
     268              : 
     269              :     // Helper to setup the connection
     270            1 :     const setupConnection = async () => {
     271              :         // Is there a Trino client?
     272            1 :         if (trinoSetup == null) {
     273            1 :             logger.error("Trino connector is unavailable", {}, LOG_CTX);
     274            1 :             return;
     275            1 :         }
     276              :         // Is there a connection id?
     277            1 :         if (connectionState == null) {
     278            0 :             logger.warn("Trino connection state is null", {}, LOG_CTX);
     279            0 :             return;
     280            0 :         }
     281              : 
     282            1 :         await connectionSetup.run(async signal => {
     283            1 :             try {
     284            1 :                 const connectionParams: connection.TrinoConnectionParams = pageState.newParams;
     285            1 :                 const trinoChannel = await trinoSetup.setup(dispatchConnectionState, connectionParams, signal);
     286            1 :                 if (trinoChannel != null) {
     287            1 :                     await performHealthCheck(queryExecutor, connectionState.connectionId, { type: 'trino', channel: trinoChannel }, dispatchConnectionState, signal);
     288              :                 }
     289            1 :             } catch (error: any) {
     290            1 :                 if (error instanceof LoggableException) {
     291            0 :                     logger.warn(error.message, error.keyValues, error.target);
     292              :                 } else {
     293            1 :                     logger.warn("Error while setting up trino connection", {
     294            1 :                         authType: (pageState.newParams.auth?.authType ?? 0).toString(),
     295            1 :                         error: stringifyError(error)
     296            1 :                     }, LOG_CTX);
     297              :                 }
     298            1 :             }
     299            1 :         });
     300            1 :     };
     301            1 :     const cancelSetup = connectionSetup.cancel;
     302            1 :     const resetSetup = async () => {
     303            1 :         if (trinoSetup) {
     304            1 :             await trinoSetup.reset(dispatchConnectionState);
     305              :         }
     306            1 :     };
     307              : 
     308              :     // Get the action button
     309              :     let connectButton: React.ReactElement = <div />;
     310              :     // Saving writes the settings into every session that uses the connection
     311            1 :     let freezeInput = saving;
     312            1 :     switch (connectionState?.connectionHealth) {
     313            1 :         case ConnectionHealth.NOT_STARTED:
     314            1 :         case ConnectionHealth.FAILED:
     315            1 :             connectButton = <Button variant={ButtonVariant.Primary} leadingVisual={PlugIcon} onClick={setupConnection}>Connect</Button>;
     316            1 :             break;
     317            1 :         case ConnectionHealth.CONNECTING:
     318            1 :             connectButton = <Button variant={ButtonVariant.Danger} leadingVisual={XIcon} onClick={cancelSetup}>Cancel</Button>;
     319            1 :             freezeInput = true;
     320            1 :             break;
     321            1 :         case ConnectionHealth.ONLINE:
     322            1 :             connectButton = <Button variant={ButtonVariant.Danger} leadingVisual={XIcon} onClick={resetSetup}>Disconnect</Button>;
     323              :             break;
     324            1 :     }
     325              : 
     326            1 :     return (
     327            1 :         <div className={style.layout}>
     328            1 :             <ConnectionInlineHeader
     329            1 :                 connector={connectorInfo}
     330            1 :                 connection={connectionState}
     331            1 :                 wrongPlatform={wrongPlatform}
     332            1 :                 setupConnection={setupConnection}
     333            1 :                 cancelSetup={cancelSetup}
     334            1 :                 resetSetup={resetSetup}
     335            1 :                 onClose={props.onClose}
     336            1 :             />
     337            1 :             <div className={style.body_container}>
     338            1 :                 <div className={style.section}>
     339            1 :                     <div className={classNames(style.section_layout, style.body_section_layout)}>
     340            1 :                         <TextField
     341            1 :                             name="Endpoint"
     342            1 :                             caption="Endpoint of the Trino Api as 'https://host:port'"
     343            1 :                             value={endpoint}
     344            1 :                             placeholder=""
     345            1 :                             validation={
     346            1 :                                 (endpoint.length ?? 0) == 0
     347            1 :                                     ? { type: VALIDATION_WARNING, value: "Endpoint is empty" }
     348            1 :                                     : undefined
     349              :                             }
     350            1 :                             leadingVisual={() => <div>URL</div>}
     351            1 :                             onChange={(e) => setEndpoint(e.target.value)}
     352            1 :                             disabled={freezeInput}
     353            1 :                             readOnly={freezeInput}
     354            1 :                             autoComplete={false}
     355            1 :                             logContext={LOG_CTX}
     356            1 :                         />
     357            1 :                     </div>
     358            1 :                 </div>
     359            1 :                 <div className={style.section}>
     360            1 :                     <div className={classNames(style.section_layout, style.body_section_layout)}>
     361            1 :                         <div className={style.section_header}>
     362            1 :                             <SegmentedControl
     363            1 :                                 aria-label="Authentication type"
     364            1 :                                 onChange={(index) => {
     365            1 :                                     const newAuthType = index === 0
     366            1 :                                         ? "AUTH_BASIC"
     367            1 :                                         : "AUTH_OAUTH";
     368              :                                     // The control also reports a click on the selected type, which must keep the credentials
     369            1 :                                     if (newAuthType !== authType) {
     370            1 :                                         setAuthType(newAuthType);
     371              :                                     }
     372            1 :                                 }}
     373              :                             >
     374            1 :                                 <SegmentedControl.Button
     375            1 :                                     selected={authType === "AUTH_BASIC"}
     376            1 :                                     disabled={freezeInput}
     377            1 :                                 >
     378              :                                     Basic
     379            1 :                                 </SegmentedControl.Button>
     380            1 :                                 <SegmentedControl.Button
     381            1 :                                     selected={authType === "AUTH_OAUTH"}
     382            1 :                                     disabled={freezeInput}
     383            1 :                                 >
     384              :                                     OAuth 2.0
     385            1 :                                 </SegmentedControl.Button>
     386            1 :                             </SegmentedControl>
     387            1 :                         </div>
     388            1 :                         <TextField
     389            1 :                             style={{ display: authType == "AUTH_BASIC" ? 'block' : 'none' }}
     390            1 :                             name="Username"
     391            1 :                             caption="Username for the Trino Api"
     392            1 :                             placeholder=""
     393            1 :                             validation={
     394            1 :                                 (basicAuthUsername?.length ?? 0) == 0
     395            1 :                                     ? { type: VALIDATION_WARNING, value: "Username is empty" }
     396            1 :                                     : undefined
     397              :                             }
     398            1 :                             leadingVisual={() => <div>ID</div>}
     399            1 :                             value={basicAuthUsername ?? ""}
     400            1 :                             onChange={(e) => setBasicAuthUsername(e.target.value)}
     401            1 :                             disabled={freezeInput}
     402            1 :                             readOnly={freezeInput}
     403            1 :                             autoComplete={false}
     404            1 :                             logContext={LOG_CTX}
     405            1 :                         />
     406            1 :                         <TextField
     407            1 :                             style={{ display: authType == "AUTH_BASIC" ? 'block' : 'none' }}
     408            1 :                             name="Secret"
     409            1 :                             caption="Password for the Trino Api"
     410            1 :                             placeholder=""
     411            1 :                             validation={
     412            1 :                                 (basicAuthSecret?.length ?? 0) == 0
     413            1 :                                     ? { type: VALIDATION_WARNING, value: "Secret is empty" }
     414            1 :                                     : undefined
     415              :                             }
     416            1 :                             leadingVisual={KeyIcon}
     417            1 :                             value={basicAuthSecret ?? ""}
     418            1 :                             onChange={(e) => setBasicAuthSecret(e.target.value)}
     419            1 :                             disabled={freezeInput}
     420            1 :                             readOnly={freezeInput}
     421            1 :                             concealed={true}
     422            1 :                             logContext={LOG_CTX}
     423            1 :                         />
     424            1 :                         <TextField
     425            1 :                             style={{ display: authType == "AUTH_OAUTH" ? 'block' : 'none' }}
     426            1 :                             name="Authorization Url"
     427            1 :                             caption="Url to start the OAuth flow"
     428            1 :                             placeholder=""
     429            1 :                             validation={
     430            1 :                                 (pageState.newParams.auth?.oauth?.authorizationUrl.length ?? 0) == 0
     431            1 :                                     ? { type: VALIDATION_WARNING, value: "Authorization Url is empty" }
     432            1 :                                     : undefined
     433              :                             }
     434            1 :                             leadingVisual={() => <div>URL</div>}
     435            1 :                             value={oauthAuthEndpoint ?? ""}
     436            1 :                             onChange={(e) => setOAuthAuthEndpoint(e.target.value)}
     437            1 :                             disabled={freezeInput}
     438            1 :                             readOnly={freezeInput}
     439            1 :                             autoComplete={false}
     440            1 :                             logContext={LOG_CTX}
     441            1 :                         />
     442            1 :                         <TextField
     443            1 :                             style={{ display: authType == "AUTH_OAUTH" ? 'block' : 'none' }}
     444            1 :                             name="Token Url"
     445            1 :                             caption="Url to retrieve an Access Token"
     446            1 :                             placeholder=""
     447            1 :                             validation={
     448            1 :                                 (pageState.newParams.auth?.oauth?.tokenUrl.length ?? 0) == 0
     449            1 :                                     ? { type: VALIDATION_WARNING, value: "Token Endpoint is empty" }
     450            1 :                                     : undefined
     451              :                             }
     452            1 :                             leadingVisual={() => <div>URL</div>}
     453            1 :                             value={oauthTokenEndpoint ?? ""}
     454            1 :                             onChange={(e) => setOAuthTokenEndpoint(e.target.value)}
     455            1 :                             disabled={freezeInput}
     456            1 :                             readOnly={freezeInput}
     457            1 :                             autoComplete={false}
     458            1 :                             logContext={LOG_CTX}
     459            1 :                         />
     460            1 :                         <TextField
     461            1 :                             style={{ display: authType == "AUTH_OAUTH" ? 'block' : 'none' }}
     462            1 :                             name="Client ID"
     463            1 :                             caption="Client ID of the OAuth application"
     464            1 :                             placeholder=""
     465            1 :                             validation={
     466            1 :                                 (pageState.newParams.auth?.oauth?.clientId?.length ?? 0) == 0
     467            1 :                                     ? { type: VALIDATION_WARNING, value: "Client ID is empty" }
     468            1 :                                     : undefined
     469              :                             }
     470            1 :                             leadingVisual={() => <div>ID</div>}
     471            1 :                             value={oauthClientId ?? ""}
     472            1 :                             onChange={(e) => setOAuthClientId(e.target.value)}
     473            1 :                             disabled={freezeInput}
     474            1 :                             readOnly={freezeInput}
     475            1 :                             autoComplete={false}
     476            1 :                             logContext={LOG_CTX}
     477            1 :                         />
     478            1 :                         <TextField
     479            1 :                             style={{ display: authType == "AUTH_OAUTH" ? 'block' : 'none' }}
     480            1 :                             name="Redirect URL"
     481            1 :                             caption="Redirect URL of the OAuth application"
     482            1 :                             placeholder=""
     483            1 :                             validation={
     484            1 :                                 (pageState.newParams.auth?.oauth?.callbackUrl?.length ?? 0) == 0
     485            1 :                                     ? { type: VALIDATION_WARNING, value: "Redirect URL is empty" }
     486            1 :                                     : undefined
     487              :                             }
     488            1 :                             leadingVisual={() => <div>URL</div>}
     489            1 :                             value={oauthRedirectUrl ?? ""}
     490            1 :                             onChange={(e) => setOAuthRedirectUrl(e.target.value)}
     491            1 :                             disabled={freezeInput}
     492            1 :                             readOnly={freezeInput}
     493            1 :                             autoComplete={false}
     494            1 :                             logContext={LOG_CTX}
     495            1 :                         />
     496            1 :                         <ValueListBuilder
     497            1 :                             title="Scope"
     498            1 :                             caption="OAuth scopes"
     499            1 :                             valueIcon={() => <div>Scope</div>}
     500            1 :                             addButtonLabel="Add Scope"
     501            1 :                             elements={oauthScopes}
     502            1 :                             modifyElements={modifyOAuthScopes}
     503            1 :                             disabled={freezeInput}
     504            1 :                             readOnly={freezeInput}
     505            1 :                         />
     506            1 :                     </div>
     507            1 :                 </div>
     508            1 :                 <div className={style.section}>
     509            1 :                     <div className={classNames(style.section_layout, style.body_section_layout)}>
     510            1 :                         <ValueListBuilder
     511            1 :                             title="Catalogs"
     512            1 :                             caption="Relations to load, 'catalog' for all schemas or 'catalog.schema' for one. Without entries the default catalog is loaded."
     513            1 :                             valueIcon={BookIcon}
     514            1 :                             addButtonLabel="Add Catalog"
     515            1 :                             elements={catalogs}
     516            1 :                             modifyElements={modifyCatalogs}
     517            1 :                             disabled={freezeInput}
     518            1 :                             readOnly={freezeInput}
     519            1 :                         />
     520            1 :                     </div>
     521            1 :                 </div>
     522            1 :                 <div className={style.section}>
     523            1 :                     <div className={classNames(style.section_layout, style.body_section_layout)}>
     524            1 :                         <TextField
     525            1 :                             name="Default Catalog"
     526            1 :                             caption="Catalog of the names that statements leave out"
     527            1 :                             value={defaultCatalog}
     528            1 :                             placeholder=""
     529            1 :                             leadingVisual={BookIcon}
     530            1 :                             onChange={(e) => setDefaultCatalog(e.target.value)}
     531            1 :                             disabled={freezeInput}
     532            1 :                             readOnly={freezeInput}
     533            1 :                             autoComplete={false}
     534            1 :                             logContext={LOG_CTX}
     535            1 :                         />
     536            1 :                         <TextField
     537            1 :                             name="Default Schema"
     538            1 :                             caption="Schema of the names that statements leave out"
     539            1 :                             value={defaultSchema}
     540            1 :                             placeholder=""
     541            1 :                             validation={
     542            1 :                                 defaultSchema.trim().length > 0 && defaultCatalog.trim().length == 0
     543            1 :                                     ? { type: VALIDATION_WARNING, value: "A default schema needs a default catalog" }
     544            1 :                                     : undefined
     545              :                             }
     546            1 :                             leadingVisual={BookIcon}
     547            1 :                             onChange={(e) => setDefaultSchema(e.target.value)}
     548            1 :                             disabled={freezeInput}
     549            1 :                             readOnly={freezeInput}
     550            1 :                             autoComplete={false}
     551            1 :                             logContext={LOG_CTX}
     552            1 :                         />
     553            1 :                     </div>
     554            1 :                 </div>
     555            1 :                 <div className={style.section}>
     556            1 :                     <div className={classNames(style.section_layout, style.body_section_layout)}>
     557            1 :                         <KeyValueListBuilder
     558            1 :                             title="Additional Metadata"
     559            1 :                             caption="Extra HTTP headers that are added to each request"
     560            1 :                             keyIcon={() => <div>Header</div>}
     561            1 :                             valueIcon={() => <div>Value</div>}
     562            1 :                             addButtonLabel="Add Header"
     563            1 :                             elements={pageState.newParamsMetadata}
     564            1 :                             modifyElements={modifyMetadata}
     565            1 :                             disabled={freezeInput}
     566            1 :                             readOnly={freezeInput}
     567            1 :                         />
     568            1 :                     </div>
     569            1 :                 </div>
     570            1 :             </div>
     571            1 :         </ div>
     572              :     );
     573            1 : };
        

Generated by: LCOV version 2.0-1