Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 12e62ce

Browse files
committedAug 23, 2021
certificate uploader
1 parent ef91a9f commit 12e62ce

17 files changed

+628
-340
lines changed
 

‎arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,17 +242,18 @@ import {
242242
UploadFirmwareDialog,
243243
UploadFirmwareDialogProps,
244244
UploadFirmwareDialogWidget,
245-
} from './dialogs/firmware-uploader/upload-firmware-dialog';
246-
import {
247-
UploadCertificateDialog,
248-
UploadCertificateDialogProps,
249-
UploadCertificateDialogWidget,
250-
} from './dialogs/certificate-uploader/upload-certificate-dialog';
245+
} from './dialogs/firmware-uploader/firmware-uploader-dialog';
246+
251247
import { UploadCertificate } from './contributions/upload-certificate';
252248
import {
253249
ArduinoFirmwareUploader,
254250
ArduinoFirmwareUploaderPath,
255251
} from '../common/protocol/arduino-firmware-uploader';
252+
import {
253+
UploadCertificateDialog,
254+
UploadCertificateDialogProps,
255+
UploadCertificateDialogWidget,
256+
} from './dialogs/certificate-uploader/certificate-uploader-dialog';
256257

257258
const ElementQueries = require('css-element-queries/src/ElementQueries');
258259

‎arduino-ide-extension/src/browser/arduino-preferences.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ export const ArduinoConfigSchema: PreferenceSchema = {
5555
'True to enable automatic update checks. The IDE will check for updates automatically and periodically.',
5656
default: true,
5757
},
58+
'arduino.board.certificates': {
59+
type: 'string',
60+
description: 'List of certificates that can be uploaded to boards',
61+
default: '',
62+
},
5863
'arduino.sketchbook.showAllFiles': {
5964
type: 'boolean',
6065
description:
@@ -123,6 +128,7 @@ export interface ArduinoConfiguration {
123128
'arduino.window.autoScale': boolean;
124129
'arduino.window.zoomLevel': number;
125130
'arduino.ide.autoUpdate': boolean;
131+
'arduino.board.certificates': string;
126132
'arduino.sketchbook.showAllFiles': boolean;
127133
'arduino.cloud.enabled': boolean;
128134
'arduino.cloud.pull.warn': boolean;

‎arduino-ide-extension/src/browser/contributions/upload-certificate.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,36 @@ import {
66
Contribution,
77
} from './contribution';
88
import { ArduinoMenus } from '../menu/arduino-menus';
9-
import { UploadCertificateDialog } from '../dialogs/certificate-uploader/upload-certificate-dialog';
9+
import { UploadCertificateDialog } from '../dialogs/certificate-uploader/certificate-uploader-dialog';
10+
import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer';
11+
import {
12+
PreferenceScope,
13+
PreferenceService,
14+
} from '@theia/core/lib/browser/preferences/preference-service';
15+
import { ArduinoPreferences } from '../arduino-preferences';
16+
import {
17+
arduinoCert,
18+
certificateList,
19+
} from '../dialogs/certificate-uploader/utils';
20+
import { ArduinoFirmwareUploader } from '../../common/protocol/arduino-firmware-uploader';
1021

1122
@injectable()
1223
export class UploadCertificate extends Contribution {
1324
@inject(UploadCertificateDialog)
1425
protected readonly dialog: UploadCertificateDialog;
1526

27+
@inject(ContextMenuRenderer)
28+
protected readonly contextMenuRenderer: ContextMenuRenderer;
29+
30+
@inject(PreferenceService)
31+
protected readonly preferenceService: PreferenceService;
32+
33+
@inject(ArduinoPreferences)
34+
protected readonly arduinoPreferences: ArduinoPreferences;
35+
36+
@inject(ArduinoFirmwareUploader)
37+
protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
38+
1639
protected dialogOpened = false;
1740

1841
registerCommands(registry: CommandRegistry): void {
@@ -27,6 +50,46 @@ export class UploadCertificate extends Contribution {
2750
},
2851
isEnabled: () => !this.dialogOpened,
2952
});
53+
54+
registry.registerCommand(UploadCertificate.Commands.REMOVE_CERT, {
55+
execute: async (certToRemove) => {
56+
const certs = this.arduinoPreferences.get('arduino.board.certificates');
57+
58+
this.preferenceService.set(
59+
'arduino.board.certificates',
60+
certificateList(certs)
61+
.filter((c) => c !== certToRemove)
62+
.join(','),
63+
PreferenceScope.User
64+
);
65+
},
66+
isEnabled: (certToRemove) => certToRemove !== arduinoCert,
67+
});
68+
69+
registry.registerCommand(UploadCertificate.Commands.UPLOAD_CERT, {
70+
execute: async ({ fqbn, address, urls }) => {
71+
return this.arduinoFirmwareUploader.uploadCertificates(
72+
`-b ${fqbn} -a ${address} ${urls
73+
.map((url: string) => `-u ${url}`)
74+
.join(' ')}`
75+
);
76+
},
77+
isEnabled: () => true,
78+
});
79+
80+
registry.registerCommand(UploadCertificate.Commands.OPEN_CERT_CONTEXT, {
81+
execute: async (args: any) => {
82+
this.contextMenuRenderer.render({
83+
menuPath: ArduinoMenus.ROOT_CERTIFICATES__CONTEXT,
84+
anchor: {
85+
x: args.x,
86+
y: args.y,
87+
},
88+
args: [args.cert],
89+
});
90+
},
91+
isEnabled: () => true,
92+
});
3093
}
3194

3295
registerMenus(registry: MenuModelRegistry): void {
@@ -35,6 +98,12 @@ export class UploadCertificate extends Contribution {
3598
label: UploadCertificate.Commands.OPEN.label,
3699
order: '1',
37100
});
101+
102+
registry.registerMenuAction(ArduinoMenus.ROOT_CERTIFICATES__CONTEXT, {
103+
commandId: UploadCertificate.Commands.REMOVE_CERT.id,
104+
label: UploadCertificate.Commands.REMOVE_CERT.label,
105+
order: '1',
106+
});
38107
}
39108
}
40109

@@ -45,5 +114,23 @@ export namespace UploadCertificate {
45114
label: 'Upload SSL Root Certificates',
46115
category: 'Arduino',
47116
};
117+
118+
export const OPEN_CERT_CONTEXT: Command = {
119+
id: 'arduino-certificate-open-context',
120+
label: 'Open context',
121+
category: 'Arduino',
122+
};
123+
124+
export const REMOVE_CERT: Command = {
125+
id: 'arduino-certificate-remove',
126+
label: 'Remove',
127+
category: 'Arduino',
128+
};
129+
130+
export const UPLOAD_CERT: Command = {
131+
id: 'arduino-certificate-upload',
132+
label: 'Upload',
133+
category: 'Arduino',
134+
};
48135
}
49136
}

‎arduino-ide-extension/src/browser/contributions/upload-firmware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
Contribution,
77
} from './contribution';
88
import { ArduinoMenus } from '../menu/arduino-menus';
9-
import { UploadFirmwareDialog } from '../dialogs/firmware-uploader/upload-firmware-dialog';
9+
import { UploadFirmwareDialog } from '../dialogs/firmware-uploader/firmware-uploader-dialog';
1010

1111
@injectable()
1212
export class UploadFirmware extends Contribution {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as React from 'react';
2+
3+
export const CertificateListComponent = ({
4+
certificates,
5+
selectedCerts,
6+
setSelectedCerts,
7+
openContextMenu,
8+
}: {
9+
certificates: string[];
10+
selectedCerts: string[];
11+
setSelectedCerts: React.Dispatch<React.SetStateAction<string[]>>;
12+
openContextMenu: (x: number, y: number, cert: string) => void;
13+
}): React.ReactElement => {
14+
const handleOnChange = (event: any) => {
15+
const target = event.target;
16+
17+
const newSelectedCerts = selectedCerts.filter(
18+
(cert) => cert !== target.name
19+
);
20+
21+
if (target.checked) {
22+
newSelectedCerts.push(target.name);
23+
}
24+
25+
setSelectedCerts(newSelectedCerts);
26+
};
27+
28+
const handleContextMenu = (event: React.MouseEvent, cert: string) => {
29+
openContextMenu(event.clientX, event.clientY, cert);
30+
};
31+
32+
return (
33+
<div className="certificate-list">
34+
{certificates.map((certificate, i) => (
35+
<label
36+
key={i}
37+
className="certificate-row"
38+
onContextMenu={(e) => handleContextMenu(e, certificate)}
39+
>
40+
<span className="fl1">{certificate}</span>
41+
<input
42+
type="checkbox"
43+
name={certificate}
44+
checked={selectedCerts.includes(certificate)}
45+
onChange={handleOnChange}
46+
/>
47+
</label>
48+
))}
49+
</div>
50+
);
51+
};
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import * as React from 'react';
2+
import {
3+
AvailableBoard,
4+
BoardsServiceProvider,
5+
} from '../../boards/boards-service-provider';
6+
import { ArduinoSelect } from '../../widgets/arduino-select';
7+
import { CertificateListComponent } from './certificate-list';
8+
9+
export const CertificateUploaderComponent = ({
10+
boardsServiceClient,
11+
certificates,
12+
// addCertificate,
13+
uploadCertificates,
14+
openContextMenu,
15+
}: {
16+
boardsServiceClient: BoardsServiceProvider;
17+
certificates: string[];
18+
addCertificate: (cert: string) => void;
19+
uploadCertificates: (
20+
fqbn: string,
21+
address: string,
22+
urls: string[]
23+
) => Promise<any>;
24+
openContextMenu: (x: number, y: number, cert: string) => void;
25+
}): React.ReactElement => {
26+
const [installFeedback, setInstallFeedback] = React.useState<
27+
'ok' | 'fail' | 'installing' | null
28+
>(null);
29+
30+
const [selectedCerts, setSelectedCerts] = React.useState<string[]>([]);
31+
32+
const [selectedBoard, setSelectedBoard] = React.useState<{
33+
label: string;
34+
value: AvailableBoard;
35+
} | null>(null);
36+
37+
const [selectBoardPlaceholder, setSelectBoardPlaceholder] =
38+
React.useState('');
39+
const [availableBoards, setAvailableBoards] = React.useState<
40+
{
41+
label: string;
42+
value: AvailableBoard;
43+
}[]
44+
>([]);
45+
46+
React.useEffect(() => {
47+
boardsServiceClient.onAvailableBoardsChanged((availableBoards) => {
48+
let placeholderTxt = 'Select a board...';
49+
let selectedBoard = -1;
50+
const boardsList = availableBoards
51+
.filter(
52+
(board) =>
53+
!!board.fqbn && board.state === AvailableBoard.State.recognized
54+
)
55+
.map((board, i) => {
56+
if (board.selected) {
57+
selectedBoard = i;
58+
}
59+
return {
60+
label: `${board.name} at ${board.port?.address}`,
61+
value: board,
62+
};
63+
});
64+
65+
if (boardsList.length === 0) {
66+
placeholderTxt = 'No board connected to serial port';
67+
}
68+
69+
setSelectBoardPlaceholder(placeholderTxt);
70+
setAvailableBoards(boardsList);
71+
setSelectedBoard(boardsList[selectedBoard] || null);
72+
});
73+
}, [boardsServiceClient]);
74+
75+
const installCertificates = async () => {
76+
if (
77+
!selectedBoard?.value ||
78+
!selectedBoard.value.fqbn ||
79+
!selectedBoard.value.port
80+
) {
81+
return;
82+
}
83+
84+
setInstallFeedback('installing');
85+
86+
try {
87+
await uploadCertificates(
88+
selectedBoard.value.fqbn,
89+
selectedBoard.value.port.address,
90+
selectedCerts
91+
);
92+
setInstallFeedback('ok');
93+
} catch {
94+
setInstallFeedback('fail');
95+
}
96+
};
97+
98+
return (
99+
<>
100+
<div className="dialogSection">
101+
<div className="dialogRow">
102+
<strong className="fl1">1. Select certificate to upload</strong>
103+
{/* <button type="button" className="theia-button primary">
104+
Add New
105+
</button> */}
106+
</div>
107+
<div className="dialogRow">
108+
<CertificateListComponent
109+
certificates={certificates}
110+
selectedCerts={selectedCerts}
111+
setSelectedCerts={setSelectedCerts}
112+
openContextMenu={openContextMenu}
113+
/>
114+
</div>
115+
</div>
116+
<div className="dialogSection">
117+
<div className="dialogRow">
118+
<strong>2. Select destination board and upload certificate</strong>
119+
</div>
120+
<div className="dialogRow">
121+
<div className="fl1">
122+
<ArduinoSelect
123+
id="board-select"
124+
menuPosition="fixed"
125+
isDisabled={availableBoards.length === 0}
126+
placeholder={selectBoardPlaceholder}
127+
options={availableBoards}
128+
value={selectedBoard}
129+
tabSelectsValue={false}
130+
onChange={(value) => {
131+
if (value) {
132+
setInstallFeedback(null);
133+
setSelectedBoard(value);
134+
}
135+
}}
136+
/>
137+
</div>
138+
<button
139+
type="button"
140+
className="theia-button primary"
141+
onClick={installCertificates}
142+
disabled={
143+
selectedCerts.length === 0 ||
144+
availableBoards.length === 0 ||
145+
!selectedBoard
146+
}
147+
>
148+
Upload
149+
</button>
150+
</div>
151+
</div>
152+
153+
{installFeedback && (
154+
<div className="dialogSection">
155+
{installFeedback === 'installing' && (
156+
<div className="dialogRow success">
157+
<svg
158+
className="status-icon"
159+
width="18"
160+
height="18"
161+
viewBox="0 0 18 18"
162+
fill="none"
163+
xmlns="http://www.w3.org/2000/svg"
164+
>
165+
<path
166+
d="M9 17.75C7.26942 17.75 5.57769 17.2368 4.13876 16.2754C2.69983 15.3139 1.57832 13.9473 0.916058 12.3485C0.253791 10.7496 0.080512 8.9903 0.418133 7.29296C0.755753 5.59563 1.58911 4.03653 2.81282 2.81282C4.03653 1.58911 5.59563 0.755753 7.29296 0.418133C8.9903 0.080512 10.7496 0.253791 12.3485 0.916058C13.9473 1.57832 15.3139 2.69983 16.2754 4.13876C17.2368 5.57769 17.75 7.26942 17.75 9C17.75 11.3206 16.8281 13.5462 15.1872 15.1872C13.5462 16.8281 11.3206 17.75 9 17.75ZM9 1.5C7.51664 1.5 6.0666 1.93987 4.83323 2.76398C3.59986 3.58809 2.63856 4.75943 2.07091 6.12988C1.50325 7.50032 1.35473 9.00832 1.64411 10.4632C1.9335 11.918 2.64781 13.2544 3.6967 14.3033C4.7456 15.3522 6.08197 16.0665 7.53683 16.3559C8.99168 16.6453 10.4997 16.4968 11.8701 15.9291C13.2406 15.3614 14.4119 14.4001 15.236 13.1668C16.0601 11.9334 16.5 10.4834 16.5 9C16.5 7.01088 15.7098 5.10323 14.3033 3.6967C12.8968 2.29018 10.9891 1.5 9 1.5Z"
167+
fill="#1DA086"
168+
/>
169+
<path
170+
d="M8.6875 5.875C9.20527 5.875 9.625 5.45527 9.625 4.9375C9.625 4.41973 9.20527 4 8.6875 4C8.16973 4 7.75 4.41973 7.75 4.9375C7.75 5.45527 8.16973 5.875 8.6875 5.875Z"
171+
fill="#1DA086"
172+
/>
173+
<path
174+
d="M12.125 12.75C12.125 12.9158 12.0592 13.0747 11.9419 13.1919C11.8247 13.3092 11.6658 13.375 11.5 13.375H6.5C6.33424 13.375 6.17527 13.3092 6.05806 13.1919C5.94085 13.0747 5.875 12.9158 5.875 12.75C5.875 12.5842 5.94085 12.4253 6.05806 12.3081C6.17527 12.1908 6.33424 12.125 6.5 12.125H8.375V8.375H7.125C6.95924 8.375 6.80027 8.30915 6.68306 8.19194C6.56585 8.07473 6.5 7.91576 6.5 7.75C6.5 7.58424 6.56585 7.42527 6.68306 7.30806C6.80027 7.19085 6.95924 7.125 7.125 7.125H9C9.16576 7.125 9.32473 7.19085 9.44194 7.30806C9.55915 7.42527 9.625 7.58424 9.625 7.75V12.125H11.5C11.6658 12.125 11.8247 12.1908 11.9419 12.3081C12.0592 12.4253 12.125 12.5842 12.125 12.75Z"
175+
fill="#1DA086"
176+
/>
177+
</svg>
178+
Uploading certificates...
179+
</div>
180+
)}
181+
{installFeedback === 'ok' && (
182+
<div className="dialogRow success">
183+
<svg
184+
className="status-icon"
185+
width="18"
186+
height="18"
187+
viewBox="0 0 18 18"
188+
fill="none"
189+
xmlns="http://www.w3.org/2000/svg"
190+
>
191+
<path
192+
d="M9 17.75C7.26942 17.75 5.57769 17.2368 4.13876 16.2754C2.69983 15.3139 1.57832 13.9473 0.916058 12.3485C0.253791 10.7496 0.080512 8.9903 0.418133 7.29296C0.755753 5.59563 1.58911 4.03653 2.81282 2.81282C4.03653 1.58911 5.59563 0.755753 7.29296 0.418133C8.9903 0.080512 10.7496 0.253791 12.3485 0.916058C13.9473 1.57832 15.3139 2.69983 16.2754 4.13876C17.2368 5.57769 17.75 7.26942 17.75 9C17.75 11.3206 16.8281 13.5462 15.1872 15.1872C13.5462 16.8281 11.3206 17.75 9 17.75ZM9 1.5C7.51664 1.5 6.0666 1.93987 4.83323 2.76398C3.59986 3.58809 2.63856 4.75943 2.07091 6.12988C1.50325 7.50032 1.35473 9.00832 1.64411 10.4632C1.9335 11.918 2.64781 13.2544 3.6967 14.3033C4.7456 15.3522 6.08197 16.0665 7.53683 16.3559C8.99168 16.6453 10.4997 16.4968 11.8701 15.9291C13.2406 15.3614 14.4119 14.4001 15.236 13.1668C16.0601 11.9334 16.5 10.4834 16.5 9C16.5 7.01088 15.7098 5.10323 14.3033 3.6967C12.8968 2.29018 10.9891 1.5 9 1.5Z"
193+
fill="#1DA086"
194+
/>
195+
<path
196+
d="M8.6875 5.875C9.20527 5.875 9.625 5.45527 9.625 4.9375C9.625 4.41973 9.20527 4 8.6875 4C8.16973 4 7.75 4.41973 7.75 4.9375C7.75 5.45527 8.16973 5.875 8.6875 5.875Z"
197+
fill="#1DA086"
198+
/>
199+
<path
200+
d="M12.125 12.75C12.125 12.9158 12.0592 13.0747 11.9419 13.1919C11.8247 13.3092 11.6658 13.375 11.5 13.375H6.5C6.33424 13.375 6.17527 13.3092 6.05806 13.1919C5.94085 13.0747 5.875 12.9158 5.875 12.75C5.875 12.5842 5.94085 12.4253 6.05806 12.3081C6.17527 12.1908 6.33424 12.125 6.5 12.125H8.375V8.375H7.125C6.95924 8.375 6.80027 8.30915 6.68306 8.19194C6.56585 8.07473 6.5 7.91576 6.5 7.75C6.5 7.58424 6.56585 7.42527 6.68306 7.30806C6.80027 7.19085 6.95924 7.125 7.125 7.125H9C9.16576 7.125 9.32473 7.19085 9.44194 7.30806C9.55915 7.42527 9.625 7.58424 9.625 7.75V12.125H11.5C11.6658 12.125 11.8247 12.1908 11.9419 12.3081C12.0592 12.4253 12.125 12.5842 12.125 12.75Z"
201+
fill="#1DA086"
202+
/>
203+
</svg>
204+
Certificates succesfully installed.
205+
</div>
206+
)}
207+
{installFeedback === 'fail' && (
208+
<div className="dialogRow warn">
209+
<svg
210+
className="status-icon"
211+
width="18"
212+
height="18"
213+
viewBox="0 0 18 18"
214+
fill="none"
215+
xmlns="http://www.w3.org/2000/svg"
216+
>
217+
<path
218+
d="M16.9373 17.125H1.06225C0.955444 17.1246 0.850534 17.0968 0.757532 17.0442C0.66453 16.9917 0.586538 16.9162 0.531 16.825C0.476145 16.73 0.447266 16.6222 0.447266 16.5125C0.447266 16.4028 0.476145 16.295 0.531 16.2L8.4685 0.57501C8.52095 0.472634 8.60063 0.386718 8.69878 0.326724C8.79692 0.26673 8.90972 0.234985 9.02475 0.234985C9.13978 0.234985 9.25258 0.26673 9.35072 0.326724C9.44887 0.386718 9.52855 0.472634 9.581 0.57501L17.5185 16.2C17.5734 16.295 17.6022 16.4028 17.6022 16.5125C17.6022 16.6222 17.5734 16.73 17.5185 16.825C17.4588 16.9238 17.3729 17.0042 17.2703 17.0571C17.1677 17.1101 17.0524 17.1336 16.9373 17.125ZM2.081 15.875H15.9185L8.99975 2.25626L2.081 15.875Z"
219+
fill="#C11F09"
220+
/>
221+
<path
222+
d="M9 14.625C9.51777 14.625 9.9375 14.2053 9.9375 13.6875C9.9375 13.1697 9.51777 12.75 9 12.75C8.48223 12.75 8.0625 13.1697 8.0625 13.6875C8.0625 14.2053 8.48223 14.625 9 14.625Z"
223+
fill="#C11F09"
224+
/>
225+
<path
226+
d="M9 11.5C8.83424 11.5 8.67527 11.4342 8.55806 11.3169C8.44085 11.1997 8.375 11.0408 8.375 10.875V6.5C8.375 6.33424 8.44085 6.17527 8.55806 6.05806C8.67527 5.94085 8.83424 5.875 9 5.875C9.16576 5.875 9.32473 5.94085 9.44194 6.05806C9.55915 6.17527 9.625 6.33424 9.625 6.5V10.875C9.625 11.0408 9.55915 11.1997 9.44194 11.3169C9.32473 11.4342 9.16576 11.5 9 11.5Z"
227+
fill="#C11F09"
228+
/>
229+
</svg>
230+
Upload failed. Please try again.
231+
</div>
232+
)}
233+
</div>
234+
)}
235+
</>
236+
);
237+
};
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import * as React from 'react';
2+
import { inject, injectable, postConstruct } from 'inversify';
3+
import { AbstractDialog, DialogProps } from '@theia/core/lib/browser/dialogs';
4+
import { Widget } from '@phosphor/widgets';
5+
import { Message } from '@phosphor/messaging';
6+
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
7+
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
8+
import { CertificateUploaderComponent } from './certificate-uploader-component';
9+
import { ArduinoPreferences } from '../../arduino-preferences';
10+
import {
11+
PreferenceScope,
12+
PreferenceService,
13+
} from '@theia/core/lib/browser/preferences/preference-service';
14+
import { CommandRegistry } from '@theia/core/lib/common/command';
15+
import { certificateList, sanifyCertString } from './utils';
16+
17+
@injectable()
18+
export class UploadCertificateDialogWidget extends ReactWidget {
19+
@inject(BoardsServiceProvider)
20+
protected readonly boardsServiceClient: BoardsServiceProvider;
21+
22+
@inject(ArduinoPreferences)
23+
protected readonly arduinoPreferences: ArduinoPreferences;
24+
25+
@inject(PreferenceService)
26+
protected readonly preferenceService: PreferenceService;
27+
28+
@inject(CommandRegistry)
29+
protected readonly commandRegistry: CommandRegistry;
30+
31+
protected certificates: string[] = [];
32+
33+
constructor() {
34+
super();
35+
}
36+
37+
@postConstruct()
38+
protected init(): void {
39+
this.arduinoPreferences.ready.then(() => {
40+
this.certificates = certificateList(
41+
this.arduinoPreferences.get('arduino.board.certificates')
42+
);
43+
});
44+
this.arduinoPreferences.onPreferenceChanged((event) => {
45+
if (
46+
event.preferenceName === 'arduino.board.certificates' &&
47+
event.newValue !== event.oldValue
48+
) {
49+
this.certificates = certificateList(event.newValue);
50+
this.update();
51+
}
52+
});
53+
}
54+
55+
private addCertificate(certificate: string) {
56+
const certString = sanifyCertString(certificate);
57+
58+
if (certString.length > 0) {
59+
this.certificates.push(sanifyCertString(certificate));
60+
}
61+
62+
this.preferenceService.set(
63+
'arduino.board.certificates',
64+
this.certificates.join(','),
65+
PreferenceScope.User
66+
);
67+
}
68+
69+
protected openContextMenu(x: number, y: number, cert: string): void {
70+
this.commandRegistry.executeCommand(
71+
'arduino-certificate-open-context',
72+
Object.assign({}, { x, y, cert })
73+
);
74+
}
75+
76+
protected uploadCertificates(
77+
fqbn: string,
78+
address: string,
79+
urls: string[]
80+
): Promise<any> {
81+
return this.commandRegistry.executeCommand('arduino-certificate-upload', {
82+
fqbn,
83+
address,
84+
urls,
85+
});
86+
}
87+
88+
protected render(): React.ReactNode {
89+
return (
90+
<CertificateUploaderComponent
91+
boardsServiceClient={this.boardsServiceClient}
92+
certificates={this.certificates}
93+
addCertificate={this.addCertificate.bind(this)}
94+
uploadCertificates={this.uploadCertificates.bind(this)}
95+
openContextMenu={this.openContextMenu.bind(this)}
96+
/>
97+
);
98+
}
99+
}
100+
101+
@injectable()
102+
export class UploadCertificateDialogProps extends DialogProps {}
103+
104+
@injectable()
105+
export class UploadCertificateDialog extends AbstractDialog<void> {
106+
@inject(UploadCertificateDialogWidget)
107+
protected readonly widget: UploadCertificateDialogWidget;
108+
109+
constructor(
110+
@inject(UploadCertificateDialogProps)
111+
protected readonly props: UploadCertificateDialogProps
112+
) {
113+
super({ title: 'Upload SSL Root Certificates' });
114+
this.contentNode.classList.add('certificate-uploader-dialog');
115+
this.acceptButton = undefined;
116+
}
117+
118+
get value(): void {
119+
return;
120+
}
121+
122+
protected onAfterAttach(msg: Message): void {
123+
if (this.widget.isAttached) {
124+
Widget.detach(this.widget);
125+
}
126+
Widget.attach(this.widget, this.contentNode);
127+
super.onAfterAttach(msg);
128+
this.update();
129+
}
130+
131+
protected onUpdateRequest(msg: Message): void {
132+
super.onUpdateRequest(msg);
133+
this.widget.update();
134+
}
135+
136+
protected onActivateRequest(msg: Message): void {
137+
super.onActivateRequest(msg);
138+
this.widget.activate();
139+
}
140+
141+
protected handleEnter(event: KeyboardEvent): boolean | void {
142+
return false;
143+
}
144+
}

‎arduino-ide-extension/src/browser/dialogs/certificate-uploader/upload-certificate-dialog.tsx

Lines changed: 0 additions & 330 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export const arduinoCert = 'arduino.cc:443';
2+
3+
export function sanifyCertString(cert: string): string {
4+
const regex = /^(?:.*:\/\/)*(\S+\.+[^:]*):*(\d*)*$/gm;
5+
6+
const m = regex.exec(cert);
7+
8+
if (!m) {
9+
return '';
10+
}
11+
12+
const domain = m[1] || '';
13+
const port = m[2] || '443';
14+
15+
if (domain.length === 0 || port.length === 0) {
16+
return '';
17+
}
18+
19+
return `${domain}:${port}`;
20+
}
21+
22+
export function certificateList(certificates: string): string[] {
23+
let certs = certificates
24+
.split(',')
25+
.map((cert) => sanifyCertString(cert.trim()))
26+
.filter((cert) => {
27+
// remove empty certificates
28+
if (!cert || cert.length === 0) {
29+
return false;
30+
}
31+
return true;
32+
});
33+
34+
// add arduino certificate at the top of the list
35+
certs = certs.filter((cert) => cert !== arduinoCert);
36+
certs.unshift(arduinoCert);
37+
return certs;
38+
}

‎arduino-ide-extension/src/browser/menu/arduino-menus.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ export namespace ArduinoMenus {
148148
...SKETCH_CONTROL__CONTEXT,
149149
'2_resources',
150150
];
151+
152+
// -- ROOT SSL CERTIFICATES
153+
export const ROOT_CERTIFICATES__CONTEXT = [
154+
'arduino-root-certificates--context',
155+
];
151156
}
152157

153158
/**
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.certificate-uploader-dialog {
2+
width: 600px;
3+
}
4+
5+
.certificate-uploader-dialog .theia-select {
6+
border: none !important;
7+
}
8+
.certificate-uploader-dialog .arduino-select__control {
9+
height: 31px;
10+
background: var(--theia-menubar-selectionBackground) !important;
11+
}
12+
13+
.certificate-uploader-dialog .dialogRow > button{
14+
margin-right: 3px;
15+
}
16+
17+
.certificate-uploader-dialog .certificate-list {
18+
border: 1px solid #BDC7C7;
19+
border-radius: 2px;;
20+
background: var(--theia-menubar-selectionBackground) !important;
21+
overflow: auto;
22+
height: 120px;
23+
flex: 1;
24+
}
25+
.certificate-uploader-dialog .certificate-list .certificate-row {
26+
display: flex;
27+
padding: 6px 10px 5px 10px
28+
}
29+
.certificate-uploader-dialog .certificate-list .certificate-row:hover {
30+
background-color: var(--theia-list-hoverBackground);
31+
}
32+
33+
.certificate-uploader-dialog .success {
34+
color: #1DA086;
35+
}
36+
37+
.certificate-uploader-dialog .warn {
38+
color: #C11F09;
39+
}
40+
41+
.certificate-uploader-dialog .status-icon {
42+
margin-right: 10px;
43+
}

‎arduino-ide-extension/src/browser/style/dialogs.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@
4343
margin-top: 0px;
4444
}
4545

46-
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow .fl1{
46+
.fl1{
4747
flex: 1;
4848
}

‎arduino-ide-extension/src/browser/style/index.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
@import './terminal.css';
99
@import './editor.css';
1010
@import './settings-dialog.css';
11-
@import './fwuploader-dialog.css';
11+
@import './firmware-uploader-dialog.css';
12+
@import './certificate-uploader-dialog.css';
1213
@import './debug.css';
1314
@import './sketchbook.css';
1415
@import './cloud-sketchbook.css';

‎arduino-ide-extension/src/common/protocol/arduino-firmware-uploader.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type FirmwareInfo = {
1111
export interface ArduinoFirmwareUploader {
1212
list(fqbn?: string): Promise<FirmwareInfo[]>;
1313
flash(firmware: FirmwareInfo, port: string): Promise<string>;
14+
uploadCertificates(command: string): Promise<any>;
1415
updatableBoards(): Promise<string[]>;
1516
availableFirmwares(fqbn: string): Promise<FirmwareInfo[]>;
1617
}

‎arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export class ArduinoFirmwareUploaderImpl implements ArduinoFirmwareUploader {
4040
return JSON.parse(raw);
4141
}
4242

43+
async uploadCertificates(command: string): Promise<any> {
44+
return await this.runCommand(['certificates', 'flash', command]);
45+
}
46+
4347
async list(fqbn?: string): Promise<FirmwareInfo[]> {
4448
const fqbnFlag = fqbn ? ['--fqbn', fqbn] : [];
4549
return await this.runCommand([

0 commit comments

Comments
 (0)
Please sign in to comment.