Browse Source

feat(logs): add ability to view live logs from nodes

master
jamaljsr 5 years ago
parent
commit
c99f025147
  1. 4
      .rescriptsrc.js
  2. 3
      TODO.md
  3. 6
      electron/appIpcListener.ts
  4. 7
      package.json
  5. 6
      src/components/designer/NodeContextMenu.tsx
  6. 2
      src/components/designer/bitcoind/ActionsTab.tsx
  7. 2
      src/components/designer/lightning/ActionsTab.tsx
  8. 137
      src/components/dockerLogs/DockerLogs.tsx
  9. 48
      src/components/dockerLogs/ViewLogsButton.tsx
  10. 2
      src/components/dockerLogs/index.ts
  11. 3
      src/components/routing/Routes.tsx
  12. 2
      src/components/routing/index.ts
  13. 5
      src/components/terminal/OpenTerminalButton.tsx
  14. 13
      src/i18n/locales/en-US.json
  15. 1
      src/lib/docker/dockerService.spec.ts
  16. 6
      src/lib/docker/dockerService.ts
  17. 2
      src/react-app-env.d.ts
  18. 9
      src/store/models/network.ts
  19. 2
      src/utils/constants.ts
  20. 122
      yarn.lock

4
.rescriptsrc.js

@ -1,9 +1,13 @@
const path = require('path');
module.exports = [
config => {
// set target on webpack config to support electron
config.target = 'electron-renderer';
// add support for hot reload of hooks
config.resolve.alias['react-dom'] = '@hot-loader/react-dom';
// https://github.com/websockets/ws/issues/1538#issuecomment-577627369
config.resolve.alias['ws'] = path.resolve(path.join(__dirname, 'node_modules/ws/index.js' ))
return config;
},
[

3
TODO.md

@ -1,11 +1,12 @@
# TODO List
- fix scroll bars globally
Small Stuff
- implement real-time channel updates from LND via GRPC streams
- implement option to auto-mine every X minutes
- switch renovatebot to dependabot and use automatic security fixes
- display docker streaming logs in the UI
- mock or install docker on build servers for e2e tests
- consistent scrollbars for all OS's (https://github.com/xobotyi/react-scrollbars-custom) (https://github.com/souhe/reactScrollbar)

6
electron/appIpcListener.ts

@ -7,7 +7,7 @@ import { APP_ROOT, BASE_URL } from './constants';
import { clearProxyCache } from './lnd/lndProxyServer';
const openWindow = async (args: { url: string }): Promise<boolean> => {
debug('opwnWindow', args);
debug('openWindow', args);
const winState = windowState({
defaultWidth: 800,
defaultHeight: 600,
@ -51,7 +51,7 @@ const clearCache = (): Promise<{ success: boolean }> => {
/**
* A mapping of electron IPC channel names to the functions to execute when
* messages are recieved
* messages are received
*/
const listeners: {
[key: string]: (...args: any) => Promise<any>;
@ -63,7 +63,7 @@ const listeners: {
/**
* Sets up the IPC listeners for the main process and maps them to async
* functions.
* @param ipc the IPC onject of the main process
* @param ipc the IPC object of the main process
*/
export const initAppIpcListener = (ipc: IpcMain) => {
const log = (msg: string, ...rest: any[]) => debug(`AppIpcListener: ${msg}`, ...rest);

7
package.json

@ -77,12 +77,14 @@
"@types/react-router": "5.1.4",
"@types/react-router-dom": "5.1.3",
"@types/redux-logger": "3.0.7",
"@types/ws": "7.2.2",
"@typescript-eslint/eslint-plugin": "2.22.0",
"@typescript-eslint/parser": "2.22.0",
"antd": "4.0.1",
"babel-plugin-emotion": "10.0.29",
"babel-plugin-import": "1.13.0",
"bitcoin-core": "3.0.0",
"bufferutil": "4.0.1",
"commitizen": "4.0.3",
"concurrently": "5.1.0",
"connected-react-router": "6.7.0",
@ -120,6 +122,7 @@
"react-dom": "16.13.0",
"react-hot-loader": "4.12.19",
"react-i18next": "11.3.3",
"react-lazylog": "4.5.1",
"react-redux": "7.2.0",
"react-router": "5.1.2",
"react-router-dom": "5.1.2",
@ -137,8 +140,10 @@
"testcafe-react-selectors": "4.0.0",
"ts-node": "8.6.2",
"typescript": "3.8.3",
"utf-8-validate": "5.0.2",
"wait-on": "4.0.1",
"webpack": "4.41.5"
"webpack": "4.41.5",
"ws": "7.2.1"
},
"husky": {
"hooks": {

6
src/components/designer/NodeContextMenu.tsx

@ -5,6 +5,7 @@ import { Dropdown, Menu } from 'antd';
import { LightningNode, Status } from 'shared/types';
import { useStoreState } from 'store';
import { AdvancedOptionsButton, RemoveNode, RestartNode } from 'components/common';
import { ViewLogsButton } from 'components/dockerLogs';
import { OpenTerminalButton } from 'components/terminal';
import { OpenChannelButtons, PaymentButtons } from './lightning/actions';
@ -76,6 +77,11 @@ const NodeContextMenu: React.FC<Props> = ({ node: { id }, children }) => {
<RestartNode menuType="stop" node={node} />,
[Status.Started].includes(node.status),
)}
{createItem(
'logs',
<ViewLogsButton type="menu" node={node} />,
[Status.Started, Status.Error].includes(node.status),
)}
{createItem('options', <AdvancedOptionsButton type="menu" node={node} />)}
{createItem('remove', <RemoveNode type="menu" node={node} />)}
</Menu>

2
src/components/designer/bitcoind/ActionsTab.tsx

@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import { Form } from 'antd';
import { BitcoinNode, Status } from 'shared/types';
import { AdvancedOptionsButton, RemoveNode, RestartNode } from 'components/common';
import { ViewLogsButton } from 'components/dockerLogs';
import { OpenTerminalButton } from 'components/terminal';
import MineBlocksInput from './actions/MineBlocksInput';
@ -23,6 +24,7 @@ const ActionsTab: React.FC<Props> = ({ node }) => {
<>
<MineBlocksInput node={node} />
<OpenTerminalButton node={node} />
<ViewLogsButton node={node} />
<Styled.Spacer />
</>
)}

2
src/components/designer/lightning/ActionsTab.tsx

@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import { Form } from 'antd';
import { LightningNode, Status } from 'shared/types';
import { AdvancedOptionsButton, RemoveNode, RestartNode } from 'components/common';
import { ViewLogsButton } from 'components/dockerLogs';
import { OpenTerminalButton } from 'components/terminal';
import { Deposit, OpenChannelButtons, PaymentButtons } from './actions';
@ -25,6 +26,7 @@ const ActionsTab: React.FC<Props> = ({ node }) => {
<OpenChannelButtons node={node} />
<PaymentButtons node={node} />
<OpenTerminalButton node={node} />
<ViewLogsButton node={node} />
<Styled.Spacer />
</>
)}

137
src/components/dockerLogs/DockerLogs.tsx

@ -0,0 +1,137 @@
/* exclude this function from test coverage because it pretty difficult to mock dependencies */
/* istanbul ignore file */
import React, { useEffect, useState } from 'react';
import { LazyLog } from 'react-lazylog';
import { useParams } from 'react-router';
import { debug, error, info } from 'electron-log';
import styled from '@emotion/styled';
import detectPort from 'detect-port';
import Docker from 'dockerode';
import { usePrefixedTranslation } from 'hooks';
import { PassThrough } from 'stream';
import WebSocket from 'ws';
import { useStoreActions } from 'store';
const docker = new Docker();
let wsServer: WebSocket.Server;
/**
* Starts a web socket server in order to stream logs from the docker
* container to the LazyLog component. LazyLog unfortunately cannot
* accept a stream directly, so we have a spin up a local WS server
* just to proxy the logs.
* @param name the name of the docker container
* @returns the port that the web socket server is listening on
*/
const startWebSocketServer = async (name: string): Promise<number> => {
const port = await detectPort(0);
wsServer = new WebSocket.Server({ port });
wsServer.on('connection', async socket => {
debug(`getting docker container with name '${name}'`);
const containers = await docker.listContainers();
debug(`all containers: ${JSON.stringify(containers)}`);
const details = containers.find(c => c.Names.includes(`/${name}`));
debug(`found: ${JSON.stringify(details, null, 2)}`);
const container = details && docker.getContainer(details.Id);
if (!container) throw new Error(`Docker container '${name}' not found`);
// get a stream of docker logs
const dockerStream = await container.logs({
follow: true,
tail: 500,
stdout: true,
stderr: true,
});
// demux and merge stdin and stderr into one stream
const logStream = new PassThrough();
container.modem.demuxStream(dockerStream, logStream, logStream);
// proxy logs from docker thru the web socket
logStream.on('data', (data: Buffer) => socket.send(data.toString('utf-8')));
// log errors
logStream.on('error', e => error('logStream Error', e));
// kill server if the container goes down while the logs window is open
container.wait(() => {
socket.send('\n** connection to docker terminated **\n\n');
socket.close();
wsServer.close();
});
});
return port;
};
const Styled = {
LazyLog: styled(LazyLog)`
background-color: #2b2b2b75;
color: #ffffff;
// copied below styles from the default component's css
overflow: auto !important;
font-family: Monaco, source-code-pro, Menlo, Consolas, 'Courier New', monospace;
font-size: 12px;
margin: 0;
white-space: pre;
will-change: initial;
outline: none;
`,
};
interface RouteParams {
type: string;
name: string;
}
const DockerLogs: React.FC = () => {
const { l } = usePrefixedTranslation('cmps.dockerLogs.DockerLogs');
const { notify } = useStoreActions(s => s.app);
const { type, name } = useParams<RouteParams>();
const [port, setPort] = useState<number>();
const [follow, setFollow] = useState(true);
useEffect(() => {
info('Rendering DockerLogs component');
document.title = `Docker Logs | ${name} (${type})`;
// to run async code in useEffect, you must wrap it in a function
const connect = async () => {
try {
setPort(await startWebSocketServer(name));
} catch (error) {
notify({ message: l('connectErr'), error });
}
};
// connect to the docker container
connect();
// return a cleanup function to run on unmount
return () => {
info('close ws server');
wsServer && wsServer.close();
};
}, [notify, name, type, l]);
// display nothing until the WS server is online
if (!port) return null;
// a scroll handler that detects if the user has scrolled to the bottom. If so,
// automatically stay at the bottom as new logs are streamed in
const handleScroll = ({ scrollTop, scrollHeight, clientHeight }: any) => {
if (follow && scrollHeight - scrollTop > clientHeight && clientHeight >= 0) {
// scrolled up nd no longer viewing the bottom of the screen
setFollow(false);
} else if (!follow && scrollHeight - scrollTop <= clientHeight && clientHeight >= 0) {
// scrolled to the bottom
setFollow(true);
}
};
return (
<Styled.LazyLog
url={`ws://127.0.0.1:${port}`}
websocket
enableSearch
onScroll={handleScroll}
follow={follow}
/>
);
};
export default DockerLogs;

48
src/components/dockerLogs/ViewLogsButton.tsx

@ -0,0 +1,48 @@
import React from 'react';
import { useAsyncCallback } from 'react-async-hook';
import { FileTextOutlined } from '@ant-design/icons';
import { Button, Form, message } from 'antd';
import { usePrefixedTranslation } from 'hooks';
import { BitcoinNode, LightningNode } from 'shared/types';
import { useStoreActions } from 'store';
import { getContainerName } from 'utils/network';
import { LOGS } from 'components/routing';
interface Props {
node: LightningNode | BitcoinNode;
type?: 'button' | 'menu';
}
const ViewLogsButton: React.FC<Props> = ({ node, type }) => {
const { l } = usePrefixedTranslation('cmps.dockerLogs.ViewLogsButton');
const { openWindow } = useStoreActions(s => s.app);
const openAsync = useAsyncCallback(async () => {
if (type === 'menu') message.info(l('openMsg'));
await openWindow(LOGS(node.implementation, getContainerName(node)));
});
// render a menu item inside of the NodeContextMenu
if (type === 'menu') {
return (
<span onClick={openAsync.execute}>
<FileTextOutlined />
<span>{l('btn')}</span>
</span>
);
}
return (
<Form.Item label={l('title')} colon={false}>
<Button
icon={<FileTextOutlined />}
block
loading={openAsync.loading}
onClick={openAsync.execute}
>
{l('btn')}
</Button>
</Form.Item>
);
};
export default ViewLogsButton;

2
src/components/dockerLogs/index.ts

@ -0,0 +1,2 @@
export { default as DockerLogs } from './DockerLogs';
export { default as ViewLogsButton } from './ViewLogsButton';

3
src/components/routing/Routes.tsx

@ -1,11 +1,13 @@
import React from 'react';
import { Route, Switch } from 'react-router';
import { DockerLogs } from 'components/dockerLogs';
import { Home } from 'components/home';
import { AppLayout } from 'components/layouts';
import { NetworkView, NewNetwork } from 'components/network';
import { NodeImagesView } from 'components/nodeImages';
import {
HOME,
LOGS,
NETWORK_NEW,
NETWORK_VIEW,
NODE_IMAGES,
@ -16,6 +18,7 @@ import { DockerTerminal } from 'components/terminal';
const Routes: React.FC = () => (
<Switch>
<Route path={LOGS(':type', ':name')} exact component={DockerLogs} />
<Route path={TERMINAL(':type', ':name')} exact component={DockerTerminal} />
<Route>
<AppLayout>

2
src/components/routing/index.ts

@ -7,3 +7,5 @@ export const NETWORK_VIEW = (id: number | string) => `/network/${id}`;
export const NODE_IMAGES = '/nodes-images';
export const TERMINAL = (type: number | string, name: number | string) =>
`/terminal/${type}/${name}`;
export const LOGS = (type: number | string, name: number | string) =>
`/logs/${type}/${name}`;

5
src/components/terminal/OpenTerminalButton.tsx

@ -1,7 +1,7 @@
import React from 'react';
import { useAsyncCallback } from 'react-async-hook';
import { CodeOutlined } from '@ant-design/icons';
import { Button, Form } from 'antd';
import { Button, Form, message } from 'antd';
import { usePrefixedTranslation } from 'hooks';
import { BitcoinNode, LightningNode } from 'shared/types';
import { useStoreActions } from 'store';
@ -14,9 +14,10 @@ interface Props {
}
const OpenTerminalButton: React.FC<Props> = ({ node, type }) => {
const { l } = usePrefixedTranslation('cmps.common.OpenTerminalButton');
const { l } = usePrefixedTranslation('cmps.terminal.OpenTerminalButton');
const { openWindow } = useStoreActions(s => s.app);
const openAsync = useAsyncCallback(async () => {
if (type === 'menu') message.info(l('openMsg'));
await openWindow(TERMINAL(node.implementation, getContainerName(node)));
});

13
src/i18n/locales/en-US.json

@ -21,10 +21,6 @@
"cmps.common.CopyIcon.message": "Copied {{label}} to clipboard",
"cmps.common.NavMenu.createNetwork": "Create Network",
"cmps.common.NavMenu.manageNodes": "Manage Nodes",
"cmps.common.OpenTerminalButton.title": "Terminal",
"cmps.common.OpenTerminalButton.btn": "Launch",
"cmps.common.OpenTerminalButton.menu": "Launch Terminal",
"cmps.common.OpenTerminalButton.info": "Run '{{cmd}}' commands directly on the node",
"cmps.common.RestartNode.title": "Restart Node",
"cmps.common.RestartNode.startBtn": "Start",
"cmps.common.RestartNode.stopBtn": "Stop",
@ -220,6 +216,10 @@
"cmps.designer.SyncButton.syncSuccess": "The designer has been synced with the Lightning nodes",
"cmps.designer.SyncButton.syncError": "Failed to sync the network",
"cmps.designer.SyncButton.syncBtnTip": "Update channels from nodes",
"cmps.dockerLogs.DockerLogs.connectErr": "Unable to connect to the container",
"cmps.dockerLogs.ViewLogsButton.title": "Docker Node Logs",
"cmps.dockerLogs.ViewLogsButton.btn": "View Logs",
"cmps.dockerLogs.ViewLogsButton.openMsg": "Opening logs...",
"cmps.home.DetectDockerModal.dockerError": "Docker Error",
"cmps.home.DetectDockerModal.notFound": "Not Found",
"cmps.home.DetectDockerModal.notDetected": "Docker Not Detected",
@ -318,6 +318,11 @@
"cmps.terminal.DockerTerminal.nodeTypeErr": "Invalid node type '{{type}}'",
"cmps.terminal.DockerTerminal.containerErr": "Docker container '{{name}}' not found",
"cmps.terminal.DockerTerminal.cliUpdating": "Updating {{cli}} to always use the regtest network",
"cmps.terminal.OpenTerminalButton.title": "Terminal",
"cmps.terminal.OpenTerminalButton.btn": "Launch",
"cmps.terminal.OpenTerminalButton.menu": "Launch Terminal",
"cmps.terminal.OpenTerminalButton.info": "Run '{{cmd}}' commands directly on the node",
"cmps.terminal.OpenTerminalButton.openMsg": "Launching the terminal...",
"store.models.bitcoind.mineError": "The number of blocks to mine must be a positve number",
"store.models.designer.notFoundError": "Chart not found for network with id {{networkId}}",
"store.models.designer.linkErrNotStarted": "The nodes must be Started first",

1
src/lib/docker/dockerService.spec.ts

@ -407,6 +407,7 @@ describe('DockerService', () => {
});
it('should call compose.upOne when a node is started', async () => {
composeMock.stopOne.mockResolvedValue(mockResult);
composeMock.upOne.mockResolvedValue(mockResult);
const node = network.nodes.lightning[0];
await dockerService.startNode(network, node);

6
src/lib/docker/dockerService.ts

@ -104,7 +104,7 @@ class DockerService implements DockerLibrary {
}
/**
* Start a network using docper-compose
* Start a network using docker-compose
* @param network the network to start
*/
async start(network: Network) {
@ -135,6 +135,10 @@ class DockerService implements DockerLibrary {
*/
async startNode(network: Network, node: CommonNode) {
this.ensureDirs(network, [node]);
// make sure the docker container is stopped. If it is already started in an error state
// then starting it would have no effect
await this.stopNode(network, node);
info(`Starting docker container for ${node.name}`);
info(` - path: ${network.path}`);
const result = await this.execute(compose.upOne, node.name, this.getArgs(network));

2
src/react-app-env.d.ts

@ -7,3 +7,5 @@ declare module '*.module.less';
declare module 'react-router-transition';
declare module 'lndconnect';
declare module 'react-lazylog';

9
src/store/models/network.ts

@ -261,10 +261,11 @@ const networkModel: NetworkModel = {
updateAdvancedOptions: thunk(
async (actions, { node, command }, { getState, injections }) => {
const networks = getState().networks;
const network = networks.find(n => n.id === node.networkId);
let network = networks.find(n => n.id === node.networkId);
if (!network) throw new Error(l('networkByIdErr', { networkId: node.networkId }));
actions.updateNodeCommand({ id: node.networkId, name: node.name, command });
await actions.save();
network = getState().networks.find(n => n.id === node.networkId) as Network;
await injections.dockerService.saveComposeFile(network);
},
),
@ -555,9 +556,9 @@ const networkModel: NetworkModel = {
// after all LN nodes are online, connect each of them to each other. This helps
// ensure that each node is aware of the entire graph and can route payments properly
if (allNodesOnline.length) {
Promise.all(allNodesOnline).then(async () => {
await getStoreActions().lightning.connectAllPeers(network);
});
Promise.all(allNodesOnline)
.then(async () => await getStoreActions().lightning.connectAllPeers(network))
.catch(e => info('Failed to connect all LN peers', e));
}
},
),

2
src/utils/constants.ts

@ -144,7 +144,7 @@ export const dockerConfigs: Record<NodeImplementation, DockerConfig> = {
'-server=1',
'-regtest=1',
'-rpcauth={{rpcUser}}:{{rpcAuth}}',
'-debug=0',
'-debug=1',
'-zmqpubrawblock=tcp://0.0.0.0:28334',
'-zmqpubrawtx=tcp://0.0.0.0:28335',
'-txindex=1',

122
yarn.lock

@ -2257,6 +2257,13 @@
mkdirp "^0.5.1"
rimraf "^2.5.2"
"@mattiasbuelens/web-streams-polyfill@^0.2.0":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@mattiasbuelens/web-streams-polyfill/-/web-streams-polyfill-0.2.1.tgz#d7c4aa94f98084ec0787be084d47167d62ea5f67"
integrity sha512-oKuFCQFa3W7Hj7zKn0+4ypI8JFm4ZKIoncwAC6wd5WwFW2sL7O1hpPoJdSWpynQ4DJ4lQ6MvFoVDmCLilonDFg==
dependencies:
"@types/whatwg-streams" "^0.0.7"
"@mrblenny/react-flow-chart@0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@mrblenny/react-flow-chart/-/react-flow-chart-0.0.9.tgz#d549ecf276daebbddc11a841b651e867adb468fa"
@ -2909,6 +2916,18 @@
"@types/unist" "*"
"@types/vfile-message" "*"
"@types/whatwg-streams@^0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@types/whatwg-streams/-/whatwg-streams-0.0.7.tgz#28bfe73dc850562296367249c4b32a50db81e9d3"
integrity sha512-6sDiSEP6DWcY2ZolsJ2s39ZmsoGQ7KVwBDI3sESQsEm9P2dHTcqnDIHRZFRNtLCzWp7hCFGqYbw5GyfpQnJ01A==
"@types/ws@7.2.2":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.2.tgz#1bd2038bc80aea60f8a20b2dcf08602a72e65063"
integrity sha512-oqnI3DbGCVI9zJ/WHdFo3CUE8jQ8CVQDUIKaDtlTcNeT4zs6UCg9Gvk5QrFx2QPkRszpM6yc8o0p4aGjCsTi+w==
dependencies:
"@types/node" "*"
"@types/yargs-parser@*":
version "13.1.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
@ -5133,6 +5152,13 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
bufferutil@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7"
integrity sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==
dependencies:
node-gyp-build "~3.7.0"
builder-util-runtime@8.6.1:
version "8.6.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.6.1.tgz#cf9a268fa51704de24f3c085aa8d1d1b3767d9ea"
@ -5763,6 +5789,11 @@ cloneable-readable@^1.0.0:
process-nextick-args "^2.0.0"
readable-stream "^2.3.5"
clsx@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702"
integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@ -6762,6 +6793,11 @@ csstype@^2.2.0, csstype@^2.5.7:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5"
integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==
csstype@^2.6.7:
version "2.6.9"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
cuint@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
@ -7297,6 +7333,14 @@ dom-converter@^0.2:
dependencies:
utila "~0.4"
dom-helpers@^5.0.0:
version "5.1.3"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821"
integrity sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==
dependencies:
"@babel/runtime" "^7.6.3"
csstype "^2.6.7"
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@ -8441,6 +8485,11 @@ fetch-mock@9.1.1:
querystring "^0.2.0"
whatwg-url "^6.5.0"
fetch-readablestream@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795"
integrity sha512-qu4mXWf4wus4idBIN/kVH+XSer8IZ9CwHP+Pd7DL7TuKNC1hP7ykon4kkBjwJF3EMX2WsFp4hH7gU7CyL7ucXw==
figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@ -9877,6 +9926,11 @@ immer@1.10.0:
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==
immutable@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
import-cwd@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
@ -11892,7 +11946,7 @@ longest@^2.0.1:
resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8"
integrity sha1-eB4YMpaqlPbU2RbcM10NF676I/g=
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -12451,6 +12505,11 @@ mississippi@^3.0.0:
stream-each "^1.1.0"
through2 "^2.0.0"
mitt@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz#cb24e6569c806e31bd4e3995787fe38a04fdf90d"
integrity sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@ -12668,6 +12727,11 @@ node-forge@0.9.0:
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==
node-gyp-build@~3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@ -15411,6 +15475,21 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
react-lazylog@4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/react-lazylog/-/react-lazylog-4.5.1.tgz#8a80405db211f35c7107b3254ef6a35301d31269"
integrity sha512-FLyodC2mYpjTpPyC1hBPAtoXpgLYi4v/3TQakDReDa7C3PWtaS99JiKZC3mvvIQ7+qfaNT22mkC+umYIhJeOCA==
dependencies:
"@mattiasbuelens/web-streams-polyfill" "^0.2.0"
fetch-readablestream "^0.2.0"
immutable "^3.8.2"
mitt "^1.1.2"
prop-types "^15.6.1"
react-string-replace "^0.4.1"
react-virtualized "^9.21.0"
text-encoding-utf-8 "^1.0.1"
whatwg-fetch "^2.0.4"
react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -15538,6 +15617,25 @@ react-scripts@3.4.0:
optionalDependencies:
fsevents "2.1.2"
react-string-replace@^0.4.1:
version "0.4.4"
resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-0.4.4.tgz#24006fbe0db573d5be583133df38b1a735cb4225"
integrity sha512-FAMkhxmDpCsGTwTZg7p/2v+/GTmxAp73so3fbSvlAcBBX36ujiGRNEaM/1u+jiYQrArhns+7eE92g2pi5E5FUA==
dependencies:
lodash "^4.17.4"
react-virtualized@^9.21.0:
version "9.21.2"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e"
integrity sha512-oX7I7KYiUM7lVXQzmhtF4Xg/4UA5duSA+/ZcAvdWlTLFCoFYq1SbauJT5gZK9cZS/wdYR6TPGpX/dqzvTqQeBA==
dependencies:
babel-runtime "^6.26.0"
clsx "^1.0.1"
dom-helpers "^5.0.0"
loose-envify "^1.3.0"
prop-types "^15.6.0"
react-lifecycles-compat "^3.0.4"
react@16.13.0:
version "16.13.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7"
@ -17981,6 +18079,11 @@ testcafe@1.8.2:
tree-kill "^1.1.0"
typescript "^3.3.3"
text-encoding-utf-8@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13"
integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==
text-extensions@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
@ -18700,6 +18803,13 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
utf-8-validate@5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.2.tgz#63cfbccd85dc1f2b66cf7a1d0eebc08ed056bfb3"
integrity sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==
dependencies:
node-gyp-build "~3.7.0"
utf8-byte-length@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
@ -19076,6 +19186,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
@ -19405,6 +19520,11 @@ ws@3.3.x:
safe-buffer "~5.1.0"
ultron "~1.1.0"
ws@7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e"
integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==
ws@^5.2.0:
version "5.2.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"

Loading…
Cancel
Save