Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(docker): add ability to specify custom paths #609

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/common/AdvancedOptionsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const AdvancedOptionsModal: React.FC<Props> = ({ network }) => {
return (
<Modal
title={l('title')}
visible={visible}
open={visible}
onCancel={() => hideAdvancedOptions()}
destroyOnClose
cancelText={l('cancelBtn')}
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/ImageUpdatesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const ImageUpdatesModal: React.FC<Props> = ({ onClose }) => {
title={l('title')}
onCancel={onClose}
destroyOnClose
visible
open
width={600}
centered
cancelText={l('closeBtn')}
Expand Down
10 changes: 9 additions & 1 deletion src/components/common/StatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import styled from '@emotion/styled';
import { Badge, Tooltip } from 'antd';
import { Status } from 'shared/types';

Expand All @@ -20,14 +21,21 @@ const badgeStatuses: BadgeStatus = {
[Status.Error]: 'error',
};

const Styled = {
Text: styled.span`
display: inline-block;
margin-left: 8px;
`,
};

const StatusBadge: React.SFC<StatusBadgeProps> = ({ status, text }) => {
const { t } = useTranslation();
return (
<>
<Tooltip overlay={t(`enums.status.${Status[status]}`)}>
<Badge status={badgeStatuses[status]} />
</Tooltip>
{text}
<Styled.Text>{text}</Styled.Text>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const SendOnChainModal: React.FC<Props> = ({ network }) => {
<>
<Modal
title={l('title')}
visible={visible}
open={visible}
onCancel={() => hideSendOnChain()}
destroyOnClose
cancelText={l('cancelBtn')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const ChangeBackendModal: React.FC<Props> = ({ network }) => {
<>
<Modal
title={l('title')}
visible={visible}
open={visible}
onCancel={() => hideChangeBackend()}
destroyOnClose
cancelText={l('cancelBtn')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const CreateInvoiceModal: React.FC<Props> = ({ network }) => {
<>
<Modal
title={l('title')}
visible={visible}
open={visible}
onCancel={() => hideCreateInvoice()}
destroyOnClose
footer={invoice ? null : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ const OpenChannelModal: React.FC<Props> = ({ network }) => {
<>
<Modal
title={l('title')}
visible={visible}
open={visible}
onCancel={() => hideOpenChannel()}
destroyOnClose
cancelText={l('cancelBtn')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const PayInvoiceModal: React.FC<Props> = ({ network }) => {
return (
<Modal
title={l('title')}
visible={visible}
open={visible}
onCancel={() => hidePayInvoice()}
destroyOnClose
cancelText={l('cancelBtn')}
Expand Down
47 changes: 47 additions & 0 deletions src/components/home/DetectDockerModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { shell } from 'electron';
import { fireEvent } from '@testing-library/dom';
import { waitFor } from '@testing-library/react';
import os from 'os';
import { injections, renderWithProviders } from 'utils/tests';
import DetectDockerModal, { dockerLinks } from './DetectDockerModal';
Expand Down Expand Up @@ -127,4 +128,50 @@ describe('DetectDockerModal component', () => {
expect(await findByText('Docker Error')).toBeInTheDocument();
expect(await findByText('test-error')).toBeInTheDocument();
});

it('should display the correct placeholders', () => {
mockOS.platform.mockReturnValue('darwin');
const { getByText, getByLabelText } = renderComponent();
fireEvent.click(getByText('Specify custom paths for Docker and Compose files'));
expect(getByLabelText('Path to Docker Unix Socket')).toHaveAttribute(
'placeholder',
'/var/run/docker.sock',
);
expect(getByLabelText('Path to docker-compose executable')).toHaveAttribute(
'placeholder',
'/usr/local/bin/docker-compose',
);
});

it('should display the correct placeholders on windows', () => {
mockOS.platform.mockReturnValue('win32');
const { getByText, getByLabelText } = renderComponent();
fireEvent.click(getByText('Specify custom paths for Docker and Compose files'));
expect(getByLabelText('Path to Docker Unix Socket')).toHaveAttribute(
'placeholder',
'//./pipe/docker_engine',
);
expect(getByLabelText('Path to docker-compose executable')).toHaveAttribute(
'placeholder',
'C:\\Program Files\\Docker Toolbox\\docker-compose',
);
});

it('should accept custom docker paths', () => {
mockDockerService.getVersions.mockResolvedValue({ docker: '', compose: '' });
const { getByText, getByLabelText } = renderComponent();
fireEvent.click(getByText('Specify custom paths for Docker and Compose files'));
expect(getByText('Path to Docker Unix Socket')).toBeInTheDocument();
expect(getByText('Path to docker-compose executable')).toBeInTheDocument();
fireEvent.change(getByLabelText('Path to Docker Unix Socket'), {
target: { value: '/test/docker.sock' },
});
fireEvent.change(getByLabelText('Path to docker-compose executable'), {
target: { value: '/test/docker-compose' },
});
fireEvent.click(getByText('Check Again'));
waitFor(() => {
expect(mockDockerService.setPaths).toBeCalledWith('a', 'b');
});
});
});
69 changes: 64 additions & 5 deletions src/components/home/DetectDockerModal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import React, { ReactNode } from 'react';
import React, { ReactNode, useCallback, useState } from 'react';
import { useAsyncCallback } from 'react-async-hook';
import { AppleOutlined, DownloadOutlined, WindowsOutlined } from '@ant-design/icons';
import {
AppleOutlined,
DownloadOutlined,
SettingOutlined,
WindowsOutlined,
} from '@ant-design/icons';
import styled from '@emotion/styled';
import { Button, Modal, Result } from 'antd';
import { Button, Form, Input, Modal, Result } from 'antd';
import { usePrefixedTranslation } from 'hooks';
import { useStoreActions, useStoreState } from 'store';
import { getPolarPlatform, PolarPlatform } from 'utils/system';
Expand All @@ -19,6 +24,11 @@ const Styled = {
width: 70%;
margin: auto;
`,
CustomizeSection: styled.div<{ collapsed?: boolean }>`
overflow: hidden;
max-height: ${props => (props.collapsed ? '0' : '300px')};
transition: max-height 0.5s;
`,
};

export const dockerLinks: Record<PolarPlatform, Record<string, string>> = {
Expand All @@ -45,12 +55,25 @@ const buttonIcons: Record<PolarPlatform, ReactNode> = {
const DetectDockerModal: React.FC = () => {
const platform = getPolarPlatform();
const { l } = usePrefixedTranslation('cmps.home.DetectDockerModal');
const [form] = Form.useForm();
const [collapsed, setCollapsed] = useState(true);
const {
dockerVersions: { docker, compose },
settings: { customDockerPaths },
} = useStoreState(s => s.app);
const { openInBrowser, getDockerVersions, notify } = useStoreActions(s => s.app);
const { openInBrowser, getDockerVersions, notify, updateSettings } = useStoreActions(
s => s.app,
);
const toggleCustomize = useCallback(() => setCollapsed(v => !v), []);
const checkAsync = useAsyncCallback(async () => {
try {
const { dockerSocketPath, composeFilePath } = form.getFieldsValue();
await updateSettings({
customDockerPaths: {
dockerSocketPath,
composeFilePath,
},
});
await getDockerVersions({ throwErr: true });
} catch (error: any) {
notify({ message: l('dockerError'), error });
Expand All @@ -66,7 +89,7 @@ const DetectDockerModal: React.FC = () => {

return (
<Modal
visible={visible}
open={visible}
closable={false}
width={600}
centered
Expand Down Expand Up @@ -99,6 +122,42 @@ const DetectDockerModal: React.FC = () => {
/>
<Styled.Details>
<DetailsList title={l('versionsTitle')} details={details} />

<Styled.CustomizeSection collapsed={collapsed}>
<Form
form={form}
layout="vertical"
initialValues={{
dockerSocketPath: customDockerPaths.dockerSocketPath,
composeFilePath: customDockerPaths.composeFilePath,
}}
>
<Form.Item name="dockerSocketPath" label={l('dockerSocketPath')}>
<Input
placeholder={
platform === 'windows'
? 'npipe:////./pipe/docker_engine'
: '/var/run/docker.sock'
}
allowClear
/>
</Form.Item>
<Form.Item name="composeFilePath" label={l('composeFilePath')}>
<Input
placeholder={
platform === 'windows'
? 'C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker-compose'
: '/usr/local/bin/docker-compose'
}
allowClear
/>
</Form.Item>
</Form>
</Styled.CustomizeSection>

<Button type="link" block icon={<SettingOutlined />} onClick={toggleCustomize}>
{l('customize')}
</Button>
</Styled.Details>
</Modal>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/nodeImages/CustomImageModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const CustomImageModal: React.FC<Props> = ({ image, onClose }) => {
return (
<Modal
title={l('title', image)}
visible
open
width={600}
destroyOnClose
maskClosable={false}
Expand Down
2 changes: 1 addition & 1 deletion src/components/nodeImages/ManagedImageModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const ManagedImageModal: React.FC<Props> = ({ image, onClose }) => {
return (
<Modal
title={l('title', image)}
visible
open
width={600}
destroyOnClose
onCancel={onClose}
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@
"cmps.home.DetectDockerModal.description": "To start a Lightning Network, you must have Docker and Docker Compose installed and running on this computer.",
"cmps.home.DetectDockerModal.download": "Download",
"cmps.home.DetectDockerModal.versionsTitle": "Installed Docker Versions",
"cmps.home.DetectDockerModal.customize": "Specify custom paths for Docker and Compose files",
"cmps.home.DetectDockerModal.dockerSocketPath": "Path to Docker Unix Socket",
"cmps.home.DetectDockerModal.composeFilePath": "Path to docker-compose executable",
"cmps.home.DetectDockerModal.checkAgain": "Check Again",
"cmps.home.GetStarted.title": "Let's get started!",
"cmps.home.GetStarted.createBtn": "Create a Lightning Network",
Expand Down
30 changes: 30 additions & 0 deletions src/lib/docker/dockerService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,36 @@ describe('DockerService', () => {
);
});

it('should use the provided custom docker paths', async () => {
dockerService.setPaths('/test/docker.sock', '/test/docker-compose');
await dockerService.getVersions();
expect(composeMock.version).toBeCalledWith(
expect.objectContaining({
executablePath: '/test/docker-compose',
}),
undefined,
);
expect(mockDockerode.prototype.constructor).toBeCalledWith({
socketPath: '/test/docker.sock',
});
});

it('should use default custom docker paths', async () => {
dockerService.setPaths('', '');
await dockerService.getVersions();
expect(composeMock.version).toBeCalledWith(
expect.not.objectContaining({
executablePath: '',
}),
undefined,
);
expect(mockDockerode.prototype.constructor).toBeCalledWith(
expect.not.objectContaining({
socketPath: '/test/docker.sock',
}),
);
});

describe('detecting versions', () => {
const dockerVersion = mockDockerode.prototype.version;
const composeVersion = composeMock.version;
Expand Down
Loading