Skip to content

Commit ec84803

Browse files
authored
feat: add ssl with certificate (#168)
1 parent ccdb275 commit ec84803

File tree

8 files changed

+239
-14
lines changed

8 files changed

+239
-14
lines changed

src/drivers/base/SQLLikeConnection.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,21 @@ export interface MySqlConnectionConfig {
1313
port: number;
1414
}
1515

16+
export type ConnectionSslOption =
17+
| boolean
18+
| {
19+
ca?: string;
20+
cert?: string;
21+
key?: string;
22+
};
23+
1624
export interface PgConnectionConfig {
1725
database: string;
1826
user: string;
1927
password: string;
2028
host: string;
2129
port: number;
22-
ssl?: boolean;
30+
ssl?: ConnectionSslOption;
2331
}
2432

2533
export interface SqliteConnectionConfig {

src/main/ipc/ipc_file_system.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
dialog,
44
SaveDialogSyncOptions,
55
shell,
6+
OpenDialogOptions,
67
} from 'electron';
8+
import fs from 'fs';
79
import saveCsvFile from '../../libs/SaveCSVFile';
810
import saveExcelFile from '../../libs/SaveExcelFile';
911
import CommunicateHandler from './../CommunicateHandler';
@@ -15,15 +17,23 @@ CommunicateHandler.handle(
1517
return dialog.showMessageBoxSync(window, options);
1618
}
1719
return 0;
18-
}
20+
},
1921
)
22+
.handle('show-open-dialog', ([options]: [OpenDialogOptions], { window }) => {
23+
if (window) {
24+
return dialog.showOpenDialog(window, options);
25+
}
26+
})
27+
.handle('read-file', ([fileName]: [string]) => {
28+
return fs.readFileSync(fileName);
29+
})
2030
.handle(
2131
'show-save-dialog',
2232
([options]: [SaveDialogSyncOptions], { window }) => {
2333
if (window) {
2434
return dialog.showSaveDialogSync(window, options);
2535
}
26-
}
36+
},
2737
)
2838
.handle('save-csv-file', ([fileName, records]: [string, object[]]) => {
2939
saveCsvFile(fileName, records);

src/main/preload.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
MessageBoxSyncOptions,
99
MenuItemConstructorOptions,
1010
SaveDialogSyncOptions,
11+
OpenDialogOptions,
12+
OpenDialogReturnValue,
1113
} from 'electron';
1214
import {
1315
ProgressInfo,
@@ -61,6 +63,14 @@ const electronHandler = {
6163
): Promise<string | undefined> =>
6264
ipcRenderer.invoke('show-save-dialog', [options]),
6365

66+
showOpenDialog: (
67+
options: OpenDialogOptions,
68+
): Promise<OpenDialogReturnValue> =>
69+
ipcRenderer.invoke('show-open-dialog', [options]),
70+
71+
readFile: (fileName: string): Promise<Buffer> =>
72+
ipcRenderer.invoke('read-file', [fileName]),
73+
6474
showFileInFolder: (fileName: string) =>
6575
ipcRenderer.invoke('show-item-in-folder', [fileName]),
6676

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2+
import styles from './certificate.module.scss';
3+
import {
4+
faCheckCircle,
5+
faCircleMinus,
6+
faCircleXmark,
7+
faFolder,
8+
} from '@fortawesome/free-solid-svg-icons';
9+
import { useCallback } from 'react';
10+
import { ConnectionSslOption } from 'drivers/base/SQLLikeConnection';
11+
12+
interface PickerButtonProps {
13+
label: string;
14+
onChange: (v?: string) => void;
15+
value?: string;
16+
}
17+
18+
interface CertificatePickerProps {
19+
value?: ConnectionSslOption;
20+
onChange: (v: ConnectionSslOption | undefined) => void;
21+
}
22+
23+
function CertificatePickerButton({
24+
label,
25+
onChange,
26+
value,
27+
}: PickerButtonProps) {
28+
const onClick = useCallback(() => {
29+
window.electron.showOpenDialog({}).then((e) => {
30+
if (!e.canceled) {
31+
if (e.filePaths.length === 1) {
32+
window.electron
33+
.readFile(e.filePaths[0])
34+
.then((content) => onChange(new TextDecoder().decode(content)));
35+
}
36+
}
37+
});
38+
}, [onChange]);
39+
40+
const className = [styles.button, value ? styles.provided : undefined]
41+
.filter(Boolean)
42+
.join(' ');
43+
44+
return (
45+
<div className={className}>
46+
<div onClick={onClick}>
47+
<FontAwesomeIcon
48+
icon={value ? faCheckCircle : faCircleMinus}
49+
style={{ opacity: value ? 1 : 0.4, marginRight: 5 }}
50+
/>
51+
52+
<span>{label}</span>
53+
</div>
54+
{value && (
55+
<div
56+
className={`${styles.icon} ${styles.close}`}
57+
onClick={() => {
58+
onChange(undefined);
59+
}}
60+
>
61+
<FontAwesomeIcon
62+
icon={faCircleXmark}
63+
style={{ opacity: 1, marginRight: 5 }}
64+
/>
65+
</div>
66+
)}
67+
{!value && (
68+
<div className={styles.icon} onClick={onClick}>
69+
<FontAwesomeIcon
70+
icon={faFolder}
71+
style={{ opacity: 0.4, marginRight: 5 }}
72+
/>
73+
</div>
74+
)}
75+
</div>
76+
);
77+
}
78+
79+
export default function CertificatePicker({
80+
value,
81+
onChange,
82+
}: CertificatePickerProps) {
83+
const onUpdate = useCallback(
84+
(property: 'ca' | 'cert' | 'key', newValue?: string) => {
85+
let tmp: ConnectionSslOption | undefined;
86+
if (!value) tmp = { [property]: newValue };
87+
else if (value === true) {
88+
tmp = { [property]: newValue };
89+
} else {
90+
tmp = { ...value, [property]: newValue };
91+
}
92+
93+
// Remove all the undefined property
94+
if (typeof tmp === 'object') {
95+
for (const tmpKey of Object.keys(tmp)) {
96+
const key = tmpKey as keyof ConnectionSslOption;
97+
if (!tmp[key]) {
98+
delete tmp[key];
99+
}
100+
}
101+
102+
if (Object.entries(tmp).length === 0) {
103+
onChange(value === true ? true : undefined);
104+
} else onChange(tmp);
105+
} else {
106+
onChange(undefined);
107+
}
108+
},
109+
[value, onChange],
110+
);
111+
112+
const ca = typeof value === 'object' ? value.ca : undefined;
113+
const cert = typeof value === 'object' ? value.cert : undefined;
114+
const key = typeof value === 'object' ? value.key : undefined;
115+
const ssl = !!value;
116+
117+
return (
118+
<div className={styles.container}>
119+
<h3>
120+
<label>
121+
<input
122+
type="checkbox"
123+
checked={ssl}
124+
onClick={() => {
125+
if (!value) onChange(true);
126+
if (value === true) onChange(undefined);
127+
}}
128+
/>
129+
<span>&nbsp;&nbsp;Over SSL</span>
130+
</label>
131+
</h3>
132+
<div className={styles.buttonGroup}>
133+
<CertificatePickerButton
134+
label="CA Certificate"
135+
value={ca}
136+
onChange={(e) => onUpdate('ca', e)}
137+
/>
138+
<CertificatePickerButton
139+
label="Certificate"
140+
value={cert}
141+
onChange={(e) => onUpdate('cert', e)}
142+
/>
143+
<CertificatePickerButton
144+
label="Key"
145+
value={key}
146+
onChange={(e) => onUpdate('key', e)}
147+
/>
148+
</div>
149+
</div>
150+
);
151+
}
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,11 @@ export default function PostgreConnectionEditor({
99
config: ConnectionStoreConfig;
1010
onChange: (value: ConnectionStoreConfig) => void;
1111
}) {
12+
console.log(config);
13+
1214
return (
1315
<Stack vertical>
1416
<RelationalDbConnectionEditor config={config} onChange={onChange} />
15-
<label>
16-
<input
17-
type="checkbox"
18-
checked={config?.ssl}
19-
onChange={(e) =>
20-
onChange({ ...config, ssl: e.currentTarget.checked })
21-
}
22-
/>
23-
&nbsp;Over SSL
24-
</label>
2517
</Stack>
2618
);
2719
}

src/renderer/components/ConnectionListTree/ConnectionConfigEditor/RelationalDbConnectionEditor.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Stack from 'renderer/components/Stack';
33
import TextField from 'renderer/components/TextField';
44
import PasswordField from 'renderer/components/PasswordField';
55
import { useState } from 'react';
6+
import CertificatePicker from './CertificatePicker';
67

78
export default function RelationalDbConnectionEditor({
89
config,
@@ -52,6 +53,11 @@ export default function RelationalDbConnectionEditor({
5253
</div>
5354
</Stack>
5455

56+
<CertificatePicker
57+
value={config?.ssl}
58+
onChange={(value) => onChange({ ...config, ssl: value })}
59+
/>
60+
5561
<TextField
5662
label="Database"
5763
value={config?.database}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
.container {
2+
h3 {
3+
font-size: 1rem;
4+
border-bottom: 1px solid var(--color-border);
5+
padding-bottom: 5px;
6+
margin-bottom: 10px;
7+
}
8+
9+
border-bottom: 1px solid var(--color-border);
10+
padding-bottom: 10px;
11+
}
12+
13+
.buttonGroup {
14+
display: flex;
15+
gap: 10px;
16+
}
17+
18+
.button {
19+
border-radius: 4px;
20+
overflow: hidden;
21+
cursor: pointer;
22+
display: flex;
23+
24+
> div {
25+
background: var(--color-input-border);
26+
padding: 10px;
27+
28+
&:hover {
29+
opacity: 0.8;
30+
}
31+
}
32+
}
33+
34+
.icon {
35+
border-left: 1px solid var(--color-surface);
36+
}
37+
38+
.button.provided {
39+
> div {
40+
background: #27ae60;
41+
color: #fff;
42+
}
43+
}
44+
45+
.close {
46+
background: #e74c3c !important;
47+
color: #fff !important;
48+
}

src/renderer/components/ConnectionListTree/ConnectionConfigEditor/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ConnectionStoreConfig } from 'drivers/base/SQLLikeConnection';
22
import MySQLConnectionEditor from './MySQLConnectionEditor';
3-
import PostgreConnectionEditor from './PostgreConnectionEditor copy';
3+
import PostgreConnectionEditor from './PostgreConnectionEditor';
44

55
export default function ConnectionConfigEditor({
66
type,

0 commit comments

Comments
 (0)