{
const scrolled = useScrolled()
return (
diff --git a/src/ui/components/Queue.js b/src/ui/components/Queue.js
deleted file mode 100644
index 144d4cd8..00000000
--- a/src/ui/components/Queue.js
+++ /dev/null
@@ -1,341 +0,0 @@
-import React, { useState } from 'react'
-import {
- getYear,
- format,
- isToday,
- formatDistance,
- formatDistanceStrict,
-} from 'date-fns'
-import Highlight from 'react-highlight/lib/optimized'
-
-const today = new Date()
-
-function formatDate(ts) {
- if (isToday(ts)) {
- return format(ts, 'HH:mm:ss')
- }
-
- return getYear(ts) === getYear(today)
- ? format(ts, 'MM/dd HH:mm:ss')
- : format(ts, 'MM/dd/yyyy HH:mm:ss')
-}
-
-function TS({ ts, prev }) {
- const date = formatDate(ts)
-
- return (
- <>
- {date}{' '}
- {ts && prev && (
- <>
- ({formatDistance(ts, prev, { includeSeconds: true })})
- >
- )}
- >
- )
-}
-
-function MenuItem({ status, count, onClick, selected }) {
- return (
-
- {status !== 'latest' && {count}} {status}
-
- )
-}
-
-const statuses = [
- 'latest',
- 'active',
- 'waiting',
- 'completed',
- 'failed',
- 'delayed',
- 'paused',
-]
-
-const fields = {
- latest: ['id', 'timestamps', 'name', 'progress', 'attempts', 'data', 'opts'],
- completed: [
- 'id',
- 'timestamps',
- 'name',
- 'progress',
- 'attempts',
- 'data',
- 'opts',
- ],
- delayed: ['id', 'timestamps', 'name', 'attempts', 'delay', 'data', 'opts'],
- paused: ['id', 'timestamps', 'name', 'attempts', 'data', 'opts'],
- active: ['id', 'timestamps', 'name', 'progress', 'attempts', 'data', 'opts'],
- waiting: ['id', 'timestamps', 'name', 'data', 'opts'],
- failed: [
- 'id',
- 'failedReason',
- 'name',
- 'timestamps',
- 'progress',
- 'attempts',
- 'retry',
- ],
-}
-
-function PlusIcon() {
- return (
-
- )
-}
-
-function PlayIcon() {
- return (
-
- )
-}
-
-function CheckIcon() {
- return (
-
- )
-}
-
-const fieldComponents = {
- id: ({ job }) => {
- return #{job.id}
- },
- timestamps: ({ job }) => {
- return (
-
-
- {job.processedOn && (
-
- )}
- {job.finishedOn && (
-
-
-
- )}
-
- )
- },
- name: ({ job }) => {
- if (job.name === '__default__') {
- return '--'
- }
- return job.name
- },
- finish: ({ job }) => {
- return
- },
- progress: ({ job }) => {
- switch (typeof job.progress) {
- case 'object':
- return (
-
- {JSON.stringify(job.progress, null, 2)}
-
- )
- case 'number':
- if (job.progress > 100) {
- return {job.progress}
- }
-
- return (
-
-
- {job.progress}
- %
-
-
- )
- default:
- return '--'
- }
- },
- attempts: ({ job }) => {
- return job.attempts
- },
- delay: ({ job }) => {
- return formatDistanceStrict(job.timestamp + job.delay, Date.now())
- },
- failedReason: ({ job }) => {
- return (
- <>
- {job.failedReason || 'NA'}
- {job.stacktrace}
- >
- )
- },
- data: ({ job }) => {
- const [showData, toggleData] = useState(false)
-
- return (
- <>
-
-
- {showData && JSON.stringify(job.data, null, 2)}
-
- >
- )
- },
- opts: ({ job }) => {
- return (
-
- {JSON.stringify(job.opts, null, 2)}
-
- )
- },
- retry: ({ retryJob }) => {
- return
- },
-}
-
-function Jobs({ retryJob, queue: { jobs, name }, status }) {
- if (!jobs.length) {
- return `No jobs with status ${status}`
- }
-
- return (
-
-
-
- {fields[status].map(field => (
- {field} |
- ))}
-
-
-
- {jobs.map(job => {
- return (
-
- {fields[status].map(field => {
- const Field = fieldComponents[field]
- return (
-
-
- |
- )
- })}
-
- )
- })}
-
-
- )
-}
-
-const actions = {
- failed: ({ retryAll, cleanAllFailed }) => {
- return (
-
-
-
-
- )
- },
- delayed: ({ cleanAllDelayed }) => {
- return
- },
-}
-
-function QueueActions(props) {
- const Actions =
- actions[props.status] ||
- (() => {
- return null
- })
- return (
-
- )
-}
-
-export default function Queue({
- retryAll,
- retryJob,
- cleanAllDelayed,
- cleanAllFailed,
- queue,
- selectStatus,
- selectedStatus,
-}) {
- return (
-
-
- {queue.name}
- {queue.version === 4 && bullmq}
-
-
- {statuses.map(status => (
-
- {selectedStatus && (
- <>
-
-
- >
- )}
-
- )
-}
diff --git a/src/ui/components/Queue.tsx b/src/ui/components/Queue.tsx
new file mode 100644
index 00000000..a9cfac4d
--- /dev/null
+++ b/src/ui/components/Queue.tsx
@@ -0,0 +1,297 @@
+import React, { useState } from 'react'
+import {
+ getYear,
+ format,
+ isToday,
+ formatDistance,
+ formatDistanceStrict,
+} from 'date-fns'
+import Highlight from 'react-highlight'
+import { Job } from 'bull'
+import { Job as JobMq } from 'bullmq'
+
+import { FIELDS, STATUSES } from './constants'
+
+const today = new Date()
+
+// FIXME: typings
+const formatDate = (ts: any) => {
+ if (isToday(ts)) {
+ return format(ts, 'HH:mm:ss')
+ }
+
+ return getYear(ts) === getYear(today)
+ ? format(ts, 'MM/dd HH:mm:ss')
+ : format(ts, 'MM/dd/yyyy HH:mm:ss')
+}
+
+const Timestamp = ({ ts, prev }: { ts: any; prev?: any }) => {
+ const date = formatDate(ts)
+
+ return (
+ <>
+ {date}{' '}
+ {ts && prev && (
+ <>
+ ({formatDistance(ts, prev, { includeSeconds: true })})
+ >
+ )}
+ >
+ )
+}
+
+// FIXME: typings
+const MenuItem = ({ status, count, onClick, selected }: any) => (
+
+ {status !== 'latest' && {count}} {status}
+
+)
+
+const PlusIcon = () => (
+
+)
+
+const PlayIcon = () => (
+
+)
+
+const CheckIcon = () => (
+
+)
+
+const fieldComponents = {
+ id: ({ job }: { job: Job | JobMq }) => #{job.id},
+
+ timestamps: ({ job }: { job: Job | JobMq }) => (
+
+
+ {job.processedOn && (
+
+ )}
+ {job.finishedOn && (
+
+
+
+ )}
+
+ ),
+
+ name: ({ job }: { job: Job | JobMq }) =>
+ job.name === '__default__' ? '--' : job.name,
+
+ finish: ({ job }: { job: Job | JobMq }) => (
+
+ ),
+
+ progress: ({ job }: { job: Job | JobMq }) => {
+ switch (typeof job.progress) {
+ case 'object':
+ return (
+
+ {JSON.stringify(job.progress, null, 2)}
+
+ )
+ case 'number':
+ if (job.progress > 100) {
+ return {job.progress}
+ }
+
+ return (
+
+
+ {job.progress}
+ %
+
+
+ )
+ default:
+ return '--'
+ }
+ },
+
+ // FIXME: typings
+ attempts: ({ job }: { job: any }) => job.attempts,
+
+ // FIXME: typings
+ delay: ({ job }: { job: any }) =>
+ formatDistanceStrict(job.timestamp + job.delay, Date.now()),
+
+ // FIXME: typings
+ failedReason: ({ job }: any) => {
+ return (
+ <>
+ {job.failedReason || 'NA'}
+ {job.stacktrace}
+ >
+ )
+ },
+
+ data: ({ job }: { job: Job | JobMq }) => {
+ const [showData, toggleData] = useState(false)
+
+ return (
+ <>
+
+
+ {showData && JSON.stringify(job.data, null, 2)}
+
+ >
+ )
+ },
+
+ opts: ({ job }: { job: Job | JobMq }) => (
+ {JSON.stringify(job.opts, null, 2)}
+ ),
+
+ // FIXME: typings
+ retry: ({ retryJob }: { retryJob: () => void }) => (
+
+ ),
+}
+
+const Jobs = ({
+ retryJob,
+ queue: { jobs, name },
+ status,
+}: {
+ // FIXME: typings
+ retryJob: any
+ queue: {
+ jobs: any
+ name: any
+ }
+ status: string
+}) => {
+ if (!jobs.length) {
+ return <>No jobs with status {status}>
+ }
+
+ return (
+
+
+
+ {FIELDS[status].map(field => (
+ {field} |
+ ))}
+
+
+
+ {jobs.map((job: Job | JobMq) => (
+
+ {FIELDS[status].map(field => {
+ // FIXME: typings
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
+ // @ts-ignore
+ const Field = fieldComponents[field]
+
+ return (
+
+
+ |
+ )
+ })}
+
+ ))}
+
+
+ )
+}
+
+const actions = {
+ failed: ({ retryAll, cleanAllFailed }: any) => (
+
+
+
+
+ ),
+ delayed: ({ cleanAllDelayed }: any) => (
+
+ ),
+}
+
+// FIXME: typings
+const QueueActions = (props: {
+ [key: string]: any
+ status: 'failed' | 'delayed'
+}) => {
+ const Actions =
+ actions[props.status] ||
+ (() => {
+ return null
+ })
+
+ return (
+
+ )
+}
+
+// FIXME: typings
+export const Queue = ({
+ cleanAllDelayed,
+ cleanAllFailed,
+ queue,
+ retryAll,
+ retryJob,
+ selectedStatus,
+ selectStatus,
+}: any) => (
+
+
+ {queue.name}
+ {queue.version === 4 && bullmq}
+
+
+ {Object.keys(STATUSES).map(status => (
+
+ {selectedStatus && (
+ <>
+
+
+ >
+ )}
+
+)
diff --git a/src/ui/components/RedisStats.js b/src/ui/components/RedisStats.js
deleted file mode 100644
index f0a648c8..00000000
--- a/src/ui/components/RedisStats.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import React from 'react'
-import formatBytes from 'pretty-bytes'
-
-function RedisLogo() {
- return (
-
- )
-}
-
-function getMemoryUsage(used_memory, total_system_memory) {
- if (!total_system_memory) {
- return formatBytes(parseInt(used_memory, 10))
- }
-
- return `${((used_memory / total_system_memory) * 100).toFixed(2)}%`
-}
-
-export default function RedisStats({ stats }) {
- if (stats == null || Object.entries(stats).length == 0) {
- return 'No stats to display'
- }
-
- const {
- redis_version,
- used_memory,
- total_system_memory,
- mem_fragmentation_ratio,
- connected_clients,
- blocked_clients,
- } = stats
-
- return (
-
-
-
-
-
-
- Version
-
{redis_version}
-
-
-
- Memory usage
-
{getMemoryUsage(used_memory, total_system_memory)}
- {total_system_memory ? (
-
- {formatBytes(parseInt(used_memory))} of{' '}
- {formatBytes(parseInt(total_system_memory))}
-
- ) : (
-
- Could not retrieve total_system_memory
-
- )}
-
-
-
- Fragmentation ratio
-
{mem_fragmentation_ratio}
-
-
-
- Connected clients
-
{connected_clients}
-
-
-
- Blocked clients
-
{blocked_clients}
-
-
- )
-}
diff --git a/src/ui/components/RedisStats.tsx b/src/ui/components/RedisStats.tsx
new file mode 100644
index 00000000..d5c3418b
--- /dev/null
+++ b/src/ui/components/RedisStats.tsx
@@ -0,0 +1,95 @@
+/* eslint-disable @typescript-eslint/camelcase */
+
+import React from 'react'
+import formatBytes from 'pretty-bytes'
+
+interface ValidMetrics {
+ total_system_memory: string
+ redis_version?: string
+ used_memory: string
+ mem_fragmentation_ratio?: string
+ connected_clients?: string
+ blocked_clients?: string
+}
+
+const RedisLogo = () => (
+
+)
+
+const getMemoryUsage = (
+ used_memory: ValidMetrics['used_memory'],
+ total_system_memory: ValidMetrics['total_system_memory'],
+) =>
+ total_system_memory
+ ? `${(
+ (parseInt(used_memory, 10) / parseInt(total_system_memory, 10)) *
+ 100
+ ).toFixed(2)}%`
+ : formatBytes(parseInt(used_memory, 10))
+
+export const RedisStats = ({ stats }: { stats: ValidMetrics }) => {
+ const {
+ redis_version,
+ used_memory,
+ total_system_memory,
+ mem_fragmentation_ratio,
+ connected_clients,
+ blocked_clients,
+ } = stats
+
+ return (
+
+
+
+
+
+
+ Version
+
{redis_version}
+
+
+
+ Memory usage
+
{getMemoryUsage(used_memory, total_system_memory)}
+ {total_system_memory ? (
+
+ {formatBytes(parseInt(used_memory))} of{' '}
+ {formatBytes(parseInt(total_system_memory))}
+
+ ) : (
+
+ Could not retrieve total_system_memory
+
+ )}
+
+
+
+ Fragmentation ratio
+
{mem_fragmentation_ratio}
+
+
+
+ Connected clients
+
{connected_clients}
+
+
+
+ Blocked clients
+
{blocked_clients}
+
+
+ )
+}
diff --git a/src/ui/components/constants.ts b/src/ui/components/constants.ts
new file mode 100644
index 00000000..d0db5ca3
--- /dev/null
+++ b/src/ui/components/constants.ts
@@ -0,0 +1,59 @@
+export const STATUSES = {
+ active: 'active',
+ completed: 'completed',
+ delayed: 'delayed',
+ failed: 'failed',
+ latest: 'latest',
+ paused: 'paused',
+ waiting: 'waiting',
+}
+
+export const FIELDS = {
+ [STATUSES.active]: [
+ 'attempts',
+ 'data',
+ 'id',
+ 'name',
+ 'opts',
+ 'progress',
+ 'timestamps',
+ ],
+ [STATUSES.completed]: [
+ 'attempts',
+ 'data',
+ 'id',
+ 'name',
+ 'opts',
+ 'progress',
+ 'timestamps',
+ ],
+ [STATUSES.delayed]: [
+ 'attempts',
+ 'data',
+ 'delay',
+ 'id',
+ 'name',
+ 'opts',
+ 'timestamps',
+ ],
+ [STATUSES.failed]: [
+ 'attempts',
+ 'failedReason',
+ 'id',
+ 'name',
+ 'progress',
+ 'retry',
+ 'timestamps',
+ ],
+ [STATUSES.latest]: [
+ 'attempts',
+ 'data',
+ 'id',
+ 'name',
+ 'opts',
+ 'progress',
+ 'timestamps',
+ ],
+ [STATUSES.paused]: ['attempts', 'data', 'id', 'name', 'opts', 'timestamps'],
+ [STATUSES.waiting]: ['data', 'id', 'name', 'opts', 'timestamps'],
+}
diff --git a/src/ui/components/hooks/useScrolled.js b/src/ui/components/hooks/useScrolled.ts
similarity index 75%
rename from src/ui/components/hooks/useScrolled.js
rename to src/ui/components/hooks/useScrolled.ts
index 97e75cf6..da9855cb 100644
--- a/src/ui/components/hooks/useScrolled.js
+++ b/src/ui/components/hooks/useScrolled.ts
@@ -1,13 +1,11 @@
import { useState, useEffect } from 'react'
-export default function useScrolled() {
+export const useScrolled = () => {
const [scrolled, setScrolled] = useState(
typeof window === 'undefined' ? false : window.scrollY > 20,
)
- function handleScroll() {
- setScrolled(window.scrollY > 20)
- }
+ const handleScroll = () => setScrolled(window.scrollY > 20)
useEffect(() => {
window.addEventListener('scroll', handleScroll)
diff --git a/src/ui/components/hooks/useStore.js b/src/ui/components/hooks/useStore.ts
similarity index 59%
rename from src/ui/components/hooks/useStore.js
rename to src/ui/components/hooks/useStore.ts
index d3b586a7..e6d6f98b 100644
--- a/src/ui/components/hooks/useStore.js
+++ b/src/ui/components/hooks/useStore.ts
@@ -3,63 +3,58 @@ import qs from 'querystring'
const interval = 5000
-export default function useStore(basePath) {
+export const useStore = (basePath: string) => {
const [state, setState] = useState({
data: null,
loading: true,
- })
- const [selectedStatuses, setSelectedStatuses] = useState({})
+ } as any)
+ const [selectedStatuses, setSelectedStatuses] = useState({} as any)
const poll = useRef()
+ const stopPolling = () => {
+ if (poll.current) {
+ clearTimeout(poll.current)
+ poll.current = undefined
+ }
+ }
useEffect(() => {
stopPolling()
runPolling()
+
return stopPolling
}, [selectedStatuses])
- const stopPolling = () => {
- if (poll.current) {
- clearTimeout(poll.current)
- poll.current = null
- }
- }
-
const runPolling = () => {
update()
- .catch(error => {
- console.error('Failed to poll', error)
- })
+ .catch(error => console.error('Failed to poll', error))
.then(() => {
- const timeoutId = setTimeout(() => {
- runPolling()
- }, interval)
- poll.current = timeoutId
+ const timeoutId = setTimeout(runPolling, interval)
+ poll.current = timeoutId as any
})
}
- const update = () => {
- return fetch(`${basePath}/queues/?${qs.encode(selectedStatuses)}`)
+ const update = () =>
+ fetch(`${basePath}/queues/?${qs.encode(selectedStatuses)}`)
.then(res => (res.ok ? res.json() : Promise.reject(res)))
.then(data => setState({ data, loading: false }))
- }
- const retryJob = queueName => job => () =>
+ const retryJob = (queueName: string) => (job: { id: string }) => () =>
fetch(`${basePath}/queues/${queueName}/${job.id}/retry`, {
method: 'put',
}).then(update)
- const retryAll = queueName => () =>
- fetch(`${basePath}/queues/${queueName}/retry`, { method: 'put' }).then(
- update,
- )
+ const retryAll = (queueName: string) => () =>
+ fetch(`${basePath}/queues/${queueName}/retry`, {
+ method: 'put',
+ }).then(update)
- const cleanAllDelayed = queueName => () =>
+ const cleanAllDelayed = (queueName: string) => () =>
fetch(`${basePath}/queues/${queueName}/clean/delayed`, {
method: 'put',
}).then(update)
- const cleanAllFailed = queueName => () =>
+ const cleanAllFailed = (queueName: string) => () =>
fetch(`${basePath}/queues/${queueName}/clean/failed`, {
method: 'put',
}).then(update)
diff --git a/src/ui/index.js b/src/ui/index.js
deleted file mode 100644
index 144fb47b..00000000
--- a/src/ui/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from 'react'
-import { render } from 'react-dom'
-import './index.css'
-import './xcode.css'
-import App from './components/App'
-
-render(, document.getElementById('root'))
diff --git a/src/ui/index.tsx b/src/ui/index.tsx
new file mode 100644
index 00000000..1d000d72
--- /dev/null
+++ b/src/ui/index.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import { render } from 'react-dom'
+
+import './index.css'
+import './xcode.css'
+import { App } from './components/App'
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
+// @ts-ignore
+const basePath = window.basePath
+
+render(, document.getElementById('root'))
diff --git a/tsconfig.json b/tsconfig.json
index 44e3b43c..beb7705c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,18 +2,19 @@
"compilerOptions": {
"outDir": "./dist",
"esModuleInterop": true,
- "lib": ["es2019"],
+ "lib": ["es2019", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"sourceMap": true,
"strict": true,
+ "jsx": "react",
"target": "es2019",
"noUnusedParameters": true,
"noUnusedLocals": true,
"resolveJsonModule": true,
"declaration": true
},
- "include": ["./src"],
- "exclude": ["./src/ui"]
+ "include": ["./src"]
+ // "exclude": ["./src/ui"]
}
diff --git a/webpack.config.js b/webpack.config.js
index fc032947..479352df 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -7,11 +7,14 @@ module.exports = {
mode: 'production',
bail: true,
devtool: false,
- entry: ['./src/ui/index.js'],
+ entry: ['./src/ui/index.tsx'],
output: {
path: path.resolve(__dirname, './static'),
filename: 'bundle.js',
},
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js', '.jsx'],
+ },
module: {
rules: [
{
@@ -19,7 +22,21 @@ module.exports = {
loader: 'babel-loader',
options: { presets: ['react-app'] },
},
- { test: /\.css$/, use: ['style-loader', 'css-loader'] },
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader'],
+ },
+ {
+ test: /\.ts(x?)$/,
+ exclude: /node_modules/,
+ use: [{ loader: 'ts-loader' }],
+ },
+ {
+ // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
+ enforce: 'pre',
+ test: /\.js$/,
+ loader: 'source-map-loader',
+ },
],
},
plugins: [
diff --git a/yarn.lock b/yarn.lock
index e10e6659..779b0ca6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1232,11 +1232,45 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.8.tgz#1d590429fe8187a02707720ecf38a6fe46ce294b"
integrity sha512-6XzyyNM9EKQW4HKuzbo/CkOIjn/evtCmsU+MUM1xDfJ+3/rNjBttM1NgN7AOQvN6tP1Sl1D1PIKMreTArnxM9A==
+"@types/pretty-bytes@^5.2.0":
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/@types/pretty-bytes/-/pretty-bytes-5.2.0.tgz#857dcf4a21839e5bfb1c188dda62f986fdfa2348"
+ integrity sha512-dJhMFphDp6CE+OAZVyqzha9KsmgeqRMbZN4dIbMSrfObiuzfjucwKdn6zu+ttrjMwmz+Vz71/xXgHx5pO0axhA==
+ dependencies:
+ pretty-bytes "*"
+
+"@types/prop-types@*":
+ version "15.7.3"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+ integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
+
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
+"@types/react-dom@^16.9.5":
+ version "16.9.5"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7"
+ integrity sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react-highlight@^0.12.2":
+ version "0.12.2"
+ resolved "https://registry.yarnpkg.com/@types/react-highlight/-/react-highlight-0.12.2.tgz#205b3437bf3611c185884a564d20548a4780480b"
+ integrity sha512-Pd0jkT7OB0eVccQeytwf1e+RoszedNHnsmJWbyr4F6407xdRz1Si7zmF9imO/Qr88DidnNv63Vyy9UfGHSMDrg==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@^16.9.18":
+ version "16.9.18"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.18.tgz#7dfb420a780e3740d43563506009834a86ae2af0"
+ integrity sha512-MvjiKX/kUE8o49ipppg49RDZ97p4XfW1WWksp/UlTUSJpisyhzd62pZAMXxAscFLoxfYOflkGANAnGkSeHTFQg==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^2.2.0"
+
"@types/serve-static@*":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
@@ -1808,6 +1842,13 @@ async-limiter@~1.0.0:
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
+async@^2.5.0:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
+ integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
+ dependencies:
+ lodash "^4.17.14"
+
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -2045,7 +2086,7 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
-braces@~3.0.2:
+braces@^3.0.1, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@@ -2317,7 +2358,7 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
-chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -2750,6 +2791,11 @@ cssstyle@^1.0.0:
dependencies:
cssom "0.3.x"
+csstype@^2.2.0:
+ version "2.6.8"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
+ integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==
+
cyclist@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
@@ -3069,6 +3115,15 @@ enhanced-resolve@4.1.0, enhanced-resolve@^4.1.0:
memory-fs "^0.4.0"
tapable "^1.0.0"
+enhanced-resolve@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66"
+ integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==
+ dependencies:
+ graceful-fs "^4.1.2"
+ memory-fs "^0.5.0"
+ tapable "^1.0.0"
+
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@@ -5559,6 +5614,14 @@ memory-fs@^0.4.0, memory-fs@^0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
+memory-fs@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c"
+ integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==
+ dependencies:
+ errno "^0.1.3"
+ readable-stream "^2.0.1"
+
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -5598,6 +5661,14 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.2"
+micromatch@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+ integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+ dependencies:
+ braces "^3.0.1"
+ picomatch "^2.0.5"
+
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -6377,7 +6448,7 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
-picomatch@^2.0.4, picomatch@^2.0.7:
+picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7:
version "2.2.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a"
integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==
@@ -6519,7 +6590,7 @@ prettier@^1.19.1, prettier@^1.7.0:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
-pretty-bytes@5.3.0:
+pretty-bytes@*, pretty-bytes@5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"
integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==
@@ -7433,6 +7504,14 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
+source-map-loader@^0.2.4:
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271"
+ integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==
+ dependencies:
+ async "^2.5.0"
+ loader-utils "^1.1.0"
+
source-map-resolve@^0.5.0:
version "0.5.2"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
@@ -7985,6 +8064,17 @@ ts-jest@^24.3.0:
semver "^5.5"
yargs-parser "10.x"
+ts-loader@^6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef"
+ integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g==
+ dependencies:
+ chalk "^2.3.0"
+ enhanced-resolve "^4.0.0"
+ loader-utils "^1.0.2"
+ micromatch "^4.0.0"
+ semver "^6.0.0"
+
ts-node@^8.6.2:
version "8.6.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35"