Merge pull request #3599 from balena-io/led-color-settings

Led color settings
This commit is contained in:
bulldozer-balena[bot] 2021-09-24 17:10:00 +00:00 committed by GitHub
commit e9f6c5ead9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 181 additions and 72 deletions

View File

@ -66,6 +66,9 @@ else
ifeq ($(shell uname -m),x86_64) ifeq ($(shell uname -m),x86_64)
HOST_ARCH = x64 HOST_ARCH = x64
endif endif
ifeq ($(shell uname -m),arm64)
HOST_ARCH = aarch64
endif
endif endif
endif endif

View File

@ -18,6 +18,8 @@ import CopySvg from '@fortawesome/fontawesome-free/svgs/solid/copy.svg';
import FileSvg from '@fortawesome/fontawesome-free/svgs/solid/file.svg'; import FileSvg from '@fortawesome/fontawesome-free/svgs/solid/file.svg';
import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg'; import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg';
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg'; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
import ChevronRightSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg';
import { sourceDestination } from 'etcher-sdk'; import { sourceDestination } from 'etcher-sdk';
import { ipcRenderer, IpcRendererEvent } from 'electron'; import { ipcRenderer, IpcRendererEvent } from 'electron';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -33,6 +35,7 @@ import {
Card as BaseCard, Card as BaseCard,
Input, Input,
Spinner, Spinner,
Link,
} from 'rendition'; } from 'rendition';
import styled from 'styled-components'; import styled from 'styled-components';
@ -134,12 +137,15 @@ const URLSelector = ({
done, done,
cancel, cancel,
}: { }: {
done: (imageURL: string) => void; done: (imageURL: string, auth?: Authentication) => void;
cancel: () => void; cancel: () => void;
}) => { }) => {
const [imageURL, setImageURL] = React.useState(''); const [imageURL, setImageURL] = React.useState('');
const [recentImages, setRecentImages] = React.useState<URL[]>([]); const [recentImages, setRecentImages] = React.useState<URL[]>([]);
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [showBasicAuth, setShowBasicAuth] = React.useState(false);
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
React.useEffect(() => { React.useEffect(() => {
const fetchRecentUrlImages = async () => { const fetchRecentUrlImages = async () => {
const recentUrlImages: URL[] = await getRecentUrlImages(); const recentUrlImages: URL[] = await getRecentUrlImages();
@ -162,11 +168,12 @@ const URLSelector = ({
imageURL, imageURL,
]); ]);
setRecentUrlImages(normalizedRecentUrls); setRecentUrlImages(normalizedRecentUrls);
await done(imageURL); const auth = username ? { username, password } : undefined;
await done(imageURL, auth);
}} }}
> >
<Flex flexDirection="column"> <Flex flexDirection="column">
<Flex style={{ width: '100%' }} flexDirection="column"> <Flex mb={15} style={{ width: '100%' }} flexDirection="column">
<Txt mb="10px" fontSize="24px"> <Txt mb="10px" fontSize="24px">
Use Image URL Use Image URL
</Txt> </Txt>
@ -178,6 +185,49 @@ const URLSelector = ({
setImageURL(evt.target.value) setImageURL(evt.target.value)
} }
/> />
<Link
mt={15}
mb={15}
fontSize="14px"
onClick={() => {
if (showBasicAuth) {
setUsername('');
setPassword('');
}
setShowBasicAuth(!showBasicAuth);
}}
>
<Flex alignItems="center">
{showBasicAuth && (
<ChevronDownSvg height="1em" fill="currentColor" />
)}
{!showBasicAuth && (
<ChevronRightSvg height="1em" fill="currentColor" />
)}
<Txt ml={8}>Authentication</Txt>
</Flex>
</Link>
{showBasicAuth && (
<React.Fragment>
<Input
mb={15}
value={username}
placeholder="Enter username"
type="text"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setUsername(evt.target.value)
}
/>
<Input
value={password}
placeholder="Enter password"
type="password"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setPassword(evt.target.value)
}
/>
</React.Fragment>
)}
</Flex> </Flex>
{recentImages.length > 0 && ( {recentImages.length > 0 && (
<Flex flexDirection="column" height="78.6%"> <Flex flexDirection="column" height="78.6%">
@ -283,6 +333,11 @@ interface SourceSelectorState {
imageLoading: boolean; imageLoading: boolean;
} }
interface Authentication {
username: string;
password: string;
}
export class SourceSelector extends React.Component< export class SourceSelector extends React.Component<
SourceSelectorProps, SourceSelectorProps,
SourceSelectorState SourceSelectorState
@ -328,7 +383,11 @@ export class SourceSelector extends React.Component<
this.setState({ imageLoading: false }); this.setState({ imageLoading: false });
} }
private async createSource(selected: string, SourceType: Source) { private async createSource(
selected: string,
SourceType: Source,
auth?: Authentication,
) {
try { try {
selected = await replaceWindowsNetworkDriveLetter(selected); selected = await replaceWindowsNetworkDriveLetter(selected);
} catch (error) { } catch (error) {
@ -351,7 +410,7 @@ export class SourceSelector extends React.Component<
}); });
} }
return new sourceDestination.Http({ url: selected }); return new sourceDestination.Http({ url: selected, auth });
} }
public isJson(jsonString: string) { public isJson(jsonString: string) {
@ -374,6 +433,7 @@ export class SourceSelector extends React.Component<
private selectSource( private selectSource(
selected: string | DrivelistDrive, selected: string | DrivelistDrive,
SourceType: Source, SourceType: Source,
auth?: Authentication,
): { promise: Promise<void>; cancel: () => void } { ): { promise: Promise<void>; cancel: () => void } {
let cancelled = false; let cancelled = false;
return { return {
@ -403,7 +463,7 @@ export class SourceSelector extends React.Component<
}, },
}); });
} }
source = await this.createSource(selected, SourceType); source = await this.createSource(selected, SourceType, auth);
if (cancelled) { if (cancelled) {
return; return;
@ -750,7 +810,7 @@ export class SourceSelector extends React.Component<
showURLSelector: false, showURLSelector: false,
}); });
}} }}
done={async (imageURL: string) => { done={async (imageURL: string, auth?: Authentication) => {
// Avoid analytics and selection state changes // Avoid analytics and selection state changes
// if no file was resolved from the dialog. // if no file was resolved from the dialog.
if (!imageURL) { if (!imageURL) {
@ -760,6 +820,7 @@ export class SourceSelector extends React.Component<
({ promise, cancel: cancelURLSelection } = this.selectSource( ({ promise, cancel: cancelURLSelection } = this.selectSource(
imageURL, imageURL,
sourceDestination.Http, sourceDestination.Http,
auth,
)); ));
await promise; await promise;
} }

View File

@ -29,13 +29,6 @@ import { observe, store } from './store';
const leds: Map<string, RGBLed> = new Map(); const leds: Map<string, RGBLed> = new Map();
const animator = new Animator([], 10); const animator = new Animator([], 10);
const red: Color = [0.78, 0, 0];
const green: Color = [0, 0.58, 0];
const blue: Color = [0, 0, 0.1];
const purple: Color = [0.7, 0, 0.78];
const white: Color = [0.7, 0.7, 0.7];
const black: Color = [0, 0, 0];
function createAnimationFunction( function createAnimationFunction(
intensityFunction: (t: number) => number, intensityFunction: (t: number) => number,
color: Color, color: Color,
@ -54,13 +47,27 @@ function one(_t: number) {
return 1; return 1;
} }
const blinkGreen = createAnimationFunction(blink, green); type LEDColors = {
const blinkPurple = createAnimationFunction(blink, purple); green: Color;
const staticRed = createAnimationFunction(one, red); purple: Color;
const staticGreen = createAnimationFunction(one, green); red: Color;
const staticBlue = createAnimationFunction(one, blue); blue: Color;
const staticWhite = createAnimationFunction(one, white); white: Color;
const staticBlack = createAnimationFunction(one, black); black: Color;
};
type LEDAnimationFunctions = {
blinkGreen: AnimationFunction;
blinkPurple: AnimationFunction;
staticRed: AnimationFunction;
staticGreen: AnimationFunction;
staticBlue: AnimationFunction;
staticWhite: AnimationFunction;
staticBlack: AnimationFunction;
};
let ledColors: LEDColors;
let ledAnimationFunctions: LEDAnimationFunctions;
interface LedsState { interface LedsState {
step: 'main' | 'flashing' | 'verifying' | 'finish'; step: 'main' | 'flashing' | 'verifying' | 'finish';
@ -132,31 +139,48 @@ export function updateLeds({
if (sourceDrive !== undefined) { if (sourceDrive !== undefined) {
if (plugged.has(sourceDrive)) { if (plugged.has(sourceDrive)) {
plugged.delete(sourceDrive); plugged.delete(sourceDrive);
mapping.push(setLeds(staticBlue, new Set([sourceDrive]))); mapping.push(
setLeds(ledAnimationFunctions.staticBlue, new Set([sourceDrive])),
);
} }
} }
if (step === 'main') { if (step === 'main') {
mapping.push( mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])), setLeds(
setLeds(staticWhite, new Set([...selectedOk, ...selectedFailed])), ledAnimationFunctions.staticBlack,
new Set([...unplugged, ...plugged]),
),
setLeds(
ledAnimationFunctions.staticWhite,
new Set([...selectedOk, ...selectedFailed]),
),
); );
} else if (step === 'flashing') { } else if (step === 'flashing') {
mapping.push( mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])), setLeds(
setLeds(blinkPurple, selectedOk), ledAnimationFunctions.staticBlack,
setLeds(staticRed, selectedFailed), new Set([...unplugged, ...plugged]),
),
setLeds(ledAnimationFunctions.blinkPurple, selectedOk),
setLeds(ledAnimationFunctions.staticRed, selectedFailed),
); );
} else if (step === 'verifying') { } else if (step === 'verifying') {
mapping.push( mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])), setLeds(
setLeds(blinkGreen, selectedOk), ledAnimationFunctions.staticBlack,
setLeds(staticRed, selectedFailed), new Set([...unplugged, ...plugged]),
),
setLeds(ledAnimationFunctions.blinkGreen, selectedOk),
setLeds(ledAnimationFunctions.staticRed, selectedFailed),
); );
} else if (step === 'finish') { } else if (step === 'finish') {
mapping.push( mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])), setLeds(
setLeds(staticGreen, selectedOk), ledAnimationFunctions.staticBlack,
setLeds(staticRed, selectedFailed), new Set([...unplugged, ...plugged]),
),
setLeds(ledAnimationFunctions.staticGreen, selectedOk),
setLeds(ledAnimationFunctions.staticRed, selectedFailed),
); );
} }
animator.mapping = mapping; animator.mapping = mapping;
@ -221,6 +245,16 @@ export async function init(): Promise<void> {
for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) { for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) {
leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames)); leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames));
} }
observe(_.debounce(stateObserver, 1000, { maxWait: 1000 }));
} }
ledColors = (await settings.get('ledColors')) || {};
ledAnimationFunctions = {
blinkGreen: createAnimationFunction(blink, ledColors['green']),
blinkPurple: createAnimationFunction(blink, ledColors['purple']),
staticRed: createAnimationFunction(one, ledColors['red']),
staticGreen: createAnimationFunction(one, ledColors['green']),
staticBlue: createAnimationFunction(one, ledColors['blue']),
staticWhite: createAnimationFunction(one, ledColors['white']),
staticBlack: createAnimationFunction(one, ledColors['black']),
};
observe(_.debounce(stateObserver, 1000, { maxWait: 1000 }));
} }

85
npm-shrinkwrap.json generated
View File

@ -1149,6 +1149,12 @@
"tar": "^4.4.2" "tar": "^4.4.2"
} }
}, },
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
},
"semver": { "semver": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@ -1156,18 +1162,18 @@
"dev": true "dev": true
}, },
"tar": { "tar": {
"version": "4.4.13", "version": "4.4.19",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==",
"dev": true, "dev": true,
"requires": { "requires": {
"chownr": "^1.1.1", "chownr": "^1.1.4",
"fs-minipass": "^1.2.5", "fs-minipass": "^1.2.7",
"minipass": "^2.8.6", "minipass": "^2.9.0",
"minizlib": "^1.2.1", "minizlib": "^1.3.3",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.5",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.2.1",
"yallist": "^3.0.3" "yallist": "^3.1.1"
} }
}, },
"yallist": { "yallist": {
@ -1233,9 +1239,9 @@
} }
}, },
"@balena/node-crc-utils": { "@balena/node-crc-utils": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@balena/node-crc-utils/-/node-crc-utils-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@balena/node-crc-utils/-/node-crc-utils-2.0.1.tgz",
"integrity": "sha512-u86QDMtkpHLlvehs3Z+yHklXRhDPL5XGCO3BCSuaD61gKzrNDUIj03cz8T/PBPPUJqn7DfWkf9sKP9VwlvxKuw==", "integrity": "sha512-l+PZFPnO0vdx1HNaYq2p89mXIW8XcLoL7XjhwXAAbJ2FOmTg+8fgUEpohX+SJMxTUAE52FBTS8GzIKErCmBNTw==",
"dev": true "dev": true
}, },
"@balena/udif": { "@balena/udif": {
@ -3108,12 +3114,20 @@
} }
}, },
"axios": { "axios": {
"version": "0.21.1", "version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dev": true, "dev": true,
"requires": { "requires": {
"follow-redirects": "^1.10.0" "follow-redirects": "^1.14.0"
},
"dependencies": {
"follow-redirects": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
"integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==",
"dev": true
}
} }
}, },
"babel-plugin-dynamic-import-node": { "babel-plugin-dynamic-import-node": {
@ -3535,11 +3549,11 @@
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dev": true,
"requires": { "requires": {
"base64-js": "^1.3.1", "base64-js": "^1.3.1",
"ieee754": "^1.1.13" "ieee754": "^1.1.13"
}, }
"dev": true
}, },
"buffer-alloc": { "buffer-alloc": {
"version": "1.2.0", "version": "1.2.0",
@ -6791,9 +6805,9 @@
"dev": true "dev": true
}, },
"etcher-sdk": { "etcher-sdk": {
"version": "6.2.1", "version": "6.2.5",
"resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-6.2.1.tgz", "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-6.2.5.tgz",
"integrity": "sha512-d7B/6/b1+NdrvpybQrb1f315LRdAIPAkMAX8Gq63dJh5f4448svBadllzwZ2D4aqVfc++8SpSX0iPi1laXh6SA==", "integrity": "sha512-kPDhrJw9AVLnhCA9GOUnLNOmLNH7WO/paWRH43xF99svWcQu9IBprm7fSeuQthZGHojchWv/u4eyUI9wDck0/A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@balena/node-beaglebone-usbboot": "^1.0.3", "@balena/node-beaglebone-usbboot": "^1.0.3",
@ -6819,7 +6833,7 @@
"tslib": "^2.0.0", "tslib": "^2.0.0",
"unbzip2-stream": "github:balena-io-modules/unbzip2-stream#4a54f56a25b58950f9e4277c56db2912d62242e7", "unbzip2-stream": "github:balena-io-modules/unbzip2-stream#4a54f56a25b58950f9e4277c56db2912d62242e7",
"unzip-stream": "^0.3.0", "unzip-stream": "^0.3.0",
"xxhash": "^0.3.0", "xxhash-addon": "^1.4.0",
"yauzl": "^2.9.2", "yauzl": "^2.9.2",
"zip-part-stream": "^1.0.3" "zip-part-stream": "^1.0.3"
}, },
@ -10523,9 +10537,9 @@
} }
}, },
"nan": { "nan": {
"version": "2.14.2", "version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
"dev": true "dev": true
}, },
"nanoid": { "nanoid": {
@ -13668,9 +13682,9 @@
} }
}, },
"smart-buffer": { "smart-buffer": {
"version": "4.1.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true "dev": true
}, },
"snapdragon": { "snapdragon": {
@ -16719,14 +16733,11 @@
"integrity": "sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w==", "integrity": "sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w==",
"dev": true "dev": true
}, },
"xxhash": { "xxhash-addon": {
"version": "0.3.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/xxhash/-/xxhash-0.3.0.tgz", "resolved": "https://registry.npmjs.org/xxhash-addon/-/xxhash-addon-1.4.0.tgz",
"integrity": "sha512-1ud2yyPiR1DJhgyF1ZVMt+Ijrn0VNS/wzej1Z8eSFfkNfRPp8abVZNV2u9tYy9574II0ZayZYZgJm8KJoyGLCw==", "integrity": "sha512-n3Ml0Vgvy7jMYJBlQIoFLjYxXNZQ5CbzW8E2Ynq2QCUpWMqCouooW7j02+7Oud5FijBuSrjQNuN/fCiz1SHN+w==",
"dev": true, "dev": true
"requires": {
"nan": "^2.13.2"
}
}, },
"y18n": { "y18n": {
"version": "5.0.5", "version": "5.0.5",

View File

@ -85,7 +85,7 @@
"electron-notarize": "^1.0.0", "electron-notarize": "^1.0.0",
"electron-rebuild": "^2.3.2", "electron-rebuild": "^2.3.2",
"electron-updater": "^4.3.5", "electron-updater": "^4.3.5",
"etcher-sdk": "^6.2.1", "etcher-sdk": "^6.2.5",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"husky": "^4.2.5", "husky": "^4.2.5",
"immutable": "^3.8.1", "immutable": "^3.8.1",