diff --git a/overseerr-api.yml b/overseerr-api.yml index c9b1cc723..43e0518de 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -116,6 +116,9 @@ components: activeProfileId: type: number example: 1 + activeProfileName: + type: string + example: 720p/1080p activeDirectory: type: string example: '/movies' @@ -125,16 +128,21 @@ components: minimumAvailability: type: string example: 'In Cinema' + isDefault: + type: boolean + example: false required: - name - hostname - port - apiKey - useSsl - - activeProfile + - activeProfileId + - activeProfileName - activeDirectory - is4k - minimumAvailability + - isDefault SonarrSettings: type: object properties: @@ -162,12 +170,18 @@ components: activeProfileId: type: number example: 1 + activeProfileName: + type: string + example: 720p/1080p activeDirectory: type: string example: '/tv/' activeAnimeProfileId: type: number nullable: true + activeAnimeProfileName: + type: string + example: 720p/1080p activeAnimeDirectory: type: string nullable: true @@ -177,6 +191,9 @@ components: enableSeasonFolders: type: boolean example: false + isDefault: + type: boolean + example: false required: - name - hostname @@ -184,9 +201,11 @@ components: - apiKey - useSsl - activeProfileId + - activeProfileName - activeDirectory - is4k - enableSeasonFolders + - isDefault PublicSettings: type: object properties: @@ -704,6 +723,15 @@ components: type: string twitterId: type: string + ServiceProfile: + type: object + properties: + id: + type: number + example: 1 + name: + type: string + example: 720p/1080p securitySchemes: cookieAuth: @@ -877,6 +905,50 @@ paths: application/json: schema: $ref: '#/components/schemas/RadarrSettings' + /settings/radarr/test: + post: + summary: Test radarr configuration + description: Test if the provided Radarr congifuration values are valid. Returns profiles and root folders on success + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + hostname: + type: string + example: '127.0.0.1' + port: + type: number + example: 7878 + apiKey: + type: string + example: yourapikey + useSsl: + type: boolean + example: false + baseUrl: + type: string + required: + - hostname + - port + - apiKey + - useSsl + responses: + '200': + description: Succesfully connected to Radarr instance + content: + application/json: + schema: + type: object + properties: + profiles: + type: array + items: + $ref: '#/components/schemas/ServiceProfile' /settings/radarr/{radarrId}: put: summary: Update existing radarr instance @@ -922,6 +994,28 @@ paths: application/json: schema: $ref: '#/components/schemas/RadarrSettings' + /settings/radarr/{radarrId}/profiles: + get: + summary: Retrieve available profiles for the Radarr instance + description: Returns an array of profile available on the Radarr server instance in JSON format + tags: + - settings + parameters: + - in: path + name: radarrId + required: true + schema: + type: integer + description: Radarr Instance ID + responses: + '200': + description: Returned list of profiles + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ServiceProfile' /settings/sonarr: get: summary: Get all sonarr settings diff --git a/package.json b/package.json index 0dd14e9ef..b4caade12 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "uuid": "^8.3.0", "winston": "^3.3.3", "xml2js": "^0.4.23", - "yamljs": "^0.3.0" + "yamljs": "^0.3.0", + "yup": "^0.29.3" }, "devDependencies": { "@babel/cli": "^7.11.6", @@ -65,6 +66,7 @@ "@types/uuid": "^8.3.0", "@types/xml2js": "^0.4.5", "@types/yamljs": "^0.2.31", + "@types/yup": "^0.29.9", "@typescript-eslint/eslint-plugin": "^4.0.0", "@typescript-eslint/parser": "^3.10.1", "babel-plugin-react-intl": "^8.2.2", diff --git a/public/images/radarr_logo.png b/public/images/radarr_logo.png new file mode 100644 index 000000000..f192d0bc1 Binary files /dev/null and b/public/images/radarr_logo.png differ diff --git a/public/images/sonarr_logo.png b/public/images/sonarr_logo.png new file mode 100644 index 000000000..3311ac54e Binary files /dev/null and b/public/images/sonarr_logo.png differ diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 238d5817e..148f395aa 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -25,8 +25,10 @@ interface DVRSettings { useSsl: boolean; baseUrl?: string; activeProfileId: number; + activeProfileName: string; activeDirectory: string; is4k: boolean; + isDefault: boolean; } export interface RadarrSettings extends DVRSettings { @@ -35,6 +37,7 @@ export interface RadarrSettings extends DVRSettings { export interface SonarrSettings extends DVRSettings { activeAnimeProfileId?: number; + activeAnimeProfileName?: string; activeAnimeDirectory?: string; enableSeasonFolders: boolean; } diff --git a/server/routes/settings.ts b/server/routes/settings.ts index 395e38381..2c56b3c5a 100644 --- a/server/routes/settings.ts +++ b/server/routes/settings.ts @@ -9,6 +9,9 @@ import { getRepository } from 'typeorm'; import { User } from '../entity/User'; import PlexAPI, { PlexLibrary } from '../api/plexapi'; import jobPlexSync from '../job/plexsync'; +import SonarrAPI from '../api/sonarr'; +import RadarrAPI from '../api/radarr'; +import logger from '../logger'; const settingsRoutes = Router(); @@ -132,6 +135,35 @@ settingsRoutes.post('/radarr', (req, res) => { return res.status(201).json(newRadarr); }); +settingsRoutes.post('/radarr/test', async (req, res, next) => { + try { + const radarr = new RadarrAPI({ + apiKey: req.body.apiKey, + url: `${req.body.useSsl ? 'https' : 'http'}://${req.body.hostname}:${ + req.body.port + }${req.body.baseUrl ?? ''}/api`, + }); + + const profiles = await radarr.getProfiles(); + const folders = await radarr.getRootFolders(); + + return res.status(200).json({ + profiles, + rootFolders: folders.map((folder) => ({ + id: folder.id, + path: folder.path, + })), + }); + } catch (e) { + logger.error('Failed to test Radarr', { + label: 'Radarr', + message: e.message, + }); + + next({ status: 500, message: 'Failed to connect to Radarr' }); + } +}); + settingsRoutes.put<{ id: string }>('/radarr/:id', (req, res) => { const settings = getSettings(); @@ -154,6 +186,36 @@ settingsRoutes.put<{ id: string }>('/radarr/:id', (req, res) => { return res.status(200).json(settings.radarr[radarrIndex]); }); +settingsRoutes.get<{ id: string }>('/radarr/:id/profiles', async (req, res) => { + const settings = getSettings(); + + const radarrSettings = settings.radarr.find( + (r) => r.id === Number(req.params.id) + ); + + if (!radarrSettings) { + return res + .status(404) + .json({ status: '404', message: 'Settings instance not found' }); + } + + const radarr = new RadarrAPI({ + apiKey: radarrSettings.apiKey, + url: `${radarrSettings.useSsl ? 'https' : 'http'}://${ + radarrSettings.hostname + }:${radarrSettings.port}${radarrSettings.baseUrl ?? ''}/api`, + }); + + const profiles = await radarr.getProfiles(); + + return res.status(200).json( + profiles.map((profile) => ({ + id: profile.id, + name: profile.name, + })) + ); +}); + settingsRoutes.delete<{ id: string }>('/radarr/:id', (req, res) => { const settings = getSettings(); diff --git a/src/components/Common/Button/index.tsx b/src/components/Common/Button/index.tsx index ac39b1adb..b1f9d05af 100644 --- a/src/components/Common/Button/index.tsx +++ b/src/components/Common/Button/index.tsx @@ -5,7 +5,8 @@ export type ButtonType = | 'primary' | 'danger' | 'warning' - | 'success'; + | 'success' + | 'ghost'; interface ButtonProps extends ButtonHTMLAttributes { buttonType?: ButtonType; @@ -30,22 +31,27 @@ const Button: React.FC = ({ break; case 'danger': buttonStyle.push( - 'text-white bg-red-600 hover:bg-red-500 focus:border-red-700 focus:shadow-outline-red active:bg-red-700' + 'text-white bg-red-600 hover:bg-red-500 focus:border-red-700 focus:shadow-outline-red active:bg-red-700 disabled:opacity-50' ); break; case 'warning': buttonStyle.push( - 'text-white bg-orange-500 hover:bg-orange-400 focus:border-orange-700 focus:shadow-outline-orange active:bg-orange-700' + 'text-white bg-orange-500 hover:bg-orange-400 focus:border-orange-700 focus:shadow-outline-orange active:bg-orange-700 disabled:opacity-50' ); break; case 'success': buttonStyle.push( - 'text-white bg-green-400 hover:bg-green-300 focus:border-green-700 focus:shadow-outline-green active:bg-green-700' + 'text-white bg-green-400 hover:bg-green-300 focus:border-green-700 focus:shadow-outline-green active:bg-green-700 disabled:opacity-50' + ); + break; + case 'ghost': + buttonStyle.push( + 'text-white bg-transaprent border border-cool-gray-600 hover:border-cool-gray-200 focus:border-cool-gray-100 active:border-cool-gray-100 disabled:opacity-50' ); break; default: buttonStyle.push( - 'leading-5 font-medium rounded-md text-gray-200 bg-cool-gray-500 hover:bg-cool-gray-400 hover:text-white focus:border-blue-300 focus:shadow-outline-blue active:text-gray-200 active:bg-cool-gray-400' + 'leading-5 font-medium rounded-md text-gray-200 bg-cool-gray-500 hover:bg-cool-gray-400 hover:text-white focus:border-blue-300 focus:shadow-outline-blue active:text-gray-200 active:bg-cool-gray-400 disabled:opacity-50' ); } diff --git a/src/components/Common/Modal/index.tsx b/src/components/Common/Modal/index.tsx index 522488f7d..a5427c5f7 100644 --- a/src/components/Common/Modal/index.tsx +++ b/src/components/Common/Modal/index.tsx @@ -103,7 +103,7 @@ const Modal: React.FC = ({ item && ( = ({ {iconSvg} )} -
+
{title && (

= ({

{children && ( -
-

- {children} -

+
+ {children}
)} {(onCancel || onOk || onSecondary || onTertiary) && ( diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index af54340ce..fcc98e802 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -8,6 +8,7 @@ import { useUser, Permission } from '../../../hooks/useUser'; const messages = defineMessages({ dashboard: 'Dashboard', requests: 'Requests', + users: 'Users', settings: 'Settings', }); @@ -68,6 +69,22 @@ const SidebarLinks: SidebarLinkProps[] = [ ), activeRegExp: /^\/requests/, }, + { + href: '/users', + messagesKey: 'users', + svgIcon: ( + + + + ), + activeRegExp: /^\/users/, + requiredPermission: Permission.MANAGE_USERS, + }, { href: '/settings', messagesKey: 'settings', diff --git a/src/components/Settings/RadarrModal.tsx b/src/components/Settings/RadarrModal.tsx new file mode 100644 index 000000000..425e3ce8e --- /dev/null +++ b/src/components/Settings/RadarrModal.tsx @@ -0,0 +1,464 @@ +import React, { useState, useEffect, useCallback, useRef } from 'react'; +import Transition from '../Transition'; +import Modal from '../Common/Modal'; +import { Formik, Field } from 'formik'; +import type { RadarrSettings } from '../../../server/lib/settings'; +import * as Yup from 'yup'; +import axios from 'axios'; +import { useToasts } from 'react-toast-notifications'; + +interface TestResponse { + profiles: { + id: number; + name: string; + }[]; + rootFolders: { + id: number; + path: string; + }[]; +} + +interface RadarrModalProps { + radarr: RadarrSettings | null; + onClose: () => void; + onSave: () => void; +} + +const RadarrModal: React.FC = ({ + onClose, + radarr, + onSave, +}) => { + const initialLoad = useRef(false); + const { addToast } = useToasts(); + const [isValidated, setIsValidated] = useState(radarr ? true : false); + const [isTesting, setIsTesting] = useState(false); + const [testResponse, setTestResponse] = useState({ + profiles: [], + rootFolders: [], + }); + const RadarrSettingsSchema = Yup.object().shape({ + hostname: Yup.string().required('You must provide a hostname/IP'), + port: Yup.number().required('You must provide a port'), + apiKey: Yup.string().required('You must provide an API Key'), + rootFolder: Yup.string().required('You must select a root folder'), + activeProfileId: Yup.string().required('You must select a profile'), + }); + + const testConnection = useCallback( + async ({ + hostname, + port, + apiKey, + baseUrl, + useSsl = false, + }: { + hostname: string; + port: number; + apiKey: string; + baseUrl?: string; + useSsl?: boolean; + }) => { + setIsTesting(true); + try { + const response = await axios.post( + '/api/v1/settings/radarr/test', + { + hostname, + apiKey, + port, + baseUrl, + useSsl, + } + ); + + setIsValidated(true); + setTestResponse(response.data); + if (initialLoad.current) { + addToast('Radarr connection established!', { + appearance: 'success', + autoDismiss: true, + }); + } + } catch (e) { + setIsValidated(false); + if (initialLoad.current) { + addToast('Failed to connect to Radarr server', { + appearance: 'error', + autoDismiss: true, + }); + } + } finally { + setIsTesting(false); + initialLoad.current = true; + } + }, + [addToast] + ); + + useEffect(() => { + if (radarr) { + testConnection({ + apiKey: radarr.apiKey, + hostname: radarr.hostname, + port: radarr.port, + baseUrl: radarr.baseUrl, + useSsl: radarr.useSsl, + }); + } + }, [radarr, testConnection]); + + return ( + + { + try { + const profileName = testResponse.profiles.find( + (profile) => profile.id === Number(values.activeProfileId) + )?.name; + + const submission = { + name: values.name, + hostname: values.hostname, + port: values.port, + apiKey: values.apiKey, + useSsl: values.ssl, + baseUrl: values.baseUrl, + activeProfileId: values.activeProfileId, + activeProfileName: profileName, + activeDirectory: values.rootFolder, + is4k: values.is4k, + minimumAvailability: 'In Cinema', + isDefault: values.isDefault, + }; + if (!radarr) { + await axios.post('/api/v1/settings/radarr', submission); + } else { + await axios.put( + `/api/v1/settings/radarr/${radarr.id}`, + submission + ); + } + + onSave(); + } catch (e) { + // set error here + } + }} + > + {({ + errors, + touched, + values, + handleSubmit, + setFieldValue, + isSubmitting, + }) => { + return ( + { + if (values.apiKey && values.hostname && values.port) { + testConnection({ + apiKey: values.apiKey, + baseUrl: values.baseUrl, + hostname: values.hostname, + port: values.port, + useSsl: values.ssl, + }); + } + }} + secondaryDisabled={ + !values.apiKey || !values.hostname || !values.port || isTesting + } + okDisabled={!isValidated || isSubmitting || isTesting} + onOk={() => handleSubmit()} + title={ + !radarr ? 'Create New Radarr Instance' : 'Edit Radarr Instance' + } + > +
+
+ +
+ +
+
+
+ +
+
+ ) => { + setIsValidated(false); + setFieldValue('name', e.target.value); + }} + className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500" + /> +
+ {errors.name && touched.name && ( +
{errors.name}
+ )} +
+
+
+ +
+
+ ) => { + setIsValidated(false); + setFieldValue('hostname', e.target.value); + }} + className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500" + /> +
+ {errors.hostname && touched.hostname && ( +
{errors.hostname}
+ )} +
+
+
+ +
+ ) => { + setIsValidated(false); + setFieldValue('port', e.target.value); + }} + className="rounded-md shadow-sm form-input block w-24 transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500" + /> + {errors.port && touched.port && ( +
{errors.port}
+ )} +
+
+
+ +
+ { + setIsValidated(false); + setFieldValue('ssl', !values.ssl); + }} + className="form-checkbox h-6 w-6 text-indigo-600 transition duration-150 ease-in-out" + /> +
+
+
+ +
+
+ ) => { + setIsValidated(false); + setFieldValue('apiKey', e.target.value); + }} + className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500" + /> +
+ {errors.apiKey && touched.apiKey && ( +
{errors.apiKey}
+ )} +
+
+
+ +
+
+ ) => { + setIsValidated(false); + setFieldValue('baseUrl', e.target.value); + }} + className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500" + /> +
+ {errors.baseUrl && touched.baseUrl && ( +
{errors.baseUrl}
+ )} +
+
+
+ +
+
+ + {testResponse.profiles.length > 0 && + testResponse.profiles.map((profile) => ( + + ))} + +
+ {errors.baseUrl && touched.baseUrl && ( +
{errors.baseUrl}
+ )} +
+
+
+ +
+
+ + {testResponse.rootFolders.length > 0 && + testResponse.rootFolders.map((folder) => ( + + ))} + +
+ {errors.baseUrl && touched.baseUrl && ( +
{errors.baseUrl}
+ )} +
+
+
+ +
+ +
+
+
+
+ ); + }} +
+
+ ); +}; + +export default RadarrModal; diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx index 01d2c21f3..c39c0e3b6 100644 --- a/src/components/Settings/SettingsPlex.tsx +++ b/src/components/Settings/SettingsPlex.tsx @@ -285,10 +285,10 @@ const SettingsPlex: React.FC = () => {

-
+
{dataSync?.running && (
void; + onDelete: () => void; +} + +const ServerInstance: React.FC = ({ + name, + address, + profileName, + isDefault4K = false, + isDefault = false, + isSSL = false, + isSonarr = false, + onEdit, + onDelete, +}) => { + return ( +
  • +
    +
    +
    +

    + {name} +

    + {isDefault && Default} + {isDefault4K && Default 4K} + {isSSL && SSL} +
    +

    + Address + {address} +

    +

    + Active Profile {profileName} +

    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
  • + ); +}; + +const SettingsServices: React.FC = () => { + const { + data: radarrData, + error: radarrError, + revalidate: revalidateRadarr, + } = useSWR('/api/v1/settings/radarr'); + const { data: sonarrData, error: sonarrError } = useSWR( + '/api/v1/settings/sonarr' + ); + const [editRadarrModal, setEditRadarrModal] = useState<{ + open: boolean; + radarr: RadarrSettings | null; + }>({ + open: false, + radarr: null, + }); + + return ( + <> +
    +

    + Radarr Settings +

    +

    + Configure your Radarr connection below. You can have multiple Radarr + configurations but only two can be active as defaults at any time (one + for standard HD and one for 4K). Administrations can override a titles + connection to use in the manage title screen. +

    +
    + {editRadarrModal.open && ( + setEditRadarrModal({ open: false, radarr: null })} + onSave={() => { + revalidateRadarr(); + setEditRadarrModal({ open: false, radarr: null }); + }} + /> + )} +
    + {!radarrData && !radarrError && } + {radarrData && !radarrError && ( +
      + {radarrData.map((radarr) => ( + setEditRadarrModal({ open: true, radarr })} + onDelete={() => console.log('delete clicked')} + /> + ))} +
    • +
      + +
      +
    • +
    + )} +
    +
    +

    + Sonarr Settings +

    +

    + Configure your Sonarr connection below. You can have multiple Sonarr + configurations but only two can be active as defaults at any time (one + for standard HD and one for 4K). Administrations can override a titles + connection to use in the manage title screen. +

    +
    +
    + {!sonarrData && !sonarrError && } + {sonarrData && !sonarrError && ( +
      + {sonarrData.map((sonarr) => ( + console.log('nada')} + onDelete={() => console.log('delete clicked')} + /> + ))} +
    • +
      + +
      +
    • +
    + )} +
    + + ); +}; + +export default SettingsServices; diff --git a/src/pages/settings/services.tsx b/src/pages/settings/services.tsx new file mode 100644 index 000000000..d7cafe8a1 --- /dev/null +++ b/src/pages/settings/services.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import type { NextPage } from 'next'; +import SettingsLayout from '../../components/Settings/SettingsLayout'; +import SettingsServices from '../../components/Settings/SettingsServices'; + +const ServicesSettingsPage: NextPage = () => { + return ( + + + + ); +}; + +export default ServicesSettingsPage; diff --git a/yarn.lock b/yarn.lock index bb4fa5fc9..7df5cde58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1059,6 +1059,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.5": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" + integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.7.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -1889,6 +1896,11 @@ resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245" integrity sha512-QcJ5ZczaXAqbVD3o8mw/mEBhRvO5UAdTtbvgwL/OgoWubvNBh6/MxLBAigtcgIFaq3shon9m3POIxQaLQt4fxQ== +"@types/yup@^0.29.9": + version "0.29.9" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.9.tgz#e2015187ae5739fd3b791b3b7ab9094f2aa5a474" + integrity sha512-ZtjjlrHuHTYctHDz3c8XgInjj0v+Hahe32N/4cDa2banibf9w6aAgxwx0jZtBjKKzmGIU4NXhofEsBW1BbqrNg== + "@typescript-eslint/eslint-plugin@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.0.0.tgz#99349a501447fed91de18346705c0c65cf603bee" @@ -5064,6 +5076,11 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +fn-name@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" + integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== + fn.name@1.x.x: version "1.1.0" resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" @@ -6456,7 +6473,7 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash-es@^4.17.14: +lodash-es@^4.17.11, lodash-es@^4.17.14: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== @@ -8309,6 +8326,11 @@ prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +property-expr@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" + integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== + proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -9761,6 +9783,11 @@ swr@^0.3.5: dependencies: dequal "2.0.2" +synchronous-promise@^2.0.13: + version "2.0.15" + resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.15.tgz#07ca1822b9de0001f5ff73595f3d08c4f720eb8e" + integrity sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg== + table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -9993,6 +10020,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -10771,3 +10803,16 @@ yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yup@^0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea" + integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ== + dependencies: + "@babel/runtime" "^7.10.5" + fn-name "~3.0.0" + lodash "^4.17.15" + lodash-es "^4.17.11" + property-expr "^2.0.2" + synchronous-promise "^2.0.13" + toposort "^2.0.2"