Skip to content

Commit

Permalink
feat: persist proxy preferences (#254)
Browse files Browse the repository at this point in the history
Co-authored-by: Uladzimir Dzmitračkoŭ <[email protected]>
  • Loading branch information
cristianoventura and going-confetti authored Oct 18, 2024
1 parent d45b1f2 commit a622a55
Show file tree
Hide file tree
Showing 17 changed files with 765 additions and 32 deletions.
22 changes: 16 additions & 6 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,27 @@ import { getCertificateSPKI } from './proxy'
import { mkdtemp } from 'fs/promises'
import path from 'path'
import os from 'os'
import { proxyPort } from './main'
import { appSettings } from './main'

const createUserDataDir = async () => {
return mkdtemp(path.join(os.tmpdir(), 'k6-studio-'))
}

function getBrowserPath() {
const { recorder } = appSettings

if (recorder.detectBrowserPath) {
return computeSystemExecutablePath({
browser: Browser.CHROME,
channel: ChromeReleaseChannel.STABLE,
})
}

return recorder.browserPath as string
}

export const launchBrowser = async (browserWindow: BrowserWindow) => {
const path = computeSystemExecutablePath({
browser: Browser.CHROME,
channel: ChromeReleaseChannel.STABLE,
})
const path = getBrowserPath()
console.info(`browser path: ${path}`)

const userDataDir = await createUserDataDir()
Expand Down Expand Up @@ -54,7 +64,7 @@ export const launchBrowser = async (browserWindow: BrowserWindow) => {
'--disable-background-networking',
'--disable-component-update',
'--disable-search-engine-choice-screen',
`--proxy-server=http://localhost:${proxyPort}`,
`--proxy-server=http://localhost:${appSettings.proxy.port}`,
`--ignore-certificate-errors-spki-list=${certificateSPKI}`,
disableChromeOptimizations,
],
Expand Down
9 changes: 8 additions & 1 deletion src/components/Form/FieldGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type FieldGroupProps = BoxProps & {
name: string
label?: React.ReactNode
hint?: React.ReactNode
hintType?: 'tooltip' | 'text'
}

export function FieldGroup({
Expand All @@ -19,6 +20,7 @@ export function FieldGroup({
name,
errors,
hint,
hintType = 'tooltip',
...props
}: FieldGroupProps) {
return (
Expand All @@ -29,12 +31,17 @@ export function FieldGroup({
<Text size="2" weight="medium">
{label}
</Text>
{hint && (
{hint && hintType === 'tooltip' && (
<Tooltip content={hint}>
<InfoCircledIcon />
</Tooltip>
)}
</Flex>
{hint && hintType === 'text' && (
<Text size="1" mb="2" as="p">
{hint}
</Text>
)}
</Label.Root>
)}
<Box>{children}</Box>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Layout/ActivityBar/ActivityBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { VersionLabel } from './VersionLabel'
import { HomeIcon } from '@/components/icons'
import { NavIconButton } from './NavIconButton'
import { ApplicationLogButton } from './ApplicationLogButton'
import { SettingsButton } from './SettingsButton'

export function ActivityBar() {
return (
Expand Down Expand Up @@ -41,6 +42,7 @@ export function ActivityBar() {

<Flex direction="column" align="center" gap="3" mt="auto">
<ThemeSwitcher />
<SettingsButton />
<ApplicationLogButton />
<VersionLabel />
</Flex>
Expand Down
25 changes: 25 additions & 0 deletions src/components/Layout/ActivityBar/SettingsButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { SettingsDialog } from '@/components/Settings/SettingsDialog'
import { GearIcon } from '@radix-ui/react-icons'
import { Tooltip, IconButton } from '@radix-ui/themes'
import { useState } from 'react'

export function SettingsButton() {
const [open, setOpen] = useState(false)

return (
<>
<Tooltip content="Settings" side="right">
<IconButton
area-label="Settings"
color="gray"
variant="ghost"
onClick={() => setOpen(true)}
>
<GearIcon />
</IconButton>
</Tooltip>

<SettingsDialog open={open} onOpenChange={setOpen} />
</>
)
}
127 changes: 127 additions & 0 deletions src/components/Settings/ProxySettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { FieldGroup } from '@/components/Form'
import { ProxyStatus } from '@/types'
import { stringAsNumber } from '@/utils/form'
import { css } from '@emotion/react'
import { Flex, Text, TextField, Checkbox } from '@radix-ui/themes'
import { useEffect, useState } from 'react'
import { Controller, useFormContext } from 'react-hook-form'
import { UpstreamProxySettings } from './UpstreamProxySettings'
import { SettingsSection } from './SettingsSection'
import { ControlledRadioGroup } from '@/components/Form/ControllerRadioGroup'
import { AppSettings } from '@/types/settings'

const modeOptions = [
{
value: 'regular',
label: 'Regular (requests are performed from this computer)',
},
{
value: 'upstream',
label: 'Upstream (requests are forwarded to an upstream server)',
},
]

export const ProxySettings = () => {
const {
formState: { errors },
control,
register,
watch,
} = useFormContext<AppSettings>()
const [proxyStatus, setProxyStatus] = useState<ProxyStatus>()

const { proxy } = watch()

useEffect(() => {
async function fetchProxyStatus() {
const status = await window.studio.proxy.getProxyStatus()
setProxyStatus(status)
}
fetchProxyStatus()

return window.studio.proxy.onProxyStatusChange((status) =>
setProxyStatus(status)
)
}, [])

return (
<SettingsSection title="Proxy">
<FieldGroup
name="proxy.port"
label="Port number"
errors={errors}
hint="What port number k6 Studio proxy should listen to in this computer (between 1 and 65535)"
hintType="text"
>
<TextField.Root
placeholder="6000"
type="number"
min={1}
{...register('proxy.port', { setValueAs: stringAsNumber })}
/>
</FieldGroup>

<Flex gap="2" my="4">
<Controller
control={control}
name="proxy.automaticallyFindPort"
render={({ field }) => (
<Text size="2" as="label">
<Checkbox
{...register('proxy.automaticallyFindPort')}
checked={field.value}
onCheckedChange={field.onChange}
/>{' '}
Allow k6 Studio to find an available port if this port is in use
</Text>
)}
/>
</Flex>

<FieldGroup
label="Proxy mode"
name="proxy.mode"
errors={errors}
hint="How k6 Studio proxy should handle requests"
hintType="text"
>
<ControlledRadioGroup
control={control}
name="proxy.mode"
options={modeOptions}
/>
</FieldGroup>

{proxy && proxy.mode === 'upstream' && <UpstreamProxySettings />}

<Flex gap="2" mt="5">
<Text size="2">
Proxy status: <ProxyStatusIndicator status={proxyStatus} />
</Text>
</Flex>
</SettingsSection>
)
}

function ProxyStatusIndicator({ status }: { status?: ProxyStatus }) {
const statusColorMap: Record<ProxyStatus, string> = {
['online']: 'var(--green-9)',
['offline']: 'var(--gray-9)',
['restarting']: 'var(--blue-9)',
}
const backgroundColor = status ? statusColorMap[status] : '#fff'

return (
<Text
size="2"
css={css`
background-color: ${backgroundColor};
border-radius: 4px;
color: #fff;
padding: var(--space-1) var(--space-2);
`}
>
{status}
</Text>
)
}
74 changes: 74 additions & 0 deletions src/components/Settings/RecorderSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { FieldGroup } from '@/components/Form'
import { AppSettings } from '@/types/settings'
import { Flex, Text, TextField, Checkbox, Button } from '@radix-ui/themes'
import { Controller, useFormContext } from 'react-hook-form'
import { SettingsSection } from './SettingsSection'

export const RecorderSettings = () => {
const {
formState: { errors },
control,
register,
watch,
setValue,
clearErrors,
} = useFormContext<AppSettings>()

const { recorder } = watch()

const handleSelectFile = async () => {
const result = await window.studio.settings.selectBrowserExecutable()
const { canceled, filePaths } = result
if (canceled || !filePaths.length) return
setValue('recorder.browserPath', filePaths[0], { shouldDirty: true })
clearErrors('recorder.browserPath')
}

return (
<SettingsSection title="Recorder">
<Flex gap="2" mb="4">
<Controller
control={control}
name="recorder.detectBrowserPath"
render={({ field }) => (
<Text size="2" as="label">
<Checkbox
{...register('recorder.detectBrowserPath')}
checked={field.value}
onCheckedChange={field.onChange}
/>{' '}
Automatically detect browser
</Text>
)}
/>
</Flex>

{recorder && !recorder.detectBrowserPath && (
<Flex>
<FieldGroup
flexGrow="1"
name="recorder.browserPath"
label="Browser Path"
errors={errors}
hint="The location of the browser executable (k6 Studio currently supports Chrome)"
hintType="text"
>
<TextField.Root type="text" {...register('recorder.browserPath')} />
</FieldGroup>

<Button
ml="2"
onClick={handleSelectFile}
style={{
display: 'flex',
alignSelf: 'center',
marginTop: errors.recorder ? 12 : 36,
}}
>
Select executable
</Button>
</Flex>
)}
</SettingsSection>
)
}
Loading

0 comments on commit a622a55

Please sign in to comment.