Loading frontend/src/components/ExerciseList/ExerciseButtons/InfoButton/ExerciseDetails.tsx +1 −0 Original line number Diff line number Diff line Loading @@ -162,6 +162,7 @@ export const ExerciseDetail: FC<ExerciseDetailProps> = ({ className={collapsibleSection} open={sandboxOpen} setOpen={setSandboxOpen} teams={teams} /> )} <ConfigInfo Loading frontend/src/components/ExerciseList/ExerciseButtons/InfoButton/SandboxInfo.tsx +159 −52 Original line number Diff line number Diff line import { Button, ButtonGroup, Collapse } from '@blueprintjs/core' import { css } from '@emotion/css' import type { TeamTokenF } from '@inject/graphql' import { ExerciseTokensQuery, useTypedQuery } from '@inject/graphql' import { css, cx } from '@emotion/css' import type { Team } from '@inject/graphql' import { ExerciseTokensQuery, useHost, useTypedQuery } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import type { Column, Row } from '@inject/shared' import { stringSortingFunction, Table } from '@inject/shared' import { Table } from '@inject/shared' import type { Dispatch, SetStateAction } from 'react' import { type FC } from 'react' import type { SandboxConfiguration } from '../../../SandboxConfigurationButton/getEnvContent' import { getSandboxConfiguration } from '../../../SandboxConfigurationButton/getEnvContent' // TODO: deduplicate const verticallyCentered = css` const rendererClassName = css` vertical-align: middle; white-space: pre-wrap; word-break: break-word; ` interface ConfigInfoProps { Loading @@ -18,6 +21,7 @@ interface ConfigInfoProps { exerciseId: string open: boolean setOpen: Dispatch<SetStateAction<boolean>> teams: Team[] } export const SandboxInfo: FC<ConfigInfoProps> = ({ Loading @@ -25,6 +29,7 @@ export const SandboxInfo: FC<ConfigInfoProps> = ({ exerciseId, open, setOpen, teams, }) => { const { t } = useTranslationFrontend() Loading @@ -36,56 +41,164 @@ export const SandboxInfo: FC<ConfigInfoProps> = ({ requestPolicy: 'network-only', }) const columns: Column<TeamTokenF>[] = [ const openSearchEnabled = window.VITE_OPENSEARCH_HOST && window.VITE_OPENSEARCH_PORT const openSearchConfigColumns: Column<Team>[] = [ { id: 'teamIds', name: t('exerciseInfo.teamIds'), style: { textAlign: 'right', width: '15ch' }, renderValue: teamToken => teamToken.teamIds.join(', '), className: verticallyCentered, sortingFunction: (a, b) => stringSortingFunction(a.teamIds.join(', '), b.teamIds.join(', ')), id: 'opensearchUser', name: t( 'exercisePanel.definitionManager.info.sandboxConfig.opensearchUsername' ), renderValue: team => team.openSearchAccess?.username || '', className: rendererClassName, }, { id: 'opensearchPassword', name: t( 'exercisePanel.definitionManager.info.sandboxConfig.opensearchPassword' ), renderValue: team => team.openSearchAccess?.password || '', className: rendererClassName, }, { id: 'opensearchIndex', name: t( 'exercisePanel.definitionManager.info.sandboxConfig.opensearchIndex' ), renderValue: team => team.openSearchAccess?.indexName || '', className: rendererClassName, }, ] const injectConfigColumns: Column<Team>[] = [ { id: 'user', name: t('exerciseInfo.user'), style: { width: '40%' }, renderValue: teamToken => teamToken.user.username, className: verticallyCentered, sortingFunction: (a, b) => stringSortingFunction(a.user.username, b.user.username), id: 'teamId', name: t('exercisePanel.definitionManager.info.sandboxConfig.teamId'), renderValue: team => team.id, className: cx( rendererClassName, css` width: 10ch; ` ), }, { id: 'users', name: t('exercisePanel.definitionManager.info.sandboxConfig.users'), renderValue: team => team.users .map(user => { const { firstName, lastName, username } = user return firstName && lastName ? `${lastName}, ${firstName} (${username})` : username }) .join('; '), className: cx( rendererClassName, css` width: 20ch; ` ), }, { id: 'token', name: t('exerciseInfo.token'), style: { width: '60%', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }, renderValue: teamToken => teamToken.token, className: verticallyCentered, sortingFunction: (a, b) => stringSortingFunction(a.token, b.token), name: t('exercisePanel.definitionManager.info.sandboxConfig.injectToken'), renderValue: team => data?.exerciseTokens.find(token => token.teamIds.includes(team.id)) ?.token || '', className: rendererClassName, }, ] const rows: Row<TeamTokenF>[] = data?.exerciseTokens.map(token => ({ id: token.token, value: token, })) || [] const columns = openSearchEnabled ? [...injectConfigColumns, ...openSearchConfigColumns] : injectConfigColumns const rows: Row<Team>[] = teams.map(team => ({ id: team.id, value: team, })) const host = useHost() const exportAsCSV = () => { const header = ['teamIds', 'username', 'token'].join(',') const csvRows = data?.exerciseTokens.map(token => { const teamIds = `"${token.teamIds.join('|')}"` const username = `"${token.user.username}"` const tokenValue = `"${token.token}"` return [teamIds, username, tokenValue].join(',') }) || [] const openSearchHeader = [ 'opensearchHost', 'opensearchIndex', 'opensearchUser', 'opensearchPassword', ].join(',') const injectHeader = [ 'teamId', 'username', 'firstName', 'lastName', 'injectHost', 'injectTeamToken', ].join(',') const header = openSearchEnabled ? `${injectHeader},${openSearchHeader}` : injectHeader const csvRows = teams.flatMap(team => team.users.map(user => { const { id: teamId, openSearchAccess } = team const { username, firstName, lastName } = user const token = data?.exerciseTokens.find( exerciseToken => exerciseToken.user.id === user.id )?.token // button is disabled when fetching, and each user should have a token if (!token) { throw new Error( `No token found for user ${username} in team ${teamId}` ) } const { injectHost, injectTeamToken, opensearchHost, opensearchIndex, opensearchUser, opensearchPassword, }: SandboxConfiguration = openSearchAccess ? getSandboxConfiguration({ host, teamId, token, openSearchAccess, }) : getSandboxConfiguration({ host, teamId, token, }) const openSearchData = [ opensearchHost ?? '', opensearchIndex ?? '', opensearchUser ?? '', opensearchPassword ?? '', ].join(',') const injectData = [ teamId, username, firstName, lastName, injectHost, injectTeamToken, ].join(',') return openSearchEnabled ? `${injectData},${openSearchData}` : injectData }) ) const csvContent = [header, ...csvRows].join('\n') const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) const link = document.createElement('a') const url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', `exercise_${exerciseId}_tokens.csv`) link.setAttribute('download', `exercise${exerciseId}-sandbox_config.csv`) link.style.visibility = 'hidden' document.body.appendChild(link) link.click() Loading @@ -98,26 +211,20 @@ export const SandboxInfo: FC<ConfigInfoProps> = ({ <Button onClick={() => setOpen(!open)} active={open} loading={fetching}> {`${ open ? t('exercisePanel.definitionManager.info.hideTokens') : t('exercisePanel.definitionManager.info.showTokens') } ${t('exercisePanel.definitionManager.info.tokens')}`} ? t('exercisePanel.definitionManager.info.sandboxConfig.hide') : t('exercisePanel.definitionManager.info.sandboxConfig.show') } ${t('exercisePanel.definitionManager.info.sandboxConfig.config')}`} </Button> <Button onClick={exportAsCSV} loading={fetching}> {t('exercisePanel.definitionManager.info.exportTokens')} <Button onClick={exportAsCSV} loading={fetching} icon='export'> {t('exercisePanel.definitionManager.info.sandboxConfig.exportCSV')} </Button> </ButtonGroup> <Collapse isOpen={open}> <Table<TeamTokenF> <Table<Team> columns={columns} className={className} rows={rows} defaultSortByColumnId='team' noDataStateProps={{ title: t('exercisePanel.definitionManager.info.noTokensTitle'), description: t( 'exercisePanel.definitionManager.info.noTokensDescription' ), }} /> </Collapse> </div> Loading frontend/src/components/SandboxConfigurationButton/getEnvContent.ts 0 → 100644 +125 −0 Original line number Diff line number Diff line import type { OpenSearchAccess } from '@inject/graphql' import { createSandboxLogUrl } from '@inject/shared' // TODO: consolidate env variable typings declare global { interface Window { VITE_OPENSEARCH_HOST: string | undefined VITE_OPENSEARCH_PORT: string | undefined } } type Empty<T> = { [K in keyof T]?: never } export type InjectSandboxConfiguration = { injectHost: string injectTeamToken: string } export type OpenSearchSandboxConfiguration = { opensearchHost: string opensearchIndex: string opensearchUser: string opensearchPassword: string } type SandboxConfigurationParams = { openSearchAccess?: OpenSearchAccess teamId: string token: string host: string } export type SandboxConfiguration = | (InjectSandboxConfiguration & Empty<OpenSearchSandboxConfiguration>) | (InjectSandboxConfiguration & OpenSearchSandboxConfiguration) // Function overloads for proper typing based on presence of openSearchAccess export function getSandboxConfiguration( params: SandboxConfigurationParams & { openSearchAccess?: never } ): InjectSandboxConfiguration export function getSandboxConfiguration( params: SandboxConfigurationParams & { openSearchAccess: OpenSearchAccess } ): InjectSandboxConfiguration & OpenSearchSandboxConfiguration export function getSandboxConfiguration({ openSearchAccess, teamId, token, host, }: SandboxConfigurationParams): SandboxConfiguration { const injectHost = createSandboxLogUrl(host, teamId) const injectTeamToken = token if ( !window.VITE_OPENSEARCH_HOST || !window.VITE_OPENSEARCH_PORT || !openSearchAccess ) { return { injectHost, injectTeamToken, } } const opensearchHost = `https://${window.VITE_OPENSEARCH_HOST}:${window.VITE_OPENSEARCH_PORT}` const opensearchIndex = openSearchAccess.indexName const opensearchUser = openSearchAccess.username const opensearchPassword = openSearchAccess.password return { injectHost, injectTeamToken, opensearchHost, opensearchIndex, opensearchUser, opensearchPassword, } } export const getEnvContent = ({ openSearchAccess, teamId, token, host, }: { openSearchAccess?: OpenSearchAccess | null teamId: string token: string host: string }) => { const sandboxConfiguration = openSearchAccess ? getSandboxConfiguration({ openSearchAccess, teamId, token, host, }) : getSandboxConfiguration({ teamId, token, host, }) const injectLoggingContent = `INJECT_HOST=${sandboxConfiguration.injectHost}\n` + `INJECT_TEAM_TOKEN=${sandboxConfiguration.injectTeamToken}\n` const isOpenSearchConfig = ( config: | InjectSandboxConfiguration | (InjectSandboxConfiguration & OpenSearchSandboxConfiguration) ): config is InjectSandboxConfiguration & OpenSearchSandboxConfiguration => 'opensearchHost' in config if (isOpenSearchConfig(sandboxConfiguration)) { const openSearchContent = `OPENSEARCH_HOST=${sandboxConfiguration.opensearchHost}\n` + `OPENSEARCH_INDEX=${sandboxConfiguration.opensearchIndex}\n` + `OPENSEARCH_USER=${sandboxConfiguration.opensearchUser}\n` + `OPENSEARCH_PASSWORD=${sandboxConfiguration.opensearchPassword}\n` return openSearchContent + injectLoggingContent } return injectLoggingContent } frontend/src/views/TraineeView/SandboxConfigurationButton.tsx→frontend/src/components/SandboxConfigurationButton/index.tsx +8 −31 Original line number Diff line number Diff line Loading @@ -6,21 +6,13 @@ import { type OpenSearchAccess, } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import { createSandboxLogUrl, dialog, dialogBody } from '@inject/shared' import { dialog, dialogBody } from '@inject/shared' import { useState, type FC } from 'react' import { getEnvContent } from './getEnvContent' // TODO: localization // TODO: verification of OpenSearch connection // TODO: consolidate declare global { interface Window { VITE_OPENSEARCH_HOST: string | undefined VITE_OPENSEARCH_PORT: string | undefined } } interface SandboxConfigurationButtonProps { openSearchAccess?: OpenSearchAccess | null hideLabel?: boolean Loading @@ -40,27 +32,12 @@ export const SandboxConfigurationButton: FC< const token = data?.userTeamToken || '' const handleDownload = () => { const openSearchHost = window.VITE_OPENSEARCH_HOST const port = window.VITE_OPENSEARCH_PORT const injectLoggingContent = `INJECT_HOST=${createSandboxLogUrl(host, teamId)}\n` + `INJECT_TEAM_TOKEN=${token}\n` const envContent = (() => { if (openSearchHost && port && openSearchAccess) { const { indexName, password, username } = openSearchAccess const openSearchContent = `OPENSEARCH_HOST=https://${openSearchHost}:${port}\n` + `OPENSEARCH_INDEX=${indexName}\n` + `OPENSEARCH_USER=${username}\n` + `OPENSEARCH_PASSWORD=${password}\n` return openSearchContent + injectLoggingContent } return injectLoggingContent })() const envContent = getEnvContent({ openSearchAccess, teamId, token, host, }) const blob = new Blob([envContent], { type: 'text/plain' }) const url = URL.createObjectURL(blob) Loading frontend/src/views/TraineeView/index.tsx +1 −1 Original line number Diff line number Diff line Loading @@ -20,13 +20,13 @@ import { } from '@inject/shared' import { Suspense, useEffect, type FC, type PropsWithChildren } from 'react' import { ExerciseStatus, ExitButton } from '../../components' import { SandboxConfigurationButton } from '../../components/SandboxConfigurationButton' import TraineeEmailFormOverlay from '../../email/EmailFormOverlay/TraineeEmailFormOverlay' import useMailToRef from '../../hooks/useMailToRef' import { RootRoute } from '../../routes/__root' import { TraineeDriveRoute } from '../../routes/_protected/trainee/$exerciseId/$teamId/drive' import ChannelButton from '../ChannelButton' import { OverviewButton } from './OverviewButton' import { SandboxConfigurationButton } from './SandboxConfigurationButton' import StopAnnounce from './StopAnnounce' import useTraineeViewData from './useTraineeViewData' Loading Loading
frontend/src/components/ExerciseList/ExerciseButtons/InfoButton/ExerciseDetails.tsx +1 −0 Original line number Diff line number Diff line Loading @@ -162,6 +162,7 @@ export const ExerciseDetail: FC<ExerciseDetailProps> = ({ className={collapsibleSection} open={sandboxOpen} setOpen={setSandboxOpen} teams={teams} /> )} <ConfigInfo Loading
frontend/src/components/ExerciseList/ExerciseButtons/InfoButton/SandboxInfo.tsx +159 −52 Original line number Diff line number Diff line import { Button, ButtonGroup, Collapse } from '@blueprintjs/core' import { css } from '@emotion/css' import type { TeamTokenF } from '@inject/graphql' import { ExerciseTokensQuery, useTypedQuery } from '@inject/graphql' import { css, cx } from '@emotion/css' import type { Team } from '@inject/graphql' import { ExerciseTokensQuery, useHost, useTypedQuery } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import type { Column, Row } from '@inject/shared' import { stringSortingFunction, Table } from '@inject/shared' import { Table } from '@inject/shared' import type { Dispatch, SetStateAction } from 'react' import { type FC } from 'react' import type { SandboxConfiguration } from '../../../SandboxConfigurationButton/getEnvContent' import { getSandboxConfiguration } from '../../../SandboxConfigurationButton/getEnvContent' // TODO: deduplicate const verticallyCentered = css` const rendererClassName = css` vertical-align: middle; white-space: pre-wrap; word-break: break-word; ` interface ConfigInfoProps { Loading @@ -18,6 +21,7 @@ interface ConfigInfoProps { exerciseId: string open: boolean setOpen: Dispatch<SetStateAction<boolean>> teams: Team[] } export const SandboxInfo: FC<ConfigInfoProps> = ({ Loading @@ -25,6 +29,7 @@ export const SandboxInfo: FC<ConfigInfoProps> = ({ exerciseId, open, setOpen, teams, }) => { const { t } = useTranslationFrontend() Loading @@ -36,56 +41,164 @@ export const SandboxInfo: FC<ConfigInfoProps> = ({ requestPolicy: 'network-only', }) const columns: Column<TeamTokenF>[] = [ const openSearchEnabled = window.VITE_OPENSEARCH_HOST && window.VITE_OPENSEARCH_PORT const openSearchConfigColumns: Column<Team>[] = [ { id: 'teamIds', name: t('exerciseInfo.teamIds'), style: { textAlign: 'right', width: '15ch' }, renderValue: teamToken => teamToken.teamIds.join(', '), className: verticallyCentered, sortingFunction: (a, b) => stringSortingFunction(a.teamIds.join(', '), b.teamIds.join(', ')), id: 'opensearchUser', name: t( 'exercisePanel.definitionManager.info.sandboxConfig.opensearchUsername' ), renderValue: team => team.openSearchAccess?.username || '', className: rendererClassName, }, { id: 'opensearchPassword', name: t( 'exercisePanel.definitionManager.info.sandboxConfig.opensearchPassword' ), renderValue: team => team.openSearchAccess?.password || '', className: rendererClassName, }, { id: 'opensearchIndex', name: t( 'exercisePanel.definitionManager.info.sandboxConfig.opensearchIndex' ), renderValue: team => team.openSearchAccess?.indexName || '', className: rendererClassName, }, ] const injectConfigColumns: Column<Team>[] = [ { id: 'user', name: t('exerciseInfo.user'), style: { width: '40%' }, renderValue: teamToken => teamToken.user.username, className: verticallyCentered, sortingFunction: (a, b) => stringSortingFunction(a.user.username, b.user.username), id: 'teamId', name: t('exercisePanel.definitionManager.info.sandboxConfig.teamId'), renderValue: team => team.id, className: cx( rendererClassName, css` width: 10ch; ` ), }, { id: 'users', name: t('exercisePanel.definitionManager.info.sandboxConfig.users'), renderValue: team => team.users .map(user => { const { firstName, lastName, username } = user return firstName && lastName ? `${lastName}, ${firstName} (${username})` : username }) .join('; '), className: cx( rendererClassName, css` width: 20ch; ` ), }, { id: 'token', name: t('exerciseInfo.token'), style: { width: '60%', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }, renderValue: teamToken => teamToken.token, className: verticallyCentered, sortingFunction: (a, b) => stringSortingFunction(a.token, b.token), name: t('exercisePanel.definitionManager.info.sandboxConfig.injectToken'), renderValue: team => data?.exerciseTokens.find(token => token.teamIds.includes(team.id)) ?.token || '', className: rendererClassName, }, ] const rows: Row<TeamTokenF>[] = data?.exerciseTokens.map(token => ({ id: token.token, value: token, })) || [] const columns = openSearchEnabled ? [...injectConfigColumns, ...openSearchConfigColumns] : injectConfigColumns const rows: Row<Team>[] = teams.map(team => ({ id: team.id, value: team, })) const host = useHost() const exportAsCSV = () => { const header = ['teamIds', 'username', 'token'].join(',') const csvRows = data?.exerciseTokens.map(token => { const teamIds = `"${token.teamIds.join('|')}"` const username = `"${token.user.username}"` const tokenValue = `"${token.token}"` return [teamIds, username, tokenValue].join(',') }) || [] const openSearchHeader = [ 'opensearchHost', 'opensearchIndex', 'opensearchUser', 'opensearchPassword', ].join(',') const injectHeader = [ 'teamId', 'username', 'firstName', 'lastName', 'injectHost', 'injectTeamToken', ].join(',') const header = openSearchEnabled ? `${injectHeader},${openSearchHeader}` : injectHeader const csvRows = teams.flatMap(team => team.users.map(user => { const { id: teamId, openSearchAccess } = team const { username, firstName, lastName } = user const token = data?.exerciseTokens.find( exerciseToken => exerciseToken.user.id === user.id )?.token // button is disabled when fetching, and each user should have a token if (!token) { throw new Error( `No token found for user ${username} in team ${teamId}` ) } const { injectHost, injectTeamToken, opensearchHost, opensearchIndex, opensearchUser, opensearchPassword, }: SandboxConfiguration = openSearchAccess ? getSandboxConfiguration({ host, teamId, token, openSearchAccess, }) : getSandboxConfiguration({ host, teamId, token, }) const openSearchData = [ opensearchHost ?? '', opensearchIndex ?? '', opensearchUser ?? '', opensearchPassword ?? '', ].join(',') const injectData = [ teamId, username, firstName, lastName, injectHost, injectTeamToken, ].join(',') return openSearchEnabled ? `${injectData},${openSearchData}` : injectData }) ) const csvContent = [header, ...csvRows].join('\n') const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) const link = document.createElement('a') const url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', `exercise_${exerciseId}_tokens.csv`) link.setAttribute('download', `exercise${exerciseId}-sandbox_config.csv`) link.style.visibility = 'hidden' document.body.appendChild(link) link.click() Loading @@ -98,26 +211,20 @@ export const SandboxInfo: FC<ConfigInfoProps> = ({ <Button onClick={() => setOpen(!open)} active={open} loading={fetching}> {`${ open ? t('exercisePanel.definitionManager.info.hideTokens') : t('exercisePanel.definitionManager.info.showTokens') } ${t('exercisePanel.definitionManager.info.tokens')}`} ? t('exercisePanel.definitionManager.info.sandboxConfig.hide') : t('exercisePanel.definitionManager.info.sandboxConfig.show') } ${t('exercisePanel.definitionManager.info.sandboxConfig.config')}`} </Button> <Button onClick={exportAsCSV} loading={fetching}> {t('exercisePanel.definitionManager.info.exportTokens')} <Button onClick={exportAsCSV} loading={fetching} icon='export'> {t('exercisePanel.definitionManager.info.sandboxConfig.exportCSV')} </Button> </ButtonGroup> <Collapse isOpen={open}> <Table<TeamTokenF> <Table<Team> columns={columns} className={className} rows={rows} defaultSortByColumnId='team' noDataStateProps={{ title: t('exercisePanel.definitionManager.info.noTokensTitle'), description: t( 'exercisePanel.definitionManager.info.noTokensDescription' ), }} /> </Collapse> </div> Loading
frontend/src/components/SandboxConfigurationButton/getEnvContent.ts 0 → 100644 +125 −0 Original line number Diff line number Diff line import type { OpenSearchAccess } from '@inject/graphql' import { createSandboxLogUrl } from '@inject/shared' // TODO: consolidate env variable typings declare global { interface Window { VITE_OPENSEARCH_HOST: string | undefined VITE_OPENSEARCH_PORT: string | undefined } } type Empty<T> = { [K in keyof T]?: never } export type InjectSandboxConfiguration = { injectHost: string injectTeamToken: string } export type OpenSearchSandboxConfiguration = { opensearchHost: string opensearchIndex: string opensearchUser: string opensearchPassword: string } type SandboxConfigurationParams = { openSearchAccess?: OpenSearchAccess teamId: string token: string host: string } export type SandboxConfiguration = | (InjectSandboxConfiguration & Empty<OpenSearchSandboxConfiguration>) | (InjectSandboxConfiguration & OpenSearchSandboxConfiguration) // Function overloads for proper typing based on presence of openSearchAccess export function getSandboxConfiguration( params: SandboxConfigurationParams & { openSearchAccess?: never } ): InjectSandboxConfiguration export function getSandboxConfiguration( params: SandboxConfigurationParams & { openSearchAccess: OpenSearchAccess } ): InjectSandboxConfiguration & OpenSearchSandboxConfiguration export function getSandboxConfiguration({ openSearchAccess, teamId, token, host, }: SandboxConfigurationParams): SandboxConfiguration { const injectHost = createSandboxLogUrl(host, teamId) const injectTeamToken = token if ( !window.VITE_OPENSEARCH_HOST || !window.VITE_OPENSEARCH_PORT || !openSearchAccess ) { return { injectHost, injectTeamToken, } } const opensearchHost = `https://${window.VITE_OPENSEARCH_HOST}:${window.VITE_OPENSEARCH_PORT}` const opensearchIndex = openSearchAccess.indexName const opensearchUser = openSearchAccess.username const opensearchPassword = openSearchAccess.password return { injectHost, injectTeamToken, opensearchHost, opensearchIndex, opensearchUser, opensearchPassword, } } export const getEnvContent = ({ openSearchAccess, teamId, token, host, }: { openSearchAccess?: OpenSearchAccess | null teamId: string token: string host: string }) => { const sandboxConfiguration = openSearchAccess ? getSandboxConfiguration({ openSearchAccess, teamId, token, host, }) : getSandboxConfiguration({ teamId, token, host, }) const injectLoggingContent = `INJECT_HOST=${sandboxConfiguration.injectHost}\n` + `INJECT_TEAM_TOKEN=${sandboxConfiguration.injectTeamToken}\n` const isOpenSearchConfig = ( config: | InjectSandboxConfiguration | (InjectSandboxConfiguration & OpenSearchSandboxConfiguration) ): config is InjectSandboxConfiguration & OpenSearchSandboxConfiguration => 'opensearchHost' in config if (isOpenSearchConfig(sandboxConfiguration)) { const openSearchContent = `OPENSEARCH_HOST=${sandboxConfiguration.opensearchHost}\n` + `OPENSEARCH_INDEX=${sandboxConfiguration.opensearchIndex}\n` + `OPENSEARCH_USER=${sandboxConfiguration.opensearchUser}\n` + `OPENSEARCH_PASSWORD=${sandboxConfiguration.opensearchPassword}\n` return openSearchContent + injectLoggingContent } return injectLoggingContent }
frontend/src/views/TraineeView/SandboxConfigurationButton.tsx→frontend/src/components/SandboxConfigurationButton/index.tsx +8 −31 Original line number Diff line number Diff line Loading @@ -6,21 +6,13 @@ import { type OpenSearchAccess, } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import { createSandboxLogUrl, dialog, dialogBody } from '@inject/shared' import { dialog, dialogBody } from '@inject/shared' import { useState, type FC } from 'react' import { getEnvContent } from './getEnvContent' // TODO: localization // TODO: verification of OpenSearch connection // TODO: consolidate declare global { interface Window { VITE_OPENSEARCH_HOST: string | undefined VITE_OPENSEARCH_PORT: string | undefined } } interface SandboxConfigurationButtonProps { openSearchAccess?: OpenSearchAccess | null hideLabel?: boolean Loading @@ -40,27 +32,12 @@ export const SandboxConfigurationButton: FC< const token = data?.userTeamToken || '' const handleDownload = () => { const openSearchHost = window.VITE_OPENSEARCH_HOST const port = window.VITE_OPENSEARCH_PORT const injectLoggingContent = `INJECT_HOST=${createSandboxLogUrl(host, teamId)}\n` + `INJECT_TEAM_TOKEN=${token}\n` const envContent = (() => { if (openSearchHost && port && openSearchAccess) { const { indexName, password, username } = openSearchAccess const openSearchContent = `OPENSEARCH_HOST=https://${openSearchHost}:${port}\n` + `OPENSEARCH_INDEX=${indexName}\n` + `OPENSEARCH_USER=${username}\n` + `OPENSEARCH_PASSWORD=${password}\n` return openSearchContent + injectLoggingContent } return injectLoggingContent })() const envContent = getEnvContent({ openSearchAccess, teamId, token, host, }) const blob = new Blob([envContent], { type: 'text/plain' }) const url = URL.createObjectURL(blob) Loading
frontend/src/views/TraineeView/index.tsx +1 −1 Original line number Diff line number Diff line Loading @@ -20,13 +20,13 @@ import { } from '@inject/shared' import { Suspense, useEffect, type FC, type PropsWithChildren } from 'react' import { ExerciseStatus, ExitButton } from '../../components' import { SandboxConfigurationButton } from '../../components/SandboxConfigurationButton' import TraineeEmailFormOverlay from '../../email/EmailFormOverlay/TraineeEmailFormOverlay' import useMailToRef from '../../hooks/useMailToRef' import { RootRoute } from '../../routes/__root' import { TraineeDriveRoute } from '../../routes/_protected/trainee/$exerciseId/$teamId/drive' import ChannelButton from '../ChannelButton' import { OverviewButton } from './OverviewButton' import { SandboxConfigurationButton } from './SandboxConfigurationButton' import StopAnnounce from './StopAnnounce' import useTraineeViewData from './useTraineeViewData' Loading